Errors
Every Soxara response — success or failure — follows the same envelope.
Success envelope
{
"success": true,
"data": { ... }
}The HTTP status is 200 (or 201 for creation). data is the actual response payload.
Error envelope
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "metadata must serialize to 4000 characters or fewer."
}
}code is a stable machine-readable string. Switch on code, not on message. Messages may
change between API versions; codes don’t. There is no details field on merchant API errors —
everything you need to act on is in code and message.
HTTP status codes
| Status | Meaning |
|---|---|
200, 201 | Success |
401 | Missing, malformed, or revoked API key |
403 | Authenticated but scope doesn’t allow this action, or the account isn’t a business account |
404 | Resource doesn’t exist (or doesn’t belong to your merchant account — we don’t distinguish, for security) |
409 | Conflict — IDEMPOTENCY_CONFLICT (same key, different body) or IDEMPOTENCY_IN_FLIGHT (same key, first request still processing) |
422 | Validation error — request was well-formed but values are invalid, including a missing/malformed Idempotency-Key |
429 | Rate limited |
500 | Internal server error — retry after a delay |
502 | Upstream unreachable (a downstream Soxara service, or a card/MTN/Orange provider) — retry after a delay |
503 | That part of Soxara is temporarily unavailable — retry with backoff |
Error codes
The codes you’ll actually see from the merchant API surface (/v1/api/*, gift cards, fleet
cards):
Authentication & authorization
| Code | Status | What happened |
|---|---|---|
UNAUTHORIZED | 401 | Bearer key missing, malformed, or revoked |
FORBIDDEN | 403 | Key is valid but lacks the required scope for this endpoint |
BUSINESS_ACCOUNT_REQUIRED | 403 | The action needs a business account (e.g. minting an API key) and this one isn’t |
Validation
| Code | Status | What happened |
|---|---|---|
VALIDATION_ERROR | 422 | A field value didn’t pass validation — see message |
Idempotency
| Code | Status | What happened |
|---|---|---|
IDEMPOTENCY_KEY_REQUIRED | 422 | The route requires an idempotency key and none was sent |
IDEMPOTENCY_KEY_INVALID | 422 | The idempotency key didn’t pass validation (e.g. wrong shape) |
IDEMPOTENCY_CONFLICT | 409 | The same idempotency key was reused with a different request body |
IDEMPOTENCY_IN_FLIGHT | 409 | The same idempotency key’s first request is still being processed — retry shortly |
See Idempotency — note that payroll runs take their own Idempotency-Key
header separate from the gateway-level one, and gift-card/fleet-card redemption take an
idempotencyKey body field instead of a header.
Resource state
| Code | Status | What happened |
|---|---|---|
NOT_FOUND | 404 | Resource doesn’t exist OR isn’t owned by your merchant account |
Rate limiting
| Code | Status | What happened |
|---|---|---|
RATE_LIMITED | 429 | Too many requests. Slow down and retry after a short delay. |
Fleet cards
| Code | Status | What happened |
|---|---|---|
FLEET_CARD_INACTIVE | 422 | The fleet card isn’t active |
CATEGORY_NOT_ALLOWED | 422 | This merchant category isn’t allowed on this fleet card |
INSUFFICIENT_BALANCE | 422 | The fleet card’s balance can’t cover this redemption |
SPEND_LIMIT_EXCEEDED | 422 | This redemption would exceed the fleet card’s spend limit |
Upstream / internal
| Code | Status | What happened |
|---|---|---|
BAD_GATEWAY | 502 | An upstream Soxara service or provider didn’t respond correctly. Retry after a delay. |
SERVICE_UNAVAILABLE | 503 | That part of Soxara is down or restarting. Retry with backoff. |
INTERNAL_ERROR | 500 | A Soxara bug. Retry once after a short delay; if it persists, open a ticket with the X-Trace-ID from the response. |
QUICKBOOKS_UPSTREAM_ERROR | varies | The QuickBooks sync connector’s upstream call failed — QuickBooks-specific, not part of the payments surface |
Retrying
Two rules:
- Always retry with the same idempotency key. That’s the whole point of idempotency — a retry on the same key doesn’t double-charge. See Idempotency.
- Use exponential backoff. Start at 500ms, double each retry, cap at 30s, give up after 5 attempts. Don’t hammer a struggling provider; that just makes the outage worse.
When not to retry:
401,403,404— these won’t change. Fix your code/key.422 VALIDATION_ERROR— same. Fix your payload.409 IDEMPOTENCY_CONFLICT— you reused a key with a different body. Generate a new key.
Retry safely on: 429 RATE_LIMITED, 500 INTERNAL_ERROR, 502 BAD_GATEWAY,
503 SERVICE_UNAVAILABLE, 409 IDEMPOTENCY_IN_FLIGHT (after a short wait — the first request is
still finishing).
Trace IDs
Every response carries an X-Trace-ID header. Send your own on the request and Soxara echoes it
back; omit it and Soxara mints one for you. When you contact support, include this — it’s the
exact handle we use to find the request in our logs.
curl -i $SOXARA_BASE/v1/api/payments/pi_3TZ... -H "Authorization: Bearer $SOXARA_KEY" \
| grep -i x-trace-id
# X-Trace-ID: 0a219f72-03ae-4786-a96e-253b01c91399