Back to app

Integrate DealFilter

Connect DealFilter to Claude, Codex or your own script - via MCP server, REST API or CLI.

DealFilter API, MCP Server and CLI

Connect DealFilter to Claude, Codex or your own script. Prerequisite: Premium or Ultra plan + API key (df_... - generate under Settings > API Access).

PlanList, get, quotaSubmitChange phase
Essential---
Premium--
Ultra (coming soon)

Table of Contents

  1. Authentication and Key Management
  2. REST API v1
  3. MCP Server
  4. OAuth Connector and Token Management
  5. CLI
  6. Error Reference
  7. Async Contract: Submit and Poll

Authentication

DealFilter uses two kinds of access tokens:

TokenPrefixOriginUsed for
Personal API keydf_Created manually under Settings > API AccessREST API, MCP server, CLI, custom scripts
OAuth connector tokendfo_Issued automatically when connecting an MCP client (e.g. Claude.ai)MCP server via the connector flow

Both are stored hashed only and are passed in the same Authorization header.

Generate an API Key

Settings > API Access (visible from Premium onwards). The key is shown in plain text only once at creation. Format: df_ + 43 characters (Base64url).

Choose a validity period

When creating a key you choose its validity: 30 days, 90 days (default), 1 year or unlimited. After expiry the key is rejected (401) - create a new one under Settings > API Access. The page shows the expiry date and a notice once a key has expired.

Renew or revoke a key

  • Renew: "Regenerate" creates a fresh key and invalidates the old one immediately.
  • Revoke: "Revoke" invalidates the key immediately without creating a new one.

Pass the Key in Requests

Authorization: Bearer df_<key>

Applies to REST API, MCP server and CLI.


REST API v1

Base URL: https://dealfilter.ai Prefix: /api/v1


POST /api/v1/applications Ultra (coming soon) — Submit inquiry

Submits a new inquiry for AI analysis. Consumes 1 credit. The analysis runs asynchronously - the response immediately contains status: "analyzing".

Request

POST /api/v1/applications
Authorization: Bearer df_<key>
Content-Type: application/json

{
  "mailText": "Subject: Senior Java Developer wanted\n\nHello,\n...",
  "channel": "EMAIL",
  "agentId": "cm1abc123",
  "newAgentName": "SOLCOM GmbH"
}
FieldTypeRequiredDescription
mailTextstring (min 10)yesFull text of the inquiry
channelEMAIL|PORTAL|PHONE|SEARCH_AGENTnoDefault: EMAIL
agentIdstringnoID of an existing recruiter
newAgentNamestringnoName of a new recruiter (alternative to agentId)

Response 202

{
  "applicationId": "cmq0go2ib000a1h9kn8dno71t",
  "status": "analyzing"
}

Retrieve analysis status: GET /api/v1/applications/{applicationId} after 30-60 seconds.


GET /api/v1/applications Premium Ultra (coming soon) — List inquiries

GET /api/v1/applications?active=true&limit=20&offset=0
Authorization: Bearer df_<key>

Query Parameters

ParameterTypeDefaultDescription
phasestring-Exact phase filter (see below)
activetrue|false-true = not archived, false = ARCHIVED only
limitint (1-100)50Number of results
offsetint0Offset for pagination

phase and active are mutually exclusive - phase takes precedence.

Phase values: INCOMING, APPLIED, PROFILE_AT_CLIENT, WON, CONTRACT_SIGNED, ARCHIVED

Response 200

{
  "items": [
    {
      "id": "cmq0go2ib000a1h9kn8dno71t",
      "status": "INCOMING",
      "channel": "PORTAL",
      "projectTitle": "Senior Software Engineer - Microservices",
      "agentName": "SOLCOM",
      "analysisStatus": "done",
      "scoreLabel": "GREEN",
      "scorePercent": 88,
      "createdAt": "2026-06-05T05:05:47.699Z",
      "lastContactAt": "2026-06-05T05:07:49.699Z"
    }
  ],
  "total": 32,
  "limit": 20,
  "offset": 0
}

GET /api/v1/applications/ Premium Ultra (coming soon) — Get single inquiry

GET /api/v1/applications/cmq0go2ib000a1h9kn8dno71t
Authorization: Bearer df_<key>

Response 200

