Skip to content

Errors

221 Pay error format, field errors, rate limit, stable codes and what to do for each one.

Every error of the 221 API (221 Pay, data routes, project routes, including 404 and 405) has the same envelope: a stable code to test in code, a readable message, a request_id to pass to support, fields when fields are rejected, and members specific to the code (limit, reset_at, retry_after_s…), at the same level.

400 · application/json
{
  "code": "INVALID_REQUEST",
  "message": "Some fields are invalid.",
  "request_id": "2f6c9a0e-5b1d-4e7f-8c3a-9d0b1e2f3a4c",
  "fields": {
    "customer_phone": "Invalid mobile number for this payment method's country.",
    "offer_code": "offer_code must hold 4 to 16 letters or digits."
  }
}
  • Test code, never message or the HTTP status: the text can change, and the same code can carry a different status depending on the route.
  • fields groups one message per rejected field, so each error can be shown next to its field.
  • message is in French by default; in English with Accept-Language: en or the ?lang=en parameter.
  • request_id is also returned in the X-Request-Id header of every response.

Rate limit

600 requests per minute per project. Beyond that: 429 RATE_LIMITED, with the Retry-After header giving the wait in seconds.

429
HTTP/1.1 429 Too Many Requests
Retry-After: 12

This is the only limit on 221 Pay routes: the daily quota (429 QUOTA_EXCEEDED) covers data routes only. See Authentication, section "Daily quota".

Method not allowed

A /v1 route called with another HTTP method answers 405 with an Allow header, the list of accepted methods, and the same envelope as the other errors, with the code METHOD_NOT_ALLOWED. The message follows Accept-Language (French by default, English when the value starts with en).

405
HTTP/1.1 405 Method Not Allowed
Allow: GET, POST
Content-Type: application/json

{"code": "METHOD_NOT_ALLOWED", "message": "Method not allowed. Allowed methods: GET, POST.", "request_id": "2f6c9a0e-5b1d-4e7f-8c3a-9d0b1e2f3a4c"}

Codes

HTTPcodeCauseWhat to do
400INVALID_REQUESTInvalid body, parameter or header. fields gives one message per rejected field, including a malformed offer_code, a field unknown to the body of POST /v1/payments (fields.<field name>), a missing Idempotency-Key on POST /v1/payments, /v1/refunds or /v1/payouts (key Idempotency-Key), or a refund above the refundable remainder (fields.amount).Fix the listed fields.
400OFFER_INVALIDPromo code unknown, expired or outside the order's conditions.Remove the promo code or use another one.
400PROJECT_REQUIREDDashboard session without a designated project.Send the X-221-External-Ref header, or call with the project key.
400BELOW_MINIMUMBank transfer below the minimum. Fields min_amount and max_amount, and fields.amount.Send at least min_amount. See Payouts.
400ABOVE_MAXIMUMBank transfer above the maximum per payout. Fields min_amount and max_amount, and fields.amount.Lower the amount or make several payouts.
400INVALID_CODEWrong verification code for a payout account. Field attempts_left.Enter the code received by e-mail.
400MISSING_CAPTURESIdentity verification submitted without all the photos (/v1/kyc/...). Field missing, the list of photos still to send.Send the missing photos, then submit the verification again.
401INVALID_API_KEYKey malformed, unknown or revoked.Check the key. See Authentication.
401UNAUTHENTICATEDNeither key nor session, or key of another project.Send the project key.
403MISSING_SCOPEKey without the scope the route (payments, data) or the webhook event requires. Member scope.Create a key with that scope.
403LIVE_NOT_ENABLEDCall in live mode (sk_221_pay_live_… key) on a project whose live mode is not activated.Have a project administrator activate live mode (see Authentication), or call with a test key.
403FORBIDDENRole insufficient for the action.Act with an administrator account or an API key.
403KYC_REQUIREDPayout or payout number before identity verification. Field kyc_status.Complete verification. See Payouts.
403MERCHANT_SUSPENDEDMerchant account suspended or closed. Field merchant_status.Contact 221. Reads keep working.
403MERCHANT_LIMIT_EXCEEDEDDaily or monthly limit reached. Fields limit_type (daily or monthly) and limit.Wait for the next period or ask for a higher limit.
403WITHDRAWAL_LIMIT_EXCEEDEDPayout limit reached. Fields limit_type (per_withdrawal, daily_count, daily_amount or monthly_amount) and limit.Lower the amount, or wait for the reset (midnight, Dakar time).
403MONTHLY_LIMIT_REACHEDMonthly total of bank transfers reached. Field remaining, the amount still possible this month.Lower the amount to remaining at most, or wait for the 1st of next month.
404NOT_FOUNDObject missing or belonging to another project.Check the id.
404FEATURE_DISABLEDFeature switched off by 221 (promo codes, for example).Call without that feature.
409DUPLICATE_MERCHANT_REFERENCEmerchant_reference already used in the project.Replay with the original Idempotency-Key to get the payment back, or choose a new reference for a new order.
409IDEMPOTENCY_CONFLICTIdempotency-Key already used with another body.Replay the original body.
409QUOTE_EXPIREDRefund or payout quote expired (2 minutes). fields.quote_hash carries the message.Request a new quote.
409QUOTE_INVALIDquote_hash unknown, already used, belonging to another merchant account, or not matching the request (other amount, other fee_payer). fields.quote_hash carries the message.Request a new quote.
409DESTINATION_NOT_VERIFIEDQuote or payout to a number or bank account whose verification code has not been entered. The message says "number" or "bank account" depending on the destination.Enter the code, then retry. See Payouts.
409INSUFFICIENT_FUNDSAvailable balance too low.Wait for new collections or lower the amount.
409INVALID_STATEThe object is not in a state that allows the action: dispute already open on the payment, or dispute that can no longer be accepted, challenged or completed; payout number already verified (/resend). The message names the current state.Read the object again and act according to its state.
409KYC_ALREADY_SUBMITTEDIdentity verification already sent for review (/v1/kyc/...).Do not send it again: follow GET /v1/kyc/status.
409OFFER_CODE_TAKENPOST /v1/offers with a promo code that already exists in the project.Choose another code.
410CODE_EXPIREDVerification code of a payout account expired (10 minutes).Request a new code with POST /v1/payout-destinations/{id}/resend.
413REQUEST_TOO_LARGERequest body over 1 MiB on a 221 Pay route (uploading a dispute document has its own limit), over 64 KiB on a data route.Reduce the body.
429TOO_MANY_ATTEMPTS5 wrong attempts on the verification code.Request a new code.
429RATE_LIMITEDRate limit reached. Data routes add the member retry_after_s.Wait Retry-After seconds.
500INTERNAL_ERRORUnexpected internal error in 221 Pay.Replay with the same Idempotency-Key and the same body; if it persists, send the request_id to 221.
503CHANNEL_UNAVAILABLERail not activated on the project, or temporarily closed. For a payout, reserved funds are returned before the response.Offer another rail or retry later.
503PAYMENTS_UNAVAILABLE221 Pay temporarily unavailable on this server.Retry later, with the same Idempotency-Key.
503NOT_CONFIGUREDFeature not set up on this server: identity verification (/v1/kyc/...) or the public address of payment links (POST /v1/payment-links). Unlike CHANNEL_UNAVAILABLE, there is nothing to retry.Contact 221 with the request_id.
507STORAGE_FULLStorage for identity verification photos temporarily full (/v1/kyc/...).Retry later.

