get_purchase_status
Omit purchase for the active/latest one; pass after_revision and the read holds up to `wait_seconds` (contract maximum 45 seconds, `HOLD_MAX_SECONDS`); the server may hold less; follow `next.args.wait_seconds`; call again as soon as it returns.
POST https://api.envoy.money/tools/get_purchase_status
Omit purchase for the active/latest one; pass after_revision and the read holds up to wait_seconds (contract maximum 45 seconds, HOLD_MAX_SECONDS); the server may hold less; follow next.args.wait_seconds; call again as soon as it returns. Without after_revision the reply is immediate. For agent checkout, ready carries the card: pay once and report. No card follows a report or disconnect. After a failed report, follow recovery: start_purchase with retry_of only when offered, or a new ready handout after reconciliation. When checkout_by is envoy, no card is released: Envoy checks out. Relay purchase.question.prompt; submit the user's answer. Stop after four unchanged holds or when told.
| Title | Service | Read only | Destructive | Idempotent | External sites |
|---|---|---|---|---|---|
| Check purchase status | shared | true | false | true | false |
Send a Central Auth bearer with agent scope. A key-bound connection also sends Envoy-Installation-Proof over this exact request. JSON bodies are limited to 64 KB; a larger body returns HTTP 413 with INVALID_INPUT.
Input
| Field | Required | Type | Description and rules |
|---|---|---|---|
purchase | No | string | Purchase ID or original idempotency key. minLength 16; maxLength 64; pattern ^(?:pur_[a-f0-9]{32}|(?:[A-Za-oq-z0-9_-]|p[A-Za-tv-z0-9_-]|pu[A-Za-qs-z0-9_-]|pur[A-Za-z0-9-])[A-Za-z0-9_-]*)$ |
after_revision | No | integer | Return early when the revision changes. minimum 0 |
wait_seconds | No | integer | With after_revision, the read holds up to wait_seconds (contract maximum 45 seconds, HOLD_MAX_SECONDS); the server may hold less; follow next.args.wait_seconds. Without after_revision the read returns at once. minimum 0; maximum 45 |
Unknown fields are rejected. The exact input schema is on the schemas page.
Synthetic request
{
"purchase": "synthetic-intent-0001"
}Hold
With after_revision, a read in a waiting state holds up to wait_seconds (contract maximum 45 seconds, HOLD_MAX_SECONDS); the server may hold less. Never assume a fixed hold length: pass next.args exactly as returned, and stop waiting at next.by. Without after_revision the read returns at once.
Success data
Every reply uses the shared envelope: ok, the purchase, next, user_action and error. Follow next; outputs may gain fields, so ignore ones you do not recognize.
This tool returns its payload in data:
Field in data | Required | Type | Description and rules |
|---|---|---|---|
payment_details | Yes | object | Secret. Release only while ready after authorization (a gift card: after its link was paid); never log or persist these values. card is present exactly when kind is card, voucher exactly when kind is gift_card. |
payment_details.kind | Yes | "card" | "gift_card" | card: the authorized one-time RuPay card, in card. gift_card: the paid gift card, in voucher. |
payment_details.card | No | object | — |
payment_details.checkout_code | No | string | Optional checkout code, this card's bank OTP: when the bank page asks for a code or OTP, enter the checkout code there once, immediately. Repeated reads do not fund another card. When checkout_code_expires_at is present, read status again once the code expires. |
payment_details.checkout_code_expires_at | No | string | When this checkout code expires. Present only with a code whose validity is known. It is not when spending access closes, and not the expiry printed on the card. format date-time |
payment_details.amount_limit | Yes | object | — |
payment_details.checkout_attempt_id | No | string | The checkout attempt carried by this handout. Include this exact value in report_checkout_result; never substitute an attempt from a later read. pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ |
payment_details.expires_at | No | string | When spending access closes. Omitted when unknown; it is not the checkout code expiry or the expiry printed on the card. format date-time |
payment_details.voucher | No | object | Present exactly when kind is gift_card. value equals purchase.amount. |
Secret paths: data.payment_details. They carry one-time card details while the purchase is ready; never log, store or show them. Examples use synthetic values only.
Synthetic result
Example synthetic-completed-receipt (synthetic data):
{
"ok": true,
"contract": "envoy@2026-10",
"tool": "get_purchase_status",
"request_id": "req_synthetic",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"server_time": "2026-09-29T12:00:00Z",
"replayed": false,
"purchase": {
"purchase_id": "pur_5b1e9c7a3f2d5a8e9c0b1d2e3f4a5b6c",
"service": "purchase",
"idempotency_key": "synthetic-intent-0001",
"revision": 4,
"terminal": true,
"state": "completed",
"user_task": null,
"reason_code": null,
"status_text": "This purchase is complete.",
"rail": "otu_card",
"rail_reason": "RAIL_OTU_PREFERRED",
"checkout_by": "agent",
"merchant": {
"name": "Example Store",
"url": "https://shop.example.test"
},
"amount": {
"minor": 249900,
"currency": "INR",
"display": "₹2,499.00"
},
"description": "Headphones",
"details": {
"complete": true,
"missing": []
},
"question": null,
"follow_up": {
"id": "feedback-pur_5b1e9c7a3f2d5a8e9c0b1d2e3f4a5b6c",
"kind": "feedback",
"prompt": "How did this purchase go? The user can rate it from 1 to 5 and add a few words."
},
"approval": {
"state": "approved",
"method": "passkey",
"decide_by": "2026-09-29T12:15:00Z",
"approval_url": null
},
"payment_ready": {
"available": false,
"kind": "card",
"expires_at": null
},
"money": {
"status": "debited",
"paid": {
"minor": 249900,
"currency": "INR",
"display": "₹2,499.00"
},
"merchant_order_ref": "SYNTHETIC-ORDER-0001"
},
"receipt": {
"merchant_order_ref": "SYNTHETIC-ORDER-0001",
"paid": {
"minor": 249900,
"currency": "INR",
"display": "₹2,499.00"
},
"card": null,
"completed_at": "2026-09-29T12:20:00Z"
},
"chain": {
"supersedes": null,
"superseded_by": null
},
"agent": {
"agent_id": "origin:https://host.example.test",
"display_name": "Synthetic Host",
"verification": "origin"
},
"created_at": "2026-09-29T12:00:00Z",
"updated_at": "2026-09-29T12:20:00Z"
},
"data": null,
"next": {
"action": "done",
"tool": null,
"args": null,
"instruction": "The purchase is complete. Tell the user, including the order reference from purchase.receipt.merchant_order_ref when it is present, then carry on with the user's task.",
"after_seconds": null,
"by": null
},
"user_action": null,
"follow_ups": [
{
"tool": "submit_answer",
"args": {
"purchase": "pur_5b1e9c7a3f2d5a8e9c0b1d2e3f4a5b6c",
"question": "feedback-pur_5b1e9c7a3f2d5a8e9c0b1d2e3f4a5b6c",
"type": "feedback"
},
"why": "Optional feedback about this completed purchase."
}
],
"error": null
}Errors
A failure has ok: false, changes nothing, and carries error plus one next. Branch on error.code and next, not on the HTTP status. The contract declares every code below for every tool; which one a call returns depends on the request and the purchase state.
| Code | HTTP status | Meaning | What the agent does |
|---|---|---|---|
INVALID_INPUT | 400 | Fix the supplied input. | Fix every field listed in error.fields, then call the same tool once more. |
UNAUTHENTICATED | 401 | Reconnect through Central Auth. | Ask the user to reconnect Envoy, then retry this call. |
NOT_ALLOWED | 403 | The operation is not allowed. | This connection may not do that. Tell the user; do not retry. |
SERVICE_NOT_ENABLED | 403 | The service is not enabled. | This Envoy service is not enabled for the user. Tell the user; do not retry. |
NOT_FOUND | 404 | Purchase absent or owned by another user. | No purchase matches that reference. Check the purchase id from this task; do not start a new purchase to recover. |
IDEMPOTENCY_CONFLICT | 409 | The same key has different input. | Retry with the original inputs unchanged, or use a new idempotency_key only for a genuinely new purchase. |
ACTIVE_PURCHASE_EXISTS | 409 | An active purchase already exists. | Another purchase is in progress. Show the user the user_action message and stop this attempt. |
STATE_CONFLICT | 409 | The operation conflicts with the current state. | This conflicts with the current state. Tell the user; do not retry. |
NO_RAIL | 422 | No rail can serve this purchase; nothing was created. | Envoy cannot pay this purchase. Tell the user why using error.message; do not retry or split it. |
RATE_LIMITED | 429 | Wait before retrying. | Wait next.after_seconds, then retry the same call unchanged. |
UNAVAILABLE | 503 | The service is unavailable; follow retryable and next. | Wait next.after_seconds, then retry the same call unchanged; never pay elsewhere meanwhile. |
CLIENT_UPDATE_REQUIRED | 426 | Update the client. | This client is too old. Tell the user to update it. |