Let an AI assistant check out at Rad Home
Rad Home supports the Universal Commerce Protocol (UCP), an open standard that lets AI shopping assistants put an order together for you. The assistant chooses the products, options and quantities, adds your shipping address and method, and gets the exact total with shipping and tax. You then review the order and pay on this website: the assistant never sees or handles your card.
How it works
- Ask an AI assistant that supports UCP to buy something from Rad Home.
- The assistant prepares the checkout: the products, their options and quantities, your shipping address and a shipping method. It can tell you the total, including shipping and tax.
- It gives you a link to finish on this website. Opening the link puts the items in your cart and fills in the checkout form with the details the assistant has.
- If the email the assistant gave belongs to an account here, you're asked to sign in with its password first, so the order goes to your account. You can check out as a guest with a different email instead. If the email has no account yet, placing the order creates one and emails you a temporary password.
- Check everything, enter your payment details on the checkout page and place the order. You get the usual order confirmation by email.
Good to know
- Opening the link replaces what is in your cart with the assistant's items. If you're signed in, that's the cart saved on your account.
- The link works for 6 hours after the assistant last changed the order. If it has expired, ask the assistant for a new one.
- Prices, stock and shipping are checked again when you open the link. If an item can no longer be bought, the cart page tells you which one.
- Treat the link like a key: until the order is placed, the checkout is canceled or the link expires, anyone who has it can open the order, with its items and the name, email, phone number and address the assistant entered. Don't share it.
- While the assistant's items are in your cart, we record the order as placed through an AI assistant, and from which assistant platform. If you remove them and shop on your own, it's an ordinary order.
- Want an assistant that only browses the store, with no checkout? See AI connector (MCP).
For AI agents and developers
The store implements UCP version 2026-08-25 over REST: the checkout capability with the fulfillment
extension (shipping). It lists no payment handlers. Buyers pay on this website through the checkout's
continue_url, so the API never takes payment details. The API never says whether the buyer's email
has an account here: when it does, the buyer signs in after opening continue_url.
Business profile
- Service:
dev.ucp.shopping, REST, endpointhttps://www.radho.me/api/ucp. - Capabilities:
dev.ucp.shopping.checkoutanddev.ucp.shopping.fulfillment, both version2026-08-25. - Payment handlers: none. Payment happens on this website.
- Currency: US dollars. Every amount is an integer number of cents.
Headers
-
UCP-Agent: profile="https://your-platform.example/.well-known/ucp"is required on every call. The store reads that profile and caches it for 5 minutes. It must be served over HTTPS from a public address, answer 200 without a redirect within 10 seconds, be at most 256 KiB, use version2026-08-25and listdev.ucp.shopping.checkout. Listdev.ucp.shopping.fulfillmenttoo to send shipping addresses through the API. Without it, the buyer enters the address on this website. -
Idempotency-Keyis required on every POST and PUT. Use a new UUID for each new request. After a successful call, sending its key again with exactly the same request (the same method, path and body, byte for byte, from the same profile) returns the stored answer, kept for 24 hours; sending that key with any other request returns 409. To retry, resend the body unchanged. Answers with an HTTP error status are not stored, so retrying such a call with its key runs it again. Content-Type: application/jsonfor request bodies. ARequest-Idheader is echoed back.
Operations
Paths are relative to the endpoint, https://www.radho.me/api/ucp.
POST /checkout-sessions- Creates a checkout from
line_items, plus optionalbuyer,contextandfulfillment. Answers 201. GET /checkout-sessions/{id}- The checkout, priced again with current prices, stock and shipping.
PUT /checkout-sessions/{id}- Replaces the whole checkout: send every line, the buyer, the context and the fulfillment again. Send back each line's
idto keep it. POST /checkout-sessions/{id}/complete- Places no order, because payment happens on the website. Returns the checkout as it stands.
POST /checkout-sessions/{id}/cancel- Cancels an open checkout. A completed one can't be canceled here.
A checkout belongs to the platform profile that created it. Calls that name another profile get not_found.
Item IDs
-
An item's
idis the product's slug (the last part of its page address, such asblue-ceramic-vase) or its whole page address on this website. The store's MCP server returns both for every product (slugandurl), and so does the sitemap. -
For a product with options, add
?options=followed by one option ID from each option group, separated by commas, for exampleproduct-slug?options=12,15. Without it, the line gets the choice the product's page starts with, and aninfomessage lists the product's options with their IDs. - In answers,
item.idis in that form (the slug, plus the options chosen) once the store has matched the line to a product and its options; a line it could not match keeps theidyou sent.item.priceis the unit price in cents on a line that can be bought, and 0 on a line with anerror.
The flow
- Create the checkout with the items. Its status is
incompletewhile something is missing, and eacherrormessage'spathpoints at what to fix. - Update it with the buyer's shipping address. The answer lists the shipping options for that address with their prices, under
fulfillment.methods[0].groups[0].options. - Update it again with
selected_option_id. When nothing is left to fix, the status isrequires_escalation, with apayment_requiredmessage (severityrequires_buyer_input) and the final totals. - Give the buyer the
continue_url(it starts withhttps://www.radho.me/ucp/checkout/). They review and pay on this website. - Once they have placed the order, GET returns status
completedwith anorderobject (id,labelandpermalink_url) and the order's actual lines and totals.
Create a checkout:
curl -X POST https://www.radho.me/api/ucp/checkout-sessions \
-H "Content-Type: application/json" \
-H 'UCP-Agent: profile="https://your-platform.example/.well-known/ucp"' \
-H "Idempotency-Key: 6f1e2d3c-4b5a-4968-8776-5a4b3c2d1e0f" \
-d '{"line_items": [{"item": {"id": "product-slug"}, "quantity": 1}],
"buyer": {"first_name": "Ada", "last_name": "Lovelace", "email": "ada@example.com"}}'
Then add the shipping address and choose a shipping option (PUT /checkout-sessions/{id} with a new
Idempotency-Key). selected_option_id must be one of the IDs the store listed under
fulfillment.methods[0].groups[0].options for that address, so send the address first if you don't have
them yet. ship_1 below is only an example: the number is the store's ID for the shipping method, not
its position.
{
"line_items": [{"id": "li_1", "item": {"id": "product-slug"}, "quantity": 1}],
"buyer": {"first_name": "Ada", "last_name": "Lovelace", "email": "ada@example.com"},
"fulfillment": {"methods": [{
"type": "shipping",
"destinations": [{"id": "home", "street_address": "1 Main St", "address_locality": "Albany",
"address_region": "NY", "postal_code": "12207", "address_country": "US"}],
"selected_destination_id": "home",
"groups": [{"id": "group_1", "selected_option_id": "ship_1"}]
}]}
}
The answer, shortened:
{
"id": "chk_…",
"status": "requires_escalation",
"currency": "USD",
"line_items": [{"id": "li_1", "item": {"id": "product-slug", "title": "…", "price": 2999}, "quantity": 1, "totals": […]}],
"totals": [{"type": "subtotal", "amount": 2999}, {"type": "fulfillment", "amount": 1995},
{"type": "tax", "amount": 300}, {"type": "total", "amount": 5294}],
"messages": [{"type": "error", "code": "payment_required", "severity": "requires_buyer_input", "content": "…"}],
"continue_url": "https://www.radho.me/ucp/checkout/chk_…",
"expires_at": "…",
"fulfillment": {"methods": [{"id": "shipping_1", "type": "shipping", "line_item_ids": ["li_1"],
"selected_destination_id": "home", "destinations": […],
"groups": [{"id": "group_1", "selected_option_id": "ship_1",
"options": [{"id": "ship_1", "title": "…", "totals": [{"type": "total", "amount": 1995}]}]}]}]}
}
Addresses, shipping and tax
- A shipping address needs
street_address,address_locality(the city),postal_codeandaddress_country: an ISO 3166-1 code such asUS(three-letter codes and English country names work too). In the United States and Canada it also needsaddress_region: the state or province, as a two-letter code such asNYorON, or its name. - Shipping options and tax are the ones the checkout page uses for the same address. The address must be in a country the store sells to.
- For an order with nothing to ship, send
context.address_country(andaddress_region) to include tax in the totals. - Until the store knows a country it sells to (from the shipping address, or from
context.address_countryfor an order with nothing to ship), the totals leave out shipping and tax, and awarningwith codeestimatesays so. While the address has shipping options and none is chosen (or the chosen one isn't offered), the totals leave out shipping, and the error onselected_option_idsays why. A US or Canadian address without a usableaddress_regiongets only the country's tax, with an error onaddress_region.
Statuses
incomplete: the agent can fix something through the API. See theerrormessages.requires_escalation: the buyer finishes on the website throughcontinue_url.completed: the buyer placed the order. The checkout includesorder.canceled: canceled through the API.
Messages
Problems with the order come back as messages on a successful answer: HTTP 201 for a create, 200 for every other call. Each path is a JSONPath to the field concerned.
missing- A required address field, the shipping address or the shipping option is missing.
not_found- The item ID can't be read, or names no active product of this store. A slug with a malformed
?options=list getsnot_foundtoo: anything but distinct option ID numbers, more than 20 IDs, oroptions=given twice. On a call for a checkout, it means there is no such checkout for your platform profile (or it has expired): the answer is HTTP 200 with no checkout in it,ucp.statuserrorand this message. Create a new checkout. invalid- Well-formed option IDs don't name exactly one option from each of the product's groups, an address field is too long, the country or the state or province isn't one the store recognizes,
selected_destination_idnames no destination, or the shipping option isn't offered for the address. item_unavailable- The product can't be bought through the API: it is priced on request, a subscription, needs details entered on its page, sold on another website, has no price, or isn't available (the store doesn't stock it, or that combination of its options). The message says what the buyer can do instead: buy a subscription or a product that needs details on the product's page, ask the store about one priced on request or without a price, or buy one sold elsewhere on that website.
out_of_stock- Not enough stock for the quantity. The message says how many are left.
invalid_quantity- The quantity must be from 1 to 100,000.
duplicate_item- The same product with the same options is in two lines. Change the first line's quantity instead.
address_undeliverable- The store doesn't sell to that country.
payment_required- Nothing is left to fix through the API: send the buyer to
continue_url. checkout_completed,checkout_canceled- The checkout is closed and can't change.
backorder(warning)- The item is on pre-order and ships when it is back in stock.
estimate(warning)- The totals leave out shipping and tax until the store knows a country it sells to (see Addresses, shipping and tax).
default_options(info)- A product with options got its page's starting choice. Lists the options and their IDs.
Errors
Problems with the call itself come back with an HTTP 4xx or 5xx status and a body of
{"code": ..., "content": ...}, except 429 (below). Two come back instead as an HTTP 200 error response,
with ucp.status error, the problem in messages and no checkout:
capabilities_incompatible, at the end of this list, and not_found for a checkout that
doesn't exist (see Messages).
- 400
invalid_profile_url - No
UCP-Agentprofile, or it isn't an absolute https URL of at most 500 characters. - 400
idempotency_key_required,invalid_idempotency_key - A POST or PUT without an
Idempotency-Key, or with one that isn't 16 to 255 printable ASCII characters. - 400
invalid_request - The body isn't valid JSON for a checkout, has no
line_itemsor more than 50, has a line without anitem.id, with anitem.idover 700 characters or with a quantity below 1, or has a fulfillment ID over 255 characters. - 409
idempotency_conflict,request_in_progress - The key was used for a different request, or a request with the same key is still running.
- 413
request_too_large - The body is over 64 KiB.
- 422
profile_malformed,version_unsupported - The platform profile isn't a UCP profile, or isn't version
2026-08-25. - 424
profile_unreachable - The store couldn't read the platform profile.
- 429
- Too many requests. Wait for the number of seconds in
Retry-After, which is always sent. This answer has nocode: its body is{"status": 429, "title": "Too many requests", "retryAfter": seconds}(application/problem+json). - 500
internal_error - The store couldn't process the request. Retry later with the same key.
- 503
idempotency_unavailable - The store can't check the key right now. Retry later with the same key.
- 200
capabilities_incompatible - The platform profile doesn't list
dev.ucp.shopping.checkoutversion2026-08-25. Sent as an error response withucp.statuserror.
Limits
- Up to 50 lines per checkout, quantities from 1 to 100,000, and request bodies up to 64 KiB.
- A checkout expires 6 hours after it was last created or updated. A completed one can still be read for 30 days.
- Every request counts toward the per-client limit this whole website has: bursts of up to 150 requests, then 5 a second.
- Creates, updates, completes and cancels are limited per client to 40 at once, then 20 a minute.
- Reading a platform profile the store hasn't cached is limited per client to 10 at once, then 10 every 10 minutes. Keep using the same profile URL.
Not supported
- Payment through the API: payment handlers, payment tokens and AP2 mandates. The buyer pays on this website.
- Coupon codes and discounts through the API. The buyer can still use them on this website.
- Order tracking (
GET /ordersand order webhooks), and the cart, catalog and identity-linking capabilities. - Checkout over MCP, A2A or the embedded protocol, and request signatures. Checkout calls are not authenticated, so keep each checkout's
idandcontinue_urlprivate.