[AVAILABLE]. Rows come from responses captured from the server and from
the integration tests that assert them, checked against the code on
2026-10-02. See Error handling for the
envelope shape this table assumes.
Authentication
| Status | Code | Message | Condition | What to do |
|---|---|---|---|---|
| 401 | none | authentication required | Authorization header missing, not a Bearer <key>, or the key doesn’t match a configured hash | Send a valid key from Authentication. Not retryable without fixing the header. |
| 503 | AUDIT_UNAVAILABLE | Caller authentication audit trail is unavailable; refusing to proceed without an audit record. | The server could not write the caller audit event a request needs (a database outage). Any authenticated route. Nothing was done. | Retry later with the same request. |
Authorization (caller is known; not permitted for this principal or action)
| Status | Code | Message | Condition | Routes | What to do |
|---|---|---|---|---|---|
| 403 | none | Caller is not permitted to assert this authority.principalId. | transaction.authority.principalId is missing/empty, or doesn’t equal the authenticated callerId (default, no allowedPrincipalIds configured for this key), or isn’t present in that key’s configured allowedPrincipalIds list (isPrincipalAllowed, packages/api/src/routes/execute.ts / transactions.ts) | POST /execute, POST /transactions | Set authority.principalId to an identity this caller’s key is actually permitted to assert. Skipped entirely (no 403 possible) when caller authentication itself is disabled. |
| 403 | CAPABILITY_NOT_ALLOWED | Caller is not permitted to invoke this capability. | intent.action is not in the key’s allowedCapabilities, or the key has no allowedCapabilities (a key with none may invoke nothing) | POST /execute, POST /transactions | Use a key granted that action; GET /callers/me lists what yours may invoke. Nothing was evaluated. |
| 403 | TENANT_NOT_ALLOWED | Caller is not permitted to act for this tenantId. | metadata.tenantId is set and is not in the key’s allowedTenantIds (a key with none may send no tenant id). The tenant id selects the signing key, so it must come from the key | POST /execute | Remove metadata.tenantId, or ask the operator to add the tenant to the key’s allowedTenantIds. Nothing was evaluated. |
Policy denial (request well-formed; the business rule said no)
| Status | Code | Message | Condition | Routes | What to do |
|---|---|---|---|---|---|
| 403 | POLICY_DENIED | Execution rejected: <policy-specific reason> | Policy evaluation returned REJECTED (ExecutionGate.enforce, packages/runtime/src/ExecutionGate.ts). This includes a required human approval that is missing, expired, for other content or already used (the reason then names the approval signal, for example humanApproved=true != verified humanApproved=false), a bound signal that does not match the Intent, and a request naming a policy version that is not the one in effect | POST /execute, POST /transactions | This is a real, observed pipeline outcome, not a bug: the request was well-formed but the business decision was no. Read the reason string; it names the specific Policy condition that failed. Distinct from the caller identity 403 above: that one is about who the caller may act as, this one is about whether the requested action itself is allowed. Previously indistinguishable from a genuine server error (both surfaced as a generic 500), now corrected. |
Validation (caller’s request is malformed)
| Status | Code | Message | Condition | Routes | What to do |
|---|---|---|---|---|---|
| 400 | none | businessTransactionId must be a valid UUID. | businessTransactionId present but not a valid UUID v1-v5 | POST /execute, POST /transactions, POST /verify, POST /receipt | Fix the ID format. Not a transient failure. |
| 400 | none | businessTransactionId is required. | businessTransactionId missing entirely | POST /replay | Include the field. /replay checks presence only, not UUID format, unlike the routes above. |
| 400 | none | authorization.authorityId must match authority.authorityId. | A Business Transaction trust-chain invariant failed (BusinessTransactionValidationError) | POST /execute, POST /transactions | Fix the request body’s internal consistency; this is one example invariant among several the shared validator enforces. |
| 400 | none | {"valid":false,"errors":["policyId is required."]} / ["policyVersion is required."] | policyId or policyVersion missing/empty, checked in that order, first miss short-circuits | POST /policies/validate | Supply both fields. This route never uses the shared {error} envelope, see Error handling. |
| 400 | INVALID_SANDBOX_APPROVAL_REQUEST | The sandbox approver signs only for capability "sandbox:receipt". or resourceId is required: the request's target, at most 200 characters. | Sandbox only: another capability, or a missing, empty or long resourceId | POST /sandbox/approvals | Ask for sandbox:receipt with the target you will send. Nothing was signed. |
Not found
| Status | Code | Message | Condition | Routes | What to do |
|---|---|---|---|---|---|
| 404 | none | Policy '<name>' version '<version>' was not found. | transaction.policy.name/version doesn’t match any published Policy (PolicyNotFoundError) | POST /execute, POST /transactions | Publish the policy first, or fix the name/version in the request. |
| 404 | none | {"valid":false,"errors":["Policy '<name>' version '<version>' was not found."]} | Same condition as above, different envelope | POST /policies/validate | Same fix; this route reshapes the error locally instead of using the shared handler. |
| 404 | APPROVAL_ISSUER_CHANGE_NOT_FOUND | Approver change '<id>' not found. | No approver change has this id | POST /approval-issuers/changes/{id}/approve, .../reject | Use the changeId the proposal returned. See Manage approvers. |
| 404 | none | Business Transaction not found. | No Business Transaction exists for the path ID | GET /transactions/{id} | Confirm the ID; nothing to retry. |
| 404 | VERIFICATION_FAILED | Execution Trust Record not found. | No Execution Trust Record exists for businessTransactionId (VerificationFailedError, reused across three POST routes) | POST /verify, POST /receipt, POST /replay | Confirm the record exists via GET /trust-records/{id} first. |
| 404 | none | Execution Trust Record not found. | Same underlying condition, but reached via a GET, no code field on this path | GET /verification/{id}, GET /receipt/latest/{id}, GET /trust-records/{id} | Same fix; note the code field’s presence depends on which route you called, not on the underlying cause. |
| 404 | none | Verification not found. | Record exists but has no Verification yet | GET /verification/{id} | In practice every Execution produces a Verification synchronously, this is an edge case for records that haven’t completed that step. |
| 404 | none | Receipt not found. | Record exists but has no Receipt yet | GET /receipt/latest/{id} | Call POST /receipt first, or the record’s Execution hasn’t reached that stage. |
| 404 | none | No authenticated caller identity available. | No caller identity on the request, in practice only reachable when caller authentication is disabled (PARMANA_AUTH_DISABLED=true, local development only) | GET /callers/me | Not reachable under normal, authenticated operation. See Authentication. |
Policy in effect (GET /policies/in-effect)
| Status | Code | Message | Condition | What to do |
|---|---|---|---|---|
| 400 | none | capability is required, for example ?capability=paytm:refund. | The capability query parameter is missing or blank | Add ?capability=<capability>. |
| 403 | CAPABILITY_NOT_ALLOWED | Caller is not permitted to invoke this capability. | A non human caller asked about a capability its key may not invoke | Ask only about capabilities your key may invoke (GET /callers/me). |
| 404 | CAPABILITY_NOT_BOUND | No policy is bound to capability "<capability>". | No policy governs this capability on this deployment | Check the capability name. Every request for it would be refused. |
| 409 | NO_APPROVED_POLICY_VERSION | Every request for "<capability>" is refused right now: <reason>. | The bound policy has no approved version yet | Every request is refused until a version is approved through policy governance. |
| 503 | POLICY_VERSION_UNAVAILABLE | Every request for "<capability>" is refused right now: <reason>. or The policy in effect for "<capability>" could not be read. | The lookup of the version in effect, or the policy file itself, failed | Retry later. Every request for the capability is refused meanwhile. |
Governance (policy changes, approvers, external connectors)
These come from the maker checker routes:/policies/{name}/{version}/pending-changes and /policies/pending-changes,
/approval-issuers/changes, and /external-connectors/changes. Every one of them, reads included, needs a human
caller; approve and reject also need a fresh step up signature from the checker. See Policy lifecycle and
approvals, Manage approvers and Connect any
external system.
| Status | Code | Message | Condition | Routes |
|---|---|---|---|---|
| 400 | none | The field and its rule, for example reason is required, at most <n> characters. | A field of the proposal is missing or invalid. Nothing was stored. | Every propose route |
| 400 | INVALID_EXTERNAL_CONNECTOR | The field and its rule, for example policy must be a policy name matching ^[a-z0-9][a-z0-9-]{0,127}$. | A registration or revoke proposal is invalid | POST /external-connectors/changes |
| 400 | EXTERNAL_ENDPOINT_ADDRESS_REFUSED | The endpoint host '<host>' does not resolve. or ... resolves to an address that is not public (<addresses>). | The endpoint is not a public HTTPS address. Checked when proposed and again when approved. | POST /external-connectors/changes, .../approve |
| 403 | NON_HUMAN_CALLER_DENIED | This action requires a caller credential provisioned as a verified human (credentialHolderType: USER). | The key is not provisioned as a human (credentialHolderType: USER), such as an agent’s key or the sandbox demo key. Recorded as a flagged audit event. | Every governance route |
| 403 | SAME_ACTOR_CANNOT_APPROVE_OWN_CHANGE | <kind> '<id>' was proposed by this same caller — the proposer (maker) may not also approve or reject it (checker). | The checker is the maker | Every approve and reject route |
| 403 | STEP_UP_AUTHORIZATION_INVALID | The step-up authorization envelope is missing, invalid, expired, replayed, or does not match this request. | The step up signature is missing, not from the checker’s registered key, expired, already used, or for another change or action | Every approve and reject route |
| 404 | PENDING_POLICY_CHANGE_NOT_FOUND | Pending Policy Change '<id>' not found. | No policy change has this id | POST /policies/pending-changes/{id}/approve, .../reject |
| 404 | EXTERNAL_CONNECTOR_CHANGE_NOT_FOUND | External connector change '<id>' not found. | No external connector change has this id | POST /external-connectors/changes/{id}/approve, .../reject |
| 409 | CONFLICT | For example A PENDING_APPROVAL change already exists for policy '<name>' version '<version>' ..., Approver change '<id>' is already APPROVED., Capability '<capability>' already has an active external connector. Revoke it first. | The change contradicts the current state: another change is already open for the same thing, the change was already resolved, or the key or registration already exists or does not | Every propose, approve and reject route |
APPROVAL_ISSUER_CHANGE_NOT_FOUND (404) is in Not found above.
Rate limited
| Status | Code | Message | Condition | Routes | What to do |
|---|---|---|---|---|---|
| 429 | RATE_LIMITED | Rate limit exceeded. Try again later. | Caller (by authenticated identity, not IP) exceeded the configured /execute rate limit, or the caller’s IP exceeded the separate, more permissive /health//ready limit, or the shared per IP limit on the unauthenticated verify routes and /handbook | POST /execute, GET /health, GET /ready, POST /refusal/verify, POST /execution-intents/verify, POST /audit/verify, /handbook/download-leads, POST /sandbox/approvals | Back off; the response carries a Retry-After header. Not a transient server issue: no nonce is consumed and nothing is signed for a request rejected here. See Authentication for the mechanism. |
Conflict
| Status | Code | Message | Condition | Routes | What to do |
|---|---|---|---|---|---|
| 409 | none | Business Transaction '<id>' already exists. | A Business Transaction with this businessTransactionId already exists (DuplicateBusinessTransactionError) | POST /execute, POST /transactions | This is not a transient failure to retry. Fetch the existing record instead, see Idempotency and nonces. |
| 409 | RECEIPT_GENERATION_FAILED | Execution Trust Record must be successfully verified before a Receipt can be generated. | The record exists but its latest Verification isn’t VERIFIED | POST /receipt | Call POST /verify first and confirm status: VERIFIED before requesting a Receipt. |
Not listed here on purpose:
NonceAlreadyConsumedError (409, code
NONCE_ALREADY_CONSUMED) is a real, tested error error-handler.ts maps,
thrown by ExecutionGateway.execute() when every other verification check
passed and only nonce consumption failed (an isolated replay of an
already-executed authorization). It is not reachable through any of the routes
this catalog covers: a resubmitted businessTransactionId is always caught
first by the DuplicateBusinessTransactionError 409 above, before a second
authorization, and so a second nonce, is ever generated. It’s a library-level
guarantee for a receiving system calling @parmana/execution-gateway
directly, not an HTTP-reachable outcome of Parmana’s own server.Signing and evidence (the action could not be recorded)
| Status | Code | Message | Condition |
|---|---|---|---|
| 503 | SIGNING_UNAVAILABLE | Evidence signing is unavailable; refusing to release the action ... Nothing was executed. | Before release, the signing readiness probe failed (a missing, disabled or denied key, a KMS or network outage, a key mismatch). Nothing was executed. Retry once signing is healthy, with a new businessTransactionId, because the original was already accepted. |
| 503 | EXECUTION_INTENT_UNAVAILABLE | The signed Execution Intent for business transaction '<id>' could not be created and stored; refusing to release the action. Nothing was executed. ... | Before release, the signed Execution Intent could not be signed and stored (a database or signing failure, or the execution_intents table is missing). Nothing was released. Retry once healthy, with a new businessTransactionId, because the original was already accepted. |
| 503 | CONNECTOR_NOT_REGISTERED | No connector is registered for capability '<action>' on this deployment. Nothing was executed. ... | The action was approved, but no connector is registered for it, usually because the connector’s credentials are not configured. The operator must configure it, then retry with a new businessTransactionId. |
| 500 | EXECUTION_RECORD_INCOMPLETE | The action for business transaction '<id>' was released ... reconcile ... authorizationId: <id>. | The action was released to the connector, then the signed Execution Trust Record could not be produced or persisted. Do not retry as a new transaction. A signed Execution Intent exists for it. Read GET /execution-intents/<businessTransactionId>: in state RELEASED, an operator can rebuild the record with POST /execution-intents/<businessTransactionId>/finalize, which never calls the connector. In state PREPARED, finalize refuses with 409, and the outcome must be reconciled against the connector by hand. |
| 409 | AUTHORIZATION_NO_LONGER_VALID | Not released: what the authorization for business transaction '<id>' rests on no longer holds (...). Nothing was executed. ... | RFC-0023 phase 4. The request was decided, but at release a business fact it rested on was past its validUntil, or the agent’s authority grant had expired or been revoked. Nothing was sent to the connector, and the body says executionStatus: NOT_EXECUTED. Send a new request, which is decided again on current facts and authority. The Execution Intent is ERRORED; close it as NOT_EXECUTED. |
| 502 | EXECUTION_OUTCOME_UNKNOWN | The action for business transaction '<id>' was released to the execution system, but the call failed, so whether it was performed is unknown. ... authorizationId: <id>. | The action was released and the call to the connector failed: unreachable, timed out, or an error response. Whether it was performed is unknown, and the Execution Intent is ERRORED. Do not retry as a new transaction. Check the target system, then close the intent with POST /execution-intents/<businessTransactionId>/resolve, recording EXECUTED or NOT_EXECUTED. The connector’s own error is never in the response; it is in the server log (execution_outcome_unknown) and in the intent’s failureReason. Before 2026-09-25 this was a bare 500 Internal Server Error (G-63). Since RFC-0023 phase 4 the body also says executionStatus: EXECUTION_UNKNOWN: never reported as failed. |
Execution Intents (operator routes)
These come from the routes under/execution-intents, see Execution Intents.
| Status | Code | Message | Condition | Routes |
|---|---|---|---|---|
| 400 | none | Request body must be an Execution Intent (intentId, businessTransactionId, authorizationId, intentHash, signature required). | The body of the verify route is not a structurally plausible intent. | POST /execution-intents/verify |
| 403 | NON_HUMAN_CALLER_DENIED | This action requires a caller credential provisioned as a verified human (credentialHolderType: USER). | The list and finalize routes need a credential provisioned as a verified human. Any other credential is refused. | GET /execution-intents/unfinalized, POST /execution-intents/<id>/finalize |
| 404 | none | Execution Intent not found. | No intent exists for the id, or it belongs to a different caller (ownership scoped). | GET /execution-intents/<id> |
| 404 | EXECUTION_INTENT_NOT_FOUND | No Execution Intent exists for business transaction '<id>'. ... | Finalize was called for a transaction with no intent. Transactions created before Execution Intents have none. | POST /execution-intents/<id>/finalize |
| 409 | EXECUTION_INTENT_RESULT_NOT_RECORDED | The Execution Intent for business transaction '<id>' is in state <state> and has no saved execution result ... | The intent exists but no execution result was saved, so the Trust Record cannot be rebuilt. The action may or may not have been released. Establish the outcome at the connector and reconcile by hand. Nothing was called or changed. | POST /execution-intents/<id>/finalize |
| 501 | EXECUTION_INTENTS_NOT_ENABLED | Execution Intents are not enabled on this deployment. | Finalize was called on a deployment where Execution Intents are off. Only possible in test or development without EXECUTION_INTENTS_CHECK=true. | POST /execution-intents/<id>/finalize |
| 400 | EXECUTION_INTENT_RESOLUTION_INVALID | The resolution is invalid: <reason> | The body of the resolve route is invalid: resolution is not NOT_EXECUTED or EXECUTED, or note is missing, blank or over 2000 characters. Nothing was changed. | POST /execution-intents/<id>/resolve |
| 409 | EXECUTION_INTENT_NOT_RESOLVABLE | The Execution Intent for business transaction '<id>' is in state <state> and cannot be resolved by hand. ... | The intent is RELEASED (rebuild the record with finalize instead), FINALIZED (already complete), or a signed Trust Record already exists for the transaction. Only PREPARED and ERRORED intents can be closed by hand. Nothing was changed. | POST /execution-intents/<id>/resolve |
Server-side (500)
| Status | Code | Message | Condition | Routes | What to do |
|---|---|---|---|---|---|
| 500 | none | Internal Server Error | A structurally incomplete but UUID-valid body (for example, businessTransactionId present but metadata missing) throws an unhandled TypeError inside BusinessTransactionValidator.validate before any typed error class is constructed | POST /execute, POST /transactions | Send every required nested object from the OpenAPI reference’s request schema. The generic message hides the real cause; it is visible only in server logs. |
What’s deliberately not here
This catalog lists errors from the routes mounted inpackages/api/src/app.ts. It does not cover
exceptions raised only in code paths unreachable from the HTTP API (for example, internal
validator classes that never escape a route handler uncaught), or client-side SDK exceptions,
see Python SDK and TypeScript SDK for how each wraps these
same HTTP responses.