From b5cd3f4df21640bc1786e217b30794793a352bc5 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Tue, 28 Jul 2026 09:05:22 +0000 Subject: [PATCH 01/16] docs: add getting started guide for building UCP servers --- docs/documentation/getting-started.md | 555 ++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 556 insertions(+) create mode 100644 docs/documentation/getting-started.md diff --git a/docs/documentation/getting-started.md b/docs/documentation/getting-started.md new file mode 100644 index 000000000..44e8c1c1e --- /dev/null +++ b/docs/documentation/getting-started.md @@ -0,0 +1,555 @@ +--- +description: Getting started guide for building UCP servers in Python and Node.js. +--- + + + +# Getting Started with UCP + +This guide will walk you through building a basic Universal Commerce Protocol (UCP) server. We provide examples for both **Python (FastAPI)** and **Node.js (Express)** using the official UCP SDKs. + +We will implement a simple checkout server that allows an agent to initiate a checkout session and retrieve it. + +## Prerequisites + +=== "Python" + + * Python 3.10 or higher installed. + * Basic familiarity with FastAPI and Pydantic. + * We recommend using [`uv`](https://docs.astral.sh/uv/) for package management. + +=== "Node.js" + + * Node.js 18 or higher installed. + * Basic familiarity with Express and Zod. + +--- + +## Project Setup + +=== "Python" + + Create a new directory and initialize the project: + + ```bash + mkdir ucp-quickstart-python + cd ucp-quickstart-python + uv init + ``` + + Add the required dependencies. `ucp-sdk` contains the Pydantic models generated from UCP schemas: + + ```bash + uv add fastapi uvicorn ucp-sdk + ``` + +=== "Node.js" + + Create a new directory and initialize the project: + + ```bash + mkdir ucp-quickstart-nodejs + cd ucp-quickstart-nodejs + npm init -y + ``` + + Configure your `package.json` to use ES Modules by adding `"type": "module"`. + + Add the required dependencies. `@ucp-js/sdk` contains the TypeScript types and Zod schemas: + + ```bash + npm install express uuid @ucp-js/sdk + npm install --save-dev typescript @types/express @types/node ts-node + ``` + + Initialize TypeScript configuration: + + ```bash + npx tsc --init + ``` + +--- + +## Implementing the Server + +We will implement the server step-by-step. The server needs to handle: + +1. **Create Checkout (`POST /checkout-sessions`)**: Receives desired items and returns a checkout session with totals and available payment handlers. +2. **Get Checkout (`GET /checkout-sessions/{id}`)**: Allows the agent to poll the session status. + +### 1. Imports and Setup + +Initialize the application and define an in-memory database to store sessions. + +=== "Python" + + ```python + # main.py + import uuid + from typing import Annotated + from fastapi import FastAPI, Header, HTTPException, status + + # Import UCP SDK models + from ucp_sdk.models.schemas.ucp import ResponseCheckoutSchema + from ucp_sdk.models.schemas.shopping.checkout import Checkout + from ucp_sdk.models.schemas.shopping.checkout_create_request import CheckoutCreateRequest + from ucp_sdk.models.schemas.shopping.types.line_item import LineItem + from ucp_sdk.models.schemas.shopping.types.item import Item + from ucp_sdk.models.schemas.shopping.types.totals import Total + from ucp_sdk.models.schemas.shopping.types.link import Link + from ucp_sdk.models.schemas.shopping.types.available_payment_instrument import AvailablePaymentInstrument + from ucp_sdk.models.schemas.payment_handler import ResponseSchema as PaymentHandlerResponse + + # Initialize FastAPI app + app = FastAPI(title="UCP Quickstart Server") + + # Simple in-memory database + checkout_sessions = {} + ``` + +=== "Node.js" + + ```typescript + // server.ts + import express from 'express'; + import { v4 as uuidv4 } from 'uuid'; + + // Import validation schemas from JS SDK + import { + CheckoutCreateRequestSchema, + CheckoutResponseSchema, + CheckoutResponse + } from '@ucp-js/sdk'; + + // Initialize Express app + const app = express(); + app.use(express.json()); + + // Simple in-memory database + const checkoutSessions: Record = {}; + ``` + +### 2. Create Checkout Endpoint (Route & Header Validation) + +Define the endpoint to create a checkout session. UCP requires `Idempotency-Key` and `UCP-Agent` headers. + +=== "Python" + + ```python + # main.py + @app.post( + "/checkout-sessions", + response_model=Checkout, + status_code=status.HTTP_201_CREATED, + response_model_exclude_none=True + ) + async def create_checkout( + body: CheckoutCreateRequest, + idempotency_key: Annotated[str, Header(alias="Idempotency-Key")], + ucp_agent: Annotated[str, Header(alias="UCP-Agent")] + ): + """Create a new UCP checkout session.""" + # Note: In a production environment, you must use the Idempotency-Key + # to prevent duplicate processing of the same request. + + # Generate a unique checkout session ID + session_id = f"chk_{uuid.uuid4().hex[:10]}" + ``` + +=== "Node.js" + + ```typescript + // server.ts + app.post('/checkout-sessions', (req, res) => { + // 1. Validate required UCP headers + const idempotencyKey = req.header('Idempotency-Key'); + const ucpAgent = req.header('UCP-Agent'); + + if (!idempotencyKey || !ucpAgent) { + return res.status(400).json({ + error: 'Missing required headers (Idempotency-Key, UCP-Agent)' + }); + } + + // 2. Validate request body against UCP schema using Zod + const validation = CheckoutCreateRequestSchema.safeParse(req.body); + if (!validation.success) { + return res.status(400).json({ errors: validation.error.errors }); + } + + const body = validation.data; + + # Generate a unique checkout session ID + const sessionId = `chk_${uuidv4().substring(0, 10)}`; + ``` + +### 3. Business Logic (Process Items & Calculate Totals) + +Process the incoming line items, resolve their prices, and calculate the subtotal, tax, and total. Prices are always in **minor units** (e.g., cents for USD). + +=== "Python" + + ```python + # main.py + # Map input line items to output line items with pricing + output_line_items = [] + subtotal = 0 + tax = 0 + + for index, item_req in enumerate(body.line_items): + # Mock product database lookup + price = 2500 # $25.00 in minor units (cents) + title = f"Flower Bouquet {item_req.item.id}" + item_subtotal = price * item_req.quantity + item_tax = int(item_subtotal * 0.08) # 8% tax + item_total = item_subtotal + item_tax + + subtotal += item_subtotal + tax += item_tax + + output_line_items.append( + LineItem( + id=f"li_{index}", + item=Item(id=item_req.item.id, title=title, price=price), + quantity=item_req.quantity, + totals=[ + Total(type="subtotal", amount=item_subtotal), + Total(type="tax", amount=item_tax), + Total(type="total", amount=item_total) + ] + ) + ) + + total = subtotal + tax + ``` + +=== "Node.js" + + ```typescript + // server.ts + // Map input line items to output line items with pricing + const outputLineItems = body.line_items.map((item, index) => { + // Mock product database lookup + const price = 2500; // $25.00 in minor units (cents) + const title = `Flower Bouquet ${item.item.id}`; + const itemSubtotal = price * item.quantity; + const itemTax = Math.floor(itemSubtotal * 0.08); // 8% tax + const itemTotal = itemSubtotal + itemTax; + + return { + id: `li_${index}`, + item: { id: item.item.id, title, price }, + quantity: item.quantity, + totals: [ + { type: 'subtotal', amount: itemSubtotal }, + { type: 'tax', amount: itemTax }, + { type: 'total', amount: itemTotal } + ] + }; + }); + + // Calculate order totals from line items + const subtotal = outputLineItems.reduce((acc, item) => { + const subtotalEntry = item.totals.find(t => t.type === 'subtotal'); + return acc + (subtotalEntry ? subtotalEntry.amount : 0); + }, 0); + const tax = outputLineItems.reduce((acc, item) => { + const taxEntry = item.totals.find(t => t.type === 'tax'); + return acc + (taxEntry ? taxEntry.amount : 0); + }, 0); + const total = subtotal + tax; + ``` + +### 4. UCP Response Construction + +Construct the UCP metadata block, advertising supported payment handlers, and assemble the final checkout response. + +=== "Python" + + ```python + # main.py + # Configure available payment handlers. + # We advertise support for a generic mock payment handler. + payment_handlers = { + "com.example.mock_pay": [ + PaymentHandlerResponse( + id="mock_pay_handler_1", + version="2026-04-08", + available_instruments=[ + AvailablePaymentInstrument(type="mock_instrument") + ] + ) + ] + } + + # Construct UCP protocol metadata + ucp_metadata = ResponseCheckoutSchema( + version="2026-04-08", + status="success", + payment_handlers=payment_handlers + ) + + # Assemble the final Checkout payload + checkout = Checkout( + ucp=ucp_metadata, + id=session_id, + status="incomplete", + currency="USD", + line_items=output_line_items, + totals=[ + Total(type="subtotal", amount=subtotal), + Total(type="tax", amount=tax), + Total(type="total", amount=total) + ], + links=[ + Link(type="terms_of_service", url="https://example.com/terms"), + Link(type="privacy_policy", url="https://example.com/privacy") + ] + ) + + # Save to database and return + checkout_sessions[session_id] = checkout + return checkout + ``` + +=== "Node.js" + + ```typescript + // server.ts + // Configure available payment handlers. + // We advertise support for a generic mock payment handler. + const ucpMetadata = { + version: '2026-04-08', + status: 'success' as const, + payment_handlers: { + 'com.example.mock_pay': [ + { + id: 'mock_pay_handler_1', + version: '2026-04-08', + available_instruments: [ + { type: 'mock_instrument' } + ] + } + ] + } + }; + + // Assemble the final Checkout payload + const checkout: CheckoutResponse = { + ucp: ucpMetadata, + id: sessionId, + status: 'incomplete', + currency: 'USD', + line_items: outputLineItems, + totals: [ + { type: 'subtotal', amount: subtotal }, + { type: 'tax', amount: tax }, + { type: 'total', amount: total } + ], + links: [ + { type: 'terms_of_service', url: 'https://example.com/terms' }, + { type: 'privacy_policy', url: 'https://example.com/privacy' } + ] + }; + + // Validate output matches CheckoutResponse schema before sending + const outputValidation = CheckoutResponseSchema.safeParse(checkout); + if (!outputValidation.success) { + console.error('Output validation failed:', outputValidation.error); + return res.status(500).json({ error: 'Internal server error' }); + } + + // Save to database and return + checkoutSessions[sessionId] = checkout; + res.status(201).json(checkout); + }); + ``` + +### 5. Get Checkout Endpoint + +Implement the retrieval route so the agent can fetch the checkout state. + +=== "Python" + + ```python + # main.py + @app.get( + "/checkout-sessions/{id}", + response_model=Checkout, + response_model_exclude_none=True + ) + async def get_checkout(id: str): + """Retrieve an existing checkout session.""" + if id not in checkout_sessions: + raise HTTPException(status_code=404, detail="Checkout session not found") + return checkout_sessions[id] + ``` + +=== "Node.js" + + ```typescript + // server.ts + app.get('/checkout-sessions/:id', (req, res) => { + const session = checkoutSessions[req.params.id]; + if (!session) { + return res.status(404).json({ error: 'Checkout session not found' }); + } + res.json(session); + }); + ``` + +--- + +## Running the Server + +=== "Python" + + Start the server using Uvicorn: + + ```bash + uv run uvicorn main:app --port 8000 --reload + ``` + +=== "Node.js" + + Add a start script to your `package.json`: + + + ```json + "scripts": { + "start": "ts-node server.ts" + } + ``` + + Start the server: + + ```bash + npm start + ``` + +Your server is now running at `http://127.0.0.1:8000`. + +--- + +## Testing the Server + +You can test your server using `curl`. + +### 1. Create a Checkout Session + +Send a `POST` request to create a checkout session with one item: + +```bash +curl -X POST http://127.0.0.1:8000/checkout-sessions \ + -H "Content-Type: application/json" \ + -H "Idempotency-Key: test-key-123" \ + -H "UCP-Agent: profile=\"https://platform.example/profile\"" \ + -d '{ + "line_items": [ + { + "item": { + "id": "prod_roses" + }, + "quantity": 2 + } + ] + }' +``` + +You should receive a response containing the UCP metadata, calculated totals, and the configured payment handler: + + +```json +{ + "ucp": { + "version": "2026-04-08", + "status": "success", + "payment_handlers": { + "com.example.mock_pay": [ + { + "version": "2026-04-08", + "id": "mock_pay_handler_1", + "available_instruments": [ + { + "type": "mock_instrument" + } + ] + } + ] + } + }, + "id": "chk_...", + "line_items": [ + { + "id": "li_0", + "item": { + "id": "prod_roses", + "title": "Flower Bouquet prod_roses", + "price": 2500 + }, + "quantity": 2, + "totals": [ + { + "type": "subtotal", + "amount": 5000 + }, + { + "type": "tax", + "amount": 400 + }, + { + "type": "total", + "amount": 5400 + } + ] + } + ], + "status": "incomplete", + "currency": "USD", + "totals": [ + { + "type": "subtotal", + "amount": 5000 + }, + { + "type": "tax", + "amount": 400 + }, + { + "type": "total", + "amount": 5400 + } + ], + "links": [ + { + "type": "terms_of_service", + "url": "https://example.com/terms" + }, + { + "type": "privacy_policy", + "url": "https://example.com/privacy" + } + ] +} +``` + +### 2. Retrieve the Checkout Session + +Retrieve the session using the `id` returned from the previous step: + +```bash +curl http://127.0.0.1:8000/checkout-sessions/ +``` + +--- + +## Next Steps + +To build a fully compliant UCP server, you will also need to: + +* Implement the checkout update endpoint (`PUT /checkout-sessions/{id}`) to handle buyer information updates (like shipping address). +* Implement the checkout completion endpoint (`POST /checkout-sessions/{id}/complete`) to process the payment instrument provided by the agent. +* Advertise your service using a [UCP Discovery Profile](core-concepts.md#discovery-capability-negotiation) at `/.well-known/ucp`. +* Run the conformance suite from the [UCP Conformance repository](https://github.com/Universal-Commerce-Protocol/conformance) against your server to verify protocol compliance. + +For a complete reference implementation, check out the [UCP Samples repository](https://github.com/Universal-Commerce-Protocol/samples). diff --git a/mkdocs.yml b/mkdocs.yml index 3c512e000..2d6a998fb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -31,6 +31,7 @@ nav: - Overview: - Home: index.md - Core Concepts: documentation/core-concepts.md + - Getting Started: documentation/getting-started.md - Specification: !ENV [ SPEC_URL, "https://ucp.dev/latest/specification/overview/", From a309f45777878328c776aceb8d6bba4dbee3d526 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Tue, 28 Jul 2026 09:35:03 +0000 Subject: [PATCH 02/16] docs: fix comment style and add missing app.listen in getting started guide Fixes a syntax error (Python comment in TS block) and adds missing Express server startup code to make the Node.js example runnable. TAG=agy CONV=867f757c-56a2-4920-b01b-bdc6da20e369 --- docs/documentation/getting-started.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/documentation/getting-started.md b/docs/documentation/getting-started.md index 44e8c1c1e..97492c292 100644 --- a/docs/documentation/getting-started.md +++ b/docs/documentation/getting-started.md @@ -179,7 +179,7 @@ Define the endpoint to create a checkout session. UCP requires `Idempotency-Key` const body = validation.data; - # Generate a unique checkout session ID + // Generate a unique checkout session ID const sessionId = `chk_${uuidv4().substring(0, 10)}`; ``` @@ -396,6 +396,11 @@ Implement the retrieval route so the agent can fetch the checkout state. } res.json(session); }); + + const PORT = 8000; + app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); + }); ``` --- From 06323ca74de70cdf30fcc09f06cb5591a28d902c Mon Sep 17 00:00:00 2001 From: damaz91 Date: Wed, 29 Jul 2026 11:13:08 +0000 Subject: [PATCH 03/16] docs: add links to SDKs in getting started guide --- docs/documentation/getting-started.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/documentation/getting-started.md b/docs/documentation/getting-started.md index 97492c292..e700c10a1 100644 --- a/docs/documentation/getting-started.md +++ b/docs/documentation/getting-started.md @@ -6,7 +6,7 @@ description: Getting started guide for building UCP servers in Python and Node.j # Getting Started with UCP -This guide will walk you through building a basic Universal Commerce Protocol (UCP) server. We provide examples for both **Python (FastAPI)** and **Node.js (Express)** using the official UCP SDKs. +This guide will walk you through building a basic Universal Commerce Protocol (UCP) server. We provide examples for both **Python (FastAPI)** and **Node.js (Express)** using the official [Python SDK](https://github.com/Universal-Commerce-Protocol/python-sdk) and [TypeScript SDK](https://github.com/Universal-Commerce-Protocol/js-sdk). We will implement a simple checkout server that allows an agent to initiate a checkout session and retrieve it. @@ -37,7 +37,7 @@ We will implement a simple checkout server that allows an agent to initiate a ch uv init ``` - Add the required dependencies. `ucp-sdk` contains the Pydantic models generated from UCP schemas: + Add the required dependencies. [`ucp-sdk`](https://github.com/Universal-Commerce-Protocol/python-sdk) contains the Pydantic models generated from UCP schemas: ```bash uv add fastapi uvicorn ucp-sdk @@ -55,7 +55,7 @@ We will implement a simple checkout server that allows an agent to initiate a ch Configure your `package.json` to use ES Modules by adding `"type": "module"`. - Add the required dependencies. `@ucp-js/sdk` contains the TypeScript types and Zod schemas: + Add the required dependencies. [`@ucp-js/sdk`](https://github.com/Universal-Commerce-Protocol/js-sdk) contains the TypeScript types and Zod schemas: ```bash npm install express uuid @ucp-js/sdk From bb8c783ba836e75707291e4f77eff31be18a3dc3 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Wed, 29 Jul 2026 11:17:07 +0000 Subject: [PATCH 04/16] docs: move getting started guide to specification section --- docs/{documentation => specification}/getting-started.md | 2 +- mkdocs.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename docs/{documentation => specification}/getting-started.md (99%) diff --git a/docs/documentation/getting-started.md b/docs/specification/getting-started.md similarity index 99% rename from docs/documentation/getting-started.md rename to docs/specification/getting-started.md index e700c10a1..d7dbe6914 100644 --- a/docs/documentation/getting-started.md +++ b/docs/specification/getting-started.md @@ -554,7 +554,7 @@ To build a fully compliant UCP server, you will also need to: * Implement the checkout update endpoint (`PUT /checkout-sessions/{id}`) to handle buyer information updates (like shipping address). * Implement the checkout completion endpoint (`POST /checkout-sessions/{id}/complete`) to process the payment instrument provided by the agent. -* Advertise your service using a [UCP Discovery Profile](core-concepts.md#discovery-capability-negotiation) at `/.well-known/ucp`. +* Advertise your service using a [UCP Discovery Profile](../documentation/core-concepts.md#discovery-capability-negotiation) at `/.well-known/ucp`. * Run the conformance suite from the [UCP Conformance repository](https://github.com/Universal-Commerce-Protocol/conformance) against your server to verify protocol compliance. For a complete reference implementation, check out the [UCP Samples repository](https://github.com/Universal-Commerce-Protocol/samples). diff --git a/mkdocs.yml b/mkdocs.yml index 2d6a998fb..a5925f69b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -31,7 +31,6 @@ nav: - Overview: - Home: index.md - Core Concepts: documentation/core-concepts.md - - Getting Started: documentation/getting-started.md - Specification: !ENV [ SPEC_URL, "https://ucp.dev/latest/specification/overview/", @@ -43,6 +42,7 @@ nav: - Announcements: documentation/announcements.md - Specification: - Overview: specification/overview.md + - Getting Started: specification/getting-started.md - Checkout Capability: - Overview: specification/checkout.md - Transports: From d36e3a4a8bb4a9c0e0b8fc57198eae98ea732213 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Wed, 29 Jul 2026 11:26:02 +0000 Subject: [PATCH 05/16] docs: add flow diagram and E2E explanation to getting started guide --- docs/specification/getting-started.md | 36 ++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/specification/getting-started.md b/docs/specification/getting-started.md index d7dbe6914..d87ab5cec 100644 --- a/docs/specification/getting-started.md +++ b/docs/specification/getting-started.md @@ -2,7 +2,7 @@ description: Getting started guide for building UCP servers in Python and Node.js. --- - + # Getting Started with UCP @@ -77,6 +77,40 @@ We will implement the server step-by-step. The server needs to handle: 1. **Create Checkout (`POST /checkout-sessions`)**: Receives desired items and returns a checkout session with totals and available payment handlers. 2. **Get Checkout (`GET /checkout-sessions/{id}`)**: Allows the agent to poll the session status. +### High-Level Flow + +Here is how the components interact during the checkout process: + +```mermaid +sequenceDiagram + autonumber + actor Agent as AI Agent + participant Server as Checkout Server (Your App) + database DB as In-Memory DB + + Note over Agent,Server: Create Checkout Session + Agent->>+Server: POST /checkout-sessions\n(Items, Headers: Idempotency-Key, UCP-Agent) + Note over Server: 1. Validate Headers & Body\n2. Calculate Totals (Minor Units)\n3. Advertise Payment Handlers + Server->>DB: Store Session + Server-->>-Agent: 201 Created (Checkout Object) + + Note over Agent,Server: Retrieve Checkout Session (Polling) + Agent->>+Server: GET /checkout-sessions/{id} + Server->>DB: Fetch Session + Server-->>-Agent: 200 OK (Checkout Object) +``` + +### How it fits into the E2E Flow + +In a complete UCP integration, the flow follows these phases: + +1. **Discovery:** The AI Agent discovers your UCP endpoints (like `/checkout-sessions`) by fetching your UCP Discovery Profile at `/.well-known/ucp`. +2. **Create Checkout (Implemented in this guide):** The Agent initiates the checkout session with the items the user wants to buy. +3. **Update Checkout:** The Agent updates the checkout session with buyer details (e.g., shipping address, email) to calculate final taxes and shipping options. +4. **Complete Checkout:** The Agent submits the payment credentials to finalize the order. + +This guide focuses on step 2 (Create Checkout) and the subsequent retrieval of the checkout session state. + ### 1. Imports and Setup Initialize the application and define an in-memory database to store sessions. From db2e6bd80bcc6601b8636810d0fa300ecbfb9b33 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Wed, 29 Jul 2026 11:27:35 +0000 Subject: [PATCH 06/16] docs: use 'platform' terminology instead of 'agent' for client role --- docs/specification/getting-started.md | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/specification/getting-started.md b/docs/specification/getting-started.md index d87ab5cec..241e53e45 100644 --- a/docs/specification/getting-started.md +++ b/docs/specification/getting-started.md @@ -8,7 +8,7 @@ description: Getting started guide for building UCP servers in Python and Node.j This guide will walk you through building a basic Universal Commerce Protocol (UCP) server. We provide examples for both **Python (FastAPI)** and **Node.js (Express)** using the official [Python SDK](https://github.com/Universal-Commerce-Protocol/python-sdk) and [TypeScript SDK](https://github.com/Universal-Commerce-Protocol/js-sdk). -We will implement a simple checkout server that allows an agent to initiate a checkout session and retrieve it. +We will implement a simple checkout server that allows a platform to initiate a checkout session and retrieve it. ## Prerequisites @@ -75,7 +75,7 @@ We will implement a simple checkout server that allows an agent to initiate a ch We will implement the server step-by-step. The server needs to handle: 1. **Create Checkout (`POST /checkout-sessions`)**: Receives desired items and returns a checkout session with totals and available payment handlers. -2. **Get Checkout (`GET /checkout-sessions/{id}`)**: Allows the agent to poll the session status. +2. **Get Checkout (`GET /checkout-sessions/{id}`)**: Allows the platform to poll the session status. ### High-Level Flow @@ -84,30 +84,30 @@ Here is how the components interact during the checkout process: ```mermaid sequenceDiagram autonumber - actor Agent as AI Agent + actor Platform as Platform participant Server as Checkout Server (Your App) database DB as In-Memory DB - Note over Agent,Server: Create Checkout Session - Agent->>+Server: POST /checkout-sessions\n(Items, Headers: Idempotency-Key, UCP-Agent) + Note over Platform,Server: Create Checkout Session + Platform->>+Server: POST /checkout-sessions\n(Items, Headers: Idempotency-Key, UCP-Agent) Note over Server: 1. Validate Headers & Body\n2. Calculate Totals (Minor Units)\n3. Advertise Payment Handlers Server->>DB: Store Session - Server-->>-Agent: 201 Created (Checkout Object) + Server-->>-Platform: 201 Created (Checkout Object) - Note over Agent,Server: Retrieve Checkout Session (Polling) - Agent->>+Server: GET /checkout-sessions/{id} + Note over Platform,Server: Retrieve Checkout Session (Polling) + Platform->>+Server: GET /checkout-sessions/{id} Server->>DB: Fetch Session - Server-->>-Agent: 200 OK (Checkout Object) + Server-->>-Platform: 200 OK (Checkout Object) ``` ### How it fits into the E2E Flow In a complete UCP integration, the flow follows these phases: -1. **Discovery:** The AI Agent discovers your UCP endpoints (like `/checkout-sessions`) by fetching your UCP Discovery Profile at `/.well-known/ucp`. -2. **Create Checkout (Implemented in this guide):** The Agent initiates the checkout session with the items the user wants to buy. -3. **Update Checkout:** The Agent updates the checkout session with buyer details (e.g., shipping address, email) to calculate final taxes and shipping options. -4. **Complete Checkout:** The Agent submits the payment credentials to finalize the order. +1. **Discovery:** The Platform discovers your UCP endpoints (like `/checkout-sessions`) by fetching your UCP Discovery Profile at `/.well-known/ucp`. +2. **Create Checkout (Implemented in this guide):** The Platform initiates the checkout session with the items the user wants to buy. +3. **Update Checkout:** The Platform updates the checkout session with buyer details (e.g., shipping address, email) to calculate final taxes and shipping options. +4. **Complete Checkout:** The Platform submits the payment credentials to finalize the order. This guide focuses on step 2 (Create Checkout) and the subsequent retrieval of the checkout session state. @@ -401,7 +401,7 @@ Construct the UCP metadata block, advertising supported payment handlers, and as ### 5. Get Checkout Endpoint -Implement the retrieval route so the agent can fetch the checkout state. +Implement the retrieval route so the platform can fetch the checkout state. === "Python" @@ -587,7 +587,7 @@ curl http://127.0.0.1:8000/checkout-sessions/ To build a fully compliant UCP server, you will also need to: * Implement the checkout update endpoint (`PUT /checkout-sessions/{id}`) to handle buyer information updates (like shipping address). -* Implement the checkout completion endpoint (`POST /checkout-sessions/{id}/complete`) to process the payment instrument provided by the agent. +* Implement the checkout completion endpoint (`POST /checkout-sessions/{id}/complete`) to process the payment instrument provided by the platform. * Advertise your service using a [UCP Discovery Profile](../documentation/core-concepts.md#discovery-capability-negotiation) at `/.well-known/ucp`. * Run the conformance suite from the [UCP Conformance repository](https://github.com/Universal-Commerce-Protocol/conformance) against your server to verify protocol compliance. From cca94083b1df7e8816dd866f8c1a160099e9f4ff Mon Sep 17 00:00:00 2001 From: damaz91 Date: Wed, 29 Jul 2026 11:33:15 +0000 Subject: [PATCH 07/16] docs: fix Mermaid diagram syntax in getting started guide --- docs/specification/getting-started.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/specification/getting-started.md b/docs/specification/getting-started.md index 241e53e45..c4d8e8a24 100644 --- a/docs/specification/getting-started.md +++ b/docs/specification/getting-started.md @@ -85,19 +85,19 @@ Here is how the components interact during the checkout process: sequenceDiagram autonumber actor Platform as Platform - participant Server as Checkout Server (Your App) - database DB as In-Memory DB + participant Server as "Checkout Server (Your App)" + database DB as "In-Memory DB" Note over Platform,Server: Create Checkout Session - Platform->>+Server: POST /checkout-sessions\n(Items, Headers: Idempotency-Key, UCP-Agent) - Note over Server: 1. Validate Headers & Body\n2. Calculate Totals (Minor Units)\n3. Advertise Payment Handlers + Platform->>+Server: POST /checkout-sessions (with Items & Headers) + Note over Server: Validate, Calculate Totals, & Advertise Payment Handlers Server->>DB: Store Session - Server-->>-Platform: 201 Created (Checkout Object) + Server-->>-Platform: 201 Created (Checkout Response) Note over Platform,Server: Retrieve Checkout Session (Polling) Platform->>+Server: GET /checkout-sessions/{id} Server->>DB: Fetch Session - Server-->>-Platform: 200 OK (Checkout Object) + Server-->>-Platform: 200 OK (Checkout Response) ``` ### How it fits into the E2E Flow From 0de8aede12624a1c6e8a71ca4fab390a4e6119ac Mon Sep 17 00:00:00 2001 From: damaz91 Date: Wed, 29 Jul 2026 12:10:55 +0000 Subject: [PATCH 08/16] docs: use participant for platform and adjust Mermaid spacing --- docs/specification/getting-started.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/specification/getting-started.md b/docs/specification/getting-started.md index c4d8e8a24..9ec20d120 100644 --- a/docs/specification/getting-started.md +++ b/docs/specification/getting-started.md @@ -82,15 +82,16 @@ We will implement the server step-by-step. The server needs to handle: Here is how the components interact during the checkout process: ```mermaid +%%{init: {'sequence': {'actorMargin': 90, 'messageMargin': 45}}}%% sequenceDiagram autonumber - actor Platform as Platform + participant Platform as "Platform" participant Server as "Checkout Server (Your App)" - database DB as "In-Memory DB" + participant DB as "In-Memory DB" Note over Platform,Server: Create Checkout Session - Platform->>+Server: POST /checkout-sessions (with Items & Headers) - Note over Server: Validate, Calculate Totals, & Advertise Payment Handlers + Platform->>+Server: POST /checkout-sessions (with Items and Headers) + Note over Server: Validate, Calculate Totals, and Advertise Payment Handlers Server->>DB: Store Session Server-->>-Platform: 201 Created (Checkout Response) From 20c3c39b094d6478ccc5b5752957c3d714205ae9 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Wed, 5 Aug 2026 11:19:42 +0000 Subject: [PATCH 09/16] Address PR 645 feedback: Add full file references and developer experience refinements --- docs/specification/getting-started.md | 350 ++++++++++++++++++++++++-- 1 file changed, 329 insertions(+), 21 deletions(-) diff --git a/docs/specification/getting-started.md b/docs/specification/getting-started.md index 9ec20d120..dcda99571 100644 --- a/docs/specification/getting-started.md +++ b/docs/specification/getting-started.md @@ -39,6 +39,9 @@ We will implement a simple checkout server that allows a platform to initiate a Add the required dependencies. [`ucp-sdk`](https://github.com/Universal-Commerce-Protocol/python-sdk) contains the Pydantic models generated from UCP schemas: + > [!NOTE] + > The Python SDK package is registered as `ucp-sdk` on PyPI, but is imported as `ucp_sdk` (with an underscore) in your Python code. + ```bash uv add fastapi uvicorn ucp-sdk ``` @@ -218,6 +221,9 @@ Define the endpoint to create a checkout session. UCP requires `Idempotency-Key` const sessionId = `chk_${uuidv4().substring(0, 10)}`; ``` +> [!NOTE] +> If the required UCP headers are missing, FastAPI (Python) will automatically return an **HTTP 422 Unprocessable Entity** error due to its built-in validation. In our Express (Node.js) implementation, we manually return an **HTTP 400 Bad Request** error. + ### 3. Business Logic (Process Items & Calculate Totals) Process the incoming line items, resolve their prices, and calculate the subtotal, tax, and total. Prices are always in **minor units** (e.g., cents for USD). @@ -432,7 +438,7 @@ Implement the retrieval route so the platform can fetch the checkout state. res.json(session); }); - const PORT = 8000; + const PORT = 3000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); @@ -447,7 +453,7 @@ Implement the retrieval route so the platform can fetch the checkout state. Start the server using Uvicorn: ```bash - uv run uvicorn main:app --port 8000 --reload + uv run uvicorn main:app --port 8080 --reload ``` === "Node.js" @@ -467,7 +473,7 @@ Implement the retrieval route so the platform can fetch the checkout state. npm start ``` -Your server is now running at `http://127.0.0.1:8000`. +Your server is now running at `http://127.0.0.1:8080` (Python) or `http://127.0.0.1:3000` (Node.js). --- @@ -480,21 +486,43 @@ You can test your server using `curl`. Send a `POST` request to create a checkout session with one item: ```bash -curl -X POST http://127.0.0.1:8000/checkout-sessions \ - -H "Content-Type: application/json" \ - -H "Idempotency-Key: test-key-123" \ - -H "UCP-Agent: profile=\"https://platform.example/profile\"" \ - -d '{ - "line_items": [ - { - "item": { - "id": "prod_roses" - }, - "quantity": 2 - } - ] - }' -``` +=== "Python" + + ```bash + curl -X POST http://127.0.0.1:8080/checkout-sessions \ + -H "Content-Type: application/json" \ + -H "Idempotency-Key: test-key-123" \ + -H "UCP-Agent: profile=\"https://platform.example/profile\"" \ + -d '{ + "line_items": [ + { + "item": { + "id": "prod_roses" + }, + "quantity": 2 + } + ] + }' + ``` + +=== "Node.js" + + ```bash + curl -X POST http://127.0.0.1:3000/checkout-sessions \ + -H "Content-Type: application/json" \ + -H "Idempotency-Key: test-key-123" \ + -H "UCP-Agent: profile=\"https://platform.example/profile\"" \ + -d '{ + "line_items": [ + { + "item": { + "id": "prod_roses" + }, + "quantity": 2 + } + ] + }' + ``` You should receive a response containing the UCP metadata, calculated totals, and the configured payment handler: @@ -577,9 +605,289 @@ You should receive a response containing the UCP metadata, calculated totals, an Retrieve the session using the `id` returned from the previous step: -```bash -curl http://127.0.0.1:8000/checkout-sessions/ -``` +=== "Python" + + ```bash + curl http://127.0.0.1:8080/checkout-sessions/ + ``` + +=== "Node.js" + + ```bash + curl http://127.0.0.1:3000/checkout-sessions/ + ``` + +--- + +## Full File Reference + +If you want to verify your code, here are the complete files for both implementations. + +=== "Python (`main.py`)" + + ```python + # main.py + import uuid + from typing import Annotated + from fastapi import FastAPI, Header, HTTPException, status + + # Import UCP SDK models + from ucp_sdk.models.schemas.ucp import ResponseCheckoutSchema + from ucp_sdk.models.schemas.shopping.checkout import Checkout + from ucp_sdk.models.schemas.shopping.checkout_create_request import CheckoutCreateRequest + from ucp_sdk.models.schemas.shopping.types.line_item import LineItem + from ucp_sdk.models.schemas.shopping.types.item import Item + from ucp_sdk.models.schemas.shopping.types.totals import Total + from ucp_sdk.models.schemas.shopping.types.link import Link + from ucp_sdk.models.schemas.shopping.types.available_payment_instrument import AvailablePaymentInstrument + from ucp_sdk.models.schemas.payment_handler import ResponseSchema as PaymentHandlerResponse + + # Initialize FastAPI app + app = FastAPI(title="UCP Quickstart Server") + + # Simple in-memory database + checkout_sessions = {} + + @app.post( + "/checkout-sessions", + response_model=Checkout, + status_code=status.HTTP_201_CREATED, + response_model_exclude_none=True + ) + async def create_checkout( + body: CheckoutCreateRequest, + idempotency_key: Annotated[str, Header(alias="Idempotency-Key")], + ucp_agent: Annotated[str, Header(alias="UCP-Agent")] + ): + """Create a new UCP checkout session.""" + # Note: In a production environment, you must use the Idempotency-Key + # to prevent duplicate processing of the same request. + + # Generate a unique checkout session ID + session_id = f"chk_{uuid.uuid4().hex[:10]}" + + # Map input line items to output line items with pricing + output_line_items = [] + subtotal = 0 + tax = 0 + + for index, item_req in enumerate(body.line_items): + # Mock product database lookup + price = 2500 # $25.00 in minor units (cents) + title = f"Flower Bouquet {item_req.item.id}" + item_subtotal = price * item_req.quantity + item_tax = int(item_subtotal * 0.08) # 8% tax + item_total = item_subtotal + item_tax + + subtotal += item_subtotal + tax += item_tax + + output_line_items.append( + LineItem( + id=f"li_{index}", + item=Item(id=item_req.item.id, title=title, price=price), + quantity=item_req.quantity, + totals=[ + Total(type="subtotal", amount=item_subtotal), + Total(type="tax", amount=item_tax), + Total(type="total", amount=item_total) + ] + ) + ) + + total = subtotal + tax + + # Configure available payment handlers. + # We advertise support for a generic mock payment handler. + payment_handlers = { + "com.example.mock_pay": [ + PaymentHandlerResponse( + id="mock_pay_handler_1", + version="2026-04-08", + available_instruments=[ + AvailablePaymentInstrument(type="mock_instrument") + ] + ) + ] + } + + # Construct UCP protocol metadata + ucp_metadata = ResponseCheckoutSchema( + version="2026-04-08", + status="success", + payment_handlers=payment_handlers + ) + + # Assemble the final Checkout payload + checkout = Checkout( + ucp=ucp_metadata, + id=session_id, + status="incomplete", + currency="USD", + line_items=output_line_items, + totals=[ + Total(type="subtotal", amount=subtotal), + Total(type="tax", amount=tax), + Total(type="total", amount=total) + ], + links=[ + Link(type="terms_of_service", url="https://example.com/terms"), + Link(type="privacy_policy", url="https://example.com/privacy") + ] + ) + + # Save to database and return + checkout_sessions[session_id] = checkout + return checkout + + @app.get( + "/checkout-sessions/{id}", + response_model=Checkout, + response_model_exclude_none=True + ) + async def get_checkout(id: str): + """Retrieve an existing checkout session.""" + if id not in checkout_sessions: + raise HTTPException(status_code=404, detail="Checkout session not found") + return checkout_sessions[id] + ``` + +=== "Node.js (`server.ts`)" + + ```typescript + // server.ts + import express from 'express'; + import { v4 as uuidv4 } from 'uuid'; + + // Import validation schemas from JS SDK + import { + CheckoutCreateRequestSchema, + CheckoutResponseSchema, + CheckoutResponse + } from '@ucp-js/sdk'; + + // Initialize Express app + const app = express(); + app.use(express.json()); + + // Simple in-memory database + const checkoutSessions: Record = {}; + + app.post('/checkout-sessions', (req, res) => { + // 1. Validate required UCP headers + const idempotencyKey = req.header('Idempotency-Key'); + const ucpAgent = req.header('UCP-Agent'); + + if (!idempotencyKey || !ucpAgent) { + return res.status(400).json({ + error: 'Missing required headers (Idempotency-Key, UCP-Agent)' + }); + } + + // 2. Validate request body against UCP schema using Zod + const validation = CheckoutCreateRequestSchema.safeParse(req.body); + if (!validation.success) { + return res.status(400).json({ errors: validation.error.errors }); + } + + const body = validation.data; + + // Generate a unique checkout session ID + const sessionId = `chk_${uuidv4().substring(0, 10)}`; + + // Map input line items to output line items with pricing + const outputLineItems = body.line_items.map((item, index) => { + // Mock product database lookup + const price = 2500; // $25.00 in minor units (cents) + const title = `Flower Bouquet ${item.item.id}`; + const itemSubtotal = price * item.quantity; + const itemTax = Math.floor(itemSubtotal * 0.08); // 8% tax + const itemTotal = itemSubtotal + itemTax; + + return { + id: `li_${index}`, + item: { id: item.item.id, title, price }, + quantity: item.quantity, + totals: [ + { type: 'subtotal', amount: itemSubtotal }, + { type: 'tax', amount: itemTax }, + { type: 'total', amount: itemTotal } + ] + }; + }); + + // Calculate order totals from line items + const subtotal = outputLineItems.reduce((acc, item) => { + const subtotalEntry = item.totals.find(t => t.type === 'subtotal'); + return acc + (subtotalEntry ? subtotalEntry.amount : 0); + }, 0); + const tax = outputLineItems.reduce((acc, item) => { + const taxEntry = item.totals.find(t => t.type === 'tax'); + return acc + (taxEntry ? taxEntry.amount : 0); + }, 0); + const total = subtotal + tax; + + // Configure available payment handlers. + // We advertise support for a generic mock payment handler. + const ucpMetadata = { + version: '2026-04-08', + status: 'success' as const, + payment_handlers: { + 'com.example.mock_pay': [ + { + id: 'mock_pay_handler_1', + version: '2026-04-08', + available_instruments: [ + { type: 'mock_instrument' } + ] + } + ] + } + }; + + // Assemble the final Checkout payload + const checkout: CheckoutResponse = { + ucp: ucpMetadata, + id: sessionId, + status: 'incomplete', + currency: 'USD', + line_items: outputLineItems, + totals: [ + { type: 'subtotal', amount: subtotal }, + { type: 'tax', amount: tax }, + { type: 'total', amount: total } + ], + links: [ + { type: 'terms_of_service', url: 'https://example.com/terms' }, + { type: 'privacy_policy', url: 'https://example.com/privacy' } + ] + }; + + // Validate output matches CheckoutResponse schema before sending + const outputValidation = CheckoutResponseSchema.safeParse(checkout); + if (!outputValidation.success) { + console.error('Output validation failed:', outputValidation.error); + return res.status(500).json({ error: 'Internal server error' }); + } + + // Save to database and return + checkoutSessions[sessionId] = checkout; + res.status(201).json(checkout); + }); + + app.get('/checkout-sessions/:id', (req, res) => { + const session = checkoutSessions[req.params.id]; + if (!session) { + return res.status(404).json({ error: 'Checkout session not found' }); + } + res.json(session); + }); + + const PORT = 3000; + app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); + }); + ``` --- From 89ba802be38b03fe01550335e13d75c52fba92e1 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Wed, 5 Aug 2026 11:30:05 +0000 Subject: [PATCH 10/16] Address PR 645 feedback: Wrap full file reference in collapsible block and rename tabs --- docs/specification/getting-started.md | 508 +++++++++++++------------- 1 file changed, 256 insertions(+), 252 deletions(-) diff --git a/docs/specification/getting-started.md b/docs/specification/getting-started.md index dcda99571..7e8fbe407 100644 --- a/docs/specification/getting-started.md +++ b/docs/specification/getting-started.md @@ -621,273 +621,277 @@ Retrieve the session using the `id` returned from the previous step: ## Full File Reference -If you want to verify your code, here are the complete files for both implementations. - -=== "Python (`main.py`)" - - ```python - # main.py - import uuid - from typing import Annotated - from fastapi import FastAPI, Header, HTTPException, status - - # Import UCP SDK models - from ucp_sdk.models.schemas.ucp import ResponseCheckoutSchema - from ucp_sdk.models.schemas.shopping.checkout import Checkout - from ucp_sdk.models.schemas.shopping.checkout_create_request import CheckoutCreateRequest - from ucp_sdk.models.schemas.shopping.types.line_item import LineItem - from ucp_sdk.models.schemas.shopping.types.item import Item - from ucp_sdk.models.schemas.shopping.types.totals import Total - from ucp_sdk.models.schemas.shopping.types.link import Link - from ucp_sdk.models.schemas.shopping.types.available_payment_instrument import AvailablePaymentInstrument - from ucp_sdk.models.schemas.payment_handler import ResponseSchema as PaymentHandlerResponse - - # Initialize FastAPI app - app = FastAPI(title="UCP Quickstart Server") - - # Simple in-memory database - checkout_sessions = {} - - @app.post( - "/checkout-sessions", - response_model=Checkout, - status_code=status.HTTP_201_CREATED, - response_model_exclude_none=True - ) - async def create_checkout( - body: CheckoutCreateRequest, - idempotency_key: Annotated[str, Header(alias="Idempotency-Key")], - ucp_agent: Annotated[str, Header(alias="UCP-Agent")] - ): - """Create a new UCP checkout session.""" - # Note: In a production environment, you must use the Idempotency-Key - # to prevent duplicate processing of the same request. - - # Generate a unique checkout session ID - session_id = f"chk_{uuid.uuid4().hex[:10]}" - - # Map input line items to output line items with pricing - output_line_items = [] - subtotal = 0 - tax = 0 - - for index, item_req in enumerate(body.line_items): - # Mock product database lookup - price = 2500 # $25.00 in minor units (cents) - title = f"Flower Bouquet {item_req.item.id}" - item_subtotal = price * item_req.quantity - item_tax = int(item_subtotal * 0.08) # 8% tax - item_total = item_subtotal + item_tax +If you want to verify your code, expand the section below to see the complete files. + +??? "Complete Code Reference" + + === "Python" + + `main.py` + ```python + # main.py + import uuid + from typing import Annotated + from fastapi import FastAPI, Header, HTTPException, status + + # Import UCP SDK models + from ucp_sdk.models.schemas.ucp import ResponseCheckoutSchema + from ucp_sdk.models.schemas.shopping.checkout import Checkout + from ucp_sdk.models.schemas.shopping.checkout_create_request import CheckoutCreateRequest + from ucp_sdk.models.schemas.shopping.types.line_item import LineItem + from ucp_sdk.models.schemas.shopping.types.item import Item + from ucp_sdk.models.schemas.shopping.types.totals import Total + from ucp_sdk.models.schemas.shopping.types.link import Link + from ucp_sdk.models.schemas.shopping.types.available_payment_instrument import AvailablePaymentInstrument + from ucp_sdk.models.schemas.payment_handler import ResponseSchema as PaymentHandlerResponse + + # Initialize FastAPI app + app = FastAPI(title="UCP Quickstart Server") + + # Simple in-memory database + checkout_sessions = {} + + @app.post( + "/checkout-sessions", + response_model=Checkout, + status_code=status.HTTP_201_CREATED, + response_model_exclude_none=True + ) + async def create_checkout( + body: CheckoutCreateRequest, + idempotency_key: Annotated[str, Header(alias="Idempotency-Key")], + ucp_agent: Annotated[str, Header(alias="UCP-Agent")] + ): + """Create a new UCP checkout session.""" + # Note: In a production environment, you must use the Idempotency-Key + # to prevent duplicate processing of the same request. + + # Generate a unique checkout session ID + session_id = f"chk_{uuid.uuid4().hex[:10]}" + + # Map input line items to output line items with pricing + output_line_items = [] + subtotal = 0 + tax = 0 + + for index, item_req in enumerate(body.line_items): + # Mock product database lookup + price = 2500 # $25.00 in minor units (cents) + title = f"Flower Bouquet {item_req.item.id}" + item_subtotal = price * item_req.quantity + item_tax = int(item_subtotal * 0.08) # 8% tax + item_total = item_subtotal + item_tax + + subtotal += item_subtotal + tax += item_tax + + output_line_items.append( + LineItem( + id=f"li_{index}", + item=Item(id=item_req.item.id, title=title, price=price), + quantity=item_req.quantity, + totals=[ + Total(type="subtotal", amount=item_subtotal), + Total(type="tax", amount=item_tax), + Total(type="total", amount=item_total) + ] + ) + ) - subtotal += item_subtotal - tax += item_tax + total = subtotal + tax + + # Configure available payment handlers. + # We advertise support for a generic mock payment handler. + payment_handlers = { + "com.example.mock_pay": [ + PaymentHandlerResponse( + id="mock_pay_handler_1", + version="2026-04-08", + available_instruments=[ + AvailablePaymentInstrument(type="mock_instrument") + ] + ) + ] + } - output_line_items.append( - LineItem( - id=f"li_{index}", - item=Item(id=item_req.item.id, title=title, price=price), - quantity=item_req.quantity, - totals=[ - Total(type="subtotal", amount=item_subtotal), - Total(type="tax", amount=item_tax), - Total(type="total", amount=item_total) - ] - ) + # Construct UCP protocol metadata + ucp_metadata = ResponseCheckoutSchema( + version="2026-04-08", + status="success", + payment_handlers=payment_handlers ) - total = subtotal + tax - - # Configure available payment handlers. - # We advertise support for a generic mock payment handler. - payment_handlers = { - "com.example.mock_pay": [ - PaymentHandlerResponse( - id="mock_pay_handler_1", - version="2026-04-08", - available_instruments=[ - AvailablePaymentInstrument(type="mock_instrument") - ] - ) - ] - } + # Assemble the final Checkout payload + checkout = Checkout( + ucp=ucp_metadata, + id=session_id, + status="incomplete", + currency="USD", + line_items=output_line_items, + totals=[ + Total(type="subtotal", amount=subtotal), + Total(type="tax", amount=tax), + Total(type="total", amount=total) + ], + links=[ + Link(type="terms_of_service", url="https://example.com/terms"), + Link(type="privacy_policy", url="https://example.com/privacy") + ] + ) - # Construct UCP protocol metadata - ucp_metadata = ResponseCheckoutSchema( - version="2026-04-08", - status="success", - payment_handlers=payment_handlers - ) + # Save to database and return + checkout_sessions[session_id] = checkout + return checkout - # Assemble the final Checkout payload - checkout = Checkout( - ucp=ucp_metadata, - id=session_id, - status="incomplete", - currency="USD", - line_items=output_line_items, - totals=[ - Total(type="subtotal", amount=subtotal), - Total(type="tax", amount=tax), - Total(type="total", amount=total) - ], - links=[ - Link(type="terms_of_service", url="https://example.com/terms"), - Link(type="privacy_policy", url="https://example.com/privacy") - ] + @app.get( + "/checkout-sessions/{id}", + response_model=Checkout, + response_model_exclude_none=True ) + async def get_checkout(id: str): + """Retrieve an existing checkout session.""" + if id not in checkout_sessions: + raise HTTPException(status_code=404, detail="Checkout session not found") + return checkout_sessions[id] + ``` + + === "Node.js" + + `server.ts` + ```typescript + // server.ts + import express from 'express'; + import { v4 as uuidv4 } from 'uuid'; + + // Import validation schemas from JS SDK + import { + CheckoutCreateRequestSchema, + CheckoutResponseSchema, + CheckoutResponse + } from '@ucp-js/sdk'; + + // Initialize Express app + const app = express(); + app.use(express.json()); + + // Simple in-memory database + const checkoutSessions: Record = {}; + + app.post('/checkout-sessions', (req, res) => { + // 1. Validate required UCP headers + const idempotencyKey = req.header('Idempotency-Key'); + const ucpAgent = req.header('UCP-Agent'); + + if (!idempotencyKey || !ucpAgent) { + return res.status(400).json({ + error: 'Missing required headers (Idempotency-Key, UCP-Agent)' + }); + } - # Save to database and return - checkout_sessions[session_id] = checkout - return checkout - - @app.get( - "/checkout-sessions/{id}", - response_model=Checkout, - response_model_exclude_none=True - ) - async def get_checkout(id: str): - """Retrieve an existing checkout session.""" - if id not in checkout_sessions: - raise HTTPException(status_code=404, detail="Checkout session not found") - return checkout_sessions[id] - ``` - -=== "Node.js (`server.ts`)" - - ```typescript - // server.ts - import express from 'express'; - import { v4 as uuidv4 } from 'uuid'; - - // Import validation schemas from JS SDK - import { - CheckoutCreateRequestSchema, - CheckoutResponseSchema, - CheckoutResponse - } from '@ucp-js/sdk'; - - // Initialize Express app - const app = express(); - app.use(express.json()); - - // Simple in-memory database - const checkoutSessions: Record = {}; - - app.post('/checkout-sessions', (req, res) => { - // 1. Validate required UCP headers - const idempotencyKey = req.header('Idempotency-Key'); - const ucpAgent = req.header('UCP-Agent'); - - if (!idempotencyKey || !ucpAgent) { - return res.status(400).json({ - error: 'Missing required headers (Idempotency-Key, UCP-Agent)' - }); - } - - // 2. Validate request body against UCP schema using Zod - const validation = CheckoutCreateRequestSchema.safeParse(req.body); - if (!validation.success) { - return res.status(400).json({ errors: validation.error.errors }); - } - - const body = validation.data; - - // Generate a unique checkout session ID - const sessionId = `chk_${uuidv4().substring(0, 10)}`; - - // Map input line items to output line items with pricing - const outputLineItems = body.line_items.map((item, index) => { - // Mock product database lookup - const price = 2500; // $25.00 in minor units (cents) - const title = `Flower Bouquet ${item.item.id}`; - const itemSubtotal = price * item.quantity; - const itemTax = Math.floor(itemSubtotal * 0.08); // 8% tax - const itemTotal = itemSubtotal + itemTax; - - return { - id: `li_${index}`, - item: { id: item.item.id, title, price }, - quantity: item.quantity, - totals: [ - { type: 'subtotal', amount: itemSubtotal }, - { type: 'tax', amount: itemTax }, - { type: 'total', amount: itemTotal } - ] - }; - }); - - // Calculate order totals from line items - const subtotal = outputLineItems.reduce((acc, item) => { - const subtotalEntry = item.totals.find(t => t.type === 'subtotal'); - return acc + (subtotalEntry ? subtotalEntry.amount : 0); - }, 0); - const tax = outputLineItems.reduce((acc, item) => { - const taxEntry = item.totals.find(t => t.type === 'tax'); - return acc + (taxEntry ? taxEntry.amount : 0); - }, 0); - const total = subtotal + tax; + // 2. Validate request body against UCP schema using Zod + const validation = CheckoutCreateRequestSchema.safeParse(req.body); + if (!validation.success) { + return res.status(400).json({ errors: validation.error.errors }); + } - // Configure available payment handlers. - // We advertise support for a generic mock payment handler. - const ucpMetadata = { - version: '2026-04-08', - status: 'success' as const, - payment_handlers: { - 'com.example.mock_pay': [ - { - id: 'mock_pay_handler_1', - version: '2026-04-08', - available_instruments: [ - { type: 'mock_instrument' } + const body = validation.data; + + // Generate a unique checkout session ID + const sessionId = `chk_${uuidv4().substring(0, 10)}`; + + // Map input line items to output line items with pricing + const outputLineItems = body.line_items.map((item, index) => { + // Mock product database lookup + const price = 2500; // $25.00 in minor units (cents) + const title = `Flower Bouquet ${item.item.id}`; + const itemSubtotal = price * item.quantity; + const itemTax = Math.floor(itemSubtotal * 0.08); // 8% tax + const itemTotal = itemSubtotal + itemTax; + + return { + id: `li_${index}`, + item: { id: item.item.id, title, price }, + quantity: item.quantity, + totals: [ + { type: 'subtotal', amount: itemSubtotal }, + { type: 'tax', amount: itemTax }, + { type: 'total', amount: itemTotal } + ] + }; + }); + + // Calculate order totals from line items + const subtotal = outputLineItems.reduce((acc, item) => { + const subtotalEntry = item.totals.find(t => t.type === 'subtotal'); + return acc + (subtotalEntry ? subtotalEntry.amount : 0); + }, 0); + const tax = outputLineItems.reduce((acc, item) => { + const taxEntry = item.totals.find(t => t.type === 'tax'); + return acc + (taxEntry ? taxEntry.amount : 0); + }, 0); + const total = subtotal + tax; + + // Configure available payment handlers. + // We advertise support for a generic mock payment handler. + const ucpMetadata = { + version: '2026-04-08', + status: 'success' as const, + payment_handlers: { + 'com.example.mock_pay': [ + { + id: 'mock_pay_handler_1', + version: '2026-04-08', + available_instruments: [ + { type: 'mock_instrument' } + ] + } ] } - ] - } - }; - - // Assemble the final Checkout payload - const checkout: CheckoutResponse = { - ucp: ucpMetadata, - id: sessionId, - status: 'incomplete', - currency: 'USD', - line_items: outputLineItems, - totals: [ - { type: 'subtotal', amount: subtotal }, - { type: 'tax', amount: tax }, - { type: 'total', amount: total } - ], - links: [ - { type: 'terms_of_service', url: 'https://example.com/terms' }, - { type: 'privacy_policy', url: 'https://example.com/privacy' } - ] - }; + }; + + // Assemble the final Checkout payload + const checkout: CheckoutResponse = { + ucp: ucpMetadata, + id: sessionId, + status: 'incomplete', + currency: 'USD', + line_items: outputLineItems, + totals: [ + { type: 'subtotal', amount: subtotal }, + { type: 'tax', amount: tax }, + { type: 'total', amount: total } + ], + links: [ + { type: 'terms_of_service', url: 'https://example.com/terms' }, + { type: 'privacy_policy', url: 'https://example.com/privacy' } + ] + }; - // Validate output matches CheckoutResponse schema before sending - const outputValidation = CheckoutResponseSchema.safeParse(checkout); - if (!outputValidation.success) { - console.error('Output validation failed:', outputValidation.error); - return res.status(500).json({ error: 'Internal server error' }); - } + // Validate output matches CheckoutResponse schema before sending + const outputValidation = CheckoutResponseSchema.safeParse(checkout); + if (!outputValidation.success) { + console.error('Output validation failed:', outputValidation.error); + return res.status(500).json({ error: 'Internal server error' }); + } - // Save to database and return - checkoutSessions[sessionId] = checkout; - res.status(201).json(checkout); - }); + // Save to database and return + checkoutSessions[sessionId] = checkout; + res.status(201).json(checkout); + }); - app.get('/checkout-sessions/:id', (req, res) => { - const session = checkoutSessions[req.params.id]; - if (!session) { - return res.status(404).json({ error: 'Checkout session not found' }); - } - res.json(session); - }); + app.get('/checkout-sessions/:id', (req, res) => { + const session = checkoutSessions[req.params.id]; + if (!session) { + return res.status(404).json({ error: 'Checkout session not found' }); + } + res.json(session); + }); - const PORT = 3000; - app.listen(PORT, () => { - console.log(`Server is running on port ${PORT}`); - }); - ``` + const PORT = 3000; + app.listen(PORT, () => { + console.log(`Server is running on port ${PORT}`); + }); + ``` --- From d5a7247df32675db85d54d735a424c1869384550 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Wed, 5 Aug 2026 11:31:57 +0000 Subject: [PATCH 11/16] Address PR 645 feedback: Fix admonition syntax for notes --- docs/specification/getting-started.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/specification/getting-started.md b/docs/specification/getting-started.md index 7e8fbe407..cc62eb0e7 100644 --- a/docs/specification/getting-started.md +++ b/docs/specification/getting-started.md @@ -39,8 +39,8 @@ We will implement a simple checkout server that allows a platform to initiate a Add the required dependencies. [`ucp-sdk`](https://github.com/Universal-Commerce-Protocol/python-sdk) contains the Pydantic models generated from UCP schemas: - > [!NOTE] - > The Python SDK package is registered as `ucp-sdk` on PyPI, but is imported as `ucp_sdk` (with an underscore) in your Python code. + !!! note + The Python SDK package is registered as `ucp-sdk` on PyPI, but is imported as `ucp_sdk` (with an underscore) in your Python code. ```bash uv add fastapi uvicorn ucp-sdk @@ -221,8 +221,8 @@ Define the endpoint to create a checkout session. UCP requires `Idempotency-Key` const sessionId = `chk_${uuidv4().substring(0, 10)}`; ``` -> [!NOTE] -> If the required UCP headers are missing, FastAPI (Python) will automatically return an **HTTP 422 Unprocessable Entity** error due to its built-in validation. In our Express (Node.js) implementation, we manually return an **HTTP 400 Bad Request** error. +!!! note + If the required UCP headers are missing, FastAPI (Python) will automatically return an **HTTP 422 Unprocessable Entity** error due to its built-in validation. In our Express (Node.js) implementation, we manually return an **HTTP 400 Bad Request** error. ### 3. Business Logic (Process Items & Calculate Totals) From aefb1787997b5ef2375f0f2d1b07bf6b9e03d981 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Wed, 5 Aug 2026 11:45:58 +0000 Subject: [PATCH 12/16] Fix broken link to core-concepts in getting-started guide --- docs/specification/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/specification/getting-started.md b/docs/specification/getting-started.md index cc62eb0e7..b15ce5dc2 100644 --- a/docs/specification/getting-started.md +++ b/docs/specification/getting-started.md @@ -901,7 +901,7 @@ To build a fully compliant UCP server, you will also need to: * Implement the checkout update endpoint (`PUT /checkout-sessions/{id}`) to handle buyer information updates (like shipping address). * Implement the checkout completion endpoint (`POST /checkout-sessions/{id}/complete`) to process the payment instrument provided by the platform. -* Advertise your service using a [UCP Discovery Profile](../documentation/core-concepts.md#discovery-capability-negotiation) at `/.well-known/ucp`. +* Advertise your service using a [UCP Discovery Profile](/documentation/core-concepts/#discovery-capability-negotiation) at `/.well-known/ucp`. * Run the conformance suite from the [UCP Conformance repository](https://github.com/Universal-Commerce-Protocol/conformance) against your server to verify protocol compliance. For a complete reference implementation, check out the [UCP Samples repository](https://github.com/Universal-Commerce-Protocol/samples). From 8ef30a86c0f2c18a74075b567802d5a1d7f9e7d2 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Tue, 11 Aug 2026 11:08:02 +0000 Subject: [PATCH 13/16] docs: address PR feedback on getting started guide --- docs/specification/getting-started.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/specification/getting-started.md b/docs/specification/getting-started.md index b15ce5dc2..380aa4737 100644 --- a/docs/specification/getting-started.md +++ b/docs/specification/getting-started.md @@ -362,6 +362,7 @@ Construct the UCP metadata block, advertising supported payment handlers, and as const ucpMetadata = { version: '2026-04-08', status: 'success' as const, + capabilities: {}, payment_handlers: { 'com.example.mock_pay': [ { @@ -485,7 +486,6 @@ You can test your server using `curl`. Send a `POST` request to create a checkout session with one item: -```bash === "Python" ```bash @@ -836,6 +836,7 @@ If you want to verify your code, expand the section below to see the complete fi const ucpMetadata = { version: '2026-04-08', status: 'success' as const, + capabilities: {}, payment_handlers: { 'com.example.mock_pay': [ { From 9cece7473aac39bd3f7d56aeed8a3f3beda66be8 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Thu, 13 Aug 2026 09:18:44 +0000 Subject: [PATCH 14/16] docs: fix Node.js walkthrough type imports and switch to tsx --- docs/specification/getting-started.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/specification/getting-started.md b/docs/specification/getting-started.md index 380aa4737..2d2375180 100644 --- a/docs/specification/getting-started.md +++ b/docs/specification/getting-started.md @@ -62,7 +62,7 @@ We will implement a simple checkout server that allows a platform to initiate a ```bash npm install express uuid @ucp-js/sdk - npm install --save-dev typescript @types/express @types/node ts-node + npm install --save-dev typescript @types/express @types/node tsx ``` Initialize TypeScript configuration: @@ -156,7 +156,7 @@ Initialize the application and define an in-memory database to store sessions. import { CheckoutCreateRequestSchema, CheckoutResponseSchema, - CheckoutResponse + type CheckoutResponse } from '@ucp-js/sdk'; // Initialize Express app @@ -464,7 +464,7 @@ Implement the retrieval route so the platform can fetch the checkout state. ```json "scripts": { - "start": "ts-node server.ts" + "start": "tsx server.ts" } ``` @@ -767,7 +767,7 @@ If you want to verify your code, expand the section below to see the complete fi import { CheckoutCreateRequestSchema, CheckoutResponseSchema, - CheckoutResponse + type CheckoutResponse } from '@ucp-js/sdk'; // Initialize Express app From 37aff458a67d54848c1d7c30281a6cf2d9dde695 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Tue, 8 Sep 2026 13:52:41 +0000 Subject: [PATCH 15/16] docs: update getting started guide for UCP 2026-08-25 and python-sdk 0.5.0 imports --- docs/specification/getting-started.md | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/specification/getting-started.md b/docs/specification/getting-started.md index 2d2375180..d72369aaf 100644 --- a/docs/specification/getting-started.md +++ b/docs/specification/getting-started.md @@ -133,9 +133,9 @@ Initialize the application and define an in-memory database to store sessions. from ucp_sdk.models.schemas.shopping.checkout_create_request import CheckoutCreateRequest from ucp_sdk.models.schemas.shopping.types.line_item import LineItem from ucp_sdk.models.schemas.shopping.types.item import Item - from ucp_sdk.models.schemas.shopping.types.totals import Total - from ucp_sdk.models.schemas.shopping.types.link import Link - from ucp_sdk.models.schemas.shopping.types.available_payment_instrument import AvailablePaymentInstrument + from ucp_sdk.models.schemas.common.types.totals import Total + from ucp_sdk.models.schemas.common.types.link import Link + from ucp_sdk.models.schemas.common.types.available_payment_instrument import AvailablePaymentInstrument from ucp_sdk.models.schemas.payment_handler import ResponseSchema as PaymentHandlerResponse # Initialize FastAPI app @@ -315,7 +315,7 @@ Construct the UCP metadata block, advertising supported payment handlers, and as "com.example.mock_pay": [ PaymentHandlerResponse( id="mock_pay_handler_1", - version="2026-04-08", + version="2026-08-25", available_instruments=[ AvailablePaymentInstrument(type="mock_instrument") ] @@ -325,7 +325,7 @@ Construct the UCP metadata block, advertising supported payment handlers, and as # Construct UCP protocol metadata ucp_metadata = ResponseCheckoutSchema( - version="2026-04-08", + version="2026-08-25", status="success", payment_handlers=payment_handlers ) @@ -360,14 +360,14 @@ Construct the UCP metadata block, advertising supported payment handlers, and as // Configure available payment handlers. // We advertise support for a generic mock payment handler. const ucpMetadata = { - version: '2026-04-08', + version: '2026-08-25', status: 'success' as const, capabilities: {}, payment_handlers: { 'com.example.mock_pay': [ { id: 'mock_pay_handler_1', - version: '2026-04-08', + version: '2026-08-25', available_instruments: [ { type: 'mock_instrument' } ] @@ -530,12 +530,12 @@ You should receive a response containing the UCP metadata, calculated totals, an ```json { "ucp": { - "version": "2026-04-08", + "version": "2026-08-25", "status": "success", "payment_handlers": { "com.example.mock_pay": [ { - "version": "2026-04-08", + "version": "2026-08-25", "id": "mock_pay_handler_1", "available_instruments": [ { @@ -640,9 +640,9 @@ If you want to verify your code, expand the section below to see the complete fi from ucp_sdk.models.schemas.shopping.checkout_create_request import CheckoutCreateRequest from ucp_sdk.models.schemas.shopping.types.line_item import LineItem from ucp_sdk.models.schemas.shopping.types.item import Item - from ucp_sdk.models.schemas.shopping.types.totals import Total - from ucp_sdk.models.schemas.shopping.types.link import Link - from ucp_sdk.models.schemas.shopping.types.available_payment_instrument import AvailablePaymentInstrument + from ucp_sdk.models.schemas.common.types.totals import Total + from ucp_sdk.models.schemas.common.types.link import Link + from ucp_sdk.models.schemas.common.types.available_payment_instrument import AvailablePaymentInstrument from ucp_sdk.models.schemas.payment_handler import ResponseSchema as PaymentHandlerResponse # Initialize FastAPI app @@ -706,7 +706,7 @@ If you want to verify your code, expand the section below to see the complete fi "com.example.mock_pay": [ PaymentHandlerResponse( id="mock_pay_handler_1", - version="2026-04-08", + version="2026-08-25", available_instruments=[ AvailablePaymentInstrument(type="mock_instrument") ] @@ -716,7 +716,7 @@ If you want to verify your code, expand the section below to see the complete fi # Construct UCP protocol metadata ucp_metadata = ResponseCheckoutSchema( - version="2026-04-08", + version="2026-08-25", status="success", payment_handlers=payment_handlers ) @@ -834,14 +834,14 @@ If you want to verify your code, expand the section below to see the complete fi // Configure available payment handlers. // We advertise support for a generic mock payment handler. const ucpMetadata = { - version: '2026-04-08', + version: '2026-08-25', status: 'success' as const, capabilities: {}, payment_handlers: { 'com.example.mock_pay': [ { id: 'mock_pay_handler_1', - version: '2026-04-08', + version: '2026-08-25', available_instruments: [ { type: 'mock_instrument' } ] From eeeffe19f37bd587af9e36b92235ab293a9263c8 Mon Sep 17 00:00:00 2001 From: damaz91 Date: Tue, 8 Sep 2026 17:16:10 +0000 Subject: [PATCH 16/16] docs: clarify real-time inventory checking and capability negotiation in getting started guide --- docs/specification/getting-started.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/specification/getting-started.md b/docs/specification/getting-started.md index d72369aaf..8b7caaeaa 100644 --- a/docs/specification/getting-started.md +++ b/docs/specification/getting-started.md @@ -239,6 +239,8 @@ Process the incoming line items, resolve their prices, and calculate the subtota for index, item_req in enumerate(body.line_items): # Mock product database lookup + # In production, perform real-time inventory verification and stock + # reservation here (catalog availability signals are advisory and can drift). price = 2500 # $25.00 in minor units (cents) title = f"Flower Bouquet {item_req.item.id}" item_subtotal = price * item_req.quantity @@ -271,6 +273,8 @@ Process the incoming line items, resolve their prices, and calculate the subtota // Map input line items to output line items with pricing const outputLineItems = body.line_items.map((item, index) => { // Mock product database lookup + // In production, perform real-time inventory verification and stock + // reservation here (catalog availability signals are advisory and can drift). const price = 2500; // $25.00 in minor units (cents) const title = `Flower Bouquet ${item.item.id}`; const itemSubtotal = price * item.quantity; @@ -305,6 +309,9 @@ Process the incoming line items, resolve their prices, and calculate the subtota Construct the UCP metadata block, advertising supported payment handlers, and assemble the final checkout response. +!!! note "Capability Negotiation" + In production, a business uses the `UCP-Agent` header to resolve the platform's profile, calculates the intersection of supported capabilities, and returns the active negotiated set in `capabilities`. For simplicity in this quickstart, full capability negotiation is omitted. See [Discovery & Capability Negotiation](/documentation/core-concepts/#discovery-capability-negotiation) for details. + === "Python" ```python @@ -324,6 +331,7 @@ Construct the UCP metadata block, advertising supported payment handlers, and as } # Construct UCP protocol metadata + # (In production, populate active capabilities from UCP-Agent negotiation) ucp_metadata = ResponseCheckoutSchema( version="2026-08-25", status="success", @@ -362,7 +370,7 @@ Construct the UCP metadata block, advertising supported payment handlers, and as const ucpMetadata = { version: '2026-08-25', status: 'success' as const, - capabilities: {}, + capabilities: {}, // In production, populate via capability negotiation from UCP-Agent payment_handlers: { 'com.example.mock_pay': [ { @@ -676,6 +684,8 @@ If you want to verify your code, expand the section below to see the complete fi for index, item_req in enumerate(body.line_items): # Mock product database lookup + # In production, perform real-time inventory verification and stock + # reservation here (catalog availability signals are advisory and can drift). price = 2500 # $25.00 in minor units (cents) title = f"Flower Bouquet {item_req.item.id}" item_subtotal = price * item_req.quantity @@ -715,6 +725,7 @@ If you want to verify your code, expand the section below to see the complete fi } # Construct UCP protocol metadata + # (In production, populate active capabilities from UCP-Agent negotiation) ucp_metadata = ResponseCheckoutSchema( version="2026-08-25", status="success", @@ -802,6 +813,8 @@ If you want to verify your code, expand the section below to see the complete fi // Map input line items to output line items with pricing const outputLineItems = body.line_items.map((item, index) => { // Mock product database lookup + // In production, perform real-time inventory verification and stock + // reservation here (catalog availability signals are advisory and can drift). const price = 2500; // $25.00 in minor units (cents) const title = `Flower Bouquet ${item.item.id}`; const itemSubtotal = price * item.quantity; @@ -836,7 +849,7 @@ If you want to verify your code, expand the section below to see the complete fi const ucpMetadata = { version: '2026-08-25', status: 'success' as const, - capabilities: {}, + capabilities: {}, // In production, populate via capability negotiation from UCP-Agent payment_handlers: { 'com.example.mock_pay': [ {