Requests, pagination and retries
Send strict inputs, keep the identifiers that make a purchase recoverable, page through history and retry only as told.
Each tool takes one JSON object. Keep the idempotency_key of every purchase you start, follow next for paging and retries, and never pay twice.
Support revision: envoy@2026-10. Last updated: 28 October 2026.
Inputs
Send only the fields the tool declares; the tool reference lists them with their rules. Amounts are rupees as strings or numbers with up to two decimals, such as "2499.00". Bodies are at most 64 KB. A field that breaks a rule returns INVALID_INPUT with error.fields, one entry per field with path, problem and expected; fix every listed field and send the call once more.
Keep these identifiers
| Identifier | Where it comes from | Keep it for |
|---|---|---|
idempotency_key | You choose it for start_purchase (16 to 64 characters, not starting with pur_) | Retrying a start without creating a second purchase. Either it or purchase_id works as the purchase argument. |
purchase_id | purchase.purchase_id in the reply | Every later call about that purchase. |
revision | purchase.revision | after_revision on a held get_purchase_status; next.args carries it for you. |
request_id, trace_id | Every reply | Quoting to support. Never as a purchase reference. |
The same idempotency_key with the same input returns the same purchase, with replayed: true. The same key with a different merchant URL or amount returns IDEMPOTENCY_CONFLICT. Never use a new key to recover a lost operation. A supported linked retry_of explicitly uses a new key and names the original purchase; follow the recovery procedure. submit_answer takes its own required idempotency_key. Retry it with the same key and the same answer, and Envoy records the answer once. The same key with a different answer returns IDEMPOTENCY_CONFLICT.
References belong to the account that made them. After a reconnect, check that you are signed in as the same owner before you read a saved purchase; NOT_FOUND under another account says nothing about the original purchase.
Page through history
list_transactions takes optional from and to dates (YYYY-MM-DD, from not after to), a limit from 1 to 50 and a cursor:
{
"from": "2026-09-01",
"to": "2026-09-30",
"limit": 20
}When there are more transactions, data.next_cursor is set and next calls list_transactions again with the same from, to and limit plus the cursor. Follow it unchanged; the cursor is bound to this connection, date range and page size. Without from and to, history starts from the newest line. It lists the same lines the user sees in the Envoy app: one line per agent purchase with the merchant as title, the agent's name as subtitle and the net amount, with fully refunded purchases left out. Describe a line the way the app does. History cannot prove whether a purchase in progress was paid. Use get_purchase_status for that.
Retry only as next says
retry: waitnext.after_seconds, then repeat the same call unchanged. This coversRATE_LIMITEDand a retryableUNAVAILABLE.call_toolafter a failure: read the purchase it names before anything else. The failed write may have happened.stop,reconnect,fix_input,update_client: do exactly that; do not loop.
Retry after a 429
Envoy allows 120 tool requests per minute for each access token. POST /tools/:tool and /mcp share that budget. This section describes POST /tools/:tool.
A call over the limit returns HTTP 429 with error.code RATE_LIMITED and next.action retry. Both next.after_seconds and error.retry_after_seconds are 60.
The reply also carries a Retry-After header, in seconds. It counts down to the end of the one-minute window, so it is at most 60. The body says 60 even when the header says less. Wait at least the header value. If there is no header, wait next.after_seconds.
Do this:
- Stop sending calls on this access token. Do not switch tokens to get around the limit.
- Wait
next.after_seconds. If you see another429, back off further. Double the wait each time, up to a few minutes. - Add random jitter to every wait, for example 0 to 25 percent more. Jitter stops many clients retrying in the same second.
- Repeat the same call unchanged. Keep the same
idempotency_keyon astart_purchase, so a retry never makes a second purchase. - Read held
get_purchase_statusasnextdirects instead of polling. A held read waits on the server and uses one request.
A 429 means the call never reached an operation. It changed nothing. Failed authentication is also limited per client IP. Over that limit, a failing request gets 429 instead of 401.
What an MCP agent sees
Over /mcp, the limiter answers HTTP 200, not 429. A tools/call returns a normal tool result with isError: true, a plain sentence, retry same_call and next.wait_seconds of 60. Wait that long, add jitter, and repeat the same call. Other over-limit MCP requests get JSON-RPC error -32000 with the message "Rate limited. Retry in one minute." See Replies and next steps.
Never repeat a merchant payment because a reply was lost. Read get_purchase_status with your saved reference, then follow its next. The full procedure is Recover a purchase; every code is in Errors and reason codes.