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 theValidationError envelope, with an errors array of field locations so you can pinpoint the offending field:
Error envelope, where detail is either a string or a structured object carrying error_kind:
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 anX-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 theAuthorization 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 ofbalance_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 isspend_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-scopeperson_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
Theworkspace_id in the path.
session_not_found
Thesession_id in the path.
person_not_found
Theperson_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
Theenvironment_id, on a session create or an environment operation.
environment_version_not_found
Theversion_id, or the environment_version label a session tried to pin.
task_not_found
Thetask.id a session tried to bind, or the task_id in the path.
409 Conflict
idempotency_conflict
TheIdempotency-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
Theturn_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. Withget_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 concurrentset-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 theValidationError 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 carryRetry-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”. HonorRetry-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. Quoterequest_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 sameturn_index with a fresh frame.
Related
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.