> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Agentic commerce

## Worked Agentic Commerce samples

An AI shopping agent drives the four operations below in order: fetch the discovery manifest, browse the catalogue, build a cart, and complete checkout under the purchaser's payment mandate. Substitute your storefront id (`org_…`) and issued mandate where shown. Successful requests below return raw ACP documents (not the `{ data, meta }` envelope the authenticated Commerce API uses).

### 1. Catalogue browse

<Note>
  `GET /api/v1/public/commerce/acp/{storefrontId}/feed`
</Note>

Return the merchant's published catalogue. The `items` array is what the agent presents to the shopper.

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl "https://api.orbit.devotel.io/api/v1/public/commerce/acp/org_8v1a2c/.well-known/agentic-commerce" \
      && curl "https://api.orbit.devotel.io/api/v1/public/commerce/acp/org_8v1a2c/feed"
    ```

    ```typescript Node.js theme={null}
    const manifest = await fetch('https://api.orbit.devotel.io/api/v1/public/commerce/acp/org_8v1a2c/.well-known/agentic-commerce')
      .then((r) => r.json());

    // The manifest names the exact endpoints the agent drives.
    const feed = await fetch(manifest.endpoints.productFeed).then((r) => r.json());

    console.log(feed.items); // [{ id, title, price, currency, ... }]
    ```

    ```python Python theme={null}
    import requests

    base = "https://api.orbit.devotel.io/api/v1/public/commerce/acp/org_8v1a2c"
    manifest = requests.get(f"{base}/.well-known/agentic-commerce").json()

    # The manifest names the exact endpoints the agent drives.
    feed = requests.get(manifest["endpoints"]["productFeed"]).json()

    print(feed["items"])
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "protocol": "agentic-commerce",
    "protocolVersion": "2025-09-29",
    "count": 2,
    "items": [
      {
        "id": "sku_espresso_1l",
        "title": "Single-origin espresso, 1 L",
        "description": "Washed Ethiopian, roasted weekly.",
        "price": 16.5,
        "currency": "USD",
        "availability": "in_stock",
        "inventoryQuantity": 24,
        "link": "https://acme.example/sku_espresso_1l",
        "category": "coffee"
      },
      {
        "id": "sku_drip_filter_pack",
        "title": "Drip filter pack, 100 ct",
        "description": null,
        "price": 8,
        "currency": "USD",
        "availability": "in_stock",
        "inventoryQuantity": 87,
        "link": null,
        "category": "coffee"
      }
    ],
    "generatedAt": 1793937772450
  }
  ```
</ResponseExample>

### 2. Cart build

<Note>
  `POST /api/v1/public/commerce/acp/{storefrontId}/checkout`
</Note>

Open a session with the agent's requested items. Every price on the response is computed server-side against the stored catalogue, so a shopper-facing message tells you when a product was unknown, out of stock, or quantity-clamped.

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://api.orbit.devotel.io/api/v1/public/commerce/acp/org_8v1a2c/checkout" \
      -H "Content-Type: application/json" \
      -d '{"id":"sess_acme_92e4","items":[{"productId":"sku_espresso_1l","quantity":2},{"productId":"sku_drip_filter_pack","quantity":1}]}'
    ```

    ```typescript Node.js theme={null}
    const res = await fetch(
      'https://api.orbit.devotel.io/api/v1/public/commerce/acp/org_8v1a2c/checkout',
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          id: 'sess_acme_92e4',
          items: [
            { productId: 'sku_espresso_1l', quantity: 2 },
            { productId: 'sku_drip_filter_pack', quantity: 1 },
          ],
        }),
      },
    );
    const session = await res.json();
    // Persist this snapshot; the update and complete calls round-trip it.
    ```

    ```python Python theme={null}
    import requests

    res = requests.post(
        "https://api.orbit.devotel.io/api/v1/public/commerce/acp/org_8v1a2c/checkout",
        json={
            "id": "sess_acme_92e4",
            "items": [
                {"productId": "sku_espresso_1l", "quantity": 2},
                {"productId": "sku_drip_filter_pack", "quantity": 1},
            ],
        },
    )
    session = res.json()  # persist this snapshot across update/complete
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "id": "sess_acme_92e4",
    "merchantId": "org_8v1a2c",
    "status": "ready_for_payment",
    "currency": "USD",
    "category": null,
    "lineItems": [
      {
        "id": "sku_espresso_1l",
        "productId": "sku_espresso_1l",
        "title": "Single-origin espresso, 1 L",
        "quantity": 2,
        "unitPrice": 16.5,
        "currency": "USD",
        "lineTotal": 33
      },
      {
        "id": "sku_drip_filter_pack",
        "productId": "sku_drip_filter_pack",
        "title": "Drip filter pack, 100 ct",
        "quantity": 1,
        "unitPrice": 8,
        "currency": "USD",
        "lineTotal": 8
      }
    ],
    "itemCount": 3,
    "subtotal": 41,
    "total": 41,
    "messages": [],
    "order": null,
    "createdAt": 1793937842610,
    "updatedAt": 1793937842610
  }
  ```
