Errors
Every error response is structured. Same shape across every endpoint:
{
"sandbox": true,
"error": {
"type": "...",
"code": "EP_...",
"message": "Human-readable, ≤ 80 chars.",
"field": "constraints.amount_usd",
"remediation": [
"Specific step a developer can take.",
"Alternative step.",
"Escalation step."
],
"request_id": "req_...",
"docs": "https://executionprotocol.dev/docs/reference/errors"
}
}
{
"sandbox": true,
"error": {
"type": "...",
"code": "EP_...",
"message": "Human-readable, ≤ 80 chars.",
"field": "constraints.amount_usd",
"remediation": [
"Specific step a developer can take.",
"Alternative step.",
"Escalation step."
],
"request_id": "req_...",
"docs": "https://executionprotocol.dev/docs/reference/errors"
}
}
type is the broad category (invalid_request, boundary_block, etc.).
code is the specific reason — gateway error codes are stable identifiers
prefixed EP_. remediation is always an array — never a single sentence —
so callers can choose their preferred fix.
Catalog
4xx — caller errors
400 EP_MALFORMED_BODY
Type: invalid_request
The request body is not valid JSON.
Remediation:
- Validate JSON with
jqor a linter before sending. - Confirm
Content-Type: application/jsonis set.
422 EP_VALIDATION_FAILED
Type: invalid_request
The body is parseable JSON but doesn't match an accepted shape — neither
a demo lookup ({ pair_id, outcome }) nor a gateway-shape request
({ archetype, constraints, … }).
Remediation:
- For the side-by-side demo, send
{ "pair_id": "hotel", "outcome": "executed" }. - For the gateway-shape sandbox, send
{ "archetype": "PAYMENT_TRANSFER", "constraints": { … } }.
403 EP_DELEGATION_BLOCK
Type: boundary_block
The action requested more spend than the delegation allows.
The blocked action still emits a signed receipt with kind: 'blocked'
and paymentStatus: 'not_charged'. The receipt's entries[] records
exactly which stage halted the action.
Remediation:
- Increase
constraints.max_total_usdto at least the requested amount. - Or split the action into multiple sub-actions under the limit.
- Or re-issue the delegation token with a higher cap.
403 EP_BOUNDARY_BLOCK
Type: boundary_block
The action is on the protocol's hard-deny list (e.g. PII export, audit-log mutation, master-key rotation). Hard-deny actions are permanently blocked regardless of delegation.
Remediation:
- Use a more specific, scoped action that the boundary can evaluate per-rule.
- Contact your policy admin to review the deny list.
The deny list cannot be overridden by an API call. Even an enterprise master key cannot bypass it. Changes happen via the policy admin path.
429 EP_RATE_LIMITED
Type: rate_limited
The sandbox enforces a per-IP token bucket. The response includes
Retry-After (seconds).
Remediation:
- Back off using the
Retry-Aftervalue. - For higher throughput, the production gateway will offer plan-scaled rate limits when it exits private beta.
Verification errors
POST /api/verify returns its own shape:
{
"valid": false,
"reason": "Receipt signature invalid: ECDSA-P256 verify failed",
"algorithm": "ES256",
"checks": {
"structural": true,
"kid_resolved": true,
"signature": false,
"not_quarantined": true
},
"diagnostics": { "chain": "…", "signature": "…", "entriesVerified": 9 }
}
{
"valid": false,
"reason": "Receipt signature invalid: ECDSA-P256 verify failed",
"algorithm": "ES256",
"checks": {
"structural": true,
"kid_resolved": true,
"signature": false,
"not_quarantined": true
},
"diagnostics": { "chain": "…", "signature": "…", "entriesVerified": 9 }
}
Verification never returns a 4xx for an invalid signature — that's a 200 OK
with valid: false. 4xx is reserved for malformed verification requests
(missing receipt field, etc.).
See also
- How it works — the deterministic pipeline.
- Authorization Boundary — what blocks vs. passes vs. escalates.
- Receipts — the receipt format and verification.