Selling to AI agents
Every store on Tuppence is open to shopping agents from the moment it sells online. An agent finds the store, searches it, builds a basket and gets it paid for, through whichever protocol it speaks. Every door runs the same catalogue, basket and checkout as the store’s website: the same prices, tax, delivery dates and policies. No language model runs on any of them; answers are data.
1. Find the store’s doors
Section titled “1. Find the store’s doors”A store lives on its own domain, or under its slug on the shared address
(https://shop.tuppence.ai/<slug>). The test store is the same path under /test/<slug>: it
answers anyone, says livemode: false, and is never indexed or listed.
| Door | Where (on the store’s address) | For |
|---|---|---|
| Guide | /agent.json, /llms.txt | Any agent: the doors, headers, tiers, limits and the catalogue in outline |
| UCP profile | /.well-known/ucp | UCP platforms: services, capabilities, payment handler, signing keys |
| UCP over REST | /ucp/v1/… (catalog/search, carts, checkout-sessions, orders/{id}) | UCP platforms |
| UCP over MCP | POST /ucp/mcp (search_catalog, create_cart, create_checkout, …) | UCP platforms that prefer MCP. meta["ucp-agent"].profile on every call |
| Store MCP | POST /mcp (search_products, create_cart, checkout_link, complete_checkout, …) | Any MCP client, such as a person’s own assistant |
| ACP | /acp/v1/checkout_sessions | Partner agents with a key from Tuppence |
On the shared address the UCP profile is https://shop.tuppence.ai/<slug>/.well-known/ucp, not
the root: the store’s agent.json links it.
Start with the guide. It names every door with its full address, the headers and the limits:
curl -sS https://shop.tuppence.ai/harbour-gifts/agent.jsonThe UCP profile lists the services, the capabilities, the payment handler (when the store takes native checkout) and the key the store signs its order updates with:
curl -sS https://shop.tuppence.ai/harbour-gifts/.well-known/ucp2. Say who you are
Section titled “2. Say who you are”The strongest valid identity a request carries wins. One that is presented and does not verify is
refused (401), never treated as anonymous.
| Header | Tier it can give |
|---|---|
Signature-Input, Signature, Signature-Agent (Web Bot Auth, RFC 9421, Ed25519 or ECDSA) | verified when the key comes from a network directory or a listed assistant’s own directory, declared otherwise |
UCP-Agent: profile="https://…" with an RFC 9421 signature by a key in that profile | verified for a listed platform, declared otherwise |
Tuppence-Agent with Tuppence-Agent-Proof (a Tuppence agent) | verified |
Tuppence-Agent: Name/1.0 (+https://…) alone, or a known assistant’s user agent | declared |
| Nothing | anonymous |
A signed write (a basket, a checkout, a completion) needs a fresh nonce; a reused one is
401 agent_signature_replayed. @authority is the store’s address as the shopper sees it.
| Tier | May | Budget |
|---|---|---|
anonymous | Browse every door, build baskets, start checkouts. Ends in a checkout link | 10 reads and 1 write a second per address |
declared | The same, on its own budget | 0.5 a second per name and address |
verified | All of that, and pay by itself within the business’s limits | 5 a second per identity |
Every store also has a budget of 20 agent requests a second. Over a budget is 429 with
Retry-After.
3. Search and build a basket
Section titled “3. Search and build a basket”Search, products and baskets are priced by the store, VAT in outside the US and before tax in the
US (tax_included). Never send a price or total: each step prices again.
Over the store MCP, any client can search. This one declares its name, so it gets its own budget:
curl -sS https://shop.tuppence.ai/harbour-gifts/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "Tuppence-Agent: GiftFinder/1.0 (+https://giftfinder.example/agent)" \ -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "search_products", "arguments": {"query": "candle"}}}'Over UCP the same search is POST /ucp/v1/catalog/search with {"query": "candle"} and your
platform’s UCP-Agent: profile="https://…" header. The store fetches that profile to agree the
capabilities, so it must be on a public https address.
4. How an agent pays
Section titled “4. How an agent pays”The checkout link (every tier). Every basket and checkout answers with continue_url: the
store’s checkout with the basket, address and delivery filled in. Hand it to the person. They see
any change in price before Pay, and pay with their own wallet.
Here an agent finds a candle, puts two in a basket for the person’s address and asks the store MCP for the link:
const STORE = "https://shop.tuppence.ai/harbour-gifts";const call = async (name, args) => { const res = await fetch(`${STORE}/mcp`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream", "Tuppence-Agent": "GiftFinder/1.0 (+https://giftfinder.example/agent)", }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name, arguments: args }, }), }); const { result } = await res.json(); const answer = JSON.parse(result.content[0].text); if (result.isError) throw new Error(JSON.stringify(answer)); return answer;};
const { products } = await call("search_products", { query: "candle" });const address = { country: "GB", postcode: "TR11 3HH" };const cart = await call("create_cart", { lines: [{ variant: products[0].variants[0].id, quantity: 2 }], address,});const linked = await call("checkout_link", { cart_token: cart.cart_token, buyer: { email: "sam.shopper@example.test", name: "Sam Shopper" }, address: { ...address, name: "Sam Shopper", line1: "1 Harbour Row", city: "Falmouth" }, delivery: cart.delivery_options[0].key,});console.log(`2 × ${products[0].title}. Pay here: ${linked.checkout_url}`);Native checkout (verified agents). Where the business’s store has a network ID (the UCP
profile’s payment handler and the store MCP’s complete_checkout name it), a verified
agent completes a ready checkout with a Shared Payment Token that the person’s agent platform
granted to that network ID, as the credential of the instrument it selects (the type the
profile’s payment handler names). The
token must cover the total in the checkout’s currency. A declined card leaves the checkout ready
for another token.
These baskets always go to the person, whatever the tier: age-restricted products, digital products, subscriptions, bookings, gift cards and products in a drop. So does an order above the business’s limit for that assistant (£250 or $250 unless the business changes it), and every order while the business is new to Tuppence.
5. Read the orders
Section titled “5. Read the orders”Agent orders are store orders with source: agent and agent_details (the assistant, tier,
protocol, and whether the person paid through the link).
curl -sS "https://api.tuppence.ai/v1/orders?source=agent&limit=10" \ -H "Authorization: Bearer $TUPPENCE_SECRET_KEY"A UCP platform reads the orders it placed at /ucp/v1/orders/{id} (or get_order over MCP) and
gets the whole order at its profile’s webhook_url on every change, signed.
Errors
Section titled “Errors”| Code | When |
|---|---|
invalid_agent_signature (401) | A signature that does not verify, a key not found, or created outside 30 seconds |
agent_signature_replayed (401) | A write’s nonce was used before |
agent_not_allowed (403) | The business switched this assistant off, or hid the product from it |
agent_blocked (403) | This identity is paused here (three declined payments in an hour) or limited to reads everywhere. Retry-After says when it ends |
requires_escalation | The person must pay: the reason and continue_url |
native_checkout_unavailable, store_not_eligible | Pay through the checkout link |
buyer_email_required | A native completion without the person’s email: the confirmation goes to them |
shared_token_expired, shared_token_over_limit, shared_token_currency_mismatch, shared_token_deactivated, shared_token_wrong_seller (402) | The token can’t pay this checkout. Each names the limit and the total |
acp_partner_required (401) | An ACP call without a partner key |
- Agent identity: prove a Tuppence agent with
Tuppence-Agent-Proof - Store checkout: what the person sees when they open the link
- Store orders