Skip to content

UCP and ACP

Janno Jaerv edited this page May 5, 2026 · 1 revision

UCP and ACP

Brief orientation on the two protocols this plugin speaks. Neither is invented here — both are public specs you'd implement against regardless of which e-commerce platform you're integrating with.

UCP — Universal Commerce Protocol

Spec: ucp.dev · GitHub: universal-commerce-protocol/ucp Version we ship: 2026-04-08

A REST-style protocol that lets agents discover and shop a storefront via well-known endpoints. The merchant publishes a discovery document at /.well-known/ucp describing services, capabilities, and supported payment handlers; agents then call REST endpoints to browse, build a cart, and check out.

What we serve

Endpoint Method Purpose
/.well-known/ucp GET Discovery — services, capabilities, payment handlers
/ucp/catalog/search POST Product search
/ucp/catalog/products/[id] GET Product detail
/ucp/checkout-sessions POST Create a checkout
/ucp/checkout-sessions/[id] GET / PUT Fetch / update checkout
/ucp/checkout-sessions/[id]/complete POST Submit payment, complete
/ucp/checkout-sessions/[id]/cancel POST Cancel
/ucp/orders/[id] GET Fetch order

Discovery shape

{
  "ucp": {
    "version": "2026-04-08",
    "services": {
      "dev.ucp.shopping": [{ "version": "2026-04-08", "transport": "rest", "endpoint": "https://store.com/ucp" }]
    },
    "capabilities": {
      "dev.ucp.shopping.catalog": [{ "version": "2026-04-08" }],
      "dev.ucp.shopping.checkout": [{ "version": "2026-04-08" }],
      "dev.ucp.shopping.order": [{ "version": "2026-04-08" }]
    },
    "payment_handlers": {
      "xyz.fd.prism_payment": [{ "id": "x402", "version": "2026-01-15", "spec": "...", "schema": "...", "config": {} }]
    }
  }
}

The payment_handlers block aggregates output from every registered payment handler. Reverse-DNS keys (e.g. xyz.fd.prism_payment) identify handler types; values are arrays of declarations the handler exposed.

ACP — Agentic Commerce Protocol

Spec: agentic-commerce-protocol GitHub · sponsored by Stripe + OpenAI Version we ship: 2026-01-30 (storefront response shape) — we track the spec at 2026-04-17 (PaymentHandler schema)

A different style. Discovery is per-session: when the agent creates a CheckoutSession, the seller's response includes a capabilities object with the intersection of supported payment handlers, currencies, intervention types, etc. There is no static .well-known/acp.json for handlers — only DiscoveryCapabilities (services list, supported currencies, etc.).

What we serve

Endpoint Method Purpose
/.well-known/acp.json GET Capabilities discovery (no per-session handlers)
/acp/checkout_sessions POST Create a session — response embeds capabilities.payment.handlers
/acp/checkout_sessions/[id] GET Get session
/acp/checkout_sessions/[id] POST Update session (ACP uses POST, not PUT)
/acp/checkout_sessions/[id]/complete POST Submit payment via payment_data, complete
/acp/checkout_sessions/[id]/cancel POST Cancel

ACP requires Authorization: Bearer <token> on every endpoint per the spec's OpenAPI (securitySchemes: bearerAuth, bearerFormat: API Key). The seller chooses the token format and issues it themselves; the spec leaves both out of scope. See Authentication for full details.

PaymentHandler shape

Per ACP 2026-04-17 schema:

{
  "id": "x402",                              // seller-defined instance id
  "name": "xyz.fd.prism_payment",            // reverse-DNS handler type
  "version": "2026-01-15",
  "spec": "https://...",                     // human-readable spec URL
  "requires_delegate_payment": false,
  "requires_pci_compliance": false,
  "psp": "prism",                            // payment service provider id
  "config_schema": "https://...",            // JSON Schema URL
  "instrument_schemas": ["https://..."],
  "config": { /* handler-specific blob */ }
}

Where they differ

UCP ACP
Discovery transport Static /.well-known/ucp Per-session in CheckoutSession response
Reverse-DNS handler type Object key under payment_handlers name field
Schema URL fields Single schema (combined) Separate config_schema + instrument_schemas[]
requires_delegate_payment / psp Not present Required
Update verb on checkout PUT POST
Auth Open by default Bearer (spec-required)

The plugin maintains a single internal handler model and serializes through two adapters — one for each wire format. See packages/core/src/lib/formatters/{ucp,acp}.ts.

Where they're the same

Both protocols share these concepts (different field names, same idea):

  • Handler instance identifier
  • Handler type identifier (reverse-DNS)
  • Dated version string (YYYY-MM-DD)
  • JSON Schema for handler config (Draft 2020-12)
  • Merchant-supplied config blob

This is why a single handler package can serve both protocols without much duplication — the discovery output formatters massage one internal model into both shapes.

Medusa-native concerns ride along

Things like shipping options come from Medusa at request time and get translated:

Medusa source UCP target ACP target
Shipping options for cart's region fulfillment.methods[] fulfillment_options[]
Cart total total_amount total
Cart line items line_items[] line_items[]
Order line items + fulfillments UCP order shape ACP order shape

The merchant configures shipping in Medusa's normal Shipping Options UI. Agents see whatever's currently active. See Architecture for the framing.

See also

Clone this wiki locally