Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tote backend for Claude Commerce Agents

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.

Proof it runs (exit 0 against live Tote, no keys)

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.

The numbers

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.

Architecture

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
Loading

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.

How the request flow works

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"]
Loading

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.

How it works

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.

Quickstart

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.dev

To 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.

Repository layout

.
├── 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

What this is not

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.

Author

Built by the Tote team, usetote.dev/team. Copyright 2026 Ishaan Samantray. Licensed under Apache-2.0.

About

Drop-in StorefrontBackend that swaps Anthropic's ACME reference store for Tote's live index of ~2,600 verified stores

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages