---
title: 'Agentic Commerce Protocol (ACP)'
description: 'Stateless, idempotent checkout endpoints that let buyer agents purchase from your shop with a single POST — ChatGPT Instant Checkout-compatible.'
canonical: 'https://cartwright.app/docs/features/agentic-commerce-protocol'
---

# Agentic Commerce Protocol (ACP) (/docs/features/agentic-commerce-protocol)



The Agentic Commerce Protocol is the spec ChatGPT Instant Checkout and other AI shopping engines speak when they want to actually buy from a shop. Cartwright ships it natively: a JSONL product feed plus stateless checkout sessions that wrap your existing pricing, tax, and inventory rules.

ACP is distinct from [A2A](/docs/features/a2a-endpoints). A2A is the negotiation surface (agent ↔ shop bargaining via Anchor-and-Resume). ACP is the purchase surface (agent ↔ shop transacts). A shop can opt into one, both, or neither.

## Feature gate [#feature-gate]

ACP is gated behind `brand.features.acp` (default `false` on most templates, `true` on `agent-marketplace`). When off, every `/api/acp/*` route returns 404 — your storefront surface stays clean.

```ts
// brand.config.ts
features: {
  acp: true,            // turn the ACP surface on
  a2a: false,           // independent of A2A
  webshop: true,        // a shop can serve humans AND ACP buyers
}
```

## GET /api/acp/feed [#get-apiacpfeed]

A newline-delimited JSON feed of your live catalogue. ChatGPT Instant Checkout reads this directly — no merchant centre upload, no XML schema, no GTIN guesswork.

```bash
curl https://example-shop.app/api/acp/feed
```

Response (one JSON object per line):

```jsonl
{"id":"prod_001","title":"Sunglass Frame Mahogany","price":{"amount":"599.00","currency":"DKK"},"availability":"in_stock","brand":"Atelier","image_link":"https://example-shop.app/img/prod_001.jpg","link":"https://example-shop.app/products/sunglass-frame-mahogany"}
{"id":"prod_002","title":"Lens Pair Polarised","price":{"amount":"299.00","currency":"DKK"},"availability":"in_stock","brand":"Atelier","image_link":"https://example-shop.app/img/prod_002.jpg","link":"https://example-shop.app/products/lens-pair-polarised"}
```

The feed is regenerated on demand — no cron, no stale snapshots. Field shape follows the ACP product-feed spec; Cartwright maps `Product`, `Category`, and `ProductVariant` rows into the canonical fields.

<Callout type="tip">
  Buyer agents typically pull the feed once per session, then refresh on cache-miss. Use `Cache-Control: max-age=300` if your inventory is volatile — the route honours it.
</Callout>

## POST /api/acp/v1/checkout\_sessions [#post-apiacpv1checkout_sessions]

Create a checkout session. The buyer agent posts line items + shipping address; Cartwright responds with totals computed by `lib/pricing.ts` and a session id the agent uses to complete or update.

```bash
curl -X POST https://example-shop.app/api/acp/v1/checkout_sessions \
  -H "Authorization: Bearer <api-key>" \
  -H "Idempotency-Key: 7f3e9a1b-4d8c-4e5a-9b2f-1a3c5d7e9f0a" \
  -H "Content-Type: application/json" \
  -d '{
    "line_items": [
      { "product_id": "prod_001", "quantity": 2 }
    ],
    "shipping_address": {
      "name": "Buyer Agent",
      "line1": "Rådhuspladsen 1",
      "city": "Copenhagen",
      "postal_code": "1550",
      "country": "DK"
    }
  }'
```

Response:

```json
{
  "id": "cs_2NjAaP1eZvKYlo2C0",
  "object": "checkout_session",
  "status": "open",
  "currency": "DKK",
  "line_items": [
    { "product_id": "prod_001", "quantity": 2, "amount_subtotal_minor": 119800 }
  ],
  "totals": {
    "subtotal_minor": 119800,
    "tax_minor": 29950,
    "shipping_minor": 4900,
    "total_minor": 154650
  },
  "expires_at": "2026-05-25T09:00:00Z"
}
```

## Update, cancel, complete [#update-cancel-complete]

Sessions are stateful (DB-backed) but the protocol is request-idempotent. Send the same `Idempotency-Key` and the same response replays.

* `GET /api/acp/v1/checkout_sessions/{id}` — read current state
* `POST /api/acp/v1/checkout_sessions/{id}/update` — change line items or shipping
* `POST /api/acp/v1/checkout_sessions/{id}/cancel` — abandon
* `POST /api/acp/v1/checkout_sessions/{id}/complete` — finalise the session. Gated behind `ACP_PAYMENT_COMPLETION=1` (default off). The full flow is now **wired**: it charges via a Stripe **Shared Payment Token** (off-session PaymentIntent), builds the order from the session line items (not the cart), replays on the idempotency key, and refunds if order creation fails. It stays &#x2A;*inert (501)** until external Stripe SPT access + ChatGPT merchant onboarding are connected — so it can never accidentally move money before you go live

