Handle errors predictably.
Keep creation retries idempotent and let your backend decide when to retry.
HTTP status codes
| Code | Meaning | Action |
|---|---|---|
| 201 | Payment created or idempotently replayed | Read payment_url; details gives the latest status. |
| 401 | Missing or invalid credentials | Check the X-API-Key header. |
| 403 | Creation paused, beta pending/expired or creation quota exhausted | Check limits and contact support. |
| 404 | Payment not found | Verify the payment ID and account. |
| 409 | Unfinished order on address; wrong merchant; webhook missing; idempotency conflict; cancel unavailable | Read the detail and resolve the conflicting state. |
| 413 | Request body exceeds 64 KiB | Reduce the request body. |
| 422 | Invalid fields or webhook endpoint | Correct the validation errors. |
| 429 | Request rate limit | Wait and respect Retry-After when supplied. |
| 503 | Infrastructure temporarily unavailable | Retry with backoff and the same idempotency key. |
Error response
{"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.
| Endpoint | Requests / window | Counter |
|---|---|---|
GET /api/v1/payments/{payment_id}/webhooks | 120 / 60 s | shared reads · per merchant |
POST /api/v1/merchant/webhook/events/{event_id}/retry | 5 / 3600 s | manual retry · per merchant · 3600 seconds |
POST /api/v1/payments | 30 / 60 s | creation · per merchant |
POST /api/v1/payments/{payment_id}/cancel | 60 / 60 s | cancellation · per merchant |
GET /api/v1/payments | 120 / 60 s | shared reads · per merchant |
GET /api/v1/payments/page | 120 / 60 s | shared reads · per merchant |
GET /api/v1/payments/{payment_id}/details | 120 / 60 s | shared reads · per merchant |
GET /api/v1/merchant/limits | 120 / 60 s | shared reads · per merchant |
GET /api/v1/merchant/webhook | 120 / 60 s | shared reads · per merchant |
PUT /api/v1/merchant/webhook | 10 / 60 s | shared webhook settings · per merchant |
POST /api/v1/merchant/webhook/rotate-key | 10 / 60 s | shared webhook settings · per merchant |
POST /api/v1/merchant/rotate-key | 5 / 60 s | API key rotation · per rotation secret |
GET /api/v1/payments/{public_token} | 120 / 60 s | public 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.
| Scope | Sustained rate | Burst allowance |
|---|---|---|
| All /api/v1/ requests combined | 10 requests / second | 30; 10 on API key rotation |
| Public payment status, in addition to the API IP limit | 5 requests / second | 20 |
| API key rotation, in addition to the API IP limit | 1 request / minute | 3 |
| Pages and static assets | 20 requests / second | 50 |
| Simultaneously active requests/connections | 30 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
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
- Keep one idempotency key for the logical order.
- Retry with the same body and key when a creation response is lost.
- Read authenticated payment details before fulfilment.
- Deduplicate webhook event IDs atomically.