# Resurface API reference

Base URL: https://getresurface.dev/api/v1 · OpenAPI: https://getresurface.dev/api/v1/openapi.json · Human docs: https://getresurface.dev/docs

## Authentication
Send your API key as `Authorization: Bearer rs_live_…`. Create keys in the dashboard (https://getresurface.dev/dashboard) after signing up (https://getresurface.dev/sign-up). Keys are shown once and stored only as a hash.

## POST /v1/score
Score one resume against a job. Accepts `multipart/form-data` (for files) or JSON.

| Field | Type | Description |
|---|---|---|
| resume | file | PDF, DOCX, HTML or TXT, up to 5 MB (multipart). Or send resume_text. |
| resume_text | string | Plain-text resume, instead of a file. |
| job_description | string | Full job description (at least 40 characters). Questions are drafted and cached. |
| job_title | string | Optional; improves question drafting. |
| rubric_id | string | Reuse the questions of a previous score or of POST /v1/rubrics. |
| questions | array | Bring your own questions: strings, or objects { question, requirement: "must" or "nice", weight, min_years }. Max 12. |

Optional header `Idempotency-Key`: a unique value per resume. Repeating a request with the same key within 24 hours returns the original result (with `Idempotent-Replayed: true`) instead of scoring and charging again.

Example:

```bash
curl https://getresurface.dev/api/v1/score \
  -H "Authorization: Bearer <your API key>" \
  -F job_title="Senior Full Stack Engineer" \
  -F "job_description=<job.txt" \
  -F resume=@candidate.pdf
```

Response fields:

| Field | Type | Description |
|---|---|---|
| score | 0–100 | Share of question weight backed by a quoted resume line. Missing information stays unknown, not contradicted: it lowers this score and widens `range`. |
| match | enum | strong_evidence, needs_verification or lower_evidence. |
| coverage | 0–1 | Share of question weight with a definite answer. |
| range | object | { lower, upper }: lower = score; upper if every unanswered question turned out true. |
| must_have_gaps | string[] | Must-have questions without evidence. |
| must_have_contradicted | string[] | Must-have questions the resume rules out. |
| questions[].verdict | enum | SUPPORTED, CONTRADICTED or INSUFFICIENT_EVIDENCE. |
| questions[].evidence | object or null | { line, text }: a verbatim resume line (ids start at L000). Present for SUPPORTED and CONTRADICTED. |
| questions[].computed | string | For years-of-experience questions: how the years were computed. |
| questions[].probabilities | object | Calibrated probability per verdict. |
| rubric | object | { id, source, questions }. Reuse rubric.id to score more resumes for the same job. |
| resume | object | { lines, language }. |
| billing | object | { billable, price_usd, free_remaining, credits_remaining }. credits_remaining is the prepaid balance. Also in the X-Resurface-Free-Remaining and X-Resurface-Credits-Remaining headers. |
| model | string | The decision model that answered, for example jev-1.13.0, or clef-flash when the fallback answered. |
| latency_ms | number | Server time for this call. A new job description adds a few seconds for drafting; reused rubrics take about 0.25 s. |

## POST /v1/rubrics
Draft screening questions from { job_title, job_description } (JSON) without scoring a resume. Not charged; cached per job text; at most 30 new job descriptions per hour.

## GET /v1/rubrics/{id}
Return a rubric's questions.

## GET /v1/usage
Free and prepaid scores remaining (free_remaining, credits_remaining) and totals.

## MCP server and Claude Code plugin
MCP endpoint: https://getresurface.dev/api/mcp (Streamable HTTP). MCP clients sign in with a Resurface account through OAuth: the endpoint answers 401 with `resource_metadata` pointing to https://getresurface.dev/.well-known/oauth-protected-resource/api/mcp, and the authorization server is https://clerk.getresurface.dev. Scripts and CI can send an API key instead: `Authorization: Bearer rs_live_…`. Tools: `score_resume` (uses one score; job_description or rubric_id, and resume_text or resume_file_base64 with resume_filename), `draft_screening_questions` (free, returns a rubric id) and `get_usage` (free). The Resurface plugin is in Claude's plugin directory (Customize → Plugins → search Resurface). In Claude Code you can also install it with `/plugin marketplace add dolevhayut/resurface` and `/plugin install resurface@resurface`. It asks you to sign in once.

## Errors
Errors are JSON: { "error": { "code", "message" } }.

| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Missing job or resume, or an unreadable file. |
| 401 | missing_api_key / invalid_api_key | Missing, invalid or revoked key. |
| 402 | free_tier_exhausted | No free or prepaid scores left. Buy a score pack in the dashboard. |
| 404 | not_found | Unknown rubric_id. |
| 413 / 415 | invalid_request | File over 5 MB, or an unsupported type such as legacy .doc. |
| 429 | rate_limited / draft_rate_limited | Over 60 scores per minute, or over 30 new job descriptions per hour. |
| 409 | idempotency_key_in_use | A request with the same Idempotency-Key is still running. |
| 502 | evaluator_unavailable | Scoring failed; the score is refunded automatically. |
| 504 | evaluator_timeout | Scoring took too long; the score is refunded automatically. Safe to retry. |

## Pricing and limits
50 free scores per account, then prepaid score packs: 100 scores for $10, 500 for $40, 2,500 for $150 (bought in the dashboard, used after the free scores). Drafting questions is free. 60 scores per minute per developer.
