Skip to main content

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

EndpointWhat it does
GET /v1/healthLiveness
GET /v1/usageCredit position
GET /v1/assessment-typesWhat you can create, and what it costs
GET /v1/round-typesInterview shapes and their default lengths

Roles and candidates

EndpointWhat it does
POST /v1/jobsRegister a role
GET /v1/jobs, GET /v1/jobs/{id}List and read roles
POST /v1/candidatesRegister a candidate
GET /v1/candidates, GET /v1/candidates/{id}List and read candidates

Assessments

EndpointWhat it does
POST /v1/assessmentsCreate one
GET /v1/assessmentsList, filterable by job_id
GET /v1/assessments/{id}Read one
GET /v1/assessments/{id}/reportThe scored result
POST /v1/assessments/{id}/launch-linkA single-use candidate link

Embedding

EndpointWhat it does
POST /v1/domainsAllowlist an origin
POST /v1/sessionsMint a browser session
POST /v1/sessions/refreshRefresh before expiry
POST /v1/launch/exchangeTrade a launch token for a session
DELETE /v1/domains/{id}Remove an origin
DELETE /v1/sessions/{jti}Revoke a session early

Webhooks

EndpointWhat it does
POST /v1/webhook-endpointsRegister an endpoint
GET /v1/webhook-endpointsList 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.

HeaderMeaning
X-RateLimit-LimitThe ceiling for this window
X-RateLimit-RemainingWhat is left
X-RateLimit-ResetUnix time the window resets
Retry-AfterSeconds 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.