Screening API
Submit a candidate against a job spec, get an AI score, and get a human-reviewed result on your webhook. Every score is approved, rejected, or edited by a human reviewer before delivery — the API never auto-delivers a model's raw output.
Quickstart
Submit a candidate with one request. The credit is charged and the request queued immediately; scoring and review happen asynchronously.
curl -X POST "https://lokerdollar.com/api/screening/v1/screen" \
-H "Authorization: Bearer lk_live_…" \
-H "Content-Type: application/json" \
-d '{"idempotencyKey":"req-backend-eng-jane-2026-08-03","candidate":{"kind":"structured","fullName":"Jane Doe","email":"jane.doe@example.com","summary":"5 years backend engineering focused on Node.js and PostgreSQL; led a 4-person team at a Series A logistics startup.","skills":["Node.js","PostgreSQL","TypeScript","AWS"],"experienceYears":5},"jobSpec":{"title":"Senior Backend Engineer","mustHaves":["Node.js","PostgreSQL","3+ years backend experience"],"niceToHaves":["AWS","Prior team-lead experience"],"salaryBandMinIdr":25000000,"salaryBandMaxIdr":40000000,"description":"Own the payments service for a Series B fintech."}}'Authentication
Pass an employer API key as a bearer token on every request. A key resolves to exactly one tenant, so a request for another tenant's data always fails, regardless of the ID in the URL.
Authorization: Bearer lk_live_…
Request states
Every screening request settles on exactly one terminal state: delivered, failed, or expired. Nothing hangs indefinitely.
| State | Meaning |
|---|---|
| received | Accepted, credit charged, queued for parsing. |
| parsed | Resume/candidate text resolved. |
| scored | AI score attached, waiting for a human reviewer. |
| pending_review | Sitting in the reviewer queue (expires after 72h if untouched). |
| approved / rejected / edited | A human reviewer decided. Every decision is a delivered result. |
| delivered | The webhook attempt succeeded. |
| failed | Terminal failure (e.g. the AI produced no usable output). Credit refunded. |
| expired | Sat in pending_review past 72h with no decision. Credit refunded. |
Endpoints
POST /api/screening/v1/screen
Submit a candidate. Body: idempotencyKey, candidate (a resume reference or structured fields), jobSpec (title, mustHaves, niceToHaves, salary band, description). Returns 201 with the new request's id and state, or 200 with the same id when idempotencyKey was already accepted.
{
"requestId": "scr_7c1e0a9b4f2d4c8e",
"state": "received"
}402 when the free-tier monthly cap or the paid credit wallet is exhausted. The body always carries error.upsell.upgradeUrl, never a silent degrade.
GET /api/screening/v1/usage
Your current credit wallet, so you can check before submitting instead of finding out via a failed request.
{
"tenantId": "biz_9f3a2c",
"planTier": "free",
"creditsRemaining": 37,
"creditsCap": 50,
"usagePercent": 0.26,
"nearLimit": false,
"resetAt": "2026-09-01T00:00:00.000Z",
"freeScreenCap": 50
}GET /api/screening/v1/audit
Paginated export of every state transition for your tenant: the audit ledger. Query: since (ISO-8601), cursor (from a prior page's nextCursor), limit (1–500, default 100). Append-only, so nothing here is ever updated or deleted.
{
"events": [
{
"id": "scrv_a1b2c3d4",
"requestId": "scr_7c1e0a9b4f2d4c8e",
"fromState": "pending_review",
"toState": "approved",
"actor": "reviewer:412",
"payload": null,
"createdAt": "2026-08-03T09:14:10.000Z"
}
],
"nextCursor": null
}Score result shape
What a completed screen looks like: a 0–100 score, per-dimension breakdowns with evidence quotes, a short rationale, model provenance (modelId/promptVersion), and the reviewer's decision once one exists.
{
"schemaVersion": 1,
"score": 78,
"dimensions": [
{
"name": "must-haves",
"score": 85,
"evidence": "5 years backend engineering focused on Node.js and PostgreSQL"
},
{
"name": "seniority fit",
"score": 70,
"evidence": "led a 4-person team at a Series A logistics startup"
}
],
"rationale": "Meets every must-have and has one prior lead role; salary expectations were not provided so band fit is unconfirmed.",
"confidence": 0.82,
"modelId": "@cf/google/gemma-4-26b-a4b-it",
"promptVersion": "screening-scorer-v1"
}After review, the same request also carries a review decision:
{
"schemaVersion": 1,
"decision": "approved",
"reviewerId": "reviewer_412",
"reviewerRole": "recruiter",
"latencyMs": 46000,
"note": "Strong must-have coverage, forward to the hiring manager."
}Delivery webhook
Once a score or a review decision exists, we POST it to your webhookUrl and sign the raw body with HMAC-SHA256 using your webhookSecret. Verify X-Screening-Signature before trusting the payload.
X-Screening-Signature: sha256=<hex> X-Screening-Event: screening.review_decided X-Screening-Request-Id: scr_7c1e0a9b4f2d4c8e
{
"requestId": "scr_7c1e0a9b4f2d4c8e",
"tenantId": "biz_9f3a2c",
"event": "screening.review_decided",
"result": {
"schemaVersion": 1,
"decision": "approved",
"reviewerId": "reviewer_412",
"reviewerRole": "recruiter",
"latencyMs": 46000,
"note": "Strong must-have coverage, forward to the hiring manager."
},
"deliveredAt": "2026-08-03T09:14:22.000Z"
}A non-2xx response or a timeout is retried with backoff; delivery attempts are also recorded in the audit ledger, so a stalled endpoint is visible from GET /audit even before you fix it.
Errors
Every error returns the same envelope with a stable code, the same contract the jobs API uses:
{ "error": { "code": "payment_required", "message": "…" } }| unauthorized | 401 |
| forbidden | 403 |
| payment_required | 402 |
| not_found | 404 |
| invalid_request | 400 |
| internal | 500 |
This page and the OpenAPI spec are generated from the same request/response contract the server validates every call against — a shape change to the API updates both without a separate rewrite.
By using the API you agree to the API Terms of Service.