Errors

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

StatusMeaning
200, 201Success
401Missing, malformed, or revoked API key
403Authenticated but scope doesn’t allow this action, or the account isn’t a business account
404Resource doesn’t exist (or doesn’t belong to your merchant account — we don’t distinguish, for security)
409Conflict — IDEMPOTENCY_CONFLICT (same key, different body) or IDEMPOTENCY_IN_FLIGHT (same key, first request still processing)
422Validation error — request was well-formed but values are invalid, including a missing/malformed Idempotency-Key
429Rate limited
500Internal server error — retry after a delay
502Upstream unreachable (a downstream Soxara service, or a card/MTN/Orange provider) — retry after a delay
503That 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

CodeStatusWhat happened
UNAUTHORIZED401Bearer key missing, malformed, or revoked
FORBIDDEN403Key is valid but lacks the required scope for this endpoint
BUSINESS_ACCOUNT_REQUIRED403The action needs a business account (e.g. minting an API key) and this one isn’t

Validation

CodeStatusWhat happened
VALIDATION_ERROR422A field value didn’t pass validation — see message

Idempotency

CodeStatusWhat happened
IDEMPOTENCY_KEY_REQUIRED422The route requires an idempotency key and none was sent
IDEMPOTENCY_KEY_INVALID422The idempotency key didn’t pass validation (e.g. wrong shape)
IDEMPOTENCY_CONFLICT409The same idempotency key was reused with a different request body
IDEMPOTENCY_IN_FLIGHT409The 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

CodeStatusWhat happened
NOT_FOUND404Resource doesn’t exist OR isn’t owned by your merchant account

Rate limiting

CodeStatusWhat happened
RATE_LIMITED429Too many requests. Slow down and retry after a short delay.

Fleet cards

CodeStatusWhat happened
FLEET_CARD_INACTIVE422The fleet card isn’t active
CATEGORY_NOT_ALLOWED422This merchant category isn’t allowed on this fleet card
INSUFFICIENT_BALANCE422The fleet card’s balance can’t cover this redemption
SPEND_LIMIT_EXCEEDED422This redemption would exceed the fleet card’s spend limit

Upstream / internal

CodeStatusWhat happened
BAD_GATEWAY502An upstream Soxara service or provider didn’t respond correctly. Retry after a delay.
SERVICE_UNAVAILABLE503That part of Soxara is down or restarting. Retry with backoff.
INTERNAL_ERROR500A Soxara bug. Retry once after a short delay; if it persists, open a ticket with the X-Trace-ID from the response.
QUICKBOOKS_UPSTREAM_ERRORvariesThe QuickBooks sync connector’s upstream call failed — QuickBooks-specific, not part of the payments surface

Retrying

Two rules:

  1. 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.
  2. 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