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
| Status | Meaning |
|---|---|
200, 201, 202 | Success |
400 | Malformed request (bad JSON, missing required field at the structure level) |
401 | Missing, malformed, or revoked API key |
403 | Authenticated but scope doesn’t allow this action |
404 | Resource doesn’t exist (or doesn’t belong to your merchant account) |
409 | Conflict — most commonly IDEMPOTENCY_CONFLICT |
422 | Validation error — request was well-formed but values are invalid |
429 | Rate limited |
500 | Internal server error — retry after a delay |
502 | Upstream provider error (Stripe, MTN, Orange) — retry after a delay |
503 | Soxara temporarily unavailable — retry with exponential backoff |
Error codes
Stable across API versions. Use these to drive your error-handling logic.
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 |
Validation
| Code | Status | What happened |
|---|---|---|
VALIDATION_ERROR | 422 | A field value didn’t pass validation — see details.field |
INVALID_PHONE_NUMBER | 422 | Phone is not in E.164 format |
INVALID_CURRENCY | 422 | Currency code isn’t supported (today: USD, LRD only) |
AMOUNT_TOO_SMALL | 422 | Amount below minimum for the currency/method |
AMOUNT_TOO_LARGE | 422 | Amount above maximum for the merchant tier or method |
Resource state
| Code | Status | What happened |
|---|---|---|
NOT_FOUND | 404 | Resource doesn’t exist OR isn’t owned by your merchant account (we don’t distinguish, for security) |
IDEMPOTENCY_CONFLICT | 409 | The same X-Idempotency-Key was used with a different request body |
ALREADY_PROCESSED | 409 | Operation already completed (e.g., refunding a payment that’s already fully refunded) |
Rate limiting
| Code | Status | What happened |
|---|---|---|
RATE_LIMITED | 429 | Per-merchant or per-endpoint rate limit exceeded. Response includes Retry-After header (seconds). |
Money & balance
| Code | Status | What happened |
|---|---|---|
INSUFFICIENT_FUNDS | 422 | Wallet, float, or merchant balance is too low for the requested operation |
INSUFFICIENT_FLOAT | 422 | Phoenix Hub’s MoMo float can’t cover this disbursement. Transient — try again after float top-up. |
Provider errors
| Code | Status | What happened |
|---|---|---|
PROVIDER_ERROR | 502 | Upstream provider (Stripe, MTN, Orange) returned an error. See details.provider and details.provider_code. |
PROVIDER_TIMEOUT | 502 | Upstream provider didn’t respond in time. Operation status is unknown — query the resource to confirm. |
PROVIDER_UNAVAILABLE | 503 | Provider is down. Retry with exponential backoff. |
Internal
| Code | Status | What happened |
|---|---|---|
INTERNAL_ERROR | 500 | Soxara bug. Retry once with a short delay; if it persists, open a ticket with the trace_id. |
SERVICE_UNAVAILABLE | 503 | A Soxara service is down or restarting. Retry with exponential backoff. |
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, 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