A single-file StorefrontBackend that swaps Anthropic's fictional ACME store for Tote, so the reference shopping agent searches, carts, and checks out against thousands of real stores instead of an in-memory fixture.
The shopping agent calls the StorefrontBackend interface with a query, a product id, and a session. tote_backend.py turns each call into a Tote REST request over HTTPS and returns real products, a real cart, and a real checkout URL that the human opens to pay.
Deep dive: the method-by-method mapping and swap instructions are in EXAMPLE.md; the interface being implemented lives in anthropics/commerce-agents; the Tote API is at usetote.dev/docs.
This is the actual output of prove.py run today against the live index. No mocks, no ANTHROPIC_API_KEY.
QUERY: 'coffee'
----------------------------------------------------
search_products -> 5 products across the index:
- Jamaica Blue Mountain, Coffee Brew Bags $25.0 [Sea Island Coffee]
- Canary Islands, Los Grimones Estate Gran Can $28.0 [Sea Island Coffee]
- Yemen, Matari Coffee $28.0 [Sea Island Coffee]
- Yemen, Ismaili Coffee $30.0 [Sea Island Coffee]
- Canary Islands, Finca Sanssouci Tenerife Cof $40.0 [Sea Island Coffee]
picked: 'Jamaica Blue Mountain, Coffee Brew Bags' from Sea Island Coffee
get_product_details -> 'Jamaica Blue Mountain, Coffee Brew Bags', 1 variant(s), img=yes
add_to_cart -> cart now has 1 item(s), subtotal $29.95
* 1 item(s) at seaislandcoffee.com x1 $29.95
checkout_handoff -> REAL checkout link (human pays here):
Check out at seaislandcoffee.com: https://seaislandcoffee.com/cart/33065916170283:1
----------------------------------------------------
RESULT: PASS — ACME backend swapped for real stores
That checkout URL is a real Shopify cart permalink at the store's own domain. The human opens it and pays there. No card ever reaches the agent.
| Figure | What it measures | Scope / caveat |
|---|---|---|
| 3,762 | verified stores reachable through this backend | live count from usetote.dev/health, measured 2026-09-08; it grows over time. Tote is an external service, not code in this repo. |
| 390,801 | products loaded in Tote's search index and returned by search_products |
the index.products field of usetote.dev/health, 2026-09-08. Not the products_indexed top-line, which sums per-store counts and overstates the searchable set. |
| 12 / 14 | StorefrontBackend methods overridden by ToteBackend |
the 2 not overridden (get_account_context, get_disclosure) are optional hooks that keep their interface defaults. Counted from tote_backend.py. |
| 4 | distinct Tote REST endpoints the backend calls | /v1/products/search, /v1/stores/{domain}/products/{handle}, /v1/cart/add, /v1/checkout_url. From tote_backend.py. |
| 0 | API keys needed to run prove.py |
the script sets no auth header, and Tote's read and cart endpoints are open to start. |
Regenerate the store and product figures with curl https://usetote.dev/health. The code figures are counted from tote_backend.py against the interface in anthropics/commerce-agents.
flowchart TB
subgraph agent["Claude shopping agent, anthropics/commerce-agents"]
EX["executor<br/>prompt, skills, gates"]
IF["StorefrontBackend<br/>interface, unchanged"]
end
subgraph repo["This repo"]
TB["ToteBackend<br/>tote_backend.py"]
end
subgraph tote["Tote, usetote.dev, external service"]
API["REST API<br/>/v1/*"]
IDX["verified index<br/>3,762 stores"]
end
STORE["store's own<br/>checkout page"]
HUMAN["human<br/>pays here"]
EX --> IF --> TB
TB -->|"HTTPS"| API --> IDX
IDX --> API --> TB --> IF --> EX
TB -->|"checkout_handoff"| STORE --> HUMAN
Everything above ToteBackend is Anthropic's blueprint and does not change: same prompt, same skills, same gates, same executor. This repo is one file. It is a stateless async HTTP client (httpx.AsyncClient, 30s timeout). The only state it holds is _session_store, an in-memory map from the agent's session_id to the one store domain that session is carting at, because a Tote cart lives at a single store. On failure it never fabricates: search_products returns [] on a non-200, add_to_cart raises Unavailable, and get_orders and get_fulfillment_options return empty because there is no source for them. Configuration is one environment variable, TOTE_BASE, which defaults to https://usetote.dev.
flowchart LR
Q["user query"] --> SP["search_products"]
SP -->|"GET /v1/products/search"| P["Product list<br/>id = domain::handle::variant_id"]
P --> GD["get_product_details"]
GD -->|"GET /v1/stores/{d}/products/{h}"| D["ProductDetails<br/>plus variants"]
D --> AC["add_to_cart"]
AC -->|"POST /v1/cart/add"| C["Cart<br/>per-store session"]
C --> CH["checkout_handoff"]
CH -->|"GET /v1/checkout_url"| H["CheckoutHandoff<br/>real store URL"]
The whole backend is tote_backend.py; prove.py drives the flow above end to end. The backend only fetches. It returns exactly what Tote's endpoints return and decides nothing on the model's behalf: the agent's own gates and fencing (in anthropics/commerce-agents) constrain what the model may do, and this file never invents an order, a shipping quote, or a price.
Cross-store product ids. Tote is one flat catalog spanning thousands of stores, but a cart must go to a specific store. ToteBackend encodes each product id as domain::handle::variant_id with _encode and reads it back with _decode, so search_products can return items from many stores while add_to_cart still routes the POST to the right store_domain. A product id without a variant_id is not buyable, and add_to_cart raises Unavailable rather than guess.
Per-store cart sessions. The agent's session.session_id is passed straight through as Tote's session_id, so a multi-item basket accumulates at one store across turns and one checkout link covers it. The first add_to_cart records that store in _session_store; get_cart and checkout_handoff read it back.
Two price units, normalized. search_products prices arrive as integer cents, so the code divides by 100 with _cents_to_dollars. get_product_details variant prices arrive as decimal strings, so the code parses them with _price. These are two different Tote endpoints returning two different units; the backend normalizes both to dollars for the Product and ProductDetails types.
Payment never flows through the agent. checkout_handoff returns the store's own checkout_url. There is no order history, so get_orders and get_order return empty. There is no shipping engine, so get_fulfillment_options returns empty. Tote's v1 cart is add-only, so update_cart_item re-adds to reach a quantity and remove_from_cart returns the cart unchanged; line edits happen on the store's own cart page.
Verified on Python 3.12 (3.10+ expected). Needs network access to usetote.dev. No GPU. No ANTHROPIC_API_KEY for the proof.
git clone https://github.com/anthropics/commerce-agents.git
cd commerce-agents
mkdir -p examples/tote/api
# copy this repo's two files into place
cp /path/to/tote_backend.py examples/tote/api/tote_backend.py
cp /path/to/prove.py examples/tote/prove.py
pip install httpx pydantic
python examples/tote/prove.py "coffee"prove.py adds the interface and example paths to sys.path itself, so it runs from the commerce-agents root with no PYTHONPATH set. Pass any query as an argument ("cotton crew socks", "matcha whisk"). It exits 0 when the search, cart, and checkout handoff all succeed against live stores.
Point the backend at a different Tote host with one variable:
export TOTE_BASE="https://your-tote-host" # default: https://usetote.devTo run it inside the full shopping agent rather than the standalone proof, follow EXAMPLE.md: drop ToteBackend in where the retail example constructs MockRetail, then run the demo as usual. That path needs the commerce-agents runtime and an ANTHROPIC_API_KEY (the agent's key, not Tote's), and is not exercised by prove.py.
.
├── tote_backend.py # the backend: StorefrontBackend implemented against Tote's REST API
├── prove.py # live end-to-end proof: search -> details -> add -> checkout, no keys
├── EXAMPLE.md # method-by-method mapping and full-agent swap instructions
├── README.md # this file
└── LICENSE # Apache-2.0
This is not a fork or a reimplementation of the shopping agent. It is one file that implements an interface Anthropic already defined; the prompt, skills, gates, and executor are unchanged and live in anthropics/commerce-agents.
It does not take payment and never sees a card. Because it hands off before payment, it has no order history and no shipping quotes, and those methods return empty rather than invent data.
A PASS from prove.py means the interface swap works against real stores today. It does not mean every store or every query resolves: a search can return nothing, and a store can be temporarily unbuyable, in which case the backend returns empty or raises Unavailable. Coverage is open-platform stores (Shopify, WooCommerce) and adapter-served stores; walled gardens are out of scope for the live-cart path.
Built by the Tote team, usetote.dev/team. Copyright 2026 Ishaan Samantray. Licensed under Apache-2.0.