Data and project route codes

These codes, upper case like the others, come from the data routes, keys, webhooks and a project's team. The members specific to the code sit at the top level of the body, next to message. The HTTP status can vary by route: test code.

HTTPcodeCauseMembers
400BAD_REQUESTUnreadable or missing JSON body on a data or project route (no fields). A field of the wrong type gives INVALID_REQUEST with fields instead.
401API_KEY_EXPIREDKey expired.expires_at
401API_KEY_REVOKEDKey revoked.revoked_at
403API_KEY_DISABLEDKey disabled by 221 for abuse.disabled_at
403FORBIDDEN_ROLEProject role insufficient for the action.role, required
429QUOTA_EXCEEDEDDaily quota of the data routes reached.limit, reset_at
400INVALID_URLWebhook URL refused (https to a public host).fields.url
409SUBSCRIPTION_EXISTSThis URL already has an endpoint.url
409SUBSCRIPTION_LIMIT5 endpoints per project reached.limit
409KEY_LIMIT_REACHED5 active keys per project reached.limit
409PROJECT_LIMIT_REACHEDProject limit reached.limit
404PROJECT_NOT_FOUNDProject missing or not the caller's.
409LAST_ADMINThe project must keep an administrator.
403OWNER_PROTECTEDOnly the owner changes their role or leaves the project.
409OWNER_MUST_STAYThe owner stays administrator.
409ALREADY_MEMBER, ALREADY_INVITEDPerson already a member, or invitation already pending.email
404INVITATION_NOT_FOUNDInvitation unknown or withdrawn.
410INVITATION_EXPIREDInvitation expired.expires_at
410INVITATION_USEDInvitation already used.
403INVITATION_EMAIL_MISMATCHInvitation addressed to another address.
409EMAIL_NOT_VERIFIEDE-mail address not confirmed.
403REAUTHENTICATION_REQUIREDSign-in less than 10 minutes old required.
403INVALID_PASSWORDWrong password.
409BALANCE_NOT_EMPTYProject cannot be deleted while funds or a payout are in progress.reason
503DATASET_UNAVAILABLEDataset not published yet.dataset
503SERVER_BUSYService busy: retry in a few seconds.

The /auth routes (sign-in, password) also answer with a flat code, message and request_id shape (also in the X-Request-Id header). Their codes are those of the sign-in service, for example INVALID_EMAIL, INVALID_EMAIL_OR_PASSWORD or VALIDATION_ERROR (unreadable body, with a single generic message). TOO_MANY_REQUESTS adds retry_after_s and the X-Retry-After header.

Retry safely

SituationRetry
400, 401, 403, 404, 409No: fix the request first.
429Yes, after Retry-After seconds.
500, 503, timeout, network cutYes, with the same Idempotency-Key and the same body.
5xx on a quoteYes: a quote moves no money.

POST /v1/payments, POST /v1/refunds and POST /v1/payouts require Idempotency-Key: a retry never creates a second operation. See Payments, section "Unknown outcome".

Report an incident

Send 221 the request_id, the time of the call and the route called. Never the API key or the webhook secret.