Exchange API v1 — error codes

Every error of the exchange API is an RFC 9457 application/problem+json document (problem.schema.json) whose code is one of the codes below (REQ-XCH-025). A code is never reused or repurposed; new codes may appear within v1, and a client treats an unknown code by its HTTP status.

The detail is a short English sentence fixed per code and situation. It never echoes the request, and a refusal raised by the Basetool behind the gateway arrives with its code’s own sentence, not the Basetool’s internal text, whatever the Accept-Language. Decide by code; show detail at most as a hint.

The gateway’s gates count their refusals on the metric basetool_ingest_exchange_refused_total, with the code in snake case as its reason label; the answers of the routes themselves, those given before the token is checked and the DPoP nonce challenge are not counted there. The per-op reasons never arrive as a problem: they appear in a change result’s results[].reason for an op that was not applied.

The Basetool behind the gateway checks the switch, the registry, the revocations and the capabilities of every request again. The gateway reads the registry through a cache of up to five seconds, so right after a change the Basetool may be the one that refuses. It then answers with the same code, status, Retry-After and detail the gateway would have — the codes raised by gateway, backend below, SCHEMA_INVALID aside — and such a refusal is not counted on the gateway’s metric. A client never sees one situation under two codes, whichever side refuses it.

Request errors #

Code HTTP Raised by Meaning Client action
CLIENT_NOT_ALLOWED 403 gateway, backend The token’s client is not in the registry. Stop; the client is not approved.
CLIENT_SUSPENDED 403 gateway, backend The client is suspended in the registry. Stop and tell the member. Nothing tells a client when the suspension ends, so do not retry on a timer: try again at the client’s next start or when the member asks.
CLIENT_REVOKED 401 gateway, backend The member disconnected this client after this connection was made: an offline token issued before the disconnect, or a token without offline_access whose sign-in (auth_time) came before it. Discard tokens; start a new device login only when the member asks.
INSTALLATION_REVOKED 401 gateway, backend The member disconnected this installation; its DPoP key is refused for good. Discard tokens and the DPoP key; reconnecting needs a new key.
CLIENT_VERSION_UNSUPPORTED 403 gateway The User-Agent version is below the client’s minimum. Ask the member to update.
EXCHANGE_DISABLED 503 gateway, backend The exchange is switched off globally. Retry-After: 30. Wait at least Retry-After, then retry.
REGISTRY_UNAVAILABLE 503 gateway, backend The gateway cannot read the client registry or the revocations, or the Basetool cannot read the revocations, and fails closed. Retry-After: 30. Wait at least Retry-After, then retry.
EXCHANGE_BUDGET_EXHAUSTED 503 gateway A Redis byte budget of the exchange is full: the member’s, the client’s or the total. Retry-After is the seconds until enough of it expires, at most 3600. The refused write does not count against the daily write quota. Wait at least Retry-After, then retry the same request under the same key. Send only what changed: a budget fills with the answers to full-list pushes and to change sets that keep conflicting.
RELAY_BUSY 503 gateway A change set of more than 100 ops, while the gateway already relays as many of those as it admits at once (four). Nothing was written, and the set does not count against the daily write quota. Retry-After: 10. Wait at least Retry-After, then retry the same request under the same key; or split it into change sets of at most 100 ops.
SCOPE_MISSING 403 gateway, backend The route’s capability is not in the token or not granted to the client. A missing consent looks the same. Start a device login requesting the scope, if the member wants it.
UNAUTHENTICATED 401 gateway The token is missing, invalid, expired, or not issued for this gateway. Refresh the token once and retry. Only when the refresh answers invalid_grant is the connection over: delete the refresh token and start a device login when the member asks. A freshly refreshed token that is refused again is not retried in a loop.
DPOP_REQUIRED 401 gateway The request carries no DPoP proof or an unbound token. Send Authorization: DPoP with a proof.
DPOP_INVALID 401 gateway The proof is invalid, replayed, for another key, or lacks the server nonce. Fix the proof; on a nonce challenge retry once with the DPoP-Nonce.
DPOP_PROOF_LIMIT 429 gateway The member already holds its cap of live DPoP proofs, over all of its clients (live proofs). Retry-After is the seconds until the member’s earliest live proof no longer counts. Wait at least Retry-After, then retry with a new proof; slow the member’s requests down.
TERMS_NOT_ACCEPTED 403 backend The member has not accepted the current terms. Ask the member to open the Basetool and accept.
PENDING_APPROVAL 403 backend The member’s registration awaits approval. Stop; nothing to sync yet.
NO_ROLE 403 backend The member holds no role. Stop and tell the member.
ACTING_MEMBER_REFUSED 403 backend The relay refused the member (unknown, disabled or deleted). Stop and tell the member.
NOT_PERMITTED 403 backend The member may not do this. Stop; do not retry.
SCHEMA_INVALID 400 gateway, backend The body or a query parameter does not match the v1 contract; errors[] points at the fields, a parameter as /<name>. A body that is not a JSON document has one error at the pointer "" and is never cached for its Idempotency-Key; a query parameter without a name has one at /. From the backend, without errors[]: the content is malformed although it matches the schema, such as a refinery draft of an unsupported panel type. Fix the request.
BATCH_TOO_LARGE 413 gateway A change set holds more than 500 ops, or is too large to hold for the member’s confirmation. Split the batch.
PAYLOAD_TOO_LARGE 413 gateway The body exceeds the size cap, or the draft built from it is too large to hand off. Split or shrink the request.
IDEMPOTENCY_KEY_MISSING 400 gateway A write carries no Idempotency-Key, or none of 8 to 128 characters of [A-Za-z0-9._~-]. Send a fresh key per logical write.
IDEMPOTENCY_KEY_REUSED 422 gateway The key was used with a different request: another route or another body. Use a fresh key.
IDEMPOTENCY_IN_PROGRESS 409 gateway The same key is still being processed. Retry the same request after a short wait.
CURSOR_EXPIRED 410 backend, gateway The cursor is older than the tombstones, or not one the server issued. Reconcile a full snapshot against the last baseline — not add-only.
VERSION_CONFLICT 409 backend A ship’s or org-stock entry’s version, or a lot’s expectedQuantity, no longer matches. Pull, merge, retry.
MASS_CHANGE_CONFIRMATION_REQUIRED 409 backend The batch exceeds the mass-change guard; it is staged. Show the member confirmationUrl; do not retry the batch.
RATE_LIMITED 429 gateway A limit per minute or per hour is exhausted: per client and member, per client, per source IP address, or the hourly account check (limits). Honour Retry-After.
QUOTA_EXCEEDED 429 gateway The daily write quota is exhausted; an org-stock change set counts each of its ops and is refused as a whole when they do not all fit. Retry after Retry-After, the next day at the latest.
BACKEND_RELAY_FAILED 502 gateway The backend did not answer usably: an error, a refusal the contract does not name, or an answer that breaks the v1 schema. Back off; retry with the same key.
SERVICE_UNAVAILABLE 503 gateway Temporarily unavailable: a store the exchange needs cannot be reached (Retry-After: 60), the daily write quota cannot be counted (Retry-After: 30), the identity provider cannot be reached to check the token (Retry-After: 5), or all members together hold the gateway’s cap of live DPoP proofs (live proofs; Retry-After is the seconds until the earliest of them no longer counts). Wait at least Retry-After, then retry the same request under the same key, with a new DPoP proof.
NOT_FOUND 404 gateway The exchange has no such route or method, or the requested document, such as a schema name, does not exist. Check the path, the method and the name.
UNSUPPORTED_MEDIA_TYPE 415 gateway A request body is not sent as Content-Type: application/json. Never cached for its Idempotency-Key. Send the body as application/json.
INTERNAL_ERROR 500 gateway An unexpected failure of the gateway, the generic fallback. Never cached. Back off and retry under the same key; report the correlationId if it persists.
LEGACY_ENDPOINT_GONE 410 gateway Retired 2026-09-28, never reused: answered a legacy /v1/* extractor endpoint after the go-live; those routes are removed, and a request there now gets the gateway’s answer for an unknown path. Update the client.

Per-op reasons in a change result #

Reason Meaning Client action
UNMATCHED The reference resolves to nothing. Show it; offer catalog/resolve.
AMBIGUOUS The reference resolves to several entries. Ask the member to pick; send bt.
DEFAULT_NOT_REMOVABLE A default-granted blueprint cannot be removed. Keep it.
STOCK_EARMARKED The lot’s stock, or the part of an org-stock book-out its earmark lines do not cover, is reserved for a job order, mission or donation. Ask the member to release the reservation in the web, or name the earmarks to take from.
STOLEN_MARKING_DISABLED The Basetool does not yet keep stolen stock apart. Keep the lot local until the server supports it.
REMOVED_ELSEWHERE The entry has a live tombstone from another channel or installation; for a ship, the named shipId was removed there. Ask the member; resend with override: true only if they agree.
UNIT_MISMATCH The quantity’s unit does not match the material’s. Fix the unit.
LOCATION_UNKNOWN The place has no warehouse location. Offer a place from catalog/locations.
LINK_TARGET_TAKEN The server ship is already linked to another external id of this installation. Pull and re-link.
VERSION_CONFLICT As above, for this op only; also an upsert without shipId for an id this installation already linked. Pull, merge, retry this op.
NOT_PERMITTED An org-stock op on a pool whose unit the member is not a Logistician of, a move-holder into such a pool or to the entry’s own holder in another pool, or an op on an entry that is unknown, personal or in another pool. Do not retry; offer only the member’s Logistician pools.
HOLDER_UNKNOWN An org-stock book-in or move-holder names a holder who is not on the receiving unit’s roster. Read the roster again and let the member pick.
INSUFFICIENT_STOCK An org-stock op takes or marks more than the entry holds. Pull and retry with the entry’s amount.
STOCK_OFFERED A partial org-stock stolen or move-pool would leave the entry below what it offers on the Basetool’s material exchange. Take less, or ask the holder to lower the offer in the web.
OVER_ALLOCATION The earmarks of one kind of an org-stock book-in add up to more than its amount. Lower the earmarks.
EARMARK_TARGET_INVALID An org-stock earmark names a job order that does not need the material or item, an unknown mission, a target twice, or one the entry is not earmarked to. Fix the earmarks.

Warnings #

A change result or resolve result may carry warnings[] with a JSON Pointer and a code. v1 defines UNKNOWN_FIELD (the server ignored a field it does not know) and LOC_KEY_UNRESOLVED (the reference’s key fields, its locKey included, resolved to no single catalogue entry; the name was tried next, when one was sent).

The problem document #

Field Sent Meaning
status always The HTTP status.
code always The code from the tables above. Decide by it.
title always A short English title, usually the status’s reason phrase.
detail always The fixed English sentence for the code; a hint at most.
correlationId always The request’s id, the same value as the X-Correlation-Id response header.
instance from the routes, not from the gateway’s filters The request path.
errors with SCHEMA_INVALID from the gateway Up to 50 {pointer, message}; the message names the violated keyword, never a value you sent.
confirmationUrl with MASS_CHANGE_CONFIRMATION_REQUIRED Where the member confirms the held batch (sync guide).
type never Absent, which RFC 9457 reads as about:blank.
retryAfterSeconds never Reserved; the delay is in the Retry-After header.

Every answer, a success included, carries X-Correlation-Id. Put it into a problem report or a log line, so the Basetool’s maintainers can find the request; it identifies nothing but the request. A client may send its own X-Correlation-Id of 1 to 128 characters of A–Z, a–z, 0–9, ., _ and -; the gateway keeps it, and replaces any other value with a random UUID.