# Order tools **This group is read only.** Nothing in it can change an order, issue a refund, cancel a fulfilment or touch a customer record. That is a decision rather than a gap, and the reasons are set out under [What this group deliberately cannot do](#what-this-group-deliberately-cannot-do). Every other group on this surface reads or writes something **the merchant authored**: a theme, a page, a price, a broadcast they wrote. This one reads records about **shoppers**, who never agreed to your integration. So it discloses less than the REST API does, on purpose, and the difference is not configurable: | | What you get here | Where the rest lives | |---|---|---| | Shopper email | Masked (`so***@example.com`), on `get_order` only | `GET /api/v1/orders/{id}` | | Shopper name | Not returned | `GET /api/v1/orders/{id}` | | Street address | Not returned | `GET /api/v1/orders/{id}` | | Phone number | Not returned | `GET /api/v1/orders/{id}` | | Delivery region | City, province, postal code, country | same | | Order metadata | Not returned | `GET /api/v1/orders/{id}` | | Buyer form responses | Not returned | `GET /api/v1/orders/{id}` | | Gift recipient details | Not returned | `GET /api/v1/orders/{id}` | **`list_orders` returns no contact details at all**, not even masked ones. A list is where bulk disclosure happens: a page of a hundred orders carrying one email each is a hundred addresses in a single tool result, and a tool result lands verbatim in a model's context window and in whatever transcripts and logs its host keeps. When a program genuinely needs the delivery details, for example to print a label, read the REST API under `read_orders`, where the caller is a program and the bytes go where that program puts them. **There is no reveal parameter, on any tool, and there will not be one.** A parameter is set by the model, not by you: its description is metadata an agent reads and a human never sees, and a switch that widens disclosure is one an agent can flip on its own initiative. Every other input on this surface (`?features=`, `?read_only=true`) can only take capability away. `?read_only=true` strips write scopes. It is a **mutation** control, not a **disclosure** control: it leaves every tool in this group fully available, because none of them writes anything in the first place. Do not treat it as a privacy setting. ### get_order Reads one order: what was bought, the money, the status, and the region it was shipped to. The buyer's email is MASKED and their name, street address and phone number are not returned at all, so this is enough to confirm you have the right order and not enough to contact anyone. **Scope:** `read_orders` From `list_orders`. Not the order number a shopper sees: the only lookup by order number the platform has also requires the shopper's email, so that a caller cannot walk the order numbers. ### list_orders Lists this store's orders, newest first, with each one's id, order number, status, total and how many lines it has. Call this to get the `order_id` that `get_order` and `list_refunds` need. **Scope:** `read_orders` Only orders in this state: `pending`, `paid`, `fulfilled`, `completed`, `cancelled` or `refunded`. Omit for every state. A value outside this list is refused rather than matched, because an unmatched status answers an empty page that reads as "this store has no orders like that". Only this buyer's orders. The id comes from a previous row of this list; there is no tool here that looks a customer up by email or name. How many, 1 to 100. Defaults to 20. The cursor printed at the end of a previous page. Pass it back verbatim; it is not a number and not an id. ### list_refunds Lists what has been refunded against one order, newest first, with each refund's amount, status and which processor moved the money back. An order that was never charged is reported as such rather than as an empty list, because "charged and nothing given back" is a different fact. **Scope:** `read_payments` This is the one tool in the group that does not read the order tables, so it carries the scope that `GET /api/v1/orders/{id}/refunds` already carries. That is deliberate: a key should see the same rows through both doors, and gating it on `read_orders` would have quietly widened what `read_orders` means. `read_payments` is not a customer-data grant, and the refund ledger holds no card number, no last four and no shopper. From `list_orders`. How many, 1 to 100. Defaults to 20. The cursor printed at the end of a previous page, verbatim. ### sales_report Aggregate sales for a period: order counts, gross merchandise value, average order value and units, with an optional daily series and an optional best-sellers list. Every figure is a total and no individual appears in any of it. **Scope:** `read_orders` GMV is **gross**, so money that later left the merchant is counted in it and reported on its own line: `refunded` is the amount refunded, and `disputed` is the charge amount of the chargebacks the merchant lost. The report prints the net figure, gross minus both, so it does not have to be re-derived. A sale means an order that reached payment, so pending and cancelled orders are excluded from everything except `total_orders`. Figures are in minor units and are summed across every currency the store took, so on a multi-currency store they are not one currency's total. RFC3339 timestamp, inclusive. Omit for the whole history. RFC3339 timestamp, exclusive. Must be after `from`. Omit for now. Optional extra sections: `timeseries` adds one row per day, `top_products` adds the best-selling variants. The headline summary is always returned. Ask for a section only when you will use it: a 90 day series is 90 rows to carry. How many best-sellers, 1 to 100. Defaults to 10. Ignored unless `include` names `top_products`. ## What this group deliberately cannot do **Issue a refund.** The platform's refund path is genuinely safe to call from a thin client: over-refund protection, the two row locks, the reason validation and the non-cash tender legs all live inside the service and none of them can be bypassed. The problem is the idempotency key. A model that retries a tool call will mint a fresh key, and the second call moves money a second time while every guard passes, because the two requests are honestly distinct. Deriving the key server-side from the order and the amount would break the legitimate "refund 500 now, 500 more later" case. Issue refunds from the dashboard or the REST API, where the key and the person responsible are both real. **Cancel, fulfil or complete an order.** Each is terminal or near-terminal, and a cancel reverses compensations with no undo. They would need `write_orders`, which this group leaves entirely unused. **Read or write a customer record.** No `list_customers`, no `get_customer`, no customer notes. They would need `read_customers`, which exports email, name, phone and postal addresses, and advertising it would ask every merchant connecting an agent to change a font colour for their whole customer directory, pre-ticked on the consent screen. Staff notes are worse again: free text written about a shopper, by name. Read customers over the REST API under `read_customers` when a program needs them. ## Narrowing a connection to this group Add `?features=orders` to the connection URL to give an agent these four tools and nothing else. The dashboard's Connect panel writes it for you. `?features=` is a **context-window convenience, not a security boundary.** A URL that omits the parameter gets every group, including this one, because a connection made before a group existed must not silently lose tools. What actually gates this group is the scope on each tool: a token that never asked for `read_orders` cannot list orders, and one that never asked for `read_payments` cannot read the refund ledger, whatever the URL says.