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:

successtrue 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 response(JSON)
{
  "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

CodeStatusWhen it occurs
200OKRequest processed successfully.
201CreatedResource created (e.g. new customer, new wallet).
400Bad RequestValidation failed — missing or invalid fields. Check the errors array.
401UnauthorizedAccess token missing, expired, or invalid. Re-authenticate.
403ForbiddenAuthenticated but not authorised. Common causes: KYB not approved, LIVE key on restricted route, account suspended.
404Not FoundThe resource (customer, transaction, wallet) does not exist.
409ConflictDuplicate request — e.g. creating a customer with an email that already exists.
422Unprocessable EntityRequest is well-formed but semantically invalid (e.g. unsupported currency for a channel).
429Too Many RequestsRate limit exceeded. Slow down and retry after the period indicated.
500Internal Server ErrorUnexpected server-side failure. Retry with back-off; contact support if it persists.
503Service UnavailablePlatform 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.

400 error response(JSON)
{
  "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"
}
Always check 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.

Error handling (TypeScript)(TypeScript)
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 200 and 201 as the only guaranteed success states
  • On 401 — silently refresh the access token and retry the original request once before surfacing an error
  • On 400 — read errors[] 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 path and timestamp