{
  "id": "cmq0go2ib000a1h9kn8dno71t",
  "status": "INCOMING",
  "channel": "PORTAL",
  "analysisStatus": "done",
  "scoreLabel": "GREEN",
  "scorePercent": 88,
  "matchScore": {
    "techFit": 90,
    "rateFit": 85,
    "locationFit": 80
  },
  "redFlags": ["No remote option mentioned"],
  "hints": ["Java 17+", "Microservices", "Kubernetes"],
  "aiSummary": "Solid Java project at a financial services end client...",
  "createdAt": "2026-06-05T05:05:47.699Z",
  "lastContactAt": "2026-06-05T05:07:49.699Z",
  "originalMail": "Subject: Senior Software Engineer...",
  "project": {
    "title": "Senior Software Engineer - Microservices",
    "description": "...",
    "location": "Frankfurt",
    "remotePercentMin": 60,
    "remotePercentMax": 80,
    "durationMonthsMin": 6,
    "durationMonthsMax": 12,
    "startDate": "2026-07-01T00:00:00.000Z"
  },
  "agentName": "SOLCOM",
  "communicationLog": [
    {
      "id": "cm2xyz456",
      "date": "2026-06-06T10:00:00.000Z",
      "type": "PHONE",
      "direction": "INBOUND",
      "contactSuccessful": true,
      "note": "Call with recruiter - rate negotiated"
    }
  ]
}

analysisStatus values:

ValueMeaning
analyzingAnalysis still running - check again later
doneScore, redFlags, hints and aiSummary available
failedAnalysis permanently failed (max. retries reached)

PATCH /api/v1/applications/ Ultra (coming soon) — Change phase

No credit consumption.

PATCH /api/v1/applications/cmq0go2ib000a1h9kn8dno71t
Authorization: Bearer df_<key>
Content-Type: application/json

{
  "status": "APPLIED"
}

Response 200

{
  "id": "cmq0go2ib000a1h9kn8dno71t",
  "status": "APPLIED"
}

GET /api/v1/quota Premium Ultra (coming soon) — Check quota

GET /api/v1/quota
Authorization: Bearer df_<key>

Response 200

{
  "used": 44,
  "limit": 500,
  "remaining": 456,
  "resetAt": "2026-07-01T00:00:00.000Z"
}

MCP Server

Endpoint: POST https://dealfilter.ai/api/mcp Transport: StreamableHTTP (stateless, no session management) Protocol: MCP 2024-11-05

Configuration in Claude Code

Settings - MCP Servers - Add server:

{
  "name": "dealfilter",
  "url": "https://dealfilter.ai/api/mcp",
  "headers": {
    "Authorization": "Bearer df_<key>"
  }
}

Configuration in Claude.ai, OpenAI Codex and compatible clients

All MCP-compatible clients (Claude.ai, OpenAI Codex, Cursor and others) use the mcpServers format:

{
  "mcpServers": {
    "dealfilter": {
      "url": "https://dealfilter.ai/api/mcp",
      "headers": {
        "Authorization": "Bearer df_<key>"
      }
    }
  }
}

MCP Protocol: initialize

POST /api/mcp
Authorization: Bearer df_<key>
Content-Type: application/json
Accept: application/json, text/event-stream

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {},
    "clientInfo": { "name": "claude-code", "version": "1.0" }
  }
}

Response (SSE)

event: message
data: {"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{"listChanged":true}},"serverInfo":{"name":"dealfilter","version":"1.0.0"}},"jsonrpc":"2.0","id":1}

MCP Protocol: tools/list

POST /api/mcp
Authorization: Bearer df_<key>
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}

Response (excerpt)

{
  "result": {
    "tools": [
      { "name": "submit_inquiry",  "description": "... (async, polling required)" },
      { "name": "list_inquiries",  "description": "..." },
      { "name": "get_inquiry",     "description": "..." },
      { "name": "change_phase",    "description": "..." },
      { "name": "get_quota",       "description": "..." }
    ]
  }
}

MCP Tools in Detail


submit_inquiry Ultra (coming soon)

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "submit_inquiry",
    "arguments": {
      "mailText": "Subject: Java Developer for 6 months\n\nHello...",
      "channel": "EMAIL",
      "newAgentName": "Hays AG"
    }
  }
}

Response

