Errors

The envelope

Errors carry a machine-readable code, an optional human sentence, and sometimes structured detail:

{ "error": "type_not_operable" }
{
  "error": "invalid_include",
  "message": "unknown include member(s): foo (accepted: properties)"
}

Branch on error. It is a stable string and will not change within v1. message is for your logs and your support tickets; it may be reworded at any time. Extra fields (keys, requirements, retryAfterSeconds) carry detail specific to that code.

Two shapes you will also meet

Authentication failures on most endpoints return a different shape:

{ "statusCode": 401, "error": "Unauthorized", "message": "Invalid token" }

Here error is a phrase, not a code. This happens because those endpoints accept either a key or a signed-in person, and the rejection is raised before the key layer sees it. A missing key and a malformed or unknown key are indistinguishable this way.

A revoked key is the exception. If the value you send is a key that has been revoked, these endpoints return the normal envelope with invalid_api_key, so you can tell a revoked credential from a typo:

From dual-auth-revoked-key.json — status 401:

{
  "error": "invalid_api_key",
  "message": "This api key has been revoked"
}

Treat any 401 as "check your key" and do not try to parse it further. The key-only endpoints (/v1/api-keys/self, /v1/aliases/*, /v1/resources/find-or-create) return the normal envelope with error: "invalid_api_key".

Some doors return sentences instead of codes for their own validation. Their scope refusals (403 subject_outside_grant, type_not_readable, type_not_operable …) are always proper codes, but these checks put an English sentence in error:

Match on the HTTP status for those, not on the string.

Every code

Authentication — fix your credential

Code Status Meaning What to do
invalid_api_key 401 No key, unknown key, or a revoked key. On endpoints that also accept a signed-in person, only a revoked key returns this code; a missing or unknown one returns the phrase shape above. Check the header. If it was working, ask whether the key was revoked. Do not retry.
missing_bearer_token 401 No Authorization header at all. Fix the request.

Scope — ask your administrator

These mean your key is working correctly and is not permitted to do this. Retrying will not help.

Code Status Meaning What to do
owner_grant_required 403 Your key has no owning organization. Any door that scopes to it can return this — the lists, the per-resource reads and writes, and find-or-create, which would otherwise have no organization to create the resource in. Ask for a correctly provisioned key.
read_types_required 403 Your key has no readable types at all. Ask for read scope.
operate_types_required 403 Your key has no writable types at all. Ask for operate scope.
type_not_readable 403 This type is in neither of your sets. Ask for it to be added to read_types.
type_not_operable 403 You may read this type but not change it. Ask for it to be moved to operate_types.
subject_outside_grant 403 The resource belongs to another organization. Stop. This is the tenancy boundary; it is not a scope you can be granted.
verb_not_granted 403 You may reach this resource, but not with this kind of action. A refusal that comes from your key's own scopes is always reported as type_not_readable, type_not_operable or outside_acts_at_scope; this code is returned only when the limit is on the side of the person who provisioned your key — they reach the resource through access granted to them directly, or through a connection shared by another organization, and that access does not include this action (or no longer does). A key never holds more than its provisioner. Ask for the action to be granted to the person who provisioned the key.
key_unbound 403 Your key has not been bound to an acting identity. Only a key provisioned outside the normal path can be in this state. Ask your administrator to re-provision the key.
actor_not_readable 403 You named an acting person (actorResourceId) your key cannot read. A key may only attribute its action to someone within its reach. Send an actorAlias instead, or leave the actor out — the action is then attributed to your key.
live_key_not_supported 403 Live updates (GET /v1/live) are held by signed-in people and kiosk stations only; an integration key cannot open a stream. Poll the resource's events instead (GET /v1/resources/:id/events) with a stored cursor.
owner_forced_to_grant 403 You passed an ownerResourceId that is not yours. Drop the parameter — the filter is applied for you.
outside_acts_at_scope 403 Your key is a station key (it acts at one place, such as a workstation) and the resource is not at that place. An integration key never receives this. Act on a resource at the station, or use an integration key.

Request — fix the request

Code Status Meaning
invalid_resource_id 400 The id in the path is not a number.
invalid_type_id 400 typeId is not a number.
invalid_status_id 400 statusId is not a number.
invalid_owner_resource_id 400 ownerResourceId is not a number.
invalid_cursor 400 cursor is not a cursor this API issued.
invalid_tag_id 400 A tagId on GET /v1/resources is not a number. Every repeated value is checked; one bad value refuses the request.
sort_not_supported 400 GET /v1/resources with sort from a key. A key list is always in resourceId order, because its cursor is the last resourceId and could not resume a name or date order exactly. message says so. Drop sort and order the rows yourself.
invalid_include 400 An include member that does not exist. message lists what is accepted.
type_required 400 A list without typeKey or typeId.
child_resource_id_required 400 Enrolment without childResourceId.
container_id_required 400 Exit without containerId, or a move without both fromContainerId and toContainerId (each a positive integer).
invalid_component_role_id 400 componentRoleId is not a positive integer.
self_membership 400 A resource cannot contain itself.
role_type_mismatch 400 That role belongs to a different type than the container.
system_and_code_required 400 Alias lookup without system and code.
unknown_external_system 400 That external system is not registered.
source_resource_id_required 400 A connection without sourceResourceId.
target_resource_id_required 400 A connection without targetResourceId.
interaction_type_id_required 400 A connection without interactionTypeId.
self_interaction 400 A resource cannot connect to itself.
ports_required 400 A connection needs both sourcePortId and targetPortId.
port_not_on_resource 400 A port you named belongs to a different resource.
invalid_interaction_id 400 The connection id in the path is not a number.
context_id_required 400 legal-targets with scope=context but no contextId.
invalid_context_id 400 contextId is not a number.
invalid_limit 400 legal-targets limit is not a whole number from 1 to 100. limit is optional: leave it out for the complete list. (The resource lists clamp their limit instead.)
invalid_enroll_into 400 GET /v1/resources/search enrollInto is not an id.
invalid_move_of 400 GET /v1/resources/search moveOf is not an id.
invalid_move_from 400 GET /v1/resources/search moveFrom is not an id.
invalid_resource_update 400 A name that is empty after trimming — on PATCH /v1/resources/:id or find-or-create. message says so.
replace_mismatch 400 POST /v1/resource-interactions with replaceInteractionId: that connection does not exist, or does not start at the same resource and port as the new one. message says so.
exclude_mismatch 400 excludeInteractionId is not a connection on that resource and port.
invalid_workspace 400 workspace is not an id.
workspace_not_in_scope 400 workspace names an owner outside your key's reach.

Not found

Code Status Meaning
port_not_found 404 / 400 The port is not on that resource (legal-targets: 404), or a port id you sent does not exist (create: 400).
interaction_not_found 404 No connection has that id.
resource_not_found 404 No resource with that id.
no_acting_resource 404 GET /v1/api-keys/self/view: your key does not act at a resource, so there is nothing to show.
type_not_found 404 No type with that code or id.
role_not_found 404 No role with that id.
alias_not_found 404 No resource bound to that identifier — or it is bound to a resource your key cannot read. An identifier you cannot read does not exist for you; the two cases are deliberately indistinguishable.

Note the difference between 404 resource_not_found and 403 subject_outside_grant: the first means nothing exists with that id, the second means something does and it is not yours.

Conflict — the current state refuses

Code Status Meaning What to do
already_a_member 409 Already inside that container — on an enrolment, or on a move whose destination already holds it. A move whose two containers are the same also returns this. Treat as success if you were making it so.
not_an_active_member 409 Not inside that container — on an exit, or on a move from a container it is not in. Same.
confirmed_key_locked 409 A property in your patch was confirmed by a person. keys lists which. Drop those keys and resend. A person must re-confirm before the value can change.
unique_key_taken 409 The resource's type (or a parent type) declares a unique key — the identifier, or one property — and another live resource of the same owner already holds that value. message says so in a sentence naming the owner; holderResourceId, key and value say which resource holds it. Raised by any create, find-or-create, property or identifier change, or unarchive. Use a different value, or work with the holder. An archived resource releases its key.
status_read_only 409 The resource sits in a status its lifecycle declares read-only (for example a sent bid). Its own state cannot change: properties, name, identifier, the members it contains, the connections it is the source of, its ports, and un-confirming a property. message says so in a sentence; resourceId and statusId say which resource and which status. Status changes, attestations, archiving, connections pointing at it, and confirming a property stay open. Move the resource on through its lifecycle, or make the change on a new revision.
attestation_not_repeatable 409 That event type is declared non-repeatable, and the resource already has one in its current status. message says so in a sentence; resourceId and eventType say which. A status change starts a new count. Do not resend. If the act really happened again, the resource must move on first — or the type must be declared repeatable.

Blocked by the model

Code Status Meaning What to do
port_max_connections 422 That port already holds as many connections as it allows. Carries portId and maxConnections. Change the existing connection (replaceInteractionId) or remove one first.
port_inactive 422 That port is switched off and takes no connections. Use another port.
accepted_type 422 That port does not accept resources of the other side's type. Pick a resource of an accepted type (see the port's effectiveTypes).
port_direction 422 The two ports face the wrong way for this connection. The source port must send (out or bidirectional) and the target port must receive (in or bidirectional). Carries sourcePortId and targetPortId. Swap the source and target, or use ports that face each other. legal-targets lists only ports that do.
compat_authoring_session_only 403 Compatibility markings are authored by signed-in people, not keys. Ask a person to mark it in the web application.
derive_target_write_blocked 400 A property in your patch is computed by a formula. keys names them and where each is computed. Remove those keys. They are maintained for you and a manual value would be overwritten.
attestation_status_gate_unmet 422 That event type requires the resource to be at a specific status first. Carries requiredSubjectStatusId and currentStatusId. Move the resource to the required status, then attest.
compat_deny 422 The membership would put an item under a setting it is marked incompatible with — a choice made on the new container (or above it) that passes down to what it contains. message names the item and the setting. May carry viaAncestorId (where the setting is made), offendingResourceId (the item that conflicts) and offenderCount; an id is left out when your key cannot read that resource. Change the conflicting choice, or put the item somewhere else.
contents_locked 422 The new container (or one above it) locks a setting, and the item — or something inside it — uses a different value. Same optional fields as compat_deny, plus kindTypeId. Align the item's setting with the locked one, or put it somewhere else.
containment_cycle 422 The resource you are adding is the container itself, or already contains it — directly or further down. Adding it would put the container inside itself. message names both. Pick a different resource, or a different container.
containment_cap 422 The resource's type limits how many containers of the destination's type it can be inside, and it is already inside that many (a move does not count the container it leaves). Carries childId, parentTypeId and maxParents; message names the type. Take it out of one of those containers first, or move it instead.
compat_type_not_connectable 422 A compatibility type (compatible_with / incompatible_with) was used for a connection on a port. Compatibility is set between resources, not as a connection. Carries interactionTypeId. Use a type from the port's allowedInteractionTypes (GET /v1/resources/:id/ports).
capture_shape 422 A transition's capture is not in the declared shape: an object naming properties, settings or viaPorts, each as a non-empty list or "all" (viaPorts maps a port name to such a list). Raised by the web application's lifecycle authoring; keys never author lifecycles. Fix the declaration.

These appear inside a legal-targets candidate's reasons, never as a response status.

Code Status Meaning
no_viable_target_port — The candidate passes every rule, but none of its ports can take this connection (direction, accepted type or its own capacity).
status_read_only — The connection's source — the anchor, or the candidate when the anchor's port receives — sits in a read-only status, so a create would be refused with 409 status_read_only. message names it.

Refusals passed through from the create/alias doors

Code Status Meaning
find_or_create_refused 400 / 403 / 409 find-or-create refused; the reason is in message. 403: the type is not in find_or_create_types. 400: the external system is unknown; or it is resync ("find-or-create is for external systems; 'resync' is the canonical system"); or the type is a built-in system type ("Cannot mint a resource of a system type"). 409: the identifier is already bound to a resource of another type ("… is already bound under … to a resource of another type"), or to a resource your key cannot read; in neither case is that resource named.
alias_bind_refused 400 / 403 / 404 / 409 aliases/bind or identity-claims/redeem refused; reason in message. 403: the external system is not in your key's bind_systems ("key not scoped to bind under '…'") — on redeem this is checked before the code. 409: the identifier is already bound elsewhere; the other resource is not named. A target outside your organization is 403 subject_outside_grant.

Transport

Code Status Meaning What to do
rate_limited 429 Your key used its whole budget for the current one-minute window (300 by default; x-ratelimit-limit says your key's own). The window is fixed: it started with your first request. Carries retryAfterSeconds and a retry-after header — the seconds left in the window. Wait the stated time, then continue.

Retrying

Only 429 is safe to retry blindly. Wait for retry-after.

5xx may be retried once with backoff, but read conventions.md first: writes are not idempotent, so a PATCH that timed out may already have been applied. Read the resource back before resending it.

Never retry a 401, 403, 404 or 409 unchanged. Every one of them means the request will be refused the same way until something changes on your side or your administrator's.