# Idempotency and limits

## Idempotency { #idempotency }

The idempotency key is your `merchant_payment_id`, unique within the project.
There is no `Idempotency-Key` header.

| What you send | Answer |
| --- | --- |
| A new `merchant_payment_id` | `201` — a new payment |
| The same `merchant_payment_id` and the same fields | `200` — the existing payment; requisites are not requested again |
| The same `merchant_payment_id` with other fields | `409 payment_idempotency_conflict` |

"The same fields" are `merchant_payment_id`, the amount, `currency`, `geo_code`,
`payment_method`, `bank_code`, `is_intrabank` and `callback_url`. Defaults are applied
before the comparison: a request without `currency` and the same request with
`"currency": "RUB"` are the same.

**What you get.** If the answer was lost — timeout, dropped connection, your server
crashed — just repeat the request with the same id. No duplicate payment, no double charge.

**A new payment attempt.** If a payment closed (`canceled`, `error`) or got `422`, a new
attempt needs a **new** `merchant_payment_id`: repeating the old one returns the old
result. Adding an attempt number works well: `order-1001-2`.

**When the id is not taken.** Rejections before the payment is accepted do not take the
`merchant_payment_id`: `429`, `503`, `401 signature_invalid`, `409 request_replayed`,
`413 request_too_large`, `400 bad_request` (unparsable body), `400 invalid_payment` (a
field failed validation). Fix the cause and send the request with the same id. If the corrected request gets `409 payment_idempotency_conflict`,
use a new id.

### Nonces and retries { #nonce }

Payment idempotency and one-time signatures are different things.

- `X-Request-Nonce` is single-use: resending the same signed request returns
  `409 request_replayed`.
- So **re-sign** on every retry: new nonce, new `created`/`expires`, the same
  `merchant_payment_id` and the same body.
- Exception: `429 too_many_concurrent_requests` does not burn the nonce, so the same signed
  request may be sent again before `expires`.

## Rate limits { #rate-limits }

| Limit | Default | When exceeded |
| --- | --- | --- |
| Requests per source IP | 600 per minute | `429 rate_limited` |
| Project requests | 600 per minute, burst 60 | `429 rate_limited` |
| Project payment creates | 60 per minute, burst 6 | `429 rate_limited` |
| Concurrent project requests | 20 | `429 too_many_concurrent_requests` |
| Request body size | 64 KB | `413 request_too_large` |

- Project limits are visible in the cabinet; your manager can change them — ask them
  manager if you expect more payments.
- A `429` carries `Retry-After`: how many seconds to wait.
- Limits are per project: all your servers share one counter.
- If rate or nonce accounting is temporarily unavailable, the API answers `503` with
  `Retry-After: 1` — not a problem with your request, retry it.

## Timeouts { #timeouts }

Creating a payment picks requisites synchronously. Keep the HTTP client
timeout at **15 seconds or more**. If a timeout still happens, repeat the request with the
same `merchant_payment_id` — see [above](#idempotency).