# Idempotency and Retries

"Idempotent" means that sending the same request twice has the same effect as sending it once. **PayMe has no idempotency key**, so you must make retries safe yourself. 

## What happens when a request runs twice

| Request | Second call while the first is **still running** | Second call **after** the first finished |
|---------|-----------------------------------------------|------------------------------------------|
| `generate-sale` | A second, separate sale | A second, separate sale — `transaction_id` is **not** checked for uniqueness |
| `generate-sale` with `buyer_key` / token (creates **and charges**) | A second charge is possible | A second charge is possible |
| `pay-sale` | Rejected: HTTP 429, code `998` "Duplicated action detected, please retry later" | If the first succeeded: rejected with HTTP 500, code `305`, current status in `status_additional_info`. If the first failed: accepted as a **new charge attempt**. |
| `refund-sale` — partial | Rejected: HTTP 429, code `998` | **Runs again** — a second refund, as long as refundable balance remains |
| `refund-sale` — full | Rejected: HTTP 429, code `998` | Rejected with code `305` |
| `capture-sale` | Rejected: HTTP 429, code `998` | An authorization can be captured only once. |



Notes:

- **`transaction_id` is your own reference.** PayMe stores and returns it but does not reject duplicates: each `generate-sale` creates a new sale with its own `payme_sale_id`. 
- **Template sales:** each `pay-sale` on a template creates a new sale, so the protections above do not apply to template sales. 
- PayMe does not promise that a retried payment can never charge twice. Check before you retry.
- An authorization lasts up to 168 hours; a capture after that fails. A void must be for the full authorized amount. 

## Before retrying, check the sale

If a request timed out and you do not know whether it ran, look the sale up instead of resending it.

[`POST /get-sales`](/payments/query-apis/post-get-sales) accepts these filters:

- `sale_payme_id` — PayMe's sale ID (one sale);
- `transaction_id` — your order ID. Because `transaction_id` is not unique, this can return several sales. 

**By default `get-sales` leaves out unpaid sales** (status `initial`). To find an unpaid sale, pass `sale_status` including `initial`. 

`get-sales` works with `seller_payme_id` alone; partners can also use `payme_client_key`. 

> ⚠ TO CONFIRM: Reading `sale_status` from `get-sales` is most likely the right way to tell whether a timed-out operation ran — capture → `completed` (not `authorized`/`failed`), full refund → `refunded`, void → a voided status. Please confirm this is the recommended check, and the exact status strings for partial refunds and voids (the docs show `partial-refund`; PayMe's API code suggests `partially-refunded`). Note that `sale_status` alone cannot show whether a **retried partial refund** already ran: compare `sale_refund_buffer` (the amount still refundable, returned by `refund-sale` and sent in each `refund` callback — `get-sales` does not return it) or count the `refund` callbacks, each of which has its own `payme_transaction_id`. [Q25]

## Retrying safely

1. Retry only on network errors, timeouts, or HTTP 429 code `998`. Never retry a response with `status_code` `1` that names a problem with your input.
2. On `998`, wait (honour the `Retry-After` header), then **check the sale** before retrying.
3. Before retrying anything that moves money (`pay-sale`, `refund-sale`, `capture-sale`, `generate-sale` with `buyer_key`), check the sale's current state as above.
4. Store PayMe's `payme_sale_id` as soon as `generate-sale` returns, so every later step refers to one sale.
5. Treat callbacks as confirmation, and process each one once — see [Callbacks](/payments/getting-started/callbacks).

> ⚠ TO CONFIRM: What client-side timeout should integrators use for Payments API calls (typical and maximum response time)? [Q29]

## PayMe's own retries

- **Callbacks:** up to 3 attempts over about 2 minutes, then dropped. Details on [Callbacks](/payments/getting-started/callbacks). 
- **Subscriptions:** a subscription in status `76` is "Failed - Pending automatic retry", so PayMe retries failed subscription charges itself. 

## Rate limits

- `generate-sale`, `pay-sale` and `capture-sale` (and tokenization and 3D Secure calls) share one **per-seller** limit — by default **100 requests per 10 seconds**. PayMe can set a different limit per account; partners can read theirs from `GET /partners` (`technical.rate_limit_max_attempts`, `technical.rate_limit_decay_seconds`).
- Over the limit: **HTTP 429**, `status_error_code` `998`, `status_additional_info` `"Too Many Attempts."`, and the headers `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `Retry-After` and `X-RateLimit-Reset`.
- `refund-sale` and `get-sales` have no per-seller limit.



> ⚠ TO CONFIRM: Is 100 requests per 10 seconds per seller also the default in **production**? [Q28]

- **Keep:** a partner endpoint documents HTTP `429` with message "Rate limit exceeded".
