Errors

Errors

Every Soxara response — success or failure — follows the same envelope.

Success envelope

{
  "success": true,
  "data": { ... }
}

The HTTP status is 200 (or 201/202 for creation/async). data is the actual response payload.

Error envelope

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "amount must be a positive integer.",
    "details": {
      "field": "amount",
      "received": -10
    }
  }
}

code is a stable machine-readable string. Switch on code, not on message. Messages may change between API versions; codes don’t.

details is optional and varies by error code. Always check for details before relying on a specific field — its presence is best-effort.

HTTP status codes

StatusMeaning
200, 201, 202Success
400Malformed request (bad JSON, missing required field at the structure level)
401Missing, malformed, or revoked API key
403Authenticated but scope doesn’t allow this action
404Resource doesn’t exist (or doesn’t belong to your merchant account)
409Conflict — most commonly IDEMPOTENCY_CONFLICT
422Validation error — request was well-formed but values are invalid
429Rate limited
500Internal server error — retry after a delay
502Upstream provider error (Stripe, MTN, Orange) — retry after a delay
503Soxara temporarily unavailable — retry with exponential backoff

Error codes

Stable across API versions. Use these to drive your error-handling logic.

Authentication & authorization

CodeStatusWhat happened
UNAUTHORIZED401Bearer key missing, malformed, or revoked
FORBIDDEN403Key is valid but lacks the required scope for this endpoint

Validation

CodeStatusWhat happened
VALIDATION_ERROR422A field value didn’t pass validation — see details.field
INVALID_PHONE_NUMBER422Phone is not in E.164 format
INVALID_CURRENCY422Currency code isn’t supported (today: USD, LRD only)
AMOUNT_TOO_SMALL422Amount below minimum for the currency/method
AMOUNT_TOO_LARGE422Amount above maximum for the merchant tier or method

Resource state

CodeStatusWhat happened
NOT_FOUND404Resource doesn’t exist OR isn’t owned by your merchant account (we don’t distinguish, for security)
IDEMPOTENCY_CONFLICT409The same X-Idempotency-Key was used with a different request body
ALREADY_PROCESSED409Operation already completed (e.g., refunding a payment that’s already fully refunded)

Rate limiting

CodeStatusWhat happened
RATE_LIMITED429Per-merchant or per-endpoint rate limit exceeded. Response includes Retry-After header (seconds).

Money & balance

CodeStatusWhat happened
INSUFFICIENT_FUNDS422Wallet, float, or merchant balance is too low for the requested operation
INSUFFICIENT_FLOAT422Phoenix Hub’s MoMo float can’t cover this disbursement. Transient — try again after float top-up.

Provider errors

CodeStatusWhat happened
PROVIDER_ERROR502Upstream provider (Stripe, MTN, Orange) returned an error. See details.provider and details.provider_code.
PROVIDER_TIMEOUT502Upstream provider didn’t respond in time. Operation status is unknown — query the resource to confirm.
PROVIDER_UNAVAILABLE503Provider is down. Retry with exponential backoff.

Internal

CodeStatusWhat happened
INTERNAL_ERROR500Soxara bug. Retry once with a short delay; if it persists, open a ticket with the trace_id.
SERVICE_UNAVAILABLE503A Soxara service is down or restarting. Retry with exponential backoff.

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, 500, 502, 503, PROVIDER_TIMEOUT, PROVIDER_UNAVAILABLE.

Trace IDs

Every Soxara response includes an X-Trace-Id header (also surfaced in error.details.trace_id on error responses). When you contact support, include this — we can look up the exact request in our logs.

curl -i $SOXARA_BASE/v1/payments/pi_3TZ... -H "Authorization: Bearer $SOXARA_KEY" \
  | grep -i x-trace-id
# X-Trace-Id: 0a219f72-03ae-4786-a96e-253b01c91399