Skip to main content

Errors & Rate Limits

This page documents the Fiatsend API's error response format, HTTP status codes, common error codes, and rate limiting behavior. Understanding these is essential for building resilient integrations that handle failures gracefully.

Error Response Format

All Fiatsend API errors follow a consistent JSON structure:

{
"status": 0,
"code": "validation_error",
"message": "Phone number must be in E.164 format",
"data": null
}

Fields

FieldTypeDescription
statusinteger0 for errors, 1 for success
codestringMachine-readable error code for programmatic handling
messagestringHuman-readable error description — safe to display to end users
dataobject | nullAdditional error context (when available), otherwise null

Error with Additional Context

Some errors include additional data to help you diagnose the issue:

{
"status": 0,
"code": "limit_exceeded",
"message": "Daily payout limit exceeded",
"data": {
"limit": "5000.00",
"used": "4800.00",
"remaining": "200.00",
"currency": "GHS",
"resetsAt": "2026-03-18T00:00:00Z"
}
}

HTTP Status Codes

Fiatsend uses standard HTTP status codes to indicate the category of the error:

Status CodeMeaningDescription
200OKRequest succeeded
201CreatedResource created successfully
400Bad RequestInvalid request — missing fields, invalid format, or business rule violation
401UnauthorizedMissing or invalid authentication token. See Authentication.
403ForbiddenValid token but insufficient permissions — usually a KYC tier restriction. See KYC.
404Not FoundResource does not exist (e.g., invalid beneficiary ID, transaction ID)
409ConflictDuplicate resource — e.g., trying to create a beneficiary that already exists
429Too Many RequestsRate limit exceeded. See Rate Limits below.
500Internal Server ErrorUnexpected server error — retry with exponential backoff
502Bad GatewayUpstream service unavailable (e.g., mobile money provider down)
503Service UnavailableFiatsend is temporarily unavailable — retry after delay
504Gateway TimeoutUpstream service timed out — retry with exponential backoff

Error Codes

The code field in the error response identifies the specific error type. Use this for programmatic error handling rather than parsing the message string.

Authentication Errors

CodeHTTP StatusDescription
unauthorized401No authentication token provided
token_expired401JWT has expired — re-authenticate via Privy
token_invalid401JWT is malformed or has been tampered with
forbidden403Authenticated but KYC tier is insufficient for this operation

Validation Errors

CodeHTTP StatusDescription
validation_error400One or more request fields are missing or invalid
invalid_phone_format400Phone number is not in E.164 format (e.g., must be +233XXXXXXXXX)
invalid_provider400Unrecognized mobile money provider code for the specified country
invalid_amount400Amount is not a valid positive number or has too many decimal places
invalid_currency400Unsupported currency code
invalid_stablecoin400Unsupported stablecoin symbol

Beneficiary Errors

CodeHTTP StatusDescription
verification_failed400Account name does not match provider records
duplicate_beneficiary409A beneficiary with this phone and provider already exists
beneficiary_not_found404No beneficiary found with the specified ID
provider_not_found404The specified provider is not available in the target country

Conversion & Payout Errors

CodeHTTP StatusDescription
quote_expired400The conversion quote has expired — fetch a new rate
insufficient_balance400Wallet does not have enough stablecoin or GHS balance for this operation
limit_exceeded400Daily or monthly transaction limit exceeded for the user's KYC tier
conversion_failed500On-chain conversion failed — check the transaction hash for details
payout_failed502Mobile money provider could not process the payout
provider_unavailable503Mobile money provider is temporarily unreachable
provider_timeout504Mobile money provider did not respond in time

Idempotency Errors

CodeHTTP StatusDescription
idempotency_conflict409The idempotency key was already used with a different request body

Rate Limits

Fiatsend enforces rate limits to protect the platform and ensure fair usage. Limits vary by endpoint category and are applied per authenticated user.

Limit Tiers

Endpoint CategoryRate LimitWindowNotes
Conversion (quotes)30 requestsPer hourGET /api/convert/rate
Conversion (execute)10 requestsPer hourPOST /api/convert/usdt-to-ghs, POST /api/convert/confirm
Payouts5 requestsPer dayPOST /api/mobile-money/transfer
Beneficiaries20 requestsPer hourPOST /api/mobile-money/verify
General API calls1,000 requestsPer hourAll other endpoints
KYC submissions3 requestsPer dayPOST /api/users/kyc-upload
info

Rate limits are per authenticated user, not per API key. If multiple services share the same user credentials, they share the same rate limit pool. For high-volume integrations, contact dev@fiatsend.com to discuss enterprise rate limits.

Rate Limit Headers

When you approach or hit a rate limit, the API response includes these headers:

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed in the current window1000
X-RateLimit-RemainingRequests remaining in the current window847
X-RateLimit-ResetUnix timestamp when the current window resets1742212800
Retry-AfterSeconds to wait before retrying (only on 429 responses)120

Example 429 Response

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1742212800
Retry-After: 3600
Content-Type: application/json
{
"status": 0,
"code": "rate_limit_exceeded",
"message": "Conversion rate limit exceeded. Please try again in 60 minutes.",
"data": {
"limit": 10,
"window": "1 hour",
"retryAfter": 3600,
"resetsAt": "2026-03-17T10:00:00Z"
}
}

Best Practices

Exponential Backoff

When you receive a 429, 500, 502, 503, or 504 response, retry with exponential backoff rather than immediately retrying:

/**
* Fetch with automatic retry and exponential backoff.
*/
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);

// Success — return immediately
if (response.ok) {
return response;
}

// Rate limited — use Retry-After header
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "60", 10);
console.log(`Rate limited. Retrying in ${retryAfter} seconds...`);
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
continue;
}

// Server error — retry with exponential backoff
if (response.status >= 500 && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
console.log(`Server error ${response.status}. Retrying in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}

// Client error or max retries exhausted — return the error response
return response;
}
}

// Usage
const response = await fetchWithRetry(
"https://api.fiatsend.com/api/convert/rate?from=USDT&to=GHS&amount=100",
{
headers: { "Authorization": `Bearer ${token}` },
}
);
# cURL with manual retry (example)
# First attempt
curl -s -o response.json -w "%{http_code}" \
"https://api.fiatsend.com/api/convert/rate?from=USDT&to=GHS&amount=100" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

# If 429, read Retry-After header and wait

Cache Rate Responses

Conversion rate quotes are valid for 5 minutes. Cache the response and reuse it within that window instead of fetching a new rate for every user interaction. This reduces your rate limit consumption significantly.

Use Webhooks Instead of Polling

Instead of repeatedly calling GET /api/transactions/:id to check if a payout has completed, configure webhooks to receive push notifications. This eliminates unnecessary API calls and gives you faster status updates.

Monitor Rate Limit Headers

Track the X-RateLimit-Remaining header in your application and slow down proactively before hitting the limit. This prevents disruptive 429 errors in the middle of critical payment flows.

tip

For production integrations, log all API responses that include X-RateLimit-Remaining below 20% of the limit. This gives you early warning before your integration starts getting throttled.

  • Authentication — Token lifecycle and auth errors
  • Operations — API endpoints that are subject to rate limits
  • Webhooks — Event-driven alternative to polling
  • Idempotency — Safely retry without duplicate payments
  • Fees & Limits — Transaction limits by KYC tier (separate from rate limits)