Make a purchase, start to finish
Start one purchase, follow its user task and checkout route, then read the purchase outcome and money status.
Call start_purchase for the user's buying intent. Follow the returned user task: approval or payment in the Envoy app, or a gift-card payment link the user pays themselves. Read checkout_by in each reply. On agent checkout, use the authorized card or paid voucher once and report the result. On Envoy checkout, follow status while Envoy handles the merchant.
Read the purchase outcome and money status separately. An uncertain outcome must be reconciled before another payment.
How one purchase reaches an outcome
start_purchaseThe agent requests one purchase.checkout_byRead it in every reply.agent: you check outUse this purchase's authorized card or paid gift-card voucher once, then report.envoy: Envoy checks outNo card is released. Do not open the payment step or report.Read this page if you build or run an agent that buys for an Envoy user, over MCP or the HTTP API. Envoy supplies the user task, payment details and money evidence. The agent owns the merchant checkout only when checkout_by is agent.
Support revision: envoy@2026-10. Last updated: 1 November 2026.
All values below are synthetic. They are offline examples, not a sandbox. Field lists and generated examples are in the tool reference. No live checkout has been recorded for this flow on this site, so no merchant or payment outcome here is a verified claim.
Read results over MCP or HTTP
This page names the HTTP envelope fields (purchase.state, next, data.payment_details). Over MCP the agent reads a briefing instead, and the same steps apply:
| HTTP envelope | MCP result |
|---|---|
purchase.state | situation: waiting_for_user, working, ready, confirming, done, stopped, needs_support, needs_details or needs_answer |
purchase.money.status | money |
next | The What to do line and next {tool, args, wait_seconds} |
user_action | The user task in the briefing; gift-card payment-link funding includes its link; an in-app task has no link |
data.payment_details | card, only while ready |
error.code | A plain sentence, retry and support_ref |
Tell the user the first part of the briefing in your own words. Never show the JSON line, codes, ids or keys. See Replies and next steps. Never copy an MCP parser into HTTP code, or the reverse, because the two reply shapes differ.
Before you start
- Get explicit authorization from the account owner for this merchant and this amount. A connection lets the agent read the account and request purchases; it is not approval.
- Every purchase moves real money. There is no test mode and no sandbox.
- An Envoy connection with the purchase service enabled. Service and sign-in failures are covered in Errors and reason codes.
- A merchant URL and a total in rupees, up to ₹1,00,000.
- Enough balance for a balance-funded purchase. For a gift-card payment link, the user pays the link themselves.
- No matching purchase in progress. Envoy allows up to five open purchases per user, and refuses a second open purchase from the same agent at the same merchant for the same amount. Envoy also refuses a second Envoy-checkout or at-checkout purchase on a site where the user already has an open purchase, at any amount, and allows at most three approvals waiting on the user at once. Any of these refusals fails
start_purchase(ACTIVE_PURCHASE_EXISTSon the HTTP API), and the briefing tells the agent to stop.
The path in one view
Call start_purchase. It returns at once with a purchase and a next.
Follow next. Call get_purchase_status as it directs.
Wait while the user completes the returned approval or payment-link task.
Read checkout_by. Envoy sets it for this purchase.
On agent checkout, pay once when the state is ready, then call report_checkout_result. On Envoy checkout, keep following next.
Read the final state.
Purchase states over HTTP and MCP
waiting_for_userMCPwaiting_for_userThe user completes the app task or pays the returned gift-card link.processingMCPworkingEnvoy prepares payment, obtains a gift-card quote or checks out.readyMCPreadyAgent route only. Pay once, then report.verifyingMCPconfirmingMoney is unsettled. Never pay again.completedMCPdoneThe purchase is complete. Read money separately, including payment collected on delivery.
needs_answer, needs_detailsEnvoy needs something only the user can give. Show the prompt word for word.failed, cancelledMCP stopped when money is not_charged or refunded; otherwise needs_support.supersededMCP working. A linked purchase replaced this one and needs its own approval.Every state is in the state table.
Start the purchase
Call start_purchase once. It returns promptly and does not wait for the card.
{
"idempotency_key": "synthetic-intent-0001",
"merchant_url": "https://shop.example.test",
"total": "2499.00",
"items": [{"name": "Headphones, black", "quantity": 1}],
"user_request": "Buy the black headphones from this shop."
}idempotency_key: one key per intent, 16 to 64 characters, not starting withpur_. Retrying with the same key continues the same purchase; a new key is a new purchase.merchant_url: anhttpsURL. Envoy takes the merchant name from its host.total: rupees with up to two decimals, as a number or a string such as"1299"or"₹1,299.00".items: one to 20 entries of{name, quantity, url, image}; onlynameis required. Put the variant in the name.user_request: the user's own words, up to 300 characters.max_total(optional): the card limit. It defaults tototaland can be at mosttotalplus the larger of 10% or ₹100. It does not authorize an amount the user has not accepted.merchant_logoanditems[].image(optional): authentichttpsimage URLs from the merchant. They help the user recognize the request on the approval screen. Send them when the merchant provides them. Never invent them or use unrelated images. Envoy drops an invalid value silently, and the call does not fail. The approval still works without them.addressoraddress_id(never both): required when Envoy checks out, because Envoy needs the delivery details instart_purchaseitself.addressis one line with the recipient's name and phone.address_idis a saved address fromget_accountwithinclude: ["addresses"]; saved addresses may be unavailable, so a typedaddressis the practical choice. Agent checkout does not need them, because you enter delivery details at the merchant.
{
"name": "start_purchase",
"arguments": {
"idempotency_key": "synthetic-intent-0001",
"merchant_url": "https://shop.example.test",
"merchant_logo": "https://shop.example.test/logo.png",
"total": "2499.00",
"items": [{"name": "Headphones, black", "quantity": 1, "image": "https://shop.example.test/headphones.jpg"}],
"user_request": "Buy the black headphones from this shop."
}
}A synthetic briefing:
Approve this purchase in the Envoy app. (₹2,499.00 at "Example Store")
What to do: Show the user this message; they can't see tool results. Then call get_purchase_status with the next arguments to check progress. Follow each returned next step until Envoy says to stop. If you need to wait for the user, ask them to reply when they finish in the Envoy app.
{"situation":"waiting_for_user","money":"not_charged","purchase_id":"pur_5b1e9c7a3f2d5a8e9c0b1d2e3f4a5b6c","next":{"tool":"get_purchase_status","args":{"purchase":"pur_5b1e9c7a3f2d5a8e9c0b1d2e3f4a5b6c","after_revision":1,"wait_seconds":45}}}This synthetic card example returns waiting_for_user with user task approve. Other routes can return a question, processing or a payment-link task. Follow the actual next. The start_purchase reference shows the full synthetic reply.
If a write is ambiguous, recover before you do anything else:
- Keep the same
idempotency_keyand the returnedpurchase_id. - After a lost reply, a timeout or
retrycheck_status_first, callget_purchase_statusbefore anything else. - Never issue a new payment for an unknown outcome.
- Follow
retryandnext. - Cancelling is not a refund, and it does not prove the card is blocked.
See Recover a purchase.
Discover what Envoy can buy
Call list_gift_card_merchants before promising a gift-card merchant. The reply lists merchant names, domains, available amounts or a whole-rupee range, and redeem_at when known. Use only the returned amounts or range. A discount_percent is a maximum saving, so say “up to”. The funding price is confirmed later. An empty list means no gift-card merchants are available here.
When the user names no merchant, or asks for Elixir or Envoy products, call search_products with the user's query. Show the returned names and prices. An empty result means no products are available for that query; do not invent an offer.
To buy returned offers, call start_purchase once for the cart. Copy each selected offer's product_id into its item, together with name and quantity. Every item must have a product ID, or none may have one. Use the returned merchant_url and the sum of the selected totals. If Envoy returns a changed total, ask the user before resending it. Offer expiry and delivery availability can change the result.
Search does not move money. An offer does not prove that purchasing is enabled for this account or deployment. Follow the returned user task and next. See the search reference and start reference for current inputs.
Envoy can also return an offer question before starting the original merchant purchase. Show that exact question and wait for the user's choice. Send their words with the supplied submit_answer args. Never choose an alternative merchant or product for the user.
Follow a gift-card purchase
Call list_gift_card_merchants before offering a gift card. To request one explicitly, call start_purchase with rail: "gift_card". Without that field, Envoy may choose another route. A ChatGPT connection has no gift cards and buys only from Elixir's marketplace. On a Claude connector, Envoy drops rail and checks out itself at the merchant total, with no voucher or payment link; do not offer a gift card or a discount there. See the gift-card guide.
Follow the gift-card guide for merchant discovery, funding, money status, voucher delivery, cancellation and deadlines. The user completes the returned approval or payment task. Do not infer a balance debit from the app task, or a charge from a voucher's face value.
Read checkout_by again after funding. Envoy may complete merchant checkout itself. An authorized agent handout carries kind: "gift_card" and the voucher's redemption instructions. A paid gift card does not prove that merchant checkout completed.
Card limits
Envoy chooses how to pay for each purchase. The agent can use a one-time RuPay card or a paid gift-card voucher. Envoy can also check out itself. checkout_by carries the decision; see Read checkout_by to know who pays.
A card pays a card limit (max_total, or total) from ₹10 to ₹10,000, in whole rupees. On the one-time card route, a total below ₹10 is refused, and nothing is created. On the HTTP API the code is NO_RAIL with reason RAIL_AMOUNT_OUTSIDE_CARD_LIMITS. The card minimum does not apply when Envoy checks out itself. A total above ₹10,000, up to ₹1,00,000, requires Envoy checkout, which may be unavailable. Above ₹1,00,000, start_purchase fails with INVALID_INPUT, and nothing is created. When neither a card nor Envoy can pay it, start_purchase fails with NO_RAIL, and nothing is created. Do not split a larger total across purchases.
The card pays at most the card limit. When expires_at is present it says when spending access closes; when it is absent there is no known deadline, so never read its absence as an expired card. Funding limits the card's value. Envoy does not guarantee merchant locking, single use, or that the issuer blocks the card the moment you pay.
Follow next
Pass next.args unchanged to next.tool. When the reply specifies a delay, wait before calling: next.wait_seconds over MCP or next.after_seconds over HTTP.
A get_purchase_status call with after_revision may hold while the purchase waits and return early when it changes. next.args.wait_seconds bounds that wait. Follow the reply's cadence; do not add extra waits.
Call next.tool with next.args exactly as the briefing gives them. If your host sends _meta.progressToken on this call, the reply can arrive as an event stream: progress messages with plain sentences, then the final result. Without a progress token, the reply is one JSON body.
Stop waiting and tell the user when any of these happens:
next.byhas passed (REST only; over MCP,nexthas no tool).- Four held reads come back unchanged.
next.actionisstop(REST only; over MCP,nexthas no tool).
At a stop, do not pay and do not start again. The Envoy app shows the user the result. To read it later, call get_purchase_status.
Wait while the user approves
State waiting_for_user means the user has a task; purchase.user_task says which. For task approve, the user completes the returned approval or payment step in the Envoy app. On a gift card, that task alone does not identify the funding source. A gift-card task pay_link instead asks the user to pay the returned link. No agent can complete either task for the user. Show the app message once (user_action on HTTP, the briefing over MCP); Envoy sends no approval link. Then follow next.tool with next.args to check progress. A held get_purchase_status can wait up to 45 seconds. If you need to wait for the user, ask them to reply when they finish in the Envoy app. For a passkey approval, expiry at approval.decide_by can fail with USER_APPROVAL_EXPIRED. A gift-card funding task can instead enter verification while Envoy checks whether payment arrived. Follow the returned state; expiry alone does not prove failure or permit another payment.
The user completes approve, add_money and enter_otp in the Envoy app. For pay_link, give the user the returned payment link once. The user opens and pays it themselves; the agent never opens or pays it. No Envoy tool accepts a payment or bank OTP.
Read checkout_by to know who pays
Envoy decides who completes the merchant checkout, and purchase.checkout_by carries the decision. Read it in each reply and act on the latest one. Do not predict it.
agent: you pay. See Agent checkout.envoy: Envoy pays. See Envoy checkout.null: not decided yet. Keep followingnext, and do not infer a route.
The latest reply decides who checks out. A supported retry_of creates a linked replacement that Envoy checks out. See Retry a failed card checkout. Never ask the user for card details instead.
State processing (working over MCP) means Envoy is preparing payment, obtaining a gift-card quote, or placing the order itself. Keep reading status. Do not start another payment.
Agent checkout
When checkout_by is agent, wait for ready, pay once, report what happened and confirm the result. Follow complete checkout after approval.
Envoy checkout
When checkout_by is envoy, Envoy handles the merchant page and releases no card. Follow complete checkout after approval.
Answer Envoy's question
If Envoy needs the user's answer, show its prompt and send the reply once. See answer Envoy's question.
Pay once when the state is ready
The authorized ready card or gift-card voucher belongs to this purchase. Use it once as instructed at the merchant. See pay once when ready.
Report the result
After agent checkout, report paid only on merchant confirmation. Otherwise report failed with a reason. See report the result.
Retry a failed card checkout on Envoy's browser
A reported failed card checkout may offer a browser retry through retry_of. Envoy stops the original card, and the linked replacement waits for its closure and refund. Follow retry a failed card checkout.
Confirm the outcome
A report records what the agent saw. Envoy confirms the outcome from money evidence. See confirm the outcome.
Stop a purchase
A cancel is not proof of a refund or a blocked card. See stop a purchase.
Run an authorized production trial
Production moves real money, and there is no test mode or sandbox. Run a trial only with the account owner's explicit authorization for that merchant and that amount.
- Use a small amount you are authorized to spend, from ₹10.
- Practice the stop path: start a purchase, call
cancel_purchase, then read the state withget_purchase_status. - Practice a lost reply: retry
start_purchasewith the same key and confirm you get the samepurchase_idwithreplayed: true.
When something goes wrong
- A call fails (
isErrorover MCP,ok: falseon HTTP). It usually changed nothing. Over MCP, follow the briefing andretry. On HTTP, readerror.codeand follownext. retryischeck_status_firstover MCP, ornextreads a purchase over HTTP (for exampleUNAVAILABLEafter a write). The write may have happened. Read that purchase first. Never start a new one, because Envoy may already hold the first. See Errors and reason codes.- A reply is lost or a call times out: read
get_purchase_statusbefore any further payment. If checkout outcome is unclear, reportfailedwithfailure_reason: other. See Recover a purchase. - Another purchase is in progress (
ACTIVE_PURCHASE_EXISTSon HTTP): show the message and stop this attempt. Do not start another. - For help, give the user the
support_ref(for exampleEV-XXXXXX) and point them to envoy.money/support.