Errors
One error envelope for every failure, a stable set of machine-readable codes, and a request id on every response for support.
Every failure, at every status code, returns the same JSON shape. You write one error handler and it covers the whole API.
The error envelope
{
"error": {
"code": "validation_failed",
"message": "MINIMUM_EFT_AMOUNT must be numeric.",
"requestid": "8f3c1a9b2d7e4f60"
}
}| Field | Use it for |
|---|---|
code | Control flow. A stable machine-readable token. Branch on this. |
message | Humans and logs. Readable and safe to surface, but the wording may change, so never branch on its text. |
requestid | Support. Correlates to the X-Request-Id response header and the server logs. Quote it when reporting a problem. |
The message never carries internal detail: no stack traces, no SQL, no connection strings, no
tokens, and no confirmation that another tenant's record exists.
Error codes
Branch on code. These strings are part of the published contract: new ones may be added, but
existing ones are never renamed or repurposed.
code | Typical status | Meaning |
|---|---|---|
unauthorized | 401 | No valid bearer token (missing, malformed, disabled, or suspended account). Every auth failure is identical. |
forbidden | 403 | Authenticated, but not permitted to perform that action. |
not_found | 404 | The merchant or nested resource does not exist, isn't yours, or is deleted (indistinguishable by design). |
validation_failed | 400 | A field or the request body failed validation (bad type, disallowed setting key, illegal status transition, unreadable JSON). |
source_out_of_scope | 400 | A copy_settings_from source names a merchant outside your scope. Distinct from not_found because you named it explicitly. |
rate_limited | 429 | The per-credential rate limit was exceeded. Honour the Retry-After header. See Rate limits. |
internal_error | 500 | An unexpected server-side fault. Not your request's fault; retry, and if it persists quote the requestid. |
Status codes at a glance
400: you sent something invalid. Fix the request; retrying unchanged will not help.401: authentication failed. See Authentication.403: authenticated but not permitted.404: the resource isn't visible to you (may not exist, or isn't yours).429: slow down; obeyRetry-After.500: server fault. Safe to retry with backoff.
Distinguish 500 from 401. A 500 on a call that normally works is a server fault, not a
statement that your token is invalid. Do not discard the token or re-authenticate in a loop. Retry
with backoff and quote the requestid if it persists.
Correlation with X-Request-Id
Every response carries an X-Request-Id header. You may set it on a request to tie it to your own
logs (letters, digits, -, _, ., up to 64 characters); if you don't, the API mints one. The same
id appears in the requestid field of any error body and in the server-side logs. It is the single
thing support needs to trace a specific call.
Transactions & FNB Smart Blocked
Query EFT transactions across your merchants, and read FNB Smart Blocked listings and statistics. Both are read-only and scoped to your merchants.
Rate Limits
Requests are limited per credential. The default is 500 requests per minute. Exceeding it returns 429 with a Retry-After header.