Guides

Errors

Every failure looks the same on the wire. Branch on error.code, log error.message, ship error.details to your error reporter.

The error envelope

Failed responses share a single, stable shape. The HTTP status tells you the broad class; error.code tells you the specific reason; error.message is human-readable; error.details (when present) gives structured context.

json
{
  "error": {
    "code": "missing_fields",
    "message": "Missing required field: orderId",
    "details": {
      "field": "orderId"
    }
  }
}

Treat error.codeas the canonical signal. Messages may change wording over time without warning — codes won't.

Error code catalog

CodeStatusWhat it means
missing_token401No Authorization header was sent, or it didn't start with 'Bearer '.
invalid_token401Token didn't match any active token — typo, wrong environment, or never minted in the first place.
token_revoked401Token was revoked in the dashboard. Mint a new one; revocations are not reversible.
token_expired401Token had an explicit expiry that's now in the past. Mint a replacement.
insufficient_scope403Token is valid but doesn't carry the scope this endpoint requires. Check the scope listed in the API reference.
ip_blocked403Request originated from an IP outside the token's allowlist. Check your egress IP, not your office WAN.
rate_limited429Token exceeded its requests-per-minute budget. Honour the Retry-After header; see the rate limits guide.
invalid_body400Request body wasn't valid JSON, or the Content-Type wasn't application/json.
missing_fields400Body was JSON but a required field was missing or empty. The `details.field` value names which.
refund_failed400Refund attempt rejected — too late, amount exceeds remaining refundable, or Paystack declined. See `details.reason`.
internal_error500Something on our side broke. Safe to retry after a short pause; ping support if it persists.

Handling errors in production

  • Branch on error.code. Switch statements over error.message will break the day we improve the wording.
  • Retry only the retryable ones. rate_limited, internal_error, and any network-level error are safe to retry with backoff. Don't retry 4xx codes other than 429 — they'll fail forever.
  • Surface error.message to humans. It's written for them. Ship error.details to your error reporter for triage.

See the rate limits guide for the canonical backoff pattern, and the idempotency guide for retrying writes safely.