API Reference
Programmatically match CVs against job descriptions using the CV Matching API.
Quick Start
- Get an API key — Create one from your Account page.
- Submit a match job —
POST /api/matchwith your CVs and a job description. - Get results — Poll
GET /api/match/{job_id}until processing completes.
Authentication
All API requests require an API key. Include it via the X-API-Key header (preferred) or as a Authorization: Bearer token.
Create and manage API keys on your Account page. Keys are created with the match:execute scope by default, which implies match:read.
curl https://cv-matching-api.fly.dev/api/match \
-H "X-API-Key: sk_live_your_key_here" \
-F "cvs=@resume.pdf" \
-F "jd_text=Registered Nurse — ICU Department..."Submit Match Job
/api/matchSubmit CVs and a job description for matching. The request must be multipart/form-data.
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| cvs | File[] | Yes | PDF, DOCX, TXT. Max 5MB each. Batch limit: Free 10, Starter 20, API 25. |
| jd_file | File | No* | Job description file. |
| jd_text | string | No* | Job description text (max 50,000 chars). |
*One of jd_file or jd_text is required.
# With a text JD
curl -X POST https://cv-matching-api.fly.dev/api/match \
-H "X-API-Key: sk_live_your_key_here" \
-F "cvs=@resume1.pdf" \
-F "cvs=@resume2.pdf" \
-F "jd_text=Warehouse Associate — Day shift, forklift certification preferred..."
# With a file JD
curl -X POST https://cv-matching-api.fly.dev/api/match \
-H "X-API-Key: sk_live_your_key_here" \
-F "cvs=@resume.pdf" \
-F "jd_file=@job_description.pdf"Response
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"cv_count": 3,
"message": "Job created. Poll /api/match/{job_id} for results.",
"skipped_cvs": []
}Poll Progress
/api/match/{job_id}/progressCheck how many CVs have been scored. Poll every 1-2 seconds until status is "completed".
curl https://cv-matching-api.fly.dev/api/match/550e8400-e29b-41d4-a716-446655440000/progress \
-H "X-API-Key: sk_live_your_key_here"Response
{
"status": "processing",
"total": 3,
"completed": 1,
"pending": 1,
"processing": 1
}Get Results
/api/match/{job_id}Retrieve the full match results once processing is complete. Each CV receives a score from 0-100 along with a detailed assessment.
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"results": [
{
"cv_filename": "alice_chen_resume.pdf",
"match_score": 82,
"executive_summary": "Strong backend engineer with 6 years of Python and FastAPI experience. Excellent systems design skills with minor gaps in frontend technologies.",
"strengths": [
"6 years of Python development including FastAPI and Django",
"Strong systems design and distributed systems experience",
"AWS and Docker deployment experience"
],
"gaps": [
"No React or frontend framework experience listed",
"Limited experience with the specific industry domain"
],
"recommendation": "Interview",
"verdict": "STRONG_MATCH"
},
{
"cv_filename": "bob_smith_resume.pdf",
"match_score": 31,
"executive_summary": "Junior developer with limited experience in the required tech stack. Shows potential but lacks seniority for the role.",
"strengths": [
"Computer Science degree from reputable university",
"Some exposure to Python through coursework"
],
"gaps": [
"Only 1 year of professional experience vs 5+ required",
"No production API development experience",
"Missing cloud infrastructure skills"
],
"recommendation": "Not recommended",
"verdict": "WEAK_MATCH"
}
]
}Verdicts
| Verdict | Score Range | Recommendation |
|---|---|---|
| STRONG_MATCH | 75-100 | Interview |
| GOOD_MATCH | 60-74 | Interview |
| MODERATE_MATCH | 40-59 | Consider |
| WEAK_MATCH | 20-39 | Not recommended |
| NOT_SUITABLE | 0-19 | Not recommended |
Complete Example
import time
import requests
API_KEY = "sk_live_your_key_here"
BASE = "https://cv-matching-api.fly.dev"
headers = {"X-API-Key": API_KEY}
# 1. Submit match job
with open("alice.pdf", "rb") as f1, open("bob.pdf", "rb") as f2:
resp = requests.post(
f"{BASE}/api/match",
headers=headers,
files=[
("cvs", ("alice.pdf", f1, "application/pdf")),
("cvs", ("bob.pdf", f2, "application/pdf")),
],
data={"jd_text": "Construction Project Manager — 5+ years commercial builds..."},
)
job = resp.json()
job_id = job["job_id"]
print(f"Job created: {job_id}")
# 2. Poll until complete (timeout after ~4 min)
for _ in range(120):
progress = requests.get(
f"{BASE}/api/match/{job_id}/progress", headers=headers
).json()
print(f" {progress['completed']}/{progress['total']} done")
if progress["status"] == "completed":
break
if progress["status"] == "failed":
raise RuntimeError("Job failed")
time.sleep(2)
# 3. Get results
results = requests.get(
f"{BASE}/api/match/{job_id}", headers=headers
).json()
for r in results["results"]:
print(f"{r['cv_filename']}: {r['match_score']}/100 — {r['verdict']}")Credits
Each CV matched against a job description consumes 1 credit. Credits are deducted when a match job is submitted.
Credit Packs
| Pack | Price | Per Match |
|---|---|---|
| 100 credits | $1.00 | $0.010 |
| 500 credits | $4.00 | $0.008 |
| 1,000 credits | $7.00 | $0.007 |
Purchase credit packs on the Pricingpage. Check your balance on the dashboard — the GET /api/credits/balance endpoint requires JWT authentication (not an API key).
When your credit balance reaches zero, API requests will return a 402 error.
Rate Limits
API keys are rate limited to 60 requests per minute by default. Limits are tracked per API key using a sliding window.
Response Headers
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests allowed per window |
| X-RateLimit-Remaining | Requests remaining in current window |
| X-RateLimit-Reset | Unix timestamp when the window resets |
When rate limited, the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait before retrying.
Error Codes
Errors are returned as JSON with a detail field describing the issue.
{
"detail": "Invalid or missing API key"
}| Code | Meaning |
|---|---|
| 400 | Bad request (missing fields, invalid file type) |
| 401 | Invalid or missing API key |
| 402 | Insufficient credits |
| 404 | Job not found |
| 429 | Rate limit exceeded |
| 500 | Server error |
MCP Server (AI Agent Integration)
Use our MCP (Model Context Protocol) server to integrate CV matching into AI assistants like Claude Desktop. The MCP server wraps the same REST API with 3 tools for file-based matching.
Setup
Add this to your Claude Desktop config (claude_desktop_config.json):
{
"mcpServers": {
"cv-matching": {
"command": "python",
"args": ["~/mcp_server.py"],
"env": {
"CV_MATCH_API_URL": "https://cv-matching-api.fly.dev",
"CV_MATCH_API_KEY": "your-api-key-here"
}
}
}
}Download mcp_server.py and save it to your home directory. Get your API key from the Account page. Requires pip install mcp httpx.
Tools
match_cvsSubmit CVs for matching| Field | Type | Required | Description |
|---|---|---|---|
| cv_paths | string[] | Yes | Absolute paths to CV files (PDF, DOCX, TXT) |
| jd_text | string | Yes | Job description text |
Returns: job_id, status, cv_count
get_match_resultsGet results for a completed job| Field | Type | Required | Description |
|---|---|---|---|
| job_id | string | Yes | Job ID from match_cvs |
Returns: results[] with scores, summaries, strengths, gaps
get_match_progressCheck processing progress| Field | Type | Required | Description |
|---|---|---|---|
| job_id | string | Yes | Job ID from match_cvs |
Returns: total, completed, pending, status
Example Usage
# In Claude Desktop, just ask:
"Match these CVs against the Senior Engineer JD"
# Claude will use the MCP tools to:
# 1. Call match_cvs with your file paths
# 2. Poll get_match_progress until complete
# 3. Retrieve results with get_match_results
# 4. Present the ranked candidates