UGCDocumentation

Errors & troubleshooting

HTTP status codes, error response shapes, retries, and common integration issues

Updated August 28, 2026

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:

json
{
  "statusCode": 400,
  "message": "Amount must be greater than 0",
  "error": "Bad Request"
}

A validation failure across multiple fields instead nests an array:

json
{
  "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:

json
{ "success": false, "error": "Card payment processing failed" }
Best Practice: Check the HTTP status first. Only when it's a 2xx should you also check for a body-level success: false on payment-processing endpoints.

HTTP Status Codes

StatusMeaningRetry?
200/201SuccessN/A
400Bad Request - invalid parametersNo - fix request
401Unauthorized - invalid/missing credentialNo - check credentials
403Forbidden - not the owner of this resourceNo - check which key/user you're using
404Not FoundNo - check the id/reference
409Conflict - concurrent updateRetry after re-fetching current state
422Validation ErrorNo - fix the request body
429Rate Limit ExceededYes - with backoff
500 / 502 / 503 / 504Server-side failureYes - 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.

StatusTypical causeWhat to do
401Missing/invalid credentialCheck the Authorization header - UGSK_live_... for self-serve, UGAU_... for an Automation-connected app
400 / 422Invalid request bodyCheck message (and, for ValidationPipe failures, the errors[] array) against the field docs
403You don't own this resource, or don't hold the right key/scopeVerify which user/key you're using
404Campaign/payment link/slot not foundVerify the id and that it belongs to the authenticated account
409 / 422Operation 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
429Too many requestsWait the number of seconds in the Retry-After header, then back off
500 / 502 / 503Server-side failureRetry 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.

JavaScript
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

IssueCauseSolution
401 errorsWrong, revoked, or missing credentialVerify you're sending the right credential for this endpoint (UGSK_ vs UGAU_)
Webhooks not receivedFirewall/wrong URLCheck the URL is publicly reachable; re-check with
404 errorsWrong id or wrong ownerConfirm the resource belongs to the authenticated user
Duplicate campaigns/linksNo idempotency handlingDe-dupe on your side before retrying a POST

Did this solve your problem?