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

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
type | HTTP | Meaning |
|---|---|---|
authentication_error | 401 | Missing, invalid or revoked credential |
permission_error | 403 | Authenticated, not permitted |
invalid_request_error | 400 | Malformed or failed validation |
not_found_error | 404 | No such resource |
rate_limit_error | 429 | Too many requests |
idempotency_error | 400/409 | Idempotency key problem |
insufficient_credits | 402 | Out of credits or sandbox quota |
capacity_error | 429 | Platform capacity, retry shortly |
api_error | 5xx | Our fault |
Codes worth handling
| Code | What to do |
|---|---|
invalid_api_key | Check the key and environment |
revoked_api_key | Rotate |
invalid_session_token | Refresh the session |
origin_not_allowed | Add the origin via POST /v1/domains |
invalid_origin | The origin is malformed; scheme and host only, no path |
invalid_launch_token | The link is expired or already used; mint a new one |
session_scope_mismatch | The session is bound to a different interview |
unprocessable_resume | The file could not be read as a resume |
invalid_round_type | Unknown round; the message lists the ones you have |
report_not_ready | Not scored yet; retry, the interview is fine |
transcript_not_ready | Processing has not finished; retry, the interview is fine |
recording_not_ready | No recording exists for this interview |
public_api_not_enabled | Ask us to enable the API |
module_not_enabled | Feature is off for this organisation |
insufficient_credits | Top up |
sandbox_quota_exceeded | Wait for the reset or ask for more |
capacity_unavailable | Back 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.