{
  "result": {
    "content": [{ "type": "text", "text": "{\"applicationId\":\"cmq0xyz123\",\"status\":\"analyzing\"}" }],
    "isError": false
  }
}

Wait 30-60 seconds, then call get_inquiry with the applicationId.


list_inquiries Premium Ultra (coming soon)

{
  "method": "tools/call",
  "params": {
    "name": "list_inquiries",
    "arguments": { "active": "true", "limit": 10, "offset": 0 }
  }
}

get_inquiry Premium Ultra (coming soon)

{
  "method": "tools/call",
  "params": {
    "name": "get_inquiry",
    "arguments": { "id": "cmq0go2ib000a1h9kn8dno71t" }
  }
}

If analysisStatus = "analyzing": poll again. If "done": evaluate scoreLabel, scorePercent, redFlags, hints, aiSummary.


change_phase Ultra (coming soon)

{
  "method": "tools/call",
  "params": {
    "name": "change_phase",
    "arguments": { "id": "cmq0go2ib000a1h9kn8dno71t", "status": "APPLIED" }
  }
}

get_quota Premium Ultra (coming soon)

{
  "method": "tools/call",
  "params": {
    "name": "get_quota",
    "arguments": {}
  }
}

OAuth Connector

MCP clients such as Claude.ai do not connect with a manually pasted df_ key but through a standards-compliant OAuth 2.0 flow (RFC 6749 with PKCE S256). DealFilter issues a dfo_ token that is managed in the background.

Discovery

DealFilter publishes metadata per RFC 8414 and RFC 9728:

GET /.well-known/oauth-authorization-server
GET /.well-known/oauth-protected-resource

The authorization server metadata lists every endpoint:

{
  "issuer": "https://dealfilter.ai",
  "authorization_endpoint": "https://dealfilter.ai/api/oauth/authorize",
  "token_endpoint": "https://dealfilter.ai/api/oauth/token",
  "registration_endpoint": "https://dealfilter.ai/api/oauth/register",
  "revocation_endpoint": "https://dealfilter.ai/api/oauth/revoke",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"]
}

Flow

  1. Client registration (POST /api/oauth/register): The client registers dynamically with redirect_uris (HTTPS only, except localhost) and receives a client_id.
  2. Authorization (GET /api/oauth/authorize): The user signs in and approves access on a consent page. PKCE with code_challenge_method=S256 is mandatory.
  3. Token exchange (POST /api/oauth/token): The client exchanges the code plus code_verifier for a dfo_ token.
{
  "access_token": "dfo_...",
  "token_type": "Bearer",
  "expires_in": 7776000
}

Token expiry and renewal

dfo_ tokens expire after 90 days (expires_in in seconds). There are deliberately no refresh tokens: when a token expires, the MCP client automatically restarts the connector flow on the next 401 and obtains a fresh token - no manual action required.

End a connection

Two ways:

  • In the app: Settings > API Access > Connected apps > "Disconnect". The app loses access immediately.
  • Via endpoint (RFC 7009): The client revokes its own token.
POST /api/oauth/revoke
Content-Type: application/json

{ "token": "dfo_<token>" }

The response is always 200, even for an unknown token (per RFC 7009).


CLI

Requirement: Node.js 18+

Installation

# Install globally once (recommended)
npm install -g @dealfilter/cli

# Or use directly without installation via npx
npx @dealfilter/cli <subcommand>

Configuration

Option 1 - Environment variable (recommended for CI/scripts):

export DEALFILTER_API_KEY=df_<key>
export DEALFILTER_BASE_URL=https://dealfilter.ai  # optional

Option 2 - Config file ~/.dealfilter.json:

{
  "apiKey": "df_<key>",
  "baseUrl": "https://dealfilter.ai"
}

