[AVAILABLE].
packages/api/src/middleware/error-handler.ts. Every
response shown here was captured from the server on 2026-10-02.The envelope
Every non 2xx response (exceptPOST /policies/validate, see below) is:
code:
error is always a plain string, never a nested object, so a caller can always display or log it without walking a
schema. Its wording can change; code and the status do not.
Branch on the status first, then on code when it is present. Never match the text of error.
Which errors carry a code
code is present when the failure is one of the server’s typed errors. Among them, the ones an integration meets
most:
The Error catalog lists every code, its trigger and what to do.
Which errors have no code
These answer{ "error": "..." } only, so tell them apart by status:
POST /policies/validate does not use this envelope. Every status it returns (200, 400, 404) is
{"valid": boolean, "errors": string[]}, for example {"valid": false, "errors": ["policyId is required."]}. The
one exception is 401: caller authentication runs before the route, so a rejected caller sees
{"error": "authentication required"}.
An incomplete body is a 400
APOST /execute or POST /transactions body without metadata, authority, authorization, intent or
policy, or with one of them null or not an object, or with a required field that is not a string, is refused with
400 naming it, for example:
policy or authorization reached the validator and was answered
with a bare 500 (G-87 in docs/VERIFICATION-GAPS.md). A fuzz test now sends damaged and arbitrary bodies to both
routes and requires that neither ever answers 500.
Fixed earlier too: “no connector registered for this capability” used to be a bare 500 too. It is now
503 CONNECTOR_NOT_REGISTERED.
The SDKs
Both SDKs raise one exception class per status. In the TypeScript SDK,AuthorizationError and InternalServerError
keep the server’s code on serverCode. See TypeScript SDK and Python SDK for
each class.