```bash
curl -X POST https://example-shop.app/api/acp/v1/checkout_sessions/cs_2NjAaP1eZvKYlo2C0/complete \
  -H "Authorization: Bearer <api-key>" \
  -H "Content-Type: application/json" \
  -d '{ "shared_payment_token": "spt_…", "idempotency_key": "7f3e9a1b-…" }'
```

When live, `complete` returns the completed session plus an `order` reference; the off-session charge resolves to `succeeded` or fails cleanly (a card requiring 3DS/SCA can't be resolved off-session, so it's treated as a failed charge with an automatic refund).

<Callout type="warn">
  **Completion is wired but inert by default.** `/complete` ships behind `ACP_PAYMENT_COMPLETION=1` (default off). The full path is now code-complete and unit-tested — Stripe **Shared Payment Token** charge (off-session PaymentIntent), order creation from the session line items, idempotency replay, and refund-on-failure. It only goes live once you connect **Stripe SPT access + ChatGPT merchant onboarding**; until then the endpoint responds **501** (it can never charge a card while the external integration is pending). `create` / `update` / `retrieve` / `cancel` are fully live.
</Callout>

## UCP: native\_commerce capability [#ucp-native_commerce-capability]

Google's Unified Commerce Protocol (UCP) lets agents discover which catalogue products are *native-buyable*. When `acp` is on, the Google Merchant feed (`/feed/google.xml`) emits `<g:native_commerce>enabled</g:native_commerce>` per item, and `/.well-known/ucp` advertises a `native_commerce` capability — gated on `acp` **plus** `merchantFeed`. The gating is deliberate: the shop never advertises a capability it can't honour.

<Callout type="info">
  The exact `native_commerce` attribute is an emerging March-2026 Google spec — verify the attribute string against Google's current Merchant Center docs before go-live. The structure and gating are correct.
</Callout>

## UCP: identity-linking (OAuth 2.0) [#ucp-identity-linking-oauth-20]

UCP also defines &#x2A;*`dev.ucp.common.identity_linking`** — how an agentic platform gets delegated, scoped permission to act on a shopper's behalf across merchants. Cartwright ships it as a full OAuth 2.0 Authorization Code + PKCE server behind the `ucpIdentityLinking` flag (default-off): RFC 8414/9728 discovery metadata, RFC 7591 dynamic registration, `/oauth/{authorize,token,revoke}` with a consent screen, and a sample protected resource at `/api/ucp/orders&#x60;. See &#x2A;*[UCP identity-linking](/docs/features/ucp-identity-linking)** for the endpoints, scopes, and setup.

## Authentication [#authentication]

All POST endpoints require a Bearer token. API keys are managed at `/admin/api-keys` and can be scoped to `acp:checkout` only — restricting what the key can do is enforced before the handler runs.

<Callout type="warn">
  The feed endpoint is **public by default** (so crawlers can index it). If your catalogue contains private inventory or wholesale pricing, override the auth check in `app/api/acp/feed/route.ts` — there's a `requireFeedAuth()` helper commented out for that case.
</Callout>

## Relationship to A2A [#relationship-to-a2a]

ACP is the **purchase** surface. A2A is the **negotiation** surface. The two sit side by side:

| Endpoint                             | Purpose                      | Spec              |
| ------------------------------------ | ---------------------------- | ----------------- |
| `GET /api/acp/feed`                  | Product discovery (JSONL)    | ACP product feed  |
| `POST /api/acp/v1/checkout_sessions` | Stateless purchase           | ACP v1            |
| `GET /api/agent-card`                | Shop discovery (signed JSON) | Cartwright A2A v1 |
| `POST /api/negotiate`                | Bargaining                   | Cartwright A2A v1 |
| `POST /api/escrow/verify`            | Verify-then-Pay release      | Cartwright A2A v1 |

A shop with only `features.acp = true` ships a "just-buy-it" surface (fixed prices, no haggling). A shop with both `acp` and `a2a` lets agents discover, negotiate, then settle via either ACP checkout or A2A escrow.

<Cards>
  <Card title="UCP identity-linking" href="/docs/features/ucp-identity-linking" />

  <Card title="WebMCP (in-browser tools)" href="/docs/features/webmcp" />

  <Card title="A2A endpoints" href="/docs/features/a2a-endpoints" />

  <Card title="Guardian middleware" href="/docs/features/guardian-middleware" />

  <Card title="Industry templates" href="/docs/features/industry-templates" />

  <Card title="AI sale walkthrough" href="/docs/guides/ai-sale-walkthrough" />
</Cards>
