Errors & troubleshooting
HTTP status codes, error response shapes, retries, and common integration issues
Use this reference when an API call returns a non-success status. Always check the HTTP status code first - there is no stable machine-readable error code across endpoints, and the response body shape varies.
Error Response Format
Most errors (auth failures, not-found, forbidden, validation) come back in the standard shape:
{
"statusCode": 400,
"message": "Amount must be greater than 0",
"error": "Bad Request"
}A validation failure across multiple fields instead nests an array:
{
"statusCode": 400,
"message": ["amount must be a positive number", "currency must be a valid ISO code"],
"error": "Bad Request"
}A small number of endpoints that process a payment inline (card/crypto charges) catch failures and return 200/201 with a body-level flag instead of a non-2xx status:
{ "success": false, "error": "Card payment processing failed" }success: false on payment-processing endpoints.HTTP Status Codes
| Status | Meaning | Retry? |
|---|---|---|
| 200/201 | Success | N/A |
| 400 | Bad Request - invalid parameters | No - fix request |
| 401 | Unauthorized - invalid/missing credential | No - check credentials |
| 403 | Forbidden - not the owner of this resource | No - check which key/user you're using |
| 404 | Not Found | No - check the id/reference |
| 409 | Conflict - concurrent update | Retry after re-fetching current state |
| 422 | Validation Error | No - fix the request body |
| 429 | Rate Limit Exceeded | Yes - with backoff |
| 500 / 502 / 503 / 504 | Server-side failure | Yes - retry with backoff |
Reading an Error by Status Code
There is no stable machine-readable error code on these endpoints - branch on the HTTP status and read message for the human-readable reason.
| Status | Typical cause | What to do |
|---|---|---|
| 401 | Missing/invalid credential | Check the Authorization header - UGSK_live_... for self-serve, UGAU_... for an Automation-connected app |
| 400 / 422 | Invalid request body | Check message (and, for ValidationPipe failures, the errors[] array) against the field docs |
| 403 | You don't own this resource, or don't hold the right key/scope | Verify which user/key you're using |
| 404 | Campaign/payment link/slot not found | Verify the id and that it belongs to the authenticated account |
| 409 / 422 | Operation not allowed in the resource's current state (e.g. topping up an already-ACTIVE campaign, or acting on a payment-link transaction that isn't in a protected/held state) | Re-fetch current status before retrying |
| 429 | Too many requests | Wait the number of seconds in the Retry-After header, then back off |
| 500 / 502 / 503 | Server-side failure | Retry with exponential backoff |
Webhook delivery failures
These aren't returned to your API caller - they show up as failed deliveries to your targetUrl, retried up to 5 times with backoff. If your endpoint never receives a delivery, verify the URL is publicly reachable; if you reject deliveries with 401, recompute the HMAC-SHA256 signature over the raw body using your hookSecret (no timestamp is included) - see .
Code Examples
Examples below share one fund a campaign flow.
const API_KEY = process.env.UGSK_LIVE || "UGSK_live_xxx";
async function fundCampaign(campaignId, body) {
const res = await fetch(
`https://api.ugc.africa/automation/actions/campaigns/${campaignId}/fund`,
{
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
}
);
const result = await res.json();
if (!res.ok || result.success === false) {
throw new Error(result.message ?? result.error ?? "request failed");
}
return result;
}Debugging Tips
| Issue | Cause | Solution |
|---|---|---|
| 401 errors | Wrong, revoked, or missing credential | Verify you're sending the right credential for this endpoint (UGSK_ vs UGAU_) |
| Webhooks not received | Firewall/wrong URL | Check the URL is publicly reachable; re-check with |
| 404 errors | Wrong id or wrong owner | Confirm the resource belongs to the authenticated user |
| Duplicate campaigns/links | No idempotency handling | De-dupe on your side before retrying a POST |
Did this solve your problem?