Programmatically send invites and fetch submission results from your own backend. Bearer-API-key auth, org-scoped.
REST API
api/v1/* is Litmus's public REST surface. Use it from a backend (ATS, your
own portal, a coding-contest workflow) to send candidate invites and pull
submission results without going through the dashboard. Every request is
scoped to the calling org via a Bearer API key minted in
Settings → API Keys.
The same key works for the MCP server; REST and MCP are two ways to call the same underlying handlers.
Quickstart
1. Mint an API key
Dashboard → Settings → API Keys → New key. You'll see the full key once; copy it.
2. List your roles
curl https://www.litmushiring.com/api/v1/roles \
-H "Authorization: Bearer litmus_sk_..."Returns:
{
"roles": [
{
"id": "role_abc123",
"title": "Backend Engineer",
"status": "open",
"team": "Platform",
"location": "Remote",
"type": "full_time",
"assessment_count": 2,
"created_at": "2026-05-22T..."
}
]
}3. Send an invite
curl -X POST https://www.litmushiring.com/api/v1/invites \
-H "Authorization: Bearer litmus_sk_..." \
-H "Content-Type: application/json" \
-d '{
"posting_id": "role_abc123",
"candidate_email": "candidate@example.com",
"candidate_name": "Sample Candidate"
}'Returns:
{
"invite_id": "cand_xyz789",
"invite_url": "https://www.litmushiring.com/sign-in?assessmentToken=..."
}Litmus emails the candidate automatically. The invite_url is the same link
the email contains; surface it in your own portal if you want.
posting_id accepts both roles and cohorts (a cohort is a bundle of
assessments). Pass the id of either — the request is identical:
curl -X POST https://www.litmushiring.com/api/v1/invites \
-H "Authorization: Bearer litmus_sk_..." \
-H "Content-Type: application/json" \
-d '{
"posting_id": "cohort_neo2026",
"candidate_email": "candidate@example.com",
"candidate_name": "Sample Candidate"
}'Find a posting's id in its dashboard URL (/company/posting/<id>).
GET /api/v1/roles lists role ids; cohort ids aren't listed there yet.
assessment_id is optional — when omitted, the posting's first pipeline
assessment is used. Pass one explicitly to target a later stage.
role_id is deprecated. The endpoint originally accepted role_id; it
still works, but only for role-type postings, and will be removed in a future
version. Use posting_id instead. Passing a cohort id via role_id returns
400 ROLE_ID_TYPE_MISMATCH.
Invite errors
| Status | error | Meaning |
|---|---|---|
| 404 | posting_id not found | Unknown id, or it belongs to another org. |
| 400 | ROLE_ID_TYPE_MISMATCH | A non-role id was sent via role_id — use posting_id. |
| 400 | SELF_SELECT_UNSUPPORTED | The assessment is in a self-select group — not yet invitable via the API. |
| 400 | POSTING_CLOSED | Reopen the posting before inviting. |
| 409 | ALREADY_INVITED | The candidate is already on that assessment. |
4. Poll for results
curl https://www.litmushiring.com/api/v1/submissions/<submission_id> \
-H "Authorization: Bearer litmus_sk_..."Returns:
{
"id": "sub_def456",
"assessment_id": "asmt_ghi789",
"candidate": { "email": "candidate@example.com", "name": "Sample Candidate" },
"score": 4.2,
"decision": "advance",
"submitted_at": "2026-05-26T...",
"report_url": "https://www.litmushiring.com/company/candidates?submission=sub_def456"
}score is null until grading completes (typically a few minutes after
the candidate submits). decision defaults to "pending" and flips when
a reviewer makes a call in the dashboard.
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/roles | List all roles in your org. |
POST | /api/v1/invites | Send an invite to one candidate. |
GET | /api/v1/submissions/{id} | Read a single submission. |
Authentication
Every request needs an Authorization: Bearer litmus_sk_... header. Keys
are minted in Settings → API Keys and live for as long as the page row
exists; revoke from the same screen.
- 401 means the header is missing, malformed, names an unknown prefix, or the key was revoked. The error message is intentionally the same in every case ("Invalid API key") so probes can't fingerprint the failure mode.
- One key can have any number of in-flight requests. There's no per-key rate limit today — but please don't hammer; we'll add caps if abuse shows up.
Webhooks?
Not yet. Polling GET /api/v1/submissions/{id} is fine for low-volume
flows (Neo's launch handles 5–10 candidates/day comfortably). When a
specific integration blocks on push delivery, we'll build webhooks with
proper retry/idempotency/signing — until then, half-built push delivery
would be worse than none.
Versioning
v1 is a frozen contract. New fields are additive; existing fields don't
change shape. Breaking changes ship as /api/v2/* with both versions
running in parallel.