Priority: env variable > config file. baseUrl is optional (default: https://dealfilter.ai).


submit Ultra (coming soon) — Submit inquiry

# Text as argument
dealfilter submit "Hello, we are looking for a Java developer for 6 months..."

# Text from file
dealfilter submit --file=/tmp/inquiry.txt

# Text from stdin (pipeline)
cat inquiry.txt | dealfilter submit
pbpaste | dealfilter submit --channel=EMAIL --agent="Hays AG"

Options:

FlagValuesDescription
--file=<path>file pathRead text from file
--channel=<value>EMAIL|PORTAL|PHONE|SEARCH_AGENTDefault: EMAIL
--agent=<name>stringNew recruiter name
--agentId=<id>stringExisting recruiter ID

Output:

{ "applicationId": "cmq0xyz123", "status": "analyzing" }

list Premium Ultra (coming soon) — List inquiries

dealfilter list --active=true       # all active
dealfilter list --phase=INCOMING    # INCOMING only
dealfilter list --limit=5 --offset=10
dealfilter list --active=false      # archived

Options:

FlagValuesDescription
--phase=<value>phase enumExact phase filter
--active=true|falsebooleanActive/archived filter
--limit=<n>1-100Default: 50
--offset=<n>intDefault: 0

Output:

{
  "items": [
    {
      "id": "cmq0go2ib000a1h9kn8dno71t",
      "status": "INCOMING",
      "channel": "PORTAL",
      "projectTitle": "Senior Software Engineer - Microservices",
      "agentName": "SOLCOM",
      "analysisStatus": "done",
      "scoreLabel": "GREEN",
      "scorePercent": 88,
      "createdAt": "2026-06-05T05:05:47.699Z",
      "lastContactAt": "2026-06-05T05:07:49.699Z"
    }
  ],
  "total": 32,
  "limit": 50,
  "offset": 0
}

get Premium Ultra (coming soon) — Get single inquiry

dealfilter get cmq0go2ib000a1h9kn8dno71t

Output (analysisStatus = "done"):

{
  "id": "cmq0go2ib000a1h9kn8dno71t",
  "status": "INCOMING",
  "analysisStatus": "done",
  "scoreLabel": "GREEN",
  "scorePercent": 88,
  "redFlags": [],
  "hints": ["Java 17", "Kubernetes", "Remote 80%"],
  "aiSummary": "Solid Java project...",
  "project": {
    "title": "Senior Software Engineer - Microservices",
    "location": "Frankfurt",
    "remotePercentMin": 60,
    "remotePercentMax": 80,
    "durationMonthsMin": 6,
    "durationMonthsMax": 12
  },
  "agentName": "SOLCOM",
  "communicationLog": []
}

Output (analysis still running):

{
  "id": "cmq0xyz123",
  "analysisStatus": "analyzing",
  "scoreLabel": null,
  "scorePercent": null,
  "redFlags": null,
  "hints": null,
  "aiSummary": null
}

phase Ultra (coming soon) — Change phase

dealfilter phase cmq0go2ib000a1h9kn8dno71t APPLIED

Valid phases: INCOMING > APPLIED > PROFILE_AT_CLIENT > WON / CONTRACT_SIGNED > ARCHIVED

Output:

{ "id": "cmq0go2ib000a1h9kn8dno71t", "status": "APPLIED" }

quota Premium Ultra (coming soon) — Check quota

dealfilter quota

Output:

{
  "used": 44,
  "limit": 500,
  "remaining": 456,
  "resetAt": "2026-07-01T00:00:00.000Z"
}

Error Reference

HTTP Statuserror valueDescription
401unauthorizedMissing or invalid Bearer token
403api.read requires Premium plan or higherRead operations: no Premium or higher
403api.write requires Ultra planSubmit/change phase: no Ultra
400invalid_idInvalid applicationId
400validation_errorRequired fields missing or wrong types
400invalid_jsonRequest body is not valid JSON
404not_foundInquiry not found (or belongs to another user)
429quota_exceededCredit limit reached
429rate_limit_exceededToo many requests in a short period

quota_exceeded response:

{
  "error": "quota_exceeded",
  "remaining": 0,
  "limit": 500,
  "used": 500
}

Async Contract

submit_inquiry / POST /api/v1/applications follows a fire-and-poll pattern:

1. POST /api/v1/applications   →  202 { applicationId, status: "analyzing" }
2. Wait 30-60 seconds
3. GET /api/v1/applications/{id}
   → analysisStatus: "analyzing"  →  wait and retry
   → analysisStatus: "done"       →  result available
   → analysisStatus: "failed"     →  analysis permanently failed

For AI agents: Treating a submit_inquiry tool call as complete does NOT mean the result is ready. Always poll with get_inquiry after submitting until analysisStatus != "analyzing". Never interpret an empty scoreLabel as a finished result.

Recommended polling interval: 30 seconds, max. 5 attempts (analysis typically takes 10-30s after submit).