API reference
Every endpoint, and the machine-readable spec.
The full contract is published as OpenAPI and served by the API itself:
GET https://api.experthire.cloud/v1/openapi.yaml
It is the source of truth. The routes below are a map, not a substitute.
Meta
| Endpoint | What it does |
|---|---|
GET /v1/health | Liveness |
GET /v1/usage | Credit position |
GET /v1/assessment-types | What you can create, and what it costs |
GET /v1/round-types | Interview shapes and their default lengths |
Roles and candidates
| Endpoint | What it does |
|---|---|
POST /v1/jobs | Register a role |
GET /v1/jobs, GET /v1/jobs/{id} | List and read roles |
POST /v1/candidates | Register a candidate |
GET /v1/candidates, GET /v1/candidates/{id} | List and read candidates |
Assessments
| Endpoint | What it does |
|---|---|
POST /v1/assessments | Create one |
GET /v1/assessments | List, filterable by job_id |
GET /v1/assessments/{id} | Read one |
GET /v1/assessments/{id}/report | The scored result |
POST /v1/assessments/{id}/launch-link | A single-use candidate link |
Embedding
| Endpoint | What it does |
|---|---|
POST /v1/domains | Allowlist an origin |
POST /v1/sessions | Mint a browser session |
POST /v1/sessions/refresh | Refresh before expiry |
POST /v1/launch/exchange | Trade a launch token for a session |
DELETE /v1/domains/{id} | Remove an origin |
DELETE /v1/sessions/{jti} | Revoke a session early |
Webhooks
| Endpoint | What it does |
|---|---|
POST /v1/webhook-endpoints | Register an endpoint |
GET /v1/webhook-endpoints | List endpoints for this environment |
DELETE /v1/webhook-endpoints/{id} | Remove one |
Pagination
Every list takes limit (default 20, max 100) and starting_after. The response
is an envelope, not a bare array.
{
"object": "list",
"data": [],
"has_more": true,
"next_cursor": "c_b2ZmXzIw",
"total_count": 143
}
Loop on has_more, passing the previous next_cursor as starting_after.
Cursors are opaque. Do not parse one, and do not construct one from an offset. The encoding is ours to change and the current shape is not part of the contract.
Rate limits
Reads are limited to 150 requests per second and writes to 30, counted per API key rather than per IP, so your test traffic cannot throttle your production traffic. Every response carries the current position.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | The ceiling for this window |
X-RateLimit-Remaining | What is left |
X-RateLimit-Reset | Unix time the window resets |
Retry-After | Seconds to wait, sent only on a 429 |
On a 429, back off for Retry-After rather than retrying immediately.
Idempotency
Send Idempotency-Key on every create. A retry with the same key replays the
original response rather than acting twice, which matters most when a timeout
leaves you unsure whether an assessment was charged.
curl -X POST https://api.experthire.cloud/v1/assessments \
-H "Authorization: Bearer $EH_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f2c1a90-3d7e-4b18-9c62-0ae4d8b71f35" \
-d '{"job_id": "$JOB_ID", "candidate_id": "$CANDIDATE_ID", "type": "ai_interview"}'
Still a placeholder: $EH_SECRET_KEY, $JOB_ID, $CANDIDATE_ID. Add them under Your values above.
A replayed response carries Idempotent-Replay: true. Keys are scoped to your
organisation and environment, so a sandbox key can never replay a live response.
Reusing one key with a different body is rejected rather than silently ignored.
Errors
Every error is {"error": {type, code, message, request_id, doc_url}}. Branch on
code, not on the message. Quote request_id in support threads.
Versioning
/v1 will not break. Fields are added without a version bump, so ignore
unknown fields: a parser that rejects them will break on a Tuesday when we ship
something unrelated. Removals and semantic changes get a new path.