{
  "name": "VOLT Catalog + Commerce API",
  "version": "v1",
  "environment": "staging",
  "demo": true,
  "description": "Catalog and SANDBOX commerce API for the fictional VOLT demo storefront. Checkout is a simulation: payment outcomes are chosen explicitly by the caller, no real payment is processed, and no order is fulfilled. Phase 3 adds the AI shopping concierge (same commerce functions). Phase 4 Stage 3 adds the authenticated agent commerce API under /api/v1/agent — same shared commerce engine; agents may prepare checkout and can never confirm it (see the agent section).",
  "links": {
    "categories": "/api/v1/categories",
    "products": "/api/v1/products",
    "search": "/api/v1/search?q=&category=&sort=price_asc&max_price_cents=&in_stock=1&f_{spec_key}_min=&f_{spec_key}=",
    "compare": "/api/v1/compare?skus={sku,sku,sku}",
    "compatibility": "/api/v1/compatibility?a={sku}&b={sku}",
    "product": "/api/v1/products/{sku}",
    "product_specs": "/api/v1/products/{sku}/specs",
    "cart": "/api/v1/cart",
    "checkout_sessions": "/api/v1/checkout/sessions",
    "orders": "/api/v1/orders/{order_number}",
    "concierge_state": "/api/v1/concierge/state",
    "concierge_message": "/api/v1/concierge/message",
    "openapi": "/api/v1/openapi.json",
    "ucp_profile": "/.well-known/ucp",
    "api_catalog": "/.well-known/api-catalog",
    "llms_txt": "/llms.txt",
    "agent_api": "/api/v1/agent",
    "acp_feed": "/api/v1/acp/feed",
    "mcp": "/mcp"
  },
  "session": "Carts and orders are bound to a session: send the volt_session cookie or an X-Volt-Session header (64 hex chars). Mutations require an Idempotency-Key header.",
  "agent": {
    "label": "(c) VOLT-specific — the agent API, its demo keys and its scopes are VOLT's own design. Auth is NOT OAuth 2.1 and must not be described as such.",
    "auth": "Send 'Authorization: Bearer <agent key>' on every /api/v1/agent/* call. Keys are issued two ways: (1) SELF-SERVICE with customer approval — POST /api/v1/agent/keys/request with {scopes, label} and receive {request_id, approval_url, claim_token}; the customer approves or denies on the approval page, and approval REQUIRES a 6-digit code that VOLT emails to the store's customer-contact email address when the request is created — the code is never exposed by this API, so an agent cannot approve its own request; then poll GET /api/v1/agent/keys/claim?request_id=… with the claim token to receive the raw key exactly once (7-day life). (2) By the store operator directly. Either way only the SHA-256 hash of each key is stored, the issuance source is recorded, and keys expire and can be revoked; revoked/expired keys return 401.",
    "scopes": {
      "catalog:read": "Agent search, product details + specs, availability, compare, compatibility.",
      "cart:write": "Create / inspect / mutate the agent's OWN cart only (another agent's cart id returns 404).",
      "checkout:prepare": "Prepare a checkout session from the agent's cart via the shared checkout engine (server-computed totals). Preparation only."
    },
    "no_confirm_scope": "There is NO checkout-confirm scope, no agent confirm endpoint, and no way for an agent credential to create an order. Checkout sessions prepared by an agent are bound to the agent's identity hash, which no browser session holds; the only confirm route requires the owning session. Confirmation is human-only, in the browser.",
    "rate_limit": "120 requests / minute / agent key; excess returns 429 with a Retry-After header.",
    "client_requirements": "Send a descriptive User-Agent header identifying your client (for example 'MyAgent/1.0') on every request, including the public discovery documents. Default signatures of some HTTP libraries (notably Python-urllib) are blocked by this host's edge bot protection (Cloudflare error 1010) before the request reaches the store; any descriptive User-Agent succeeds.",
    "compatibility_sequencing": "When an item depends on another product, verify the pair BEFORE adding the dependent item to a cart: GET /api/v1/agent/compatibility?a={sku}&b={sku} (MCP: check_compatibility). Add in dependency order — the primary product first, its verified accessories after.",
    "idempotency": "All agent mutations require an Idempotency-Key header (8-200 chars) — the same convention as the session API. Replaying a checkout-prepare key returns the original session; no second session is created.",
    "sandbox_restrictions": {
      "demo": true,
      "fictional_store": true,
      "real_payments": false,
      "fulfilled_orders": false,
      "note": "Sandbox checkout only: simulated payment outcomes, demonstration shipping data (a documented demo address is used when an agent omits shipping), no fulfillment. Fixture SKUs (VOLT-TX-*) are test fixtures and are excluded from agent search exactly as from every other listing."
    },
    "handoff_boundary": "Agents prepare; humans confirm. Stage 4 (live): POST a handoff for a prepared session and the agent receives a single-use, 15-minute customer review URL (/handoff/{token}; only the token SHA-256 hash is stored, the raw token is returned once). The customer page re-prices from the live catalog and discloses any change vs the prepared total; only the human approve action on that page creates the simulated order (shared confirmCheckout). There is still no agent confirm endpoint or scope. Revoking a key blocks new handoffs but does not invalidate an already-issued unexpired handoff — the customer approval is the trust anchor.",
    "self_service_authorization": {
      "step_1_request": "POST /api/v1/agent/keys/request {scopes: subset of [catalog:read, cart:write, checkout:prepare], label: human-readable purpose shown to the customer, agent_name?: optional}. Any other scope is refused at request time — no confirm scope exists. Response 201: {request_id, approval_url, claim_token, expires_at, state:'pending', approval_email:'sent'}. Creation emails a 6-digit approval code to the store's customer-contact email address (only its hash is stored; the code is never in any API response). If the email cannot be sent, creation returns 502 approval_email_failed and the request is terminal (state 'email_failed'). Pending requests expire in 15 minutes; creation is rate-limited per IP with a global pending cap.",
      "step_2_consent": "Give approval_url to the customer. The page shows the label, each scope in plain language, the 7-day lifetime, and the explicit boundary that the agent will NOT be able to place orders. Approving OR denying additionally requires the 6-digit code from the approval email: a missing or wrong code decides nothing, and 5 incorrect codes lock the request (terminal state 'locked'). Approve (code + page form) mints the key server-side; Deny is terminal. The page never contains key material OR the code. An agent cannot complete approval itself — it holds the URL and the claim token, but the code goes only to the customer's mailbox. (Staging boundary: VOLT has no customer accounts, so mailbox possession is the customer factor — stronger than link possession, still not an authenticated customer session. Production must bind approval to an authenticated customer session.)",
      "step_3_claim": "GET /api/v1/agent/keys/claim?request_id=… with the claim token as Authorization: Bearer (or X-Claim-Token). Before approval: {state:'pending'}. Denied/expired/locked/email_failed: terminal {state}. After approval: {state:'approved', api_key, key_id, scopes, expires_at} EXACTLY ONCE (no-store); a second claim returns {state:'claimed'} with no key."
    },
    "endpoints": {
      "key_request": "POST /api/v1/agent/keys/request (public; see self_service_authorization)",
      "key_claim": "GET /api/v1/agent/keys/claim?request_id={id} (claim token as Bearer or X-Claim-Token)",
      "search": "/api/v1/agent/search?q=&category=&sort=price_asc&max_price_cents=&in_stock=1&f_{spec_key}_min=&f_{spec_key}=",
      "product": "/api/v1/agent/products/{sku}",
      "availability": "/api/v1/agent/products/{sku}/availability",
      "compare": "/api/v1/agent/compare?skus={sku,sku,sku}",
      "compatibility": "/api/v1/agent/compatibility?a={sku}&b={sku}",
      "create_cart": "POST /api/v1/agent/carts",
      "cart": "/api/v1/agent/carts/{cart_id}",
      "add_item": "POST /api/v1/agent/carts/{cart_id}/items",
      "set_qty": "PATCH /api/v1/agent/carts/{cart_id}/items/{sku}",
      "remove_item": "DELETE /api/v1/agent/carts/{cart_id}/items/{sku}",
      "checkout_prepare": "POST /api/v1/agent/carts/{cart_id}/checkout-prepare",
      "checkout_handoff": "POST /api/v1/agent/checkout-sessions/{checkout_session_id}/handoff (or POST /api/v1/agent/handoffs {checkout_session_id}) — returns the single-use customer review URL once",
      "acp_feed": "GET /api/v1/acp/feed?format=jsonl|csv|json — ACP-format product feed export (public, no auth)",
      "mcp_server": "POST /mcp — Streamable HTTP MCP server; tools mirror the agent endpoints 1:1 (search_products, get_product, check_availability, compare_products, check_compatibility, create_cart, add_to_cart, update_cart_item, remove_cart_item, get_cart, prepare_checkout, create_handoff); same keys, scopes, rate limit and event trail (events carry via:'mcp')"
    },
    "versions": {
      "api": "v1",
      "ucp_profile": "2026-08-25",
      "openapi": "3.1.0",
      "mcp": "2025-11-25 (handshake era, verified with the official MCP Python SDK client) + 2026-07-28 (stateless server/discover + tools)",
      "acp_feed": "OpenAI Product Feed Spec (developers.openai.com/commerce/specs/feed, accessed 2026-11-02)"
    },
    "links": {
      "ucp_profile": "/.well-known/ucp",
      "api_catalog": "/.well-known/api-catalog",
      "openapi": "/api/v1/openapi.json",
      "llms_txt": "/llms.txt",
      "acp_feed": "/api/v1/acp/feed",
      "mcp": "/mcp",
      "sitemap": "/sitemap.xml"
    },
    "claim_labels": {
      "ucp_profile": "(a) standards-conformant profile document per UCP 2026-08-25 §1 (required registries present; payment_handlers {} per the §1.5 namespace trap — VOLT has no legitimately namespaced payment handler). NOT implemented: UCP capability negotiation (UCP-Agent intersection), RFC 9421 request signing, supported_versions leaf profiles. Authentication is the API-key alternative the spec permits (§1.4).",
      "ucp_checkout_capability": "(b) experimental adapter — UCP-shaped checkout PREPARATION with human completion in a browser, sandbox only. Never describe as 'UCP checkout'.",
      "api_catalog": "(a) RFC 9727 linkset.",
      "openapi_document": "(a) as a description format (OpenAPI 3.x); the API it describes is (c) VOLT-specific. Its path is convention, discovered via api-catalog / Link headers / llms.txt.",
      "llms_txt": "(b) experimental/conventional convenience index — a community proposal, not a standard.",
      "agent_api_and_keys": "(c) VOLT-specific.",
      "self_service_authorization": "(c) VOLT-specific — an API-key issuance flow gated by a human approval page PLUS a 6-digit code emailed out-of-band to the store's customer-contact mailbox (build .54; the page alone was the gate in build .52). NOT OAuth 2.1, NOT an authorization-server standard; described plainly in the discovery document's self_service_authorization section. Staging boundary: mailbox possession is the customer factor (no customer accounts exist); production must bind approval to an authenticated customer session.",
      "human_handoff": "(c) VOLT-specific (Stage 4).",
      "acp_feed": "(a) as a conformant ARTIFACT only: 'ACP-format product feed export (spec-conformant artifact; OpenAI ingestion requires approved merchant onboarding — not claimed).' Generated from live D1 at request time; documented field gaps (no gtin/mpn — the spec forbids inventing them; no sale_price mapping) are in STAGE5-REPORT.md. Any claim that OpenAI will ingest or display it is (d).",
      "acp_checkout_and_discovery": "NOT SHIPPED (Stage 5 decision): no ACP checkout endpoints and no /.well-known/acp.json. Both are bucket (b) shape-only surfaces no platform traffic can reach without approved-partner onboarding; Stage 3+4 checkout preparation + human handoff already provide the working interoperability. No ChatGPT/Instant Checkout claim of any kind.",
      "mcp_server": "(a) standards-conformant per MCP with a declared dual-era surface: 2025-11-25 handshake era implemented in full and verified end-to-end with the official MCP Python SDK client (named client, named version — the only compatibility claim made); 2026-07-28 stateless era implemented for server/discover + tools/list + tools/call + ping and probe-tested. Auth is (c) VOLT demo keys — NOT OAuth 2.1; no Protected Resource Metadata is published and no OAuth claim is made.",
      "ap2": "(d) off-limits — not implemented, not claimed.",
      "platform_surfaces": "(d) off-limits — no claim that Google AI Mode / Gemini / ChatGPT or any platform will surface or transact with VOLT."
    }
  }
}