{"version":"1.2","spec":"Trusteed Agent Checkout Guide","canonical_url":"https://trusteed.xyz/.well-known/agent-checkout-guide.json","agent_instruction":"Before a checkout write, read this guide, select testing or buying, and use that store's bucket endpoints. Branch on JSON-RPC error.data.code and follow its remediation.","mandate_schema_url":"https://trusteed.xyz/.well-known/payment-mandate.schema.json","human_readable_url":"https://trusteed.xyz/en/developers/agent-checkout","content_updated":"2026-10-05","generated_at":"2026-10-05T19:34:50.870Z","canonical_scope":"Ordered, end-to-end path for an agent: which endpoint to call at each step, which credential it needs, and what to do with each error. For WHAT exists see mcp.json (tools) and trusteed-capabilities.json (rail states, signed). This document does not override either.","authentication_matrix":{"discovery":{"endpoint":"POST /{storeSlug}/mcp/discovery","auth_mode":"anonymous","credential":"none"},"customer":{"endpoint":"POST /{storeSlug}/mcp/customer","auth_mode":"oauth_customer","credential":"OAuth 2.1 Bearer token bound to the customer"},"merchant_checkout":{"endpoint":"POST /{storeSlug}/mcp/checkout","environment":"real merchant","auth_mode":"agentic_trust","credential":"OAuth 2.1 Bearer token with agent client identity","api_key_accepted":false,"note":"Payment mandate and idempotency requirements depend on the tool; inspect tools/list."},"demo_checkout":{"endpoint":"POST /demo-store/mcp/checkout","environment":"synthetic demo","auth_mode":"sandbox_key","credential":"agnt_sandbox_ key in Authorization: Bearer or X-Agent-Api-Key","note":"The sandbox key is not an OAuth token and works only on demo-store."},"agent_rest":{"endpoint":"/api/v1/agent/*","auth_mode":"api_key","credential":"X-Agent-Api-Key","note":"This REST API is separate from merchant MCP. Its OAuth replacement is not registered as a working route; do not assume the MCP token works here."},"legacy_mcp":{"endpoint":"POST /{storeSlug}/mcp","auth_mode":"per_tool_deprecated","credential":"Inspect the selected tool and the response challenge; prefer the dedicated bucket."},"advertised_api_key_sunset":"2026-10-01","advertised_api_key_sunset_enforced":false,"advertised_api_key_sunset_note":"The policy advertises this date for its legacy fallback. It is not proof that every API-key route has an OAuth replacement or that keys cease working on that date."},"decision_table":[{"goal":"test_protocol","store":"demo-store only","credential":"create_sandbox_key on the public discovery bucket","mandate_required_for":["preview_checkout","complete_checkout","process_agent_payment","complete_visa_payment","ucp_complete_checkout"],"confirmation":"Required for a non-zero demo cart before complete_checkout; the endpoint does not verify human presence.","real_settlement":false,"path":"paths.testing"},{"goal":"buy_for_user","store":"merchant selected from merchant-index.json","credential":"OAuth 2.1 client-credentials token for the merchant","mandate_required_for":["preview_checkout","complete_checkout","process_agent_payment","complete_visa_payment","ucp_complete_checkout"],"confirmation":"Depends on merchant and payment policy; do not infer approval from the mandate.","real_settlement":false,"path":"paths.buying","reason":"Published settlement rails are sandbox today; check the signed capabilities registry before each attempt."}],"minimal_demo_sequence":[{"step":1,"action":"create_sandbox_key","method":"tools/call","url":"https://api.trusteed.xyz/demo-store/mcp/discovery","arguments":{},"save":["api_key","mandate_subject"],"on_error":"UPSTREAM_UNAVAILABLE: respect the 25 keys per IP per 24h quota; retain and reuse a working key."},{"step":2,"action":"search_products","method":"tools/call","url":"https://api.trusteed.xyz/demo-store/mcp/discovery","save":["product.id","product.currency"],"on_error":"Use products returned by the live store; never substitute a SKU for product_id."},{"step":3,"action":"create_cart","method":"tools/call","url":"https://api.trusteed.xyz/demo-store/mcp/checkout","headers":{"Authorization":"Bearer <api_key>"},"arguments":{"items":[{"product_id":"<product.id>","quantity":1}],"idempotency_key":"<fresh token>"},"when_headers_unavailable":"Send agent_key as a tool argument.","save":["cart_id"],"on_error":"no_credentials: send the sandbox key. No payment mandate is required for create_cart."},{"step":4,"action":"get_shipping_rates","method":"tools/call","url":"https://api.trusteed.xyz/demo-store/mcp/checkout","headers":{"Authorization":"Bearer <api_key>"},"arguments":{"cart_id":"<cart_id>","shipping_address":{"address1":"<buyer street>","city":"<buyer city>","country_code":"<buyer country>","zip":"<buyer postal code>"}},"save":["rate.handle","cheapest_shipping_handle"],"on_error":"SHIPPING_COUNTRY_UNSUPPORTED: read allowed_countries from the error and ask the buyer for a valid destination."},{"step":5,"action":"select_shipping_option","method":"tools/call","url":"https://api.trusteed.xyz/demo-store/mcp/checkout","headers":{"Authorization":"Bearer <api_key>"},"arguments":{"cart_id":"<cart_id>","shipping_handle":"<chosen rate.handle>","idempotency_key":"<fresh token for this selection>"},"on_error":"Reuse this idempotency_key only for an identical shipping selection retry."},{"step":6,"action":"preview_checkout","method":"tools/call","url":"https://api.trusteed.xyz/demo-store/mcp/checkout","headers":{"Authorization":"Bearer <api_key>","X-Payment-Mandate":"<JSON claims from mandate_schema_url>"},"arguments":{"cart_id":"<cart_id>"},"when_headers_unavailable":"Send agent_key and payment_mandate as tool arguments.","on_error":"Read error_decision_table; set sub to mandate_subject, aud to demo-store, currency to the cart currency, and exp to a future Unix timestamp."},{"step":7,"action":"record_demo_confirmation","method":"POST","url":"https://api.trusteed.xyz/api/v1/demo/checkout-approval","headers":{"X-Agent-Api-Key":"<same api_key used to create the cart>"},"body":{"store_slug":"demo-store","checkout_session_id":"<cart_id>"},"when_payment_option":"Only when you chose a `payment_option` at `complete_checkout` (the response is a pending result, `order_placed: false`, with an `approval_url` of the form `…?token=<approval_token>`). The call above, with only the agent key, then answers 401 `approval_token_required`. Do instead: (1) POST `${API}/api/v1/demo/checkout-approval/summary` with `{ checkout_session_id, approval_token }` to read `quote_version`; (2) POST `${API}/api/v1/demo/checkout-approval` with `{ store_slug, checkout_session_id, approval_token, quote_version, buyer: { name, email } }`; (3) call `complete_checkout` again with the SAME `checkout_session_id` and `idempotency_key`. A MOCK cart has no token and uses the call above. The intended path is to hand `approval_url` to the person; approving it yourself with the token is for testing on demo-store only, and it does not prove the buyer consented. Never invent `buyer` data: send only what the person gave you for this purchase.","on_error":"The session must have been previewed. This API records confirmation but reports human_presence_verified: false."},{"step":8,"action":"complete_checkout","method":"tools/call","url":"https://api.trusteed.xyz/demo-store/mcp/checkout","headers":{"Authorization":"Bearer <api_key>","X-Payment-Mandate":"<same current mandate>"},"arguments":{"checkout_session_id":"<cart_id>","idempotency_key":"<fresh token for this completion>","payment_method":"MOCK"},"when_headers_unavailable":"Send agent_key and payment_mandate as tool arguments.","success":"status COMPLETED and order_id; simulated settlement only","on_error":"Read error.data.code; retry an identical completion with the same idempotency_key."}],"error_decision_table":[{"code":"no_credentials","action":"Get a demo sandbox key or follow the real merchant WWW-Authenticate challenge; retry on the checkout bucket."},{"code":"demo_sandbox_mandate_required","action":"Add X-Payment-Mandate or payment_mandate with all six claims from mandate_schema_url."},{"code":"payment_mandate_required","action":"Add X-Payment-Mandate or payment_mandate with all six claims from mandate_schema_url."},{"code":"demo_sandbox_mandate_malformed","action":"Read remediation.mandate_issues, fix the named claims and retry."},{"code":"payment_mandate_malformed","action":"Read remediation.mandate_issues, fix the named claims and retry."},{"code":"payment_mandate_not_platform_issued","action":"This store wants a mandate issued by the platform, not one you write yourself. Call preview_checkout for this cart and give the person the approval_url it returns. Once the person has approved, GET approval_status_url with your agent credential and send the mandate it returns in X-Payment-Mandate on the same tool call (same checkout_session_id and same idempotency key). Do not open another approval while one is pending: wait for the person."},{"code":"MANDATE_LIMIT_EXCEEDED","action":"Reduce cart total to the authorized cap or obtain a new user-approved spending cap."},{"code":"MANDATE_EXPIRED","action":"Renew the mandate with a future Unix-seconds exp."},{"code":"MANDATE_AUDIENCE_MISMATCH","action":"Set aud to the exact store slug in the MCP URL."},{"code":"MANDATE_CURRENCY_MISMATCH","action":"Set currency to the cart currency."},{"code":"MANDATE_SUBJECT_MISMATCH","action":"Use the authenticated identity; demo create_sandbox_key returns mandate_subject."},{"code":"CHECKOUT_BLOCKED_BY_POLICY","action":"Read get_merchant_profile.agentPolicySummary and adjust the cart or destination."}],"which_path_are_you_on":{"question":"Are you exercising the protocol, or buying something for a user with real money?","testing":{"goal":"Exercise the full protocol end to end, with fabricated goods and simulated payment.","store":"demo-store, and only demo-store","credential":"self-service sandbox key (see `paths.testing`)","money":"NONE. Payments are simulated; `payment_method: \"MOCK\"` confirms an order in our database and charges nobody.","warning":"Do NOT present this path to a user as a purchase, and do not carry its credential to a real merchant: the sandbox key is rejected everywhere except demo-store, by a constant in the code rather than by configuration."},"buying":{"goal":"Complete a real purchase on a real merchant on behalf of a user.","store":"any merchant in the merchant index","credential":"OAuth 2.1 client-credentials token issued by agreement — there is no dynamic client registration (`POST /register` is 404) and no self-service path","money":"None today, and this is the honest answer: every payment rail is SANDBOX in the signed capability registry (x402, ACP, AP2). No rail settles funds with real value in production (x402 settles test transfers on Base Sepolia, in the demonstration store only). Read `paths.buying.today` before promising a user anything."}},"read_this_first":["Use the bucket endpoints, not the legacy alias. `/{store}/mcp` still answers, but it routes some tools through a different dispatcher than others; a flow that works there can fail halfway with no useful error.","`api.trusteed.xyz` and `trusteed.xyz` are the only hosts that serve this platform. Any `*.up.railway.app` endpoint is residual infrastructure and serves no store: it answers `308` towards the canonical host, preserving method and body, so a client that follows redirects survives one more hop. Do not rely on it — the redirect is a courtesy for connectors whose URL you cannot edit, not a supported endpoint. Point your configuration at `api.trusteed.xyz`.","No payment rail settles funds in production today. Checkout here builds a cart and evaluates merchant policy and trust; it does not capture money. The authoritative per-rail state is the signed registry at /.well-known/trusteed-capabilities.json.","`demo-store` is a synthetic catalogue for exercising the protocol. Products, prices, stock and payments are fabricated.","You CAN complete a purchase on demo-store today, with no merchant credentials — see `paths.testing`. It was measured end to end at the 2026-09-09 probe, to `status: \"COMPLETED\"`: that is evidence the flow works, NOT a claim about this minute. Reproduce it yourself — POST /api/v1/sandbox/key and follow the steps in `paths.testing`. What you cannot do is settle anonymously: `preview_checkout` and `complete_checkout` need a sandbox key plus a mandate, because they were deliberately removed from the unauthenticated path (it routed through a dispatcher that issues no Trust Receipt and does not enforce the agent mandate). That is an authentication wall, not an identity one: a valid HTTP signature does not lift it, and no signature is required to pass it."],"tools_listed_here":{"meaning":"A CEILING, not an inventory. These are the platform's tools for each bucket, excluding any that depend on a per-store or platform feature flag. They are the most a merchant could expose, never a promise of what one does.","per_store_truth":"Each merchant enables a subset in its own configuration, and `demo-store` is no exception: it does not enable every tool listed here.","how_to_find_out":"Call `tools/list` on the bucket endpoint of the store you care about. That is the only authoritative answer, and it is one round trip."},"endpoints":{"discovery":{"url_template":"https://api.trusteed.xyz/{store_slug}/mcp/discovery","transport":"MCP Streamable HTTP (JSON-RPC 2.0)","auth":"none","tools":["browse_catalog_feed","browse_categories","compare_products","explore_store","get_merchant_profile","get_page_content","get_payment_methods","get_product_details","get_site_map","get_tool_details","onx_get_inventory","run_scenario","search_docs","search_products","search_products_enriched","search_tools"],"notes":"Public and unauthenticated. Agent identity enforcement never blocks this bucket. `tools` is a ceiling — see `tools_listed_here`."},"checkout":{"url_template":"https://api.trusteed.xyz/{store_slug}/mcp/checkout","transport":"MCP Streamable HTTP (JSON-RPC 2.0)","auth":"OAuth 2.1 Bearer token (Authorization header) for real merchants; on `demo-store`, a sandbox key (see `paths.testing`, also accepted as `X-Agent-Api-Key`). Both also require an `X-Payment-Mandate` header, but only on the tools that consume it — the ones that evaluate the cap or settle: `preview_checkout`, `complete_checkout`, `process_agent_payment`, `complete_visa_payment`, `ucp_complete_checkout`. The payment mandate is distinct from the idempotency key: `select_shipping_option` requires `idempotency_key` even though it does not require a payment mandate.","tools":["apply_discount","attach_agent_identity","cancel_payment_mandate","complete_checkout","complete_visa_payment","create_cart","create_payment_mandate","get_shipping_rates","preview_checkout","process_agent_payment","select_shipping_option","ucp_add_items_to_checkout","ucp_cancel_checkout","ucp_complete_checkout","ucp_create_checkout","ucp_get_checkout","ucp_update_checkout","update_payment_mandate","verify_age_over_n","verify_legal_person_authorization","verify_residence_country"],"notes":"Idempotency-Key is required for the idempotent tools. A request without credentials is denied with `error.data.code: \"no_credentials\"` — an auth failure, not a signature failure. How it travels depends on the store: on a real merchant it is HTTP 401 with a `WWW-Authenticate` challenge; on `demo-store` it is HTTP 200 carrying the JSON-RPC error (`error.code -32000`, `error.data.code`, remediation) and NO challenge, because the demo credential is self-served and there is no login to go to. Branch on `error.data.code`, not on the HTTP status. `tools` is a ceiling — see `tools_listed_here`."},"customer":{"url_template":"https://api.trusteed.xyz/{store_slug}/mcp/customer","transport":"MCP Streamable HTTP (JSON-RPC 2.0)","auth":"OAuth 2.1 Bearer token bound to a customer","tools":["check_agent_consent","enroll_payment_method","get_acp_orders","onx_cancel_order","onx_create_return","onx_get_fulfillments","onx_get_orders","onx_get_returns"],"notes":"Order lookups, cancellations and returns for a known customer. `tools` is a ceiling — see `tools_listed_here`."},"legacy_alias":{"url_template":"https://api.trusteed.xyz/{store_slug}/mcp","status":"deprecated","notes":"Kept for existing integrations. Prefer the three buckets above: they have one dispatcher, one auth model and one error contract."},"rest_openapi":{"url":"https://api.trusteed.xyz/api/v1/openapi.json","auth":"X-Agent-Api-Key header","notes":"A DIFFERENT surface with a DIFFERENT credential model. On `demo-store`, the sandbox key is accepted on MCP as `X-Agent-Api-Key` or `Authorization: Bearer` — the same header this REST surface takes (see `paths.testing` step 2). On a real merchant, the MCP endpoints require OAuth 2.1 and do not accept an API key in either header: `authenticateAgenticTrust` requires `type: \"oauth2\"` with a `clientId`, which an API key never carries (see `paths.buying.what_you_would_need`). Nor does this REST surface accept the OAuth Bearer token issued for a real merchant on MCP."}},"paths":{"testing":{"applies_to":"demo-store ONLY. Simulated payment. Not a purchase.","merchant_policy_is_enforced_here":"This store now publishes a real merchant policy in `get_merchant_profile` → `agentPolicySummary`, derived from its configured rules, and the policy is enforced. Two things you cannot guess: (1) a cart above the published `maxCartValue` is refused at `complete_checkout` with `CHECKOUT_BLOCKED_BY_POLICY`, NOT at `preview_checkout` — merchant rules are not evaluated during preview, so a clean preview does not mean the cart can settle; (2) a shipping destination outside the published `allowedShippingCountries` is refused earlier, at `get_shipping_rates`. Read the published policy before building the cart: it is the cheapest way to avoid both.","merchant_cap_is_not_your_mandate_cap":"Do not confuse the two ceilings. `agentPolicySummary.maxCartValue` is the MERCHANT's, published by the store and the same for every agent. `max_amount_cents` in your `X-Payment-Mandate` is YOURS, and it is the one whose denial tells you the threshold back (`MANDATE_LIMIT_EXCEEDED`). Either can refuse the same cart, at different steps and with different codes, and the remedy differs: one means shop smaller here, the other means come back with a bigger mandate.","trust_band_is_not_a_contradiction":"This store scores in the RESTRICTED band, and that is expected: the score measures real operational history, and a synthetic catalogue has none. The trust guidance in /.well-known/agent-policy.json governs purchases that move a user's money; this lane settles nothing. Do NOT carry that exemption anywhere else — see `trustScoreGuidanceScope` there. For the live figure call get_merchant_profile; do not hardcode it.","summary":"Complete, working protocol run on demo-store without merchant credentials. Every step below was measured end to end at the 2026-09-09 probe, to `status: \"COMPLETED\"` with an idempotent replay returning the same `order_id`. That is evidence the flow works, not a claim about this minute: reproduce it yourself with POST /api/v1/sandbox/key and the steps below. Mind the quota before you start — 25 sandbox keys per IP per 24h, so keep the one you get.","steps":[{"step":1,"what":"Get a sandbox key","call":"POST https://api.trusteed.xyz/api/v1/sandbox/key with an empty JSON body","returns":"`agnt_sandbox_<32 hex>`, valid 24h","caveat":"Rate limited to 25 keys per IP per 24h. Keep the key — do not request a new one per attempt, or you will lock yourself out for a day."},{"step":2,"what":"Send it on every checkout call","call":"`Authorization: Bearer agnt_sandbox_…` (or `X-Agent-Api-Key`)","caveat":"This credential works ONLY against demo-store, and only on the checkout bucket. It is not an OAuth token and does not unlock any real merchant."},{"step":3,"what":"Declare your spending cap (the payment mandate)","call":"`X-Payment-Mandate` header carrying JSON (or a JWS whose payload is that JSON) with ALL of: `mandate_id`, `max_amount_cents` (integer, > 0), `currency` (exactly 3 characters), `exp` (unix seconds), `sub` (the identity you authenticate as), `aud` (the store slug). It is a spending cap you declare to limit yourself: its signature is not verified, it authorises nothing and it does not prove the buyer's consent.","caveat":"A missing or malformed mandate is an authorization denial, not an authentication one — re-authenticating will not help. A store that requires a platform-issued mandate answers `payment_mandate_not_platform_issued` instead of accepting one you wrote: see `error_decision_table`. On demo-store it arrives in-band (HTTP 200, JSON-RPC error whose `error.data.code` says whether the mandate was missing or malformed); read `error.data.code`, not the HTTP status. A WELL-FORMED mandate that does not fit is not a 403: it comes back as a tool error at `preview_checkout`, and the code tells you which boundary you crossed. This is the demo-store rail, which carries its own mandate boundary and denies here. Before assuming the same on a real merchant, read `the_cap_is_not_your_spending_control` in `paths.buying`.","enforced_boundaries":["`MANDATE_LIMIT_EXCEEDED` — cart total above `max_amount_cents`. Remediation `reduce_cart_total`. Spending exactly the cap is allowed.","`MANDATE_EXPIRED` — `exp` is in the past. Remediation `renew_mandate`. Reducing the cart will NOT help.","`MANDATE_AUDIENCE_MISMATCH` — `aud` names a different store. Remediation `present_mandate_for_this_store`.","`MANDATE_CURRENCY_MISMATCH` — `currency` differs from the cart's. Remediation `present_mandate_in_cart_currency`.","`MANDATE_SUBJECT_MISMATCH` — `sub` is not the identity this call authenticated as; `details.expected_sub` carries the value to use. Remediation `present_mandate_for_this_agent`. Today the comparison is measured and only denies where the server enables it (`PAYMENT_MANDATE_SUBJECT_CHECK_MODE=enforce`), so write the right `sub` anyway."],"a_denial_is_not_a_receipt":"A mandate denial produces NO Trust Receipt, in either pass. Receipts attest payment operations; nothing was paid, so there is nothing to attest. Do not poll the receipt endpoint after a denial — it will answer 404 with a reason, on purpose, not 202.","reading_the_denial":"Branch on `code`, or on `details.remediation` — never on the prose. All four also carry `details.mandate_error` with the same distinction. The numbers in `details` (`limit_cents`, `cart_total_cents`) are YOUR mandate's, never the merchant's policy: this axis never leaks a merchant threshold."},{"step":4,"what":"Record demo confirmation out of band","call":"POST https://api.trusteed.xyz/api/v1/demo/checkout-approval with `{ store_slug, checkout_session_id }` and your sandbox key in `X-Agent-Api-Key`","returns":"`approval_ref` + `channel: demo_playground_confirmation`","caveat":"Do this AFTER preview_checkout and BEFORE complete_checkout. It is required for a non-zero demo cart. If you pass a `payment_option`, the approval needs the single-use `approval_token` from `approval_url?token=…` plus `quote_version` (from `/api/v1/demo/checkout-approval/summary`) and `buyer`; the agent key alone gets 401 `approval_token_required` (see `minimal_demo_sequence` step 7). The endpoint records that a caller submitted confirmation; it reports human_presence_verified: false and identity_verified: false. The same sandbox key that created the cart is required by default, with an explicit deployment rollback flag. The record does not prove buyer consent. MCP elicitation cannot supply it (see known_limitations)."},{"step":5,"what":"Settle","call":"`complete_checkout` with `checkout_session_id`, `idempotency_key`, `buyer`, and `payment_method: \"MOCK\"` (enum: ACP | KYAPAY | PAYPAL | MOCK)","returns":"`status: \"COMPLETED\"` and an `order_id`","caveat":"Use a DIFFERENT `Idempotency-Key` HTTP header per distinct call, and the SAME one to retry. Reusing one key across different calls is 422 `idempotency_key_mismatch`. On a retry you get the FIRST execution's JSON-RPC payload and status code back unchanged — including its `idempotent: false`, which described that first execution. The FRAMING is not identical and is not meant to be: the first execution is streamed as `text/event-stream`, the replay is sent as `application/json`. Compare the parsed JSON, never the bytes. The signal that you are seeing a replay is the `X-Idempotency-Replay: true` response header, not the body.","if_not_mock":"If you pass `payment_method: \"PAYPAL\"` or `\"KYAPAY\"` instead of `\"MOCK\"`, `complete_checkout` can come back `status: \"PENDING_EXTERNAL_CONFIRMATION\"` with `order_placed: false`, an `approval_url` and a `next_action` — no order has completed yet. There are two equivalent ways to resolve it: a person opens `approval_url`, or the integrator calls the REST settlement endpoint. Neither is something the model does on the person's behalf. `MOCK` never takes this branch: it always resolves straight to `COMPLETED`."},{"step":6,"what":"Collect the signed receipt","call":"GET https://api.trusteed.xyz/api/v1/trust/receipts/by-session/{checkout_session_id} with the same credential","returns":"`receipt_id`, `call_id`, `jws` and where to verify it","outcomes":["200 — the receipt, signed (`signature_status: \"signed\"`) or explicitly `unsigned`. An unsigned receipt is published as such rather than passed off as verifiable.","202 `receipt_pending` / `checkout_in_progress` with `retry_after_seconds` — the purchase happened (or is still running) and the queue has not finished. Retry.","404 `receipt_not_issued_checkout_not_completed` / `..._canceled` / `..._expired` — the session never completed a purchase. There is no receipt and there never will be. Do NOT retry.","404 `receipt_not_found` — the session is not yours, or does not exist. The two are deliberately indistinguishable.","500 `receipt_issuance_overdue` — the purchase completed but issuance is past its budget. That is our failure, not yours; it is surfaced as an error so it counts as one."],"caveat":"202 means 'not yet', never 'never' — and it is now only ever sent for a checkout that actually completed or is in flight. A session denied at `preview_checkout` answers 404 with a reason. The receipt is released only to the agent whose credential matches the one recorded on the session; anything else is a 404, on purpose."}],"do_not_generalise":"None of this transfers to a real merchant. The sandbox credential, the MOCK payment method and the demo approval endpoint are all scoped to demo-store by code constants. If you are buying for a user, read `paths.buying` instead."},"buying":{"applies_to":"Any real merchant in the merchant index.","today":"You cannot complete a money-moving purchase on this platform today, and no amount of correct integration changes that. Every payment rail (x402, ACP, AP2) is SANDBOX in the signed capability registry. Treat checkout here as cart construction plus policy and trust evaluation, and tell your user exactly that.","what_you_would_need":["An OAuth 2.1 client-credentials token with a client id. `authenticateAgenticTrust` rejects anything that is not `oauth2` with a `clientId` — an API key never satisfies it.","An `X-Payment-Mandate` header with the same six claims listed in `paths.testing` step 3. Missing or malformed is 403 `payment_mandate_required` / `payment_mandate_malformed`.","An agent trust signal above the floor: identities reporting NONE/UNVERIFIED/empty are rejected with 403 `agent_trust_required`.","The merchant to have a settlement rail configured. Most do not."],"the_cap_is_not_your_spending_control":"Presence and structure of the mandate are checked on the tools that consume it — `preview_checkout`, `complete_checkout`, `process_agent_payment`, `complete_visa_payment`, `ucp_complete_checkout` — not on `create_cart` or the other checkout tools; those are the two 403s above. The cap itself is a separate matter: on demo-store `max_amount_cents` is compared against the cart and denies the purchase, but on a real merchant it may be read and recorded without denying anything. Do not treat this header as your spending control here. Enforce your own ceiling before you call, and read the boundary codes in `paths.testing` step 3 as what you get WHEN the comparison is applied, never as evidence that it was.","how_to_authenticate":{"discovery_chain":["Call any checkout tool without a token on a REAL merchant. You get HTTP 401 with `WWW-Authenticate: Bearer realm=\"checkout-mcp\", resource=…, resource_metadata=…`. On `demo-store` you get none of that: the denial is HTTP 200 with the JSON-RPC error (`error.code -32000`, `error.data.code: \"no_credentials\"`, remediation) and no challenge — there is no OAuth to follow there; take the sandbox key in `paths.testing`. Branch on `error.data.code`, not on the HTTP status.","Fetch the `resource_metadata` URL — the RFC 9728 Protected Resource Metadata of this API.","Read `authorization_servers[0]` from it. Today that is `https://auth.trusteed.xyz/`.","Fetch that server's `/.well-known/oauth-authorization-server` and use its `token_endpoint` with the `client_credentials` grant.","Send the resulting token as `Authorization: Bearer …`, plus the `X-Payment-Mandate` header."],"note":"Do NOT look for `/.well-known/oauth-authorization-server` on this API: it is a protected resource, not an authorization server, and serving that document there would be wrong. The API publishes `/.well-known/oauth-protected-resource`, which is what the challenge points at.","credentials":"Dynamic client registration (RFC 7591) is open: POST to the `registration_endpoint` published in the authorization server metadata and you get a client back, no credential needed. Registered clients are third-party applications — a user consent screen is always shown and cannot be skipped. Client credentials for server-to-server use are still issued by agreement: write to developers@trusteed.xyz."},"known_rough_edges":["Until September 2026 the API's Protected Resource Metadata declared a `resource` on a different host than the one serving it; that mismatch is fixed. The bare `/.well-known/oauth-protected-resource` on the site host is intentionally not served — follow the `resource_metadata` URL from the challenge, which names the per-resource document.","Until 2026-09-09 the bucket challenges carried only `realm`, with no `resource_metadata`: the chain above was unfollowable from its first step even though every later step was live."]}},"flow":[{"step":1,"goal":"Find merchants","call":"GET https://trusteed.xyz/.well-known/merchant-index.json","produces":"store slugs plus, per row, the MCP endpoint that actually serves that merchant (they are not all on the same host)","note":"Use the endpoint published for each row. Do not assume every merchant answers on the same host as the index."},{"step":2,"goal":"Find products","call":"tools/call `search_products` on the discovery endpoint","produces":"product ids to pass to `create_cart`; the field is `id` and it is NOT the SKU","note":"`get_product_details` takes the same id. Never invent ids."},{"step":3,"goal":"Judge the merchant","call":"tools/call `get_merchant_profile` on the discovery endpoint","produces":"trust score (0-100 scale, version 4.1), verification level and a verifyUrl that re-serves the same number signed","note":"Every discovery response also carries a `merchant_trust` block with the same figure. If two surfaces disagree, the signed one wins and it is a bug — report it."},{"step":4,"goal":"Build the cart","call":"tools/call `create_cart` on the CHECKOUT endpoint","requires":"A credential: OAuth 2.1 Bearer for a real merchant, or a sandbox key on demo-store. `X-Payment-Mandate` is required too, but only on the tools that consume it (`preview_checkout`, `complete_checkout`, `process_agent_payment`, `complete_visa_payment`, `ucp_complete_checkout`) — `create_cart` itself does not ask for one.","produces":"cart_id","note":"This is where the credential starts to matter. Send it from step 4 onwards to avoid a mid-flow auth denial (401 on a real merchant, an in-band `error.data.code` on demo-store) — and send it to the CHECKOUT endpoint: the legacy alias lets some of these tools through unauthenticated and rejects the others, so a flow that mixes endpoints breaks halfway with no useful error."},{"step":5,"goal":"Shipping","call":"`get_shipping_rates`, then `select_shipping_option` with `{ cart_id, shipping_handle, idempotency_key }`","request_example":{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"select_shipping_option","arguments":{"cart_id":"<cart_id from create_cart>","shipping_handle":"<handle from get_shipping_rates>","idempotency_key":"shipping-<unique token for this cart and option>"}}},"produces":"totals including shipping","note":"Exact argument names: `get_shipping_rates` takes `shipping_address` (with `address1`, `city`, `province_code`, `zip`, `country_code`). `select_shipping_option` takes `cart_id`, `shipping_handle` (the `handle` field of a rate), and a fresh `idempotency_key` argument. Reuse that key only to retry the same cart and option."},{"step":6,"goal":"Review before paying","call":"`preview_checkout`","produces":"final total, the merchant policy verdict, and a state commitment binding what you were shown","note":"If the merchant's catalogue changed since you looked, this is where you are told, and you must re-confirm rather than proceed."},{"step":7,"goal":"Settle","call":"`complete_checkout`","requires":"Idempotency-Key","produces":"order status and, where the merchant has receipts enabled, a signed Trust Receipt","note":"Retry with the SAME Idempotency-Key; a new key is a second attempt, not a retry. A non-zero demo cart needs a confirmation recorded beforehand at the endpoint in `paths.testing` step 4. That endpoint does not verify human presence. Real payment methods may carry their own human confirmation; MCP elicitation cannot provide it here."}],"identity":{"summary":"Signing your requests is OPTIONAL for discovery and recommended for checkout. What is never required is that you sign — what matters is that if you do sign, the signature verifies.","profiles_accepted":[{"profile":"web-bot-auth","spec":"draft-meunier-web-bot-auth-architecture","note":"The profile ChatGPT and other crawler/agent fleets sign with. Publish your key at /.well-known/http-message-signatures-directory on the origin you name in `Signature-Agent`."},{"profile":"RFC 9421 without a tag","note":"Plain HTTP Message Signatures. Verified the same way."},{"profile":"agent-browser-auth / agent-payer-auth","note":"Visa Trusted Agent Protocol."}],"unsigned_requests":"Allowed. An unsigned request is `unverified`, never `spoofed`, and the production mode (`block_spoofed`) lets it through outside of merchant-specific policy.","unknown_profile":"A signature carrying a `tag` we do not know is `unverified` too — not rejected. It is simply not promoted to `verified`.","rejected":"Only a signature we can prove wrong is rejected: key not found in the directory you pointed at, signature that does not validate, replayed nonce, or expired beyond 300s. The 403 names which one in `error.data.reason`.","caveat":"Until 2026-09-09 a valid `web-bot-auth` signature was rejected as spoofed, because that profile tag was missing from our allowlist. If you integrated before that date and worked around it by stripping your signature, you can stop."},"tool_error_shape":{"meaning":"A tool that fails answers with `isError: true` and `structuredContent.error.code`, one of the codes in the error catalogue. That field is the canonical machine-readable code. Some clients add their own `error_code` next to it, derived from their transport, and it can differ from ours. Branch on `structuredContent.error.code`.","branch_on":"structuredContent.error.code","catalogue":"https://trusteed.xyz/en/developers/errors"},"errors":[{"code":"agent_signature_spoofed","http":403,"meaning":"Your signature was present and could be proven wrong. `error.data.reason` says how.","remedy":"Check `reason`. `agent_signature_key_not_found` → the keyid is not in the directory served from your `Signature-Agent` origin. `agent_signature_invalid` → the signature base does not match (most often a signed component we reconstruct differently, e.g. `@authority` behind your proxy). `signature_expired` → your `created` is older than 300s."},{"code":"no_credentials","http":{"real_merchant":401,"demo-store":200},"meaning":"No credential on a bucket that requires one. This is NOT about signatures. On a real merchant it is HTTP 401 with a `WWW-Authenticate` challenge. On `demo-store` it is HTTP 200 with the JSON-RPC error (`error.code -32000`, `error.data.code: \"no_credentials\"`, remediation) and no challenge. The code in `error.data.code` is the same in both; the HTTP status is not.","remedy":"Branch on `error.data.code`, not on the HTTP status. Real merchant: obtain an OAuth 2.1 token by following the challenge's `resource_metadata`. demo-store: take a sandbox key with `create_sandbox_key` (see `paths.testing`) — reconnecting or re-authenticating via OAuth will not produce a credential the demo accepts."},{"code":"tool_not_found_in_bucket","http":404,"meaning":"The tool exists but not on the endpoint you called.","remedy":"Use the endpoint listed for that tool in `endpoints` above."},{"code":"PRODUCT_NOT_FOUND","meaning":"The product id does not exist in that store.","remedy":"Use the `id` returned by `search_products`. A SKU is not an id."},{"code":"SHIPPING_COUNTRY_UNSUPPORTED","meaning":"The merchant does not ship to that country. `details` carries `requested_country`, `allowed_countries` and `derived_from` — the rules the allowlist came from, so you can see it is the merchant's configuration and not a platform default.","remedy":"Use an address in `allowed_countries`, or tell the user this merchant does not serve their country. The cart is untouched, so you can retry with a different address on the same session. A merchant that has configured NO country rules does not produce this error and never blocks on destination — absence of the field in `get_merchant_profile` is absence of a declared rule, not permission to ship anywhere."},{"code":"ENFORCEMENT_UNAVAILABLE","meaning":"We could not read the merchant's rules. This is our failure, not a verdict about your request.","remedy":"Retry with backoff. It is deliberately NOT reported as a policy denial: we do not know that anything was denied, and telling you to 'try another country' could be false. Do not treat it as permission either."},{"code":"QUANTITY_LIMIT_EXCEEDED","meaning":"A single cart line asked for more units than the protocol accepts. `details` carries `requested_quantity` and `maximum_quantity`, so you do not have to find the ceiling by bisection.","remedy":"Split the order or lower the quantity. Do NOT read `maximum_quantity` as the limit that will be enforced: it is a sanity bound of the cart API, deliberately loose because a merchant may raise its own per-line ceiling above the platform default. The limit that actually decides is the platform per-line ceiling — lower than this bound — evaluated at `complete_checkout` and raisable by the merchant. A quantity that clears `create_cart` can still be refused there with `CHECKOUT_BLOCKED_BY_POLICY`."},{"code":"INSUFFICIENT_STOCK","meaning":"The catalogue replica says there are fewer units than you asked for. `details` carries `available_quantity` and `source` — it is a snapshot of the catalogue, which can lag the merchant's platform.","remedy":"Lower the quantity to `available_quantity` or pick another variant. A stock of zero in the replica is NOT treated as sold out — absence of data is not evidence — so this error only ever fires against a positive figure."},{"code":"idempotency_key_mismatch","http":422,"meaning":"Same Idempotency-Key, different arguments.","remedy":"A retry must repeat the same tool with the same arguments. They are compared as an RFC 8785 (JCS) canonical hash of `{tool, arguments}`, so key order and whitespace are irrelevant — any changed VALUE is a mismatch. Changing the cart means a new key."}],"protocol_fields":{"summary":"Three fields are protocol-level, not business data: the credential, the payment mandate and the idempotency key. Each can travel as an HTTP header OR as a tool argument when the argument channel is enabled. It is enabled by default with an explicit MCP_MANDATE_META_CHANNEL=false rollback. Use headers when your client can set them; use arguments when it cannot — a remote MCP connector (claude.ai, ChatGPT) generally cannot set headers.","precedence":"header > params._meta > tool argument. Precedence is about how governed the channel is, NOT about trust: the agent-declared payment mandate is not signature-verified and does not prove buyer consent.","cost_of_the_argument_channel":"A credential sent as an argument crosses telemetry, evidence and logs, where a header does not. It is stripped at the edge before dispatch and never written to a log, not even truncated. Prefer the header whenever your client can set one.","fields":[{"name":"agent_key","header":"X-Agent-Api-Key (or Authorization: Bearer <key>)","argument":"agent_key","format":"`agnt_sandbox_` + 32 lowercase hex characters","how_to_get_it":"Call the `create_sandbox_key` tool — it needs no credential and is in the tool list of `demo-store`. Valid 24h.","accepted":"demo-store only. Payments there are simulated.","not_accepted":"Any other store. A real merchant needs an OAuth 2.1 client-credentials token carrying an agent trust level, provisioned in advance by the agent operator — no user log in grants it, and a sandbox key never works there."},{"name":"payment_mandate","header":"X-Payment-Mandate","argument":"payment_mandate","format":"JSON object (or a JWS whose payload is that JSON). As an argument, both a JSON object and a JSON string are accepted.","claims":{"mandate_id":"string, 1-128 chars","max_amount_cents":"INTEGER in minor units, > 0. Enforced against the cart total: a cart above it is denied, and the denial travels into the signed receipt.","currency":"exactly 3 characters in the parser, and it MUST match the cart's currency — a mandate in another currency is denied outright because its cap is not comparable","exp":"INTEGER of unix seconds. NOT an ISO 8601 date string — `\"2026-09-22T16:49:23Z\"` is rejected.","sub":"string — the identity you authenticate as. The server compares it with your authenticated identity; when they differ the denial names the expected value in `details.expected_sub`.","aud":"string — the STORE SLUG this mandate is for (the slug in the URL you are calling, e.g. `demo-store`). NOT a domain: `trusteed.xyz` is rejected with MANDATE_AUDIENCE_MISMATCH. Mandates are not portable across merchants."},"not_accepted":"Missing any claim, a non-integer `exp`, a currency other than the cart's, or an `aud` naming a different store. The denial names the offending field in `remediation.mandate_issues` — in-band on demo-store (HTTP 200, `error.data.code: demo_sandbox_mandate_malformed`), 403 `payment_mandate_malformed` on a real merchant."},{"name":"idempotency_key","header":"Idempotency-Key","argument":"idempotency_key","format":"UUID-v4, or a bounded ASCII token of 1-128 chars","accepted":"A DIFFERENT key per distinct call, and the SAME key to retry that call. Unlike the other two, this one is also a declared parameter of `complete_checkout` and reaches the tool.","not_accepted":"Reusing one key across different requests: 422 `idempotency_key_mismatch`. The key itself is excluded from the idempotency fingerprint, so sending it by header or by argument yields the same fingerprint for the same order."}]},"known_limitations":["UCP tools on `demo-store`: `ucp_create_checkout`, `ucp_get_checkout`, `ucp_update_checkout` and `ucp_cancel_checkout` are served on /demo-store/mcp/checkout, with the same sandbox key and headers as `create_cart` (see `paths.testing`). On `demo-store`, `ucp_complete_checkout` is served only while the platform has the test settlement enabled: call `tools/list` on /demo-store/mcp/checkout and use it if it is listed; if it is not, use `complete_checkout`. When listed it still needs a sandbox key (sent as `Authorization: Bearer` or `X-Agent-Api-Key`) and a `payment_mandate` whose `sub` is `demo-sandbox:<id>` (the id `create_sandbox_key` returns); listing is not authorization, and a call without them answers `no_credentials`. Path: `ucp_create_checkout`, then `ucp_update_checkout` (`fulfillment.destination`, then `fulfillment.selected_option_id`), then `preview_checkout` with `cart_id` set to the UCP `checkout_id`, then `ucp_complete_checkout` with `buyer` (name and email) and `payment_option` chosen by the person (or `x402_payload` signed for the exact offer the server returns). The first call returns `requires_escalation` with an `approval_url` that someone must open before the purchase can be placed; the approval is a link and does not prove a person was present; repeat the call after it. It settles with test funds only (Base Sepolia or Stripe test mode), no real money moves, and each purchase is capped: the `total_note` of `preview_checkout` states the cap and whether your total fits, and a total over it is refused with `over_demo_cap` and nothing is charged. Avoid the deprecated alias /{store}/mcp for these names: it dispatches them only when the request carries NO `Authorization` header, and a Bearer token routes you to a dispatcher that does not know them, so you get `Tool not found`. On other stores the `ucp_*` tools are served on /{store}/mcp/checkout when the merchant enabled them — call `tools/list` there. Note that `ucp_complete_checkout` does not get the price-and-stock revalidation before charging (State Witness) that `complete_checkout` gets. If you need UCP semantics rather than these tool names, use the UCP surface at /.well-known/ucp.","MCP elicitation (`elicitation/create`) is not reachable on any of our transports: they run without a session id, so the SDK never negotiates the capability. Tools that would elicit fall back to an out-of-band confirmation channel instead. That demo channel does not verify human presence. Do not wait for an elicitation prompt.","The `demo-store` catalogue is synthetic and its payments are simulated."],"related_documents":{"tools":"https://trusteed.xyz/mcp.json","signed_capability_registry":"https://trusteed.xyz/.well-known/trusteed-capabilities.json","merchant_index":"https://trusteed.xyz/.well-known/merchant-index.json","agent_policy":"https://trusteed.xyz/.well-known/agent-policy.json","error_catalogue":"https://trusteed.xyz/en/developers/errors","receipt_lookup":"https://api.trusteed.xyz/api/v1/trust/receipts/by-session/{checkout_session_id}","oauth":"https://trusteed.xyz/.well-known/oauth-authorization-server","payment_mandate_schema":"https://trusteed.xyz/.well-known/payment-mandate.schema.json","ucp":"https://trusteed.xyz/.well-known/ucp","llms_txt":"https://trusteed.xyz/llms.txt"}}