Errors and reason codes
What each error code and reason code means, and the next step the reply gives your agent.
HTTP tool results. This page describes the full reply that
POST /tools/:toolreturns:error.code, reason codes,purchase.stateandnext.action. An MCP agent does not receive these tool-result fields. If you build an MCP agent, read Replies and next steps instead. It lists eachsituation,moneyandretryvalue and what to do.
Over MCP, a tool-result failure is a plain sentence for the user, a What to do line, retry and support_ref. Envoy keeps the codes in the result's _meta["money.envoy/support"] for support and hosts. Give the user the support_ref and support can find the code.
Before an MCP tool call starts, an authentication refusal is an HTTP 401 transport response, not a tool result. Read WWW-Authenticate to find Envoy's sign-in metadata. Its body has AUTH_REQUIRED for a missing or invalid bearer, or INSTALLATION_PROOF_REQUIRED when a key-bound connection lacks a valid proof. The body request_id matches the X-Request-Id header. Renew a rejected bearer once if the host has a refresh token; otherwise reconnect. For a proof refusal, use a fresh proof from the original installation. If that key is lost, reconnect as a new connection. See Request protection.
A failed call usually changed nothing. When its next reads a purchase (for example UNAVAILABLE after a write), the write may have happened. Read that purchase first. Never start a new one, because Envoy may already hold the first. The reply always says what to do next.
Support revision: envoy@2026-10. Last updated: 1 November 2026.
How an error reaches you over HTTP
| Field | Meaning |
|---|---|
error.code | One of the 12 codes below. Branch on this. |
error.message | Safe to show the user. For NO_RAIL it explains the rail reason. |
error.fields | Up to 10 rejected fields, each with path, problem and expected. |
error.reason_code | Optional. Why a purchase or rail decision went this way (see below). |
error.retryable | Whether repeating the same call is allowed. |
error.retry_after_seconds | Seconds to wait before a retry, when set. |
next | What to do now: action, tool, args, instruction, after_seconds, by. Follow it. |
The HTTP reply also has the HTTP status below. next.instruction is for the agent. error.message and purchase.status_text are safe to show the user.
Reconcile before you pay again
Never pay twice. This outranks every retry rule on this page.
- After a lost reply or timeout, read
get_purchase_statusbefore any further payment. If you cannot tell whether checkout completed, reportfailedwithfailure_reason: other. See Recover a purchase. - When the reason is
ORDER_OUTCOME_UNKNOWN, the purchase result is not known. Tell the user to contact Envoy support in the app. Do not pay again or restart. - While the state is
verifying, money is unsettled: keep reading status asnextdirects. - Retrying
start_purchasewith the sameidempotency_keycontinues the same purchase. A new key is a new purchase.
Error codes
When an error concerns an existing purchase, the reply includes it and next reads that purchase with get_purchase_status. Otherwise next.action is the one shown. The full table with the agent's instruction for each code is in the reference. Each code has its own heading in Each error code.
| Code | HTTP | What happened | next.action | Next steps |
|---|---|---|---|---|
INVALID_INPUT | 400 | An input field is wrong; error.fields names each one. | fix_input | Fix every listed field. Call the same tool once more. |
UNAUTHENTICATED | 401 | The access token is missing, expired or revoked. | reconnect | Renew once with your refresh token. If renewal fails or you have none, ask the user to reconnect Envoy. Then retry. |
NOT_ALLOWED | 403 | This connection may not do that. | stop | Tell the user. Do not retry, because the answer will not change. |
SERVICE_NOT_ENABLED | 403 | The purchase service is not enabled for this user or host. | stop | Tell the user. Do not retry. |
NOT_FOUND | 404 | No purchase matches that reference for this user. | stop | Check the reference from this task. Do not start a new purchase to recover. |
IDEMPOTENCY_CONFLICT | 409 | The key was already used with a different merchant URL or amount. | fix_input | Resend the original input unchanged. Use a new key only for a genuinely new purchase. |
ACTIVE_PURCHASE_EXISTS | 409 | Another purchase is in progress. | stop | Show user_action.message to the user. Stop this attempt. |
STATE_CONFLICT | 409 | The call does not fit the purchase's current state. | stop | Do not retry the call. Read the purchase with get_purchase_status. |
NO_RAIL | 422 | Envoy cannot pay this purchase; nothing was created. | stop | Tell the user why, using error.message. Do not retry or split the purchase. |
RATE_LIMITED | 429 | Too many requests for this access token. | retry | Wait next.after_seconds. Repeat the same call unchanged. See Retry after a 429. |
UNAVAILABLE | 503 | Envoy or an upstream service is unavailable. | retry when error.retryable is true; otherwise stop or a read of the purchase | Follow next. Never pay another way meanwhile. |
CLIENT_UPDATE_REQUIRED | 426 | This client is too old. | update_client | Tell the user to update the client. |
Each error code
Each heading below is a link target for one code.
INVALID_INPUT
An input field broke a rule. error.fields lists up to 10 fields with path, problem and expected. Fix all of them, then call the same tool once more. Over MCP this reads retry after_fix and a fix list.
submit_answer also refuses an answer that looks like a one-time code or holds card details, unless the open question asks for the user's merchant login. Envoy never echoes the refused value. If Envoy cannot tell which question the answer is for, error.fields names the open question's id. Ask the user again, then send the answer with that question.
UNAUTHENTICATED
The access token is missing, expired or revoked. Renew once with your refresh token if you hold one. If you cannot renew, the user reconnects Envoy through the host. A reconnect proves who you are and changes no purchase. See Reconnect and resume your task.
NOT_ALLOWED
This connection may not do that. Tell the user. Do not retry.
SERVICE_NOT_ENABLED
The purchase service is not enabled for this user or host. Tell the user. Do not retry.
NOT_FOUND
The reference does not match a purchase for this user. The reference is wrong for this user. The purchase is not gone. It also covers a purchase owned by someone else. Check the purchase_id or idempotency_key from this task. Do not start a new purchase.
IDEMPOTENCY_CONFLICT
The idempotency_key was used before with different input. Resend the original input unchanged, or read the existing purchase. Use a new key only for a different purchase. For submit_answer, the same key with a different answer returns this code.
ACTIVE_PURCHASE_EXISTS
The reply carries a user_action with task resolve_active_purchase and the message "You already have a purchase in progress. Finish or cancel it in the Envoy app first." Show it and stop. Do not start another purchase. Find the active purchase with get_account; see Recover a lost purchase reference.
STATE_CONFLICT
The call does not fit where the purchase is now. Common cases:
report_checkout_resultbefore the purchase isready.cancel_purchaseon a purchase that already completed or failed.submit_answerwith aquestionthat is no longer the open one. Envoy has moved on. Callget_purchase_statusand read the new question. Never send the old answer to the new question.submit_answerwithtypedetailswhen no question is open.submit_answerwithtypefeedbackwhile the purchase is still in progress. Feedback is optional and never blocks checkout.
Do not retry the call. Read the purchase and follow its next.
NO_RAIL
Nothing exists to cancel or recover. error.reason_code is the rail reason, for example RAIL_AMOUNT_OUTSIDE_CARD_LIMITS. Tell the user using error.message. Do not retry or split the purchase into smaller ones. See Rail reasons.
RATE_LIMITED
You sent too many requests for this access token. Wait next.after_seconds, then repeat the same call unchanged. Add jitter and back off if you see it again. See Retry after a 429.
UNAVAILABLE
Envoy or an upstream service is unavailable. If next calls get_purchase_status with your original reference, the write may or may not have happened. Read the purchase before anything else. If error.retryable is true, wait next.after_seconds and repeat the same call. If it is false, tell the user to try again later. Never pay another way meanwhile, because the first purchase may still complete.
CLIENT_UPDATE_REQUIRED
This client is too old (HTTP 426). Tell the user to update it.
Reason codes
purchase.reason_code says why a purchase is in its state. A reason code never arrives alone. It arrives in one of three places:
- On
purchase.reason_code, next to apurchase.state. Most reasons in the first two tables below. - On
error.reason_code, next to the error codeNO_RAIL. Only rail reasons. - On
purchase.rail_reason, which says which payment path Envoy chose. Not an error.
Over MCP the reason shapes the briefing's wording and appears only in _meta. The agent never sees the code. The meaning below is the sentence Envoy shows as purchase.status_text, safe to show the user.
The Arrives with column comes from Envoy's state mapping and the service code. "Reserved, not sent today" means the contract lists the code but no service path produces it yet. The contract only adds codes and never removes them, so a reserved code stays listed. Handle it by its meaning, in case it starts to appear.
Policy and user decisions
| Reason code | Meaning | Arrives with | Next steps |
|---|---|---|---|
POLICY_DENIED | Your Envoy spending settings do not allow this purchase. | State failed | Stop. Tell the user. Do not retry. If the user already approved it, Envoy refused it itself; give support the support_ref. |
POLICY_AMOUNT_LIMIT | This purchase is above your Envoy spending limit. | State failed, when Envoy refuses the amount at the start | Stop. A new purchase for the same amount is refused the same way. |
POLICY_ACTIVE_PURCHASE_EXISTS | Another purchase is still in progress or waiting for approval, so this one could not start. | State failed, when the start is refused because another purchase is open | Stop. Tell the user to finish or cancel the other purchase first; a new purchase may work after that. |
POLICY_ADMISSION_UNAVAILABLE | Envoy could not check this purchase against your spending settings, so it did not go ahead. | Reserved, not sent today | Stop. Tell the user. |
POLICY_FRAUD_DECLINED | This purchase was declined by a security check. | Reserved, not sent today | Stop. Do not start a new purchase. |
POLICY_AGENT_LIMIT | This purchase is above the limit set for this connection. | Reserved, not sent today | Stop. Do not start a new purchase for the same amount. |
USER_DECLINED | You declined this purchase in the Envoy app. | State failed | Stop. The user chose this. Start a new purchase only if the user asks. |
USER_APPROVAL_EXPIRED | The approval expired before Envoy could prepare the card. Nothing was charged. | State failed | Stop. A new attempt needs a new key and a new approval. |
USER_OTP_EXPIRED | The one-time code from your bank expired before it was entered. | Reserved, not sent today | Stop. A new attempt needs a new key. |
USER_CANCELLED | You cancelled this purchase in the Envoy app. | State cancelled | Done. Carry on with the user's task. |
USER_FUNDS_NOT_ADDED | The gift-card funding task expired unpaid. | State failed | Stop this purchase. Follow the returned recovery instruction; do not pay through an expired funding task. |
Order outcomes
| Reason code | Meaning | Arrives with | Next steps |
|---|---|---|---|
ORDER_CANCELLED_BY_AGENT | This purchase was cancelled on your behalf. | State cancelled, after you called cancel_purchase and nothing was charged | Done. |
ORDER_CARD_NOT_ACCEPTED | The merchant did not accept the Envoy card. | State failed, when you reported failed with failure_reason card_not_accepted and Envoy then returned the card's money | Stop. Tell the user. Another card purchase at this merchant is refused the same way. |
ORDER_CARD_DECLINED | The card payment was declined. | State failed, when you reported failed with failure_reason card_declined and Envoy then returned the card's money | Stop. Do not pay again. Tell the user. |
ORDER_OTP_FAILED | The bank one-time code check failed, so the payment did not go through. | State failed, when you reported failed with failure_reason otp_failed and Envoy then returned the card's money | Stop. Do not retry the code. Tell the user. |
ORDER_PRICE_CHANGED | The merchant's price changed. Nothing was charged. | State failed, when you reported failed with failure_reason price_changed and Envoy then returned the card's money | Stop. Tell the user the new price. |
ORDER_OUT_OF_STOCK | The item is out of stock at the merchant. | State failed, when you reported failed with failure_reason out_of_stock and Envoy then returned the card's money | Stop. Tell the user. A new purchase hits the same stock. |
ORDER_CHECKOUT_FAILED | Checkout at the merchant did not go through. | State failed: when Envoy checks out itself (checkout_by envoy) and its checkout fails, or when you reported failed with failure_reason site_error or other and Envoy then returned the card's money | Stop. Tell the user. A new purchase may be tried when nothing was charged. |
ORDER_CHECKOUT_BLOCKED | The merchant blocked this checkout. | State failed: when Envoy checks out itself (checkout_by envoy) and the site blocks it, or when you reported failed with failure_reason site_blocked and Envoy then returned the card's money | Stop. Another card purchase at this merchant is refused the same way. |
ORDER_CARD_EXPIRED_UNUSED | The one-time card expired before it was used. Nothing was charged. | State failed | Stop. A new purchase needs a new approval. |
ORDER_ISSUER_UNAVAILABLE | The card issuer was unavailable, so the purchase could not be paid. | State failed | Stop. Tell the user. A new purchase may work later. |
ORDER_CARD_REQUIRED | This merchant is not available without an Envoy card. list_gift_card_merchants lists the merchants that are. Nothing was charged. | State failed | Stop. Tell the user. Offer a merchant from list_gift_card_merchants. |
ORDER_INTEGRATION_ERROR | Envoy hit an internal problem with this purchase and is checking it. | State failed or verifying | Follow next. In verifying, never pay again. Give support the support_ref. |
ORDER_OUTCOME_UNKNOWN | The purchase result is not known yet. Do not pay again until it is resolved. | State verifying | See below. |
ORDER_CONNECTION_REVOKED | The connection that made this purchase was removed from Envoy. | Reserved, not sent today | Stop. The user connects again to start a new purchase. |
Over MCP, a purchase that Envoy refused after the user approved it reads "Envoy couldn't complete this payment after you approved it". It does not say "your spending settings do not allow this purchase", because the user's settings are not the cause.
ORDER_OUTCOME_UNKNOWN is the recovery reason. It arrives with state verifying. Keep reading status as next directs. Never pay again. If next.action becomes stop, tell the user to contact Envoy support in the app.
A failure_reason in report_checkout_result (card_not_accepted, card_declined, otp_failed, price_changed, out_of_stock, site_error, site_blocked, other) records what you saw. Like every report, it moves a ready purchase to verifying until Envoy has money evidence. If you cannot tell whether checkout completed, report failed with failure_reason: other. Envoy refuses agent cancellation after a report unless it reopens the card with a new ready handout.
Rail reasons
purchase.rail_reason says which payment path Envoy chose. With NO_RAIL, error.reason_code says why Envoy could not pay, and error.message repeats the meaning. Envoy chooses the path for each purchase. The agent can use a one-time card or paid gift-card voucher, or Envoy checks out itself. purchase.checkout_by carries the result; see Read checkout_by to know who pays. Do not predict the path from a rail reason.
The generated reason-code reference lists the exact current values and reserved codes. It includes host restrictions, merchant gateways, gift cards and available marketplace offers. A declared value does not mean that its route is enabled for this account.
For a refusal, use error.message and follow next. For a selected route, read the latest checkout_by. Never infer permission to pay from a reason code alone.
States and next.action
Every purchase reply has a purchase.state and a next.action. This table shows the pair for all ten states. Over MCP the agent reads situation instead; see Replies and next steps.
purchase.state | next.action | What it means for you |
|---|---|---|
needs_details | call_tool (submit_answer) | Envoy needs more details. Show the user purchase.question.prompt word for word. Then call submit_answer with next.args and the user's words as answer. |
needs_answer | call_tool (submit_answer) | Envoy asks the user a question in purchase.question. Show the prompt word for word and wait. Then call submit_answer with next.args and the user's exact words as answer. |
waiting_for_user | call_tool (get_purchase_status, held) | Show the returned user task once. App tasks stay in the app. For pay_link, give the link and amount to the user; never open or pay it yourself. Follow status as directed. |
processing | call_tool (get_purchase_status, held) | Envoy is preparing payment or checking out. Keep reading status. Start nothing else. |
ready | checkout (report_checkout_result) | Authorized card or gift-card details are in data.payment_details on get_purchase_status. Check kind and follow the handout. Pay once, then report. Any other tool sends you to get_purchase_status first with call_tool. |
verifying | call_tool (get_purchase_status, held) | Money is unsettled. Keep reading status. Never pay again or restart. |
completed | done | The purchase is complete. Read money.status separately; the merchant may collect on delivery. Tell the user what the reply confirms. |
failed | stop | The purchase is over. Tell the user from status_text. With ORDER_OUTCOME_UNKNOWN, tell the user to contact support. |
cancelled | done | The purchase was cancelled. Carry on with the user's task. |
superseded | call_tool (get_purchase_status on the replacement) | Another purchase replaced this one. It needs its own approval. |
A held state changes to stop after four unchanged held reads or when next.by passes. Stop waiting then, and do not pay. The Envoy app shows the user the result.
The other next.action values come with errors and reads: fix_input, retry, reconnect, update_client, stop (see the error table) and continue (a successful read such as get_account).