Skip to main content
Every error carries a machine-readable error_kind. Branch on the kind, never on the status: several kinds share a status and want different remedies, and new kinds can be added within a status you already handle.

The two envelopes

Parse defensively. Two shapes exist and they differ. A request-shape failure (HTTP 422 from the validation layer) uses the ValidationError envelope, with an errors array of field locations so you can pinpoint the offending field:
Everything else uses the Error envelope, where detail is either a string or a structured object carrying error_kind:
Some kinds carry extra keys on detail alongside error_kind. Those are named per kind below. Whenever detail carries a registered error_kind, the body also carries docs_url: a direct link to that kind’s entry on this page.

Correlation

Every response carries an X-Request-Id header, and error bodies echo it as request_id. Quote it in any support request or bug report.

Open union

error_kind is an open union. New kinds may be added inside a status you already handle, so treat an unrecognized kind as the generic case for its status rather than failing on it.

401 Unauthorized

invalid_api_key

The bearer token is missing, malformed, unknown, or revoked. One kind covers all four deliberately: distinguishing them would confirm which guesses are real keys. Check the Authorization header. If the key was revoked, mint a replacement in Settings, then Developers.

402 Payment Required

All three refuse the turn without applying it, and the session becomes terminal. Branch on the kind, because the first two share a terminal status of balance_exhausted.

credits_exhausted

No spendable credits are left. Top up.

plan_allowance_exhausted

The plan’s allowance for this period is spent. It refreshes on the date in the message, or top up to continue now.

spend_cap_reached

The workspace’s own per-period spend cap was reached. Raise the cap, or wait for the period to roll over. The session’s terminal status is spend_cap_reached.

403 Forbidden

insufficient_scope

The API key lacks the scope this operation requires. detail.required_scope names it. Mint a key with the scope, or ask for it if the key manages a registry rather than just running against it. The :write scopes on environments and tasks are never granted by default.

api_key_not_permitted

This operation does not admit API keys at all, and no scope grants it. Call it with an ish user access token.

person_out_of_scope

A session can only be opened for a person the workspace owns or one published in the platform library. Real-person “me” profiles are out of scope by policy. Use an in-scope person_id.

404 Not Found

Each of these means the same thing: the record was not found, or it is not reachable with this credential. The two are deliberately indistinguishable, so a wrong id and someone else’s id look identical. Check the id, and check that your token’s workspace owns it.

workspace_not_found

The workspace_id in the path.

session_not_found

The session_id in the path.

person_not_found

The person_id on a session create, or in the path on a person read. A person your workspace neither owns nor can reach in the platform library answers this rather than a 403, so a guessed id cannot be used to test which ids are real.

environment_not_found

The environment_id, on a session create or an environment operation.

environment_version_not_found

The version_id, or the environment_version label a session tried to pin.

task_not_found

The task.id a session tried to bind, or the task_id in the path.

409 Conflict

idempotency_conflict

The Idempotency-Key was reused with a different request body. A key maps to exactly one create request. Use a fresh key, or resend the identical body to replay the original.

session_not_open

You posted a turn to a session that has already ended. The session is terminal; open a new one.

turn_index_mismatch

The turn_index you sent does not match the session’s current turn. detail.expected_turn_index carries the index the server expected. Resubmit at that index.

environment_name_taken

An environment with this name already exists in the workspace. Names are unique per workspace, case-insensitive. With get_or_create, only a name held by a different kind conflicts.

environment_address_taken

Another environment in the workspace already claims this address.

environment_archived

The environment resolves but is retired, so it cannot accept new sessions or new versions. Register a new environment, or describe one inline on the session.

environment_version_label_taken

The label collides with another live version of this environment. detail.existing_version_id carries the holder.

environment_version_archived

The version is archived. An archived version cannot be pinned by a new session, and cannot become the default, since it would refuse the very sessions the pointer sent it.

environment_version_config_frozen

config and example_actions are frozen once any session references this version, so a PATCH that carries either is refused. Metadata stays editable for the life of the row. Send only the fields you actually changed: a PATCH that echoes an unchanged config back is refused the same way as one that edits it.

environment_version_conflict

A concurrent write to this version won the race. Re-read and retry.

environment_version_default_conflict

A concurrent set-default won the race. Re-read the environment and retry.

task_name_taken

A task with this name already exists in the workspace family. Names are unique per family, case-insensitive.

task_archived

The task resolves but is retired. Register a new task, or pass the instructions inline on the session.

task_in_published_listing

A published listing references this task, so it cannot be retired. detail.listing_id names the listing. Unpublish or edit the listing first.

422 Unprocessable Entity

Most 422s come from the request-validation layer and use the ValidationError envelope described above, with no error_kind. The two below are different: they are raised by the handler, carry the Error envelope, and name a kind. Both come from people search, where the rule tree is something you author and we plan.

search_rule_invalid

The rule is malformed, or it names an attribute, source, or operator this API does not publish. detail names the offender: source and key for an unknown attribute, or the compiler’s message for an operator that does not fit the attribute’s type. Check the rule against GET .../people/schema, which is the authoritative vocabulary: an attribute is filterable only if the schema gives it a filter block, and only with the operators that block lists.

search_rule_too_complex

The rule is well-formed but past a size cap: more than 2 levels of nested groups, more than 15 conditions in one group, or more than 25 in total. detail carries the offending count alongside the limit, so you do not have to bisect your own filter. Combining values with in costs one condition instead of one per value, and is usually the whole fix.

429 Too Many Requests

Both are transient and both carry Retry-After. They reset on different clocks.

rate_limited

A rate limit upstream of your request was exceeded. On this surface that is the API key’s own request rate limit, which resets within the minute; the same kind is used elsewhere on the platform when an upstream provider limits us, which is why the remedy is expressed as “retry later” rather than “slow down”. Honor Retry-After, also echoed as detail.retry_after_seconds. Not applied to user access tokens.

session_quota_exceeded

The API key’s daily session-create quota is used up. It resets on a day boundary. Existing sessions are unaffected, so a loop already running keeps running; only new creates are refused. API keys only, not user tokens.

500 Internal Server Error

None of these is a state to handle. Quote request_id and the kind in a bug report.

session_actor_missing

payer_unresolved

environment_unresolved

502 Bad Gateway

decision_invalid

The participant produced an invalid choice twice, and no turn was recorded. Retriable: resubmit the same turn_index with a fresh frame.

Run the session loop

Where each error lands in the loop, and how to recover without losing the turn.

Authentication

Scopes, rotation, and what a key can reach.