Skip to main content

Reference

Errors, rate limits, pagination and the machine-readable spec.

Expert Hire developer workflow for Reference
Build against the same evidence trail the product uses.

OpenAPI

Every operation is listed in the API reference, which is generated from the spec rather than written by hand.

The running API serves the same spec it was built from:

GET https://prep-api.experthire.cloud/v1/openapi.yaml

A copy is published here as prep-v1.yaml. Generate a client from either rather than from anything pasted into a wiki. Neither can drift from the deployment: CI fails the build if a route and the spec disagree in either direction.

Errors

Every error has the same shape:

{
  "error": {
    "type": "invalid_request_error",
    "code": "resume_not_found",
    "message": "No resume with id res_... exists for this organisation.",
    "param": "resume_id",
    "request_id": "req_01HX...",
    "doc_url": "https://docs.experthire.io/reference/errors#resume_not_found"
  }
}

Branch on code. It is a closed set and stable. message is for humans and may be reworded.

Quote request_id when you contact support. It is on every response, including successful ones.

Types

typeHTTPMeaning
authentication_error401Missing, invalid or revoked credential
permission_error403Authenticated, not permitted
invalid_request_error400Malformed or failed validation
not_found_error404No such resource
rate_limit_error429Too many requests
idempotency_error400/409Idempotency key problem
insufficient_credits402Out of credits or sandbox quota
capacity_error429Platform capacity, retry shortly
api_error5xxOur fault

Codes worth handling

CodeWhat to do
invalid_api_keyCheck the key and environment
revoked_api_keyRotate
invalid_session_tokenRefresh the session
origin_not_allowedAdd the origin via POST /v1/domains
invalid_originThe origin is malformed; scheme and host only, no path
invalid_launch_tokenThe link is expired or already used; mint a new one
session_scope_mismatchThe session is bound to a different interview
unprocessable_resumeThe file could not be read as a resume
invalid_round_typeUnknown round; the message lists the ones you have
report_not_readyNot scored yet; retry, the interview is fine
transcript_not_readyProcessing has not finished; retry, the interview is fine
recording_not_readyNo recording exists for this interview
public_api_not_enabledAsk us to enable the API
module_not_enabledFeature is off for this organisation
insufficient_creditsTop up
sandbox_quota_exceededWait for the reset or ask for more
capacity_unavailableBack off and retry

A resource that exists but belongs to another organisation returns 404, not 403. That is deliberate: a 403 would confirm the id is real.

Rate limits

Limits are per API key, not per IP, so you are not affected by anyone else's traffic. Rough tiers: reads are generous, writes are moderate, and anything that runs a model is tight.

429 responses carry X-RateLimit-*. Back off exponentially. If your workload genuinely needs more, we can raise an individual key without a deploy.

Pagination

List endpoints are cursor paginated:

GET /v1/webhook-endpoints?limit=50&starting_after=<id>

limit defaults to 20 and caps at 100. Follow next_cursor while has_more is true, and treat it as opaque: do not parse it, and do not compute your own offsets.

On interviews and other record lists the cursor is a position in the data, so it holds even while rows are being inserted underneath you. The one exception is the question bank, where the ordering is a ranking rather than a timeline and a page taken during a bank refresh can shift. That page says so.

Retries and duplicate work

Send an Idempotency-Key on every create. It is a header you choose, one per logical operation, and it is what makes a timed-out retry safe:

curl -X POST https://prep-api.experthire.cloud/v1/interviews \
  -H "Authorization: Bearer $EH_SECRET_KEY" \
  -H "Idempotency-Key: 8f14e45f-ea1a-4a2b-9a1f-2c3d4e5f6071" \
  -H "Content-Type: application/json" \
  -d '{"candidate_id": "...", "round_type": "general_interview", "difficulty": 2, "role": "Backend Engineer"}'

Still a placeholder: $EH_SECRET_KEY. Add it under Your values above.

Repeat the call with the same key and the same body and you get the first response back verbatim, with Idempotent-Replay: true. One interview, one credit. Reuse the key with a different body and you get 409 idempotency_key_reuse, because that is a bug on your side rather than a retry. A retry that races the first call still in flight gets 409 idempotency_in_progress.

Keys are remembered for 24 hours and scoped to your organisation. Only a successful response is stored, so a call that failed validation releases the key and you can correct the body and retry with it.

The header is optional, and skipping it is the mistake worth naming: without it a POST that times out may or may not have completed, and a blind retry creates a second record and spends a second credit.

Versioning

The path carries the major version. We will not make a breaking change to /v1.

Additive changes ship without notice, so parse defensively: ignore unknown fields, and do not assume an optional field is present.

If /v2 ever happens, /v1 gets at least twelve months of support and we will tell you before the clock starts.