Status Codes
Tulu Switch uses standard HTTP status codes. Every response body also carries a statusCode field that mirrors the HTTP status, a success boolean, and a message string — so you can decide how to handle a result without parsing the HTTP layer separately.
Response Shape
All successful and error responses share the same envelope:
success — true on success, false on any error
statusCode — mirrors the HTTP status code (e.g. 200, 400, 401)
message — human-readable description of the result
data — response payload; null on error responses
errors — array of field-level validation messages (present on 400 responses)
timestamp — ISO 8601 datetime of the response
path — request path (present on error responses)
{
"success": true,
"statusCode": 200,
"message": "Success",
"data": {
"id": "cus_abc123",
"email": "ada.obi@example.com"
},
"timestamp": "2025-01-01T00:00:00.000Z"
}HTTP Status Codes
| Code | Status | When it occurs |
|---|---|---|
| 200 | OK | Request processed successfully. |
| 201 | Created | Resource created (e.g. new customer, new wallet). |
| 400 | Bad Request | Validation failed — missing or invalid fields. Check the errors array. |
| 401 | Unauthorized | Access token missing, expired, or invalid. Re-authenticate. |
| 403 | Forbidden | Authenticated but not authorised. Common causes: KYB not approved, LIVE key on restricted route, account suspended. |
| 404 | Not Found | The resource (customer, transaction, wallet) does not exist. |
| 409 | Conflict | Duplicate request — e.g. creating a customer with an email that already exists. |
| 422 | Unprocessable Entity | Request is well-formed but semantically invalid (e.g. unsupported currency for a channel). |
| 429 | Too Many Requests | Rate limit exceeded. Slow down and retry after the period indicated. |
| 500 | Internal Server Error | Unexpected server-side failure. Retry with back-off; contact support if it persists. |
| 503 | Service Unavailable | Platform or upstream provider temporarily unavailable. Retry with back-off. |
Error Responses
Error responses set success: false and include a message with a plain-English explanation. 400 responses also include an errors array listing the specific fields that failed validation.
{
"success": false,
"statusCode": 400,
"message": "Currency NGN is not supported for the STABLECOIN channel",
"errors": [
"currency must be one of: USDC, USDT"
],
"data": null,
"timestamp": "2025-01-01T00:00:00.000Z",
"path": "/v2/purse/deposit"
}body.errors on a 400 to surface field-level messages to your users. The top-level message describes the first or most significant error only.Handling in Code
Branch on body.statusCode (or the HTTP status) for machine logic; use body.message for user-facing feedback.
async function callApi(url: string, options: RequestInit) {
const res = await fetch(url, options);
const body = await res.json();
if (!body.success) {
// Use body.statusCode for logic; body.message for user feedback
switch (body.statusCode) {
case 400:
// Validation error — check body.errors for field-level detail
throw new Error(`Bad request: ${body.message}`);
case 401:
// Token expired — re-authenticate and retry
await refreshAccessToken();
break;
case 403:
// Account not verified or feature not enabled
throw new Error("Access denied: complete KYB to proceed");
case 404:
throw new Error("Resource not found");
case 409:
// Duplicate request — usually safe to ignore or de-duplicate
break;
case 429:
// Rate limited — back off and retry
await sleep(1000);
break;
case 500:
case 503:
// Transient error — retry with exponential back-off
throw new Error("Server error, retry later");
default:
throw new Error(body.message ?? "Unknown error");
}
}
return body.data;
}Best Practices
- Treat
200and201as the only guaranteed success states - On
401— silently refresh the access token and retry the original request once before surfacing an error - On
400— readerrors[]to show field-level feedback; do not retry without fixing the payload - On
500/503— retry with exponential back-off (e.g. 1 s → 2 s → 4 s), then give up and alert - On
409— check whether the resource was already created before creating a duplicate - Log all non-2xx responses with the full body for debugging; include
pathandtimestamp