# UCP and ACP

> The Universal Commerce Protocol and the Agentic Commerce Protocol, as every Storeware shop speaks them.

Source: https://storeware.ai/docs/agents/ucp-acp

Every Storeware shop speaks two agent commerce protocols: the [Universal Commerce Protocol](https://github.com/Universal-Commerce-Protocol/ucp) (UCP) and the Agentic Commerce Protocol (ACP). Both are served from `https://api.storeware.ai` for every shop, both take an agent key, and both are served by the same checkout underneath, so prices, stock and the shop's rules are the same whichever one an agent speaks.

Storeware pins each protocol to one dated release and tests its answers against that release's own schemas. Read the version from the shop's discovery document rather than from this page.

## UCP

### Discovery

A shop's profile is at `https://<shop>/.well-known/ucp`. It names the protocol version, the capabilities and extensions the shop offers, the endpoint for each transport, the payment handlers, and the keys the shop signs with. The platform's own profile, which names no shop, is at [`https://api.storeware.ai/.well-known/ucp`](https://api.storeware.ai/.well-known/ucp).

A profile may also list `supported_versions`: an older release the shop still answers, each with a profile of its own at `/.well-known/ucp/<version>`. A platform on a release the shop does not answer is refused with `version_unsupported`, which is HTTP 422 over REST.

### Transports

| Transport | Endpoint |
| --- | --- |
| REST | `https://api.storeware.ai/api/ucp/v1` |
| MCP | `https://api.storeware.ai/ucp/mcp` |
| A2A | `https://api.storeware.ai/api/ucp/a2a`, named in the shop's profile beside its agent card at `https://<shop>/.well-known/agent-card.json` |

### Calling it

```http
POST https://api.storeware.ai/api/ucp/v1/catalog/search
Authorization: Bearer swa_...
UCP-Agent: profile="https://platform.example/.well-known/ucp"
Content-Type: application/json
```

- **The key.** An agent key, as `Authorization: Bearer`. The REST routes also take it as `X-API-Key`, and refuse a request that names two different keys.
- **Your profile.** Every request names the calling platform's own profile in `UCP-Agent`. Over MCP it goes in `meta["ucp-agent"].profile` instead. Capabilities are negotiated against it, so an extension your profile does not declare is left out of the answer.
- **Signatures.** A request signed with RFC 9421 is checked against the keys in your profile, and a signature that does not verify is refused. An unsigned request is accepted.
- **Retries.** Send `Idempotency-Key` on a write, and a retry with the same key gets the first answer.

### What a shop offers

| Capability | REST routes |
| --- | --- |
| Catalogue search and lookup | `POST /catalog/search`, `POST /catalog/lookup`, `POST /catalog/product` |
| Checkout | `POST /checkout-sessions`, `GET` and `PUT /checkout-sessions/{id}`, `POST /checkout-sessions/{id}/complete`, `POST /checkout-sessions/{id}/cancel` |
| Cart | `POST /carts`, `GET` and `PUT /carts/{id}`, `POST /carts/{id}/cancel` |
| Order | `GET /orders/{id}`, and order webhooks |

Beside those, a shop's profile declares fulfilment, discounts, buyer consent, AP2 mandates and payment authentication on checkout, and two extensions of Storeware's own: `ai.storeware.shopping.answer`, which explains a catalogue search (see [Answers](https://storeware.ai/docs/agents/answers)), and `ai.storeware.shopping.terms`, for a checkout that needs the shopper to accept terms.

A cart and a checkout session are the same basket, so a cart's id is its checkout id. A cart's totals are estimates, because tax and delivery need an address. See [Universal Cart](https://storeware.ai/docs/agents/universal-cart).

### Identity linking

A shopper can link a platform to their account at a shop with OAuth on the shop's own hostname: the metadata is at `https://<shop>/.well-known/oauth-authorization-server`, and the shopper consents at the shop's `/account/authorize`. The one scope a shop names is `dev.ucp.shopping.order:read`, which lets the platform read the shopper's orders. Self registration is on the same server: see [How an agent signs in](https://storeware.ai/docs/agents/agent-auth#acting-for-a-shopper).

### Permalinks

A shop's profile also declares `dev.ucp.shopping.permalink`: a basket as a link a person opens. `https://<shop>/buy/<item>:<quantity>`, with pairs joined by commas, lands the shopper on the shop with those items in their basket.

## ACP

### Discovery

A shop's ACP discovery document is at `https://<shop>/.well-known/acp.json`, and the platform's at [`https://api.storeware.ai/.well-known/acp.json`](https://api.storeware.ai/.well-known/acp.json).

### Calling it

The base is `https://api.storeware.ai/api/acp`, with an agent key as `Authorization: Bearer`.

| Resource | Routes |
| --- | --- |
| Checkout sessions | `POST /checkout_sessions`, `GET` and `POST /checkout_sessions/{id}`, `POST /checkout_sessions/{id}/complete`, `POST /checkout_sessions/{id}/cancel` |
| Carts | `POST /carts`, `GET` and `PUT /carts/{id}`, `POST /carts/{id}/cancel` |

- **Version.** Send `API-Version`. A version the shop does not speak is a 400 with `unsupported_api_version` and a `supported_versions` list. A request without the header is served at the current version, and every response says which version answered.
- **Retries.** `Idempotency-Key` is honoured on every `POST`.
- **Money** is integer minor units everywhere.
- **Errors** are flat objects with `type`, `code`, `message` and, where it helps, `param`.

A cart id and a checkout session id are the same identifier. A cart that has been placed, cancelled or has expired is a 404 at `/carts`, while `/checkout_sessions` still serves it.

### Paying

Storeware takes delegated payment and does not issue it. The agent gets a shared payment token from Stripe and passes it to `complete`, using the payment handler the session advertises. Storeware never takes a card number, so `delegate_payment` is not served.

Store credit on a session is shown as a total of type `store_credit`, and the card is charged the total less that credit. A shop that has turned on stablecoins also advertises an x402 handler: see [x402](https://storeware.ai/docs/agents/x402).

### Signing requests

Signing is set per agent key. A key with a public key registered must sign every request; a key without one carries on unsigned.

| | |
| --- | --- |
| Algorithm | Ed25519, or ES256 over P-256. The key is registered as an SPKI PEM. |
| Signed value | `timestamp` + `\n` + method + `\n` + path + `\n` + the raw body as sent |
| Headers | `Signature` (base64url) and `Timestamp` (RFC 3339) |
| Window | 300 seconds either side of now |

Ed25519 is the one to choose unless you also sign AP2 mandates, which need ES256. An ES256 signature is the raw `r||s` pair, not DER.

### Order webhooks

Orders a key placed are posted to the URL registered on that key: `order_create` once, then `order_update` for every change, each carrying the whole current order. A delivery is signed `Merchant-Signature: t=<unix>,v1=<hex>`, over the timestamp, a dot and the raw body, with a 300 second tolerance. Revoking the key stops delivery.
