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
error | HTTP | Cause | Raised by |
|---|---|---|---|
SELLER_NOT_FOUND | 404 | Unknown merchantId. | /discover, /settle, /merchants/:id/mpp/config, /merchants/:id/mpp/charge, /bazaar/transactions?merchantId=… |
INVALID_PAYMENT | 400 | Payment validation failed (missing merchantId, zero amount, payTo mismatch, invalid signature, amount over per-tx limit). | /verify, /settle |
UNSUPPORTED_NETWORK | 400 | Registration requested a CAIP-2 not enabled on this relay. Response details include supportedNetworks and an example payload. | /register |
REPLAY_DETECTED | 409 | Signature / nonce already settled. | /settle |
CIRCUIT_BREAKER | 503 | Per-tx limit, daily volume limit, or global pause is active. | /settle |
RATE_LIMIT | 429 | Per-IP rate limit exceeded on this endpoint. | All rate-limited endpoints |
ROUTE_NOT_CONFIGURED | 404 | No gas allowance schedule for the from / to pair. | /bridge/fees, /bazaar/cost-comparison |
ERROR | varies (defaults 500) | Custom operational error raised by a route handler. | Any |
INTERNAL_ERROR | 500 | Unhandled 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:
invalidReason | Meaning |
|---|---|
missing_merchantId | extra.merchantId not set. |
seller_not_found | merchantId unknown. |
network_not_supported | network not enabled on this relay. |
payTo_mismatch | payTo is not the relay's address on this network. |
invalid_signature | Cryptographic check failed. |
amount_too_small | Amount ≤ 0 or below scheme minimum. |
expired_authorization | validBefore already passed. |
invalidMessage is the human-readable counterpart. Surface it to the buyer's client so they can retry correctly.
Troubleshooting by error
CIRCUIT_BREAKERon every settle — checkredis-cli GET facilitator:pause,GET volume:daily:YYYY-MM-DD, andMAX_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_FOUNDon 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 inpackages/shared/src/networks/gas-schedule.tsand redeploy.INTERNAL_ERROR— always a bug. Report it with theworkflowId(if applicable) and the requestidfrom 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.