</ResponseExample>

Any line's problem — unknown, out of stock, or clamped quantity — comes back in `messages` with `type: "error"` and a `code` you can surface to the shopper. Repeat a re-priced session through [update](#update-a-public-acp-checkout-session), or replace items entirely with a `POST` to `checkout/update`.

### 3. Payment-token mint (AP2 mandate issue)

<Note>
  `POST /api/v1/commerce/agent-mandate`
</Note>

A mandate is the scoped, spend-capped purchase authorization a principal hands the shopping agent. Issue it on the authenticated Commerce API (API key or session JWT), then hand the returned mandate snapshot to the agent for the pay step below. The completion endpoint only accepts a mandate inside its own scope — the ledger reference in the [Commerce API reference](/api-reference/commerce) describes `issue`, `authorize`, `charge`, `verify`, and `revoke`.

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://api.orbit.devotel.io/api/v1/commerce/agent-mandate" \
      -H "X-API-Key: dv_live_sk_your_key_here" \ \
      -H "Content-Type: application/json" \
      -d '{"id":"mandate_acme_shop_1","agentId":"agent_coffee_webshop","principalId":"contact_61f2cdb1","maxPerTransaction":75,"totalCap":250,"currency":"USD","allowedMerchants":["org_8v1a2c"],"allowedCategories":["coffee"],"expiresAt":1793940000000}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel-orbit/node';

    const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY! });

    const { data: mandate } = await orbit.request(
      'POST',
      '/commerce/agent-mandate',
      {
        id: 'mandate_acme_shop_1',
        agentId: 'agent_coffee_webshop',
        principalId: 'contact_61f2cdb1',
        maxPerTransaction: 75,
        totalCap: 250,
        currency: 'USD',
        allowedMerchants: ['org_8v1a2c'],
        allowedCategories: ['coffee'],
        expiresAt: 1793940000000,
      },
    );
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    mandate_payload = {
        "id": "mandate_acme_shop_1",
        "agentId": "agent_coffee_webshop",
        "principalId": "contact_61f2cdb1",
        "maxPerTransaction": 75,
        "totalCap": 250,
        "currency": "USD",
        "allowedMerchants": ["org_8v1a2c"],
        "allowedCategories": ["coffee"],
        "expiresAt": 1793940000000,
    }
    res = requests.post(
        "https://api.orbit.devotel.io/api/v1/commerce/agent-mandate",
        headers=headers,
        json=mandate_payload,
    )
    mandate = res.json()["data"]
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "id": "mandate_acme_shop_1",
      "agentId": "agent_coffee_webshop",
      "principalId": "contact_61f2cdb1",
      "maxPerTransaction": 75,
      "totalCap": 250,
      "currency": "USD",
      "allowedMerchants": ["org_8v1a2c"],
      "allowedCategories": ["coffee"],
      "spent": 0,
      "authorizationCount": 0,
      "status": "active",
      "consentDigest": "9a4c0e1d9b27fcb8a25fede6c1c1ab90512ef0c9...",
      "expiresAt": 1793940000000,
      "createdAt": 1793937855190,
      "updatedAt": 1793937855190,
      "revokedAt": null
    },
    "meta": {
      "request_id": "req_issue_mandate",
      "timestamp": "2026-09-04T14:32:31.590Z"
    }
  }
  ```
</ResponseExample>

### 4. Checkout complete

<Note>
  `POST /api/v1/public/commerce/acp/{storefrontId}/checkout/complete`
</Note>

The agent's retirement of the mandate into a settled checkout. The signed request is verified before any priced line is evaluated; the response is a raw ACP receipt. Merchant-side order fulfilment subscribes to the `commerce.payment_request.status_changed` webhook event.

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://api.orbit.devotel.io/api/v1/public/commerce/acp/org_8v1a2c/checkout/complete" \
      -H "Content-Type: application/json" \
      -H "Signature-Agent: agent_coffee_webshop" \
      -d '{"session":<step 2 session snapshot>,"mandate":<step 3 mandate snapshot>}'
    ```

    ```typescript Node.js theme={null}
    const body = {
      session,  // the ready_for_payment snapshot from step 2
      mandate,  // the snapshot returned at step 3
      // The catalogue cannot be supplied here: pricing runs only against
      // the merchant's stored storefront.
    };

    const res = await fetch(
      'https://api.orbit.devotel.io/api/v1/public/commerce/acp/org_8v1a2c/checkout/complete',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          // Web Bot Auth signature header carrying the agent's identity; either a
          // verified Orbit agent key or a merchant-trusted directory entry.
          'Signature-Agent': 'agent_coffee_webshop',
        },
        body: JSON.stringify(body),
      },
    );

    const receipt = await res.json();
    // receipt.session is now "completed"; receipt.order is the settled order.
    ```

    ```python Python theme={null}
    import requests

    payload = {
        "session": session,   # ready_for_payment snapshot from step 2
        "mandate": mandate,   # snapshot returned at step 3
        # The catalogue cannot be supplied here: pricing runs only against
        # the merchant's stored storefront.
    }
    res = requests.post(
        "https://api.orbit.devotel.io/api/v1/public/commerce/acp/org_8v1a2c/checkout/complete",
        headers={
            # Web Bot Auth signature header carrying the agent's identity.
            "Signature-Agent": "agent_coffee_webshop",
        },
        json=payload,
    )
    receipt = res.json()
    # receipt["session"] is "completed"; receipt["order"] is the settled order.
    ```
  </CodeGroup>
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "session": {
      "id": "sess_acme_92e4",
      "merchantId": "org_8v1a2c",
      "status": "completed",
      "currency": "USD",
      "category": null,
      "lineItems": [
        {
          "id": "sku_espresso_1l",
          "productId": "sku_espresso_1l",
          "title": "Single-origin espresso, 1 L",
          "quantity": 2,
          "unitPrice": 16.5,
          "currency": "USD",
          "lineTotal": 33
        },
        {
          "id": "sku_drip_filter_pack",
          "productId": "sku_drip_filter_pack",
          "title": "Drip filter pack, 100 ct",
          "quantity": 1,
          "unitPrice": 8,
          "currency": "USD",
          "lineTotal": 8
        }
      ],
      "itemCount": 3,
      "subtotal": 41,
      "total": 41,
      "messages": [],
      "order": {
        "id": "ord_sess_acme_92e4",
        "checkoutSessionId": "sess_acme_92e4",
        "mandateId": "mandate_acme_shop_1",
        "authorizationId": "mandate_acme_shop_1-auth-1",
        "amount": 41,
        "currency": "USD"
      },
      "createdAt": 1793937842610,
      "updatedAt": 1793937855190
    },
    "mandate": {
      "id": "mandate_acme_shop_1",
      "agentId": "agent_coffee_webshop",
      "principalId": "contact_61f2cdb1",
      "maxPerTransaction": 75,
      "totalCap": 250,
      "currency": "USD",
      "allowedMerchants": ["org_8v1a2c"],
      "allowedCategories": ["coffee"],
      "spent": 41,
      "authorizationCount": 1,
      "status": "active",
      "consentDigest": "9a4c0e1d9b27fcb8a25fede6c1c1ab90512ef0c9...",
      "expiresAt": 1793940000000,
      "createdAt": 1793937855190,
      "updatedAt": 1793937855190,
      "revokedAt": null
    },
    "authorization": {
      "authorized": true,
      "reason": "ok",
      "mandateId": "mandate_acme_shop_1",
      "authorizationId": "mandate_acme_shop_1-auth-1",
      "amount": 41,
      "reference": "sess_acme_92e4",
      "remainingCap": 209
    }
  }
  ```
</ResponseExample>

A `422` at completion carries the deny reason ("exceeds per-transaction cap", "category not allowed") so the agent can re-purchase against a corrected cart, not blind-retry the same request.
