Skip to content

Error codes

Every failure is a code, never a sentence — you render it, in your own checkout, in your own language. The body is always the same shape:

json
{ "error": { "code": "BAD_AMOUNT", "doc": "https://docs.pamopay.co/errors/BAD_AMOUNT", "request_id": "req_…" } }

FORBIDDEN_SCOPE adds scope, the one thing you need to fix it. Nothing else ever adds a field. Every code links to its own page, and this table is rendered from the API's own catalogue: it cannot answer a code that is not here.

Retry says whether the same request may be sent again unchanged: same request means yes, with the same Idempotency-Key; fix request means something must change first; never means a person has to act.

CodeHTTPWhat happenedRetry
BAD_KEY401The Authorization header was missing, was not Bearer <key>, or carried a key we did not issue.fix request
KEY_REVOKED401This key was retired from the dashboard and has now stopped.never
KEY_EXPIRED401The key reached the lifetime it was given when it was made.never
FORBIDDEN_SCOPE403The key is valid and this route is not among the things it was made to do. scope in the error body names what was needed.never
MERCHANT_NOT_LIVE403A live key for a business that has not gone live, or has been suspended. This is about the account, not the key.never
RATE_LIMITED429This key made more calls in the current minute than it is allowed. Retry-After says how many seconds until the window resets.same request
USE_SANDBOX_URL400A sk_test_ key was sent to the live base URL. Test keys are served by the sandbox at its own URL.fix request
LIVE_KEY_ON_SANDBOX400A sk_live_ key was sent to the sandbox base URL, which never moves money and will not pretend to.fix request
BAD_AMOUNT400amount_minor is not an integer, is not a whole number of shillings (a multiple of 100), or is outside TZS 100–20,000,000.fix request
BAD_CURRENCY400Only TZS is supported.fix request
BAD_RAIL400rail is not one of mpesa, airtel, tigo, halopesa, azampesa.fix request
BAD_MSISDN400msisdn is not a Tanzanian mobile number.fix request
BAD_NUMBER400The number to notify about a bill is not a Tanzanian mobile number.fix request
AMOUNT_TOO_SMALL400After the fee there would be nothing left for the business.fix request
BAD_REQUEST400A field or query parameter is the wrong shape, or (413) the body is over 64 kB.fix request
BAD_CUSTOMER_REF400A bill needs customer_ref — the student number, the tenant, the invoice — and it was missing or over 64 characters.fix request
BAD_EXPIRY400expires_at is not an ISO-8601 instant, or is already in the past.fix request
NO_REGISTERED_WEBSITE400success_url was given, and this business has no website registered, so there is nowhere a return URL is allowed to go.fix request
BAD_RETURN_URL400success_url is not https, or is not on the website registered for this business. A return URL on a payments domain is an open redirect otherwise.fix request
BAD_URL400url for a webhook endpoint is not https, has no host, carries credentials, or is over 500 characters.fix request
BAD_TILL400till is not six digits.fix request
UNKNOWN_TILL404No open till with that number belongs to this business.fix request
TILL_CLOSED409That till has been closed and cannot ring up a sale.never
NOT_FOUND404No such object for this business. An object belonging to another business is also NOT_FOUND — telling a caller which references exist would be a way to enumerate them.fix request
ALREADY_QUEUED409A replay was asked for on an event that is already queued or in flight. What you wanted is going to happen.never
IDEMPOTENCY_REQUIRED400This request takes money and needs an Idempotency-Key header: 8 to 128 characters of letters, digits, hyphen or underscore. It was missing or the wrong shape.fix request
METHOD_NOT_ALLOWED405The path exists and does not answer this verb.fix request
IDEMPOTENT_MISMATCH409The same Idempotency-Key was sent with a different request. Nothing was created; the first request under that key stands.fix request
LEDGER_UNAVAILABLE503The charge could not be opened — the ledger did not answer, or no tariff covers this business on this rail. Nothing was taken.same request

By status

  • 400 — the request. Change it and send it again.
  • 401 — the key: unrecognised, retired or expired.
  • 403 — a real key that may not do this: outside its scopes, or a live key on a business that is not live.
  • 404 — no such object for this business. Another business's object is 404 too, never 403.
  • 405 — right path, wrong verb.
  • 409 — your intent is already true, or contradicts an earlier request under the same key.
  • 413 — the body is over 64 kB.
  • 429 — too many calls this minute. Retry-After says how long.
  • 503 — ours. Nothing was taken; retry with the same key.

Every code, scope and route on this site is rendered from the API's own source.