Error Object
API error response structure and error codes
There is no single universal error envelope - the exact shape depends on what rejected the request. See for the full breakdown; this page covers the two concrete shapes you'll actually parse.
Shape 1: framework-level errors (the majority)
Auth failures, validation failures, and most thrown errors return this shape:
{
"statusCode": 400,
"message": "amount must be a positive number",
"error": "Bad Request"
}message can be a single string or an array of strings (one per failed validation rule). There is no machine-readable code field in this shape - branch on the HTTP status code, not on the message text, which is meant for humans and can change.
| Field | Type | Description | |
|---|---|---|---|
| statusCode | number | HTTP status code, repeated in the body | |
| message | string \ | string[] | Human-readable description of what went wrong |
| error | string | Short name for the status code (e.g. "Bad Request", "Unauthorized") |
Shape 2: inline payment-processing errors
A handful of endpoints that process a card or crypto charge synchronously catch failures and return this instead of throwing:
{
"success": false,
"error": "The card was declined by the issuing bank"
}Here error is a plain string, not an object - there's no error.code/error.message to destructure. Check success only on the specific endpoints documented as using this shape; everywhere else, check the HTTP status.
HTTP Status Codes
| Status Code | Meaning | Usage |
|---|---|---|
| 400 | Bad Request | Invalid request parameters or body |
| 401 | Unauthorized | Invalid, missing, or revoked credential |
| 403 | Forbidden | Credential doesn't have permission for this resource or action |
| 404 | Not Found | Resource doesn't exist, or doesn't belong to your account |
| 409 | Conflict | Resource state conflict (e.g. already actioned, duplicate idempotency key) |
| 422 | Unprocessable Entity | Request was well-formed but failed validation |
| 429 | Too Many Requests | Rate limit exceeded - see Retry-After header |
| 500 | Internal Server Error | Unexpected server error - safe to retry |
| 502 / 503 | Bad Gateway / Service Unavailable | Upstream or platform temporarily unavailable - retry with backoff |
For guidance on what typically causes each status and how to respond, see Reading an Error by Status Code.
Error Handling Example
const res = await fetch(`https://api.ugc.africa/automation/actions/campaigns/${campaignId}/fund`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(fundData)
});
const result = await res.json();
if (!res.ok || result.success === false) {
throw new Error(result.message ?? result.error ?? 'request failed');
}
return result;Rate Limit Details
A 429 response carries no retry details in the body - back off using the Retry-After header:
{
"statusCode": 429,
"message": "ThrottlerException: Too Many Requests",
"error": "Too Many Requests"
}- General API: 100 requests per minute per API key
- Retry with exponential backoff once
Retry-Afterhas elapsed
Best Practices
1. Check the HTTP status, not a `success` field - most endpoints have no success field at all
2. Branch on status code, not on `message` text - message strings are for humans and can change
3. Implement retry logic - retry with exponential backoff for 429s and 5xx errors, never for 4xx
4. Log the full response body - useful for debugging even without a machine-readable code
5. Use idempotency keys - prevent duplicate side effects when you retry a create/action call
Did this solve your problem?