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).
| Plan | List, get, quota | Submit | Change phase |
|---|---|---|---|
| Essential | - | - | - |
| Premium | ✓ | - | - |
| Ultra (coming soon) | ✓ | ✓ | ✓ |
Table of Contents
- Authentication and Key Management
- REST API v1
- MCP Server
- OAuth Connector and Token Management
- CLI
- Error Reference
- Async Contract: Submit and Poll
Authentication
DealFilter uses two kinds of access tokens:
| Token | Prefix | Origin | Used for |
|---|---|---|---|
| Personal API key | df_ | Created manually under Settings > API Access | REST API, MCP server, CLI, custom scripts |
| OAuth connector token | dfo_ | 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"
}
| Field | Type | Required | Description |
|---|---|---|---|
mailText | string (min 10) | yes | Full text of the inquiry |
channel | EMAIL|PORTAL|PHONE|SEARCH_AGENT | no | Default: EMAIL |
agentId | string | no | ID of an existing recruiter |
newAgentName | string | no | Name 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
| Parameter | Type | Default | Description |
|---|---|---|---|
phase | string | - | Exact phase filter (see below) |
active | true|false | - | true = not archived, false = ARCHIVED only |
limit | int (1-100) | 50 | Number of results |
offset | int | 0 | Offset 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:
| Value | Meaning |
|---|---|
analyzing | Analysis still running - check again later |
done | Score, redFlags, hints and aiSummary available |
failed | Analysis 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
- Client registration (
POST /api/oauth/register): The client registers dynamically withredirect_uris(HTTPS only, exceptlocalhost) and receives aclient_id. - Authorization (
GET /api/oauth/authorize): The user signs in and approves access on a consent page. PKCE withcode_challenge_method=S256is mandatory. - Token exchange (
POST /api/oauth/token): The client exchanges thecodepluscode_verifierfor adfo_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:
| Flag | Values | Description |
|---|---|---|
--file=<path> | file path | Read text from file |
--channel=<value> | EMAIL|PORTAL|PHONE|SEARCH_AGENT | Default: EMAIL |
--agent=<name> | string | New recruiter name |
--agentId=<id> | string | Existing 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:
| Flag | Values | Description |
|---|---|---|
--phase=<value> | phase enum | Exact phase filter |
--active=true|false | boolean | Active/archived filter |
--limit=<n> | 1-100 | Default: 50 |
--offset=<n> | int | Default: 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 Status | error value | Description |
|---|---|---|
| 401 | unauthorized | Missing or invalid Bearer token |
| 403 | api.read requires Premium plan or higher | Read operations: no Premium or higher |
| 403 | api.write requires Ultra plan | Submit/change phase: no Ultra |
| 400 | invalid_id | Invalid applicationId |
| 400 | validation_error | Required fields missing or wrong types |
| 400 | invalid_json | Request body is not valid JSON |
| 404 | not_found | Inquiry not found (or belongs to another user) |
| 429 | quota_exceeded | Credit limit reached |
| 429 | rate_limit_exceeded | Too 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).