Error codes

April 18, 2026 · View on GitHub

Every error returned by the relay uses the same JSON envelope:

{
  "error": "CODE",
  "message": "Human-readable detail",
  "details": { "optional": "context" }
}

Canonical codes

errorHTTPCauseRaised by
SELLER_NOT_FOUND404Unknown merchantId./discover, /settle, /merchants/:id/mpp/config, /merchants/:id/mpp/charge, /bazaar/transactions?merchantId=…
INVALID_PAYMENT400Payment validation failed (missing merchantId, zero amount, payTo mismatch, invalid signature, amount over per-tx limit)./verify, /settle
UNSUPPORTED_NETWORK400Registration requested a CAIP-2 not enabled on this relay. Response details include supportedNetworks and an example payload./register
REPLAY_DETECTED409Signature / nonce already settled./settle
CIRCUIT_BREAKER503Per-tx limit, daily volume limit, or global pause is active./settle
RATE_LIMIT429Per-IP rate limit exceeded on this endpoint.All rate-limited endpoints
ROUTE_NOT_CONFIGURED404No gas allowance schedule for the from / to pair./bridge/fees, /bazaar/cost-comparison
ERRORvaries (defaults 500)Custom operational error raised by a route handler.Any
INTERNAL_ERROR500Unhandled exception.Any — check logs / traces.

Validation failures

Request-shape failures (missing required fields, wrong types, minLength violations) return 400 Bad Request with Elysia's default error format — not the envelope above. You will see:

{
  "type": "validation",
  "on": "body",
  "property": "/wallet",
  "message": "Expected string to have a length of at least 10"
}

If you write a client, handle both shapes.

invalidReason inside /verify

POST /verify returns HTTP 200 with isValid: false and a machine-readable invalidReason. These are not top-level errors. Common values:

invalidReasonMeaning
missing_merchantIdextra.merchantId not set.
seller_not_foundmerchantId unknown.
network_not_supportednetwork not enabled on this relay.
payTo_mismatchpayTo is not the relay's address on this network.
invalid_signatureCryptographic check failed.
amount_too_smallAmount ≤ 0 or below scheme minimum.
expired_authorizationvalidBefore already passed.

invalidMessage is the human-readable counterpart. Surface it to the buyer's client so they can retry correctly.

Troubleshooting by error

  • CIRCUIT_BREAKER on every settle — check redis-cli GET facilitator:pause, GET volume:daily:YYYY-MM-DD, and MAX_TX_AMOUNT. See trigger circuit breakers.
  • REPLAY_DETECTED — the EIP-3009 nonce or Stellar authorization nonce has already been consumed. The buyer must sign a fresh one. There is no "release nonce" — this is a security feature.
  • SELLER_NOT_FOUND on a newly-registered merchant — check you are hitting the same relay instance you registered with (PostgreSQL, not Redis, persists sellers).
  • ROUTE_NOT_CONFIGURED — the pair does not have a gas schedule entry. Add one in packages/shared/src/networks/gas-schedule.ts and redeploy.
  • INTERNAL_ERROR — always a bug. Report it with the workflowId (if applicable) and the request id from the logs.

No alerting on RATE_LIMIT

429s are part of normal operation — chatty agents hit them. Do not alert on RATE_LIMIT at the infra level; monitor it per-endpoint for abuse patterns instead.