Handle errors predictably.

Keep creation retries idempotent and let your backend decide when to retry.

HTTP status codes

CodeMeaningAction
201Payment created or idempotently replayedRead payment_url; details gives the latest status.
401Missing or invalid credentialsCheck the X-API-Key header.
403Creation paused, beta pending/expired or creation quota exhaustedCheck limits and contact support.
404Payment not foundVerify the payment ID and account.
409Unfinished order on address; wrong merchant; webhook missing; idempotency conflict; cancel unavailableRead the detail and resolve the conflicting state.
413Request body exceeds 64 KiBReduce the request body.
422Invalid fields or webhook endpointCorrect the validation errors.
429Request rate limitWait and respect Retry-After when supplied.
503Infrastructure temporarily unavailableRetry with backoff and the same idempotency key.

Error response

409 Conflict
{"detail":"Finish the current payment with this amount on this address first"}

Creation-only access errors return 403 with an object under detail: code and message. Codes are beta_not_activated, beta_expired, creation_paused, total_quota_exhausted or daily_quota_exhausted; a concurrent full-account block can return account_inactive. Check GET /api/v1/merchant/limits before retrying. Inactive accounts normally fail authentication with 401. A saved idempotent replay still returns its original response within retention after beta expiry or quota exhaustion, but counts toward the technical rate limit.

Validation errors return a list under detail with the invalid field and reason. Do not treat all errors as a reason to create a fresh order.

Creation access and quotas

New payment creation may be restricted by pending/expired beta, an operator pause, total quota or the UTC daily quota. Read your access state and creation quotas through GET /api/v1/merchant/limits. Existing payments and webhook delivery continue; technical rate limits are separate.

Rate limits by endpoint

These are the configured defaults. Application counters use Redis and a fixed 60-second window starting with the first request. Later requests do not extend the window. Limits are shared across all API keys for the same merchant.

EndpointRequests / windowCounter
GET /api/v1/payments/{payment_id}/webhooks120 / 60 sshared reads · per merchant
POST /api/v1/merchant/webhook/events/{event_id}/retry5 / 3600 smanual retry · per merchant · 3600 seconds
POST /api/v1/payments30 / 60 screation · per merchant
POST /api/v1/payments/{payment_id}/cancel60 / 60 scancellation · per merchant
GET /api/v1/payments120 / 60 sshared reads · per merchant
GET /api/v1/payments/page120 / 60 sshared reads · per merchant
GET /api/v1/payments/{payment_id}/details120 / 60 sshared reads · per merchant
GET /api/v1/merchant/limits120 / 60 sshared reads · per merchant
GET /api/v1/merchant/webhook120 / 60 sshared reads · per merchant
PUT /api/v1/merchant/webhook10 / 60 sshared webhook settings · per merchant
POST /api/v1/merchant/webhook/rotate-key10 / 60 sshared webhook settings · per merchant
POST /api/v1/merchant/rotate-key5 / 60 sAPI key rotation · per rotation secret
GET /api/v1/payments/{public_token}120 / 60 spublic status · per payment token

The manual webhook retry endpoint uses its separate 3600-second window, starting at the first request. The shared-read endpoints together allow 120 requests per merchant per window, not 120 each. Webhook URL updates and signing-key rotations together allow 10. Creation and cancellation have separate counters. Rotating the API key does not reset merchant counters. Idempotent creation retries count toward the creation rate limit, even though they do not consume the payment creation quota. Validated requests that reach a counter count even if later processing fails; rejected over-limit attempts also count without extending the window. Rotation attempts are counted per supplied nonempty secret, including invalid secrets.

Public status has a separate counter for each payment token, shared by all viewers. Visible hosted checkout polls every 3.8–4 seconds while PENDING or CHECKING (about 15 requests per minute per viewer). Hidden pages pause polling and refresh immediately when visible again. Network failures use bounded retries up to 30 seconds; the online event triggers an immediate check.

Additional nginx limits per client IP

Requests through localhost:8080 or the production nginx also pass these shared IP limits. Direct localhost:8000 API calls only use application limits. nginx uses a sustained rate with a burst allowance, rather than the application’s fixed window; allowed bursts pass immediately.

ScopeSustained rateBurst allowance
All /api/v1/ requests combined10 requests / second30; 10 on API key rotation
Public payment status, in addition to the API IP limit5 requests / second20
API key rotation, in addition to the API IP limit1 request / minute3
Pages and static assets20 requests / second50
Simultaneously active requests/connections30 per IP—

Clients behind the same public IP share nginx counters, including different merchants and payment tokens. Either layer can reject a request with 429.

Handling 429

Application limit · example
HTTP/1.1 429 Too Many Requests
Retry-After: 17
Content-Type: application/json

{"detail":"Rate limit exceeded"}

Application responses include Retry-After in seconds until the window expires. Wait at least that long, add jitter, and reuse the same idempotency key and body when retrying payment creation. nginx 429 responses do not currently include Retry-After and may return HTML; check the HTTP status before parsing JSON and use exponential backoff when the header is absent. A rate-limiter outage returns 503 rather than allowing unmetered requests.

Recover from uncertain responses

  1. Keep one idempotency key for the logical order.
  2. Retry with the same body and key when a creation response is lost.
  3. Read authenticated payment details before fulfilment.
  4. Deduplicate webhook event IDs atomically.