search_products
Search Elixir's marketplace when the user wants a product and names no merchant, or asks for Elixir or Envoy.
POST https://api.envoy.money/tools/search_products
Search Elixir's marketplace when the user wants a product and names no merchant, or asks for Elixir or Envoy. Show each with its price. To buy one or several, call start_purchase once with items [{name, quantity, product_id}], total = the sum of their total.display (e.g. "₹998.00") and their merchant_url. 1mg products may add a handling fee; if Envoy answers with a different total, ask the user and resend with it. Moves no money.
| Title | Service | Read only | Destructive | Idempotent | External sites |
|---|---|---|---|---|---|
| Search Elixir marketplace | purchase | 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 |
|---|---|---|---|
query | Yes | string | What the user wants, in their words. minLength 1; maxLength 200 |
quantity | No | integer | How many; default 1. minimum 1; maximum 99; default 1 |
address_id | No | string | Saved address ID from get_account to deliver to; default: the default saved address. minLength 1; maxLength 64 |
Unknown fields are rejected. The exact input schema is on the schemas page.
Synthetic request
{
"query": "whey protein 1kg",
"quantity": 2
}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 |
|---|---|---|---|
products | Yes | array of object | Products from Elixir's own marketplace, price for one and total for the quantity. Empty when Elixir has nothing to offer right now. maxItems 5 |
products[].product_id | Yes | string | This product's offer id. Never show it to the user. pattern ^ofr_[a-f0-9]{32}$ |
products[].name | Yes | string | Product name, as Elixir lists it. minLength 1; maxLength 200 |
products[].brand | Yes | string or null | Brand, when Elixir lists one. minLength 1; maxLength 500 |
products[].image | Yes | string or null | Product image. format uri; pattern ^https:// |
products[].price | Yes | object | — |
products[].quantity | Yes | integer | Quantity priced. minimum 1; maximum 999 |
products[].total | Yes | object | — |
products[].valid_until | Yes | string | The price holds until this time; search again after it. format date-time |
products[].merchant_url | Yes | string | Elixir's store. Pass it to start_purchase as merchant_url. format uri; pattern ^https:// |
Synthetic result
Example synthetic-products (synthetic data):
{
"ok": true,
"contract": "envoy@2026-10",
"tool": "search_products",
"request_id": "req_synthetic",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"server_time": "2026-09-29T12:00:00Z",
"replayed": false,
"purchase": null,
"data": {
"products": [
{
"product_id": "ofr_0123456789abcdef0123456789abcdef",
"name": "Whey Protein Isolate 1kg",
"brand": "Synthetic Nutrition",
"image": "https://cdn.example.test/whey.jpg",
"price": {
"minor": 49900,
"currency": "INR",
"display": "₹499.00"
},
"quantity": 2,
"total": {
"minor": 99800,
"currency": "INR",
"display": "₹998.00"
},
"valid_until": "2026-09-29T12:30:00Z",
"merchant_url": "https://elixir.cards"
}
]
},
"next": {
"action": "continue",
"tool": null,
"args": null,
"instruction": "Show the user each product with its price and total. To buy what the user picks, one or several, call start_purchase once with items [{name, quantity, product_id}], total = the sum of their total.display (e.g. \"₹998.00\"), their merchant_url and the same address_id you searched with. Never buy one the user did not pick.",
"after_seconds": null,
"by": null
},
"user_action": null,
"follow_ups": [],
"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. |