Skip to main content

Error response format

Every error response from the Mandate API returns JSON with a consistent structure. The exact fields depend on whether the error is a policy block or a general API error.

Policy block response

{
  "allowed": false,
  "blockReason": "per_tx_limit_exceeded",
  "blockDetail": "$500.00 exceeds $100/tx limit",
  "declineMessage": "This transaction exceeds your per-transaction limit of $100."
}

General error response

{
  "error": "Invalid or missing runtime key"
}
The blockReason field is only present on policy blocks (422) and circuit breaker blocks (403). The declineMessage field is an adversarial counter-message designed to override prompt injection attempts.

HTTP status code reference

StatusMeaningSDK Error ClassWhen It Happens
200SuccessNoneTransaction validated successfully, event posted, or status retrieved
202Approval requiredApprovalRequiredErrorTransaction passes policy but requires human approval
400Bad requestMandateErrorMissing required fields, invalid JSON, or malformed parameters
401UnauthorizedMandateErrorMissing Authorization header, invalid runtime key, or expired key
403Circuit breaker activeCircuitBreakerErrorAgent’s circuit breaker is tripped. All transactions blocked.
404Not foundMandateErrorIntent ID does not exist or belongs to a different agent
409ConflictMandateErrorDuplicate intent hash or attempt to transition an intent in a wrong state
410GoneMandateErrorApproval expired. The 1-hour TTL has elapsed.
422Policy blockedPolicyBlockedError or RiskBlockedErrorTransaction violates a policy rule or is flagged by risk scanning
429Rate limitedMandateErrorToo many requests. Back off and retry.
500Server errorMandateErrorTransient server issue. Safe to retry with exponential backoff.

SDK error class hierarchy

MandateError (base)
  statusCode: number
  blockReason?: string

├── PolicyBlockedError (422)
│     detail?: string
│     declineMessage?: string

├── CircuitBreakerError (403)
│     blockReason: "circuit_breaker_active"

├── ApprovalRequiredError (202)
│     intentId: string
│     approvalId: string
│     approvalReason?: string

└── RiskBlockedError (422)
      blockReason: "aegis_critical_risk"

Mapping responses to error classes

The SDK automatically maps API responses to the correct error class:
  • 403 with circuit_breaker_active maps to CircuitBreakerError
  • 422 with aegis_critical_risk maps to RiskBlockedError
  • 422 with any other blockReason maps to PolicyBlockedError
  • 202 with requiresApproval: true maps to ApprovalRequiredError
  • All other non-2xx responses map to MandateError

Handling errors

Always check specific subclasses before the base class:
import {
  CircuitBreakerError,
  RiskBlockedError,
  ApprovalRequiredError,
  PolicyBlockedError,
  MandateError,
} from '@mandate.md/sdk';

try {
  await client.validate(payload);
} catch (err) {
  if (err instanceof CircuitBreakerError) {
    // 403: halt all operations
  } else if (err instanceof RiskBlockedError) {
    // 422: dangerous address, do not retry
  } else if (err instanceof ApprovalRequiredError) {
    // 202: poll for approval
    await client.waitForApproval(err.intentId);
  } else if (err instanceof PolicyBlockedError) {
    // 422: show decline message
    console.log(err.declineMessage);
  } else if (err instanceof MandateError) {
    // Generic: check statusCode for retry logic
    if (err.statusCode >= 500) {
      // Safe to retry with backoff
    }
  }
}

Next Steps

SDK Error Classes

Detailed reference for each error class with properties and recovery patterns.

Block Reasons

All blockReason values with causes and resolutions.