"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.
| 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. pay-sale on a template creates a new sale, so the protections above do not apply to template sales. If a request timed out and you do not know whether it ran, look the sale up instead of resending it.
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_statusfromget-salesis most likely the right way to tell whether a timed-out operation ran — capture →completed(notauthorized/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 showpartial-refund; PayMe's API code suggestspartially-refunded). Note thatsale_statusalone cannot show whether a retried partial refund already ran: comparesale_refund_buffer(the amount still refundable, returned byrefund-saleand sent in eachrefundcallback —get-salesdoes not return it) or count therefundcallbacks, each of which has its ownpayme_transaction_id. [Q25]
998. Never retry a response with status_code 1 that names a problem with your input.998, wait (honour the Retry-After header), then check the sale before retrying.pay-sale, refund-sale, capture-sale, generate-sale with buyer_key), check the sale's current state as above.payme_sale_id as soon as generate-sale returns, so every later step refers to one sale.⚠ TO CONFIRM: What client-side timeout should integrators use for Payments API calls (typical and maximum response time)? [Q29]
76 is "Failed - Pending automatic retry", so PayMe retries failed subscription charges itself.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).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]
429 with message "Rate limit exceeded".