Concepts
Errors
Errors use standard HTTP status codes and a consistent JSON body.
402 Payment Required
{
"error": {
"type": "insufficient_funds_error",
"code": "insufficient_funds",
"message": "Wallet balance (2000 NGN) is lower than the 5250 NGN required. Fund your wallet and retry.",
"param": null,
"request_id": "req_3c1f0b8e5a7d4c29b6e1a0f4"
}
}| Field | Meaning |
|---|---|
type | Broad category. Safe to switch on. |
code | Specific, stable machine-readable reason. Safe to switch on. |
message | Human-readable explanation. Do not parse it. |
param | The request field at fault, when there is one. Nested fields use dots, e.g. targeting.locations. |
request_id | Quote this to support. |
Error catalog
| HTTP | type | code | Meaning |
|---|---|---|---|
| 400 | invalid_request_error | invalid_parameterinvalid_budgetinvalid_task_typeinvalid_locationunknown_parameterinvalid_jsonidempotency_key_required | The request is malformed or breaks a rule. param names the field. |
| 400 | invalid_request_error | budget_too_lowbudget_too_high | Budget is outside the allowed range for the Growth Action type or your account limit. |
| 401 | authentication_error | invalid_api_key | Missing, malformed, revoked or expired API key. |
| 402 | insufficient_funds_error | insufficient_funds | Wallet balance is lower than the total cost. Nothing was charged. |
| 403 | permission_error | insufficient_scopeaccount_suspendedtest_mode_onlypartner_inactive | The key cannot do this, or the account is suspended. |
| 404 | not_found_error | not_foundroute_not_found | No such resource (or it belongs to another partner or environment). |
| 405 | invalid_request_error | method_not_allowed | Wrong HTTP method for this path. The Allow header lists valid ones. |
| 409 | conflict_error | duplicate_external_referenceinvalid_staterequest_in_progress | The request conflicts with current state, e.g. pausing a completed campaign. |
| 413 | invalid_request_error | body_too_large | Body is larger than 64 KB. |
| 415 | invalid_request_error | unsupported_media_type | Send Content-Type: application/json. |
| 422 | invalid_request_error | idempotency_key_reuse | The same Idempotency-Key was used with a different request. |
| 429 | rate_limit_error | rate_limiteddaily_limit_reached | Too many requests, or the daily campaign cap was reached. See Retry-After. |
| 500 | api_error | internal_error | Something went wrong on our side. Safe to retry with the same Idempotency-Key; quote request_id if it persists. |
Handling errors in code
const res = await fetch(url, options);
const body = await res.json();
if (!res.ok) {
const { type, code, message, param, request_id } = body.error;
if (res.status === 429) { await sleep(Number(res.headers.get("retry-after") ?? 5) * 1000); /* then retry */ }
else if (code === "insufficient_funds") { /* ask your customer to top up */ }
else { throw new Error(`${code} (${request_id}): ${message}`); }
}res = requests.post(url, headers=headers, json=payload, timeout=30)
if not res.ok:
err = res.json()["error"]
if res.status_code == 429:
time.sleep(int(res.headers.get("retry-after", 5))) # then retry
elif err["code"] == "insufficient_funds":
... # ask your customer to top up
else:
raise RuntimeError(f'{err["code"]} ({err["request_id"]}): {err["message"]}')