> ## 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.

# Commerce API: cart, checkout, and pay-by-link

> Omnichannel conversational commerce with one persistent cart, pay-by-link, native WhatsApp and RCS checkout, carrier billing, and AI shopping storefronts.

# Commerce API

Commerce lets a customer start shopping on one channel and finish on another — one cart, one checkout state machine, reconciled against whichever channel actually captures the payment. It also covers the "let an AI agent buy on the customer's behalf" surfaces: scoped payment mandates, machine-to-machine metered sessions, and the storefront protocols third-party shopping agents (ChatGPT Instant Checkout, Google AP2-style agents) speak.

Every handler re-derives totals and prices server-side, so a client-forged subtotal can never pass reconciliation. Every snapshot (cart, mandate, session, subscription) is a serializable object your application round-trips in the request body — this API computes the next valid state, your application is responsible for persisting it. (The [conversational commerce checkout guide](/guides/conversational-commerce-checkout) walks the cart → checkout flow end to end; the worked samples below extend to the agent-mandate, MPP, DCB, ACP/UCP, and subscriptions clusters.)

**Base path:** `/api/v1/commerce`

**Authentication:** API key (`X-API-Key`) or session JWT.

## Cart & checkout

One cart snapshot that any channel — WhatsApp Pay, RCS catalog carousels, Apple Messages for Business, Instagram DM product shares — folds contributions into.

| Method | Path                      | Purpose                                                                        |
| ------ | ------------------------- | ------------------------------------------------------------------------------ |
| `POST` | `/cart`                   | Create a fresh, empty cart in the `browsing` state                             |
| `POST` | `/cart/merge`             | Fold a per-channel order contribution into the cart, re-deriving totals        |
| `POST` | `/cart/transition`        | Apply a checkout event (illegal transitions reject with `VALIDATION_ERROR`)    |
| `POST` | `/cart/reconcile-payment` | Match a payment captured on any channel against the cart subtotal              |
| `POST` | `/cart/checkout-channel`  | Resolve an ordered checkout-channel preference, restricted to capable channels |
| `POST` | `/cart/abandonment-check` | True when the cart has gone idle past a threshold while still recoverable      |

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/commerce/cart \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "cartId": "cart_abc123" }'
```

Response — the cart snapshot you persist and round-trip in later calls:

```json theme={null}
{
  "data": {
    "cartId": "cart_abc123",
    "state": "browsing",
    "lines": [],
    "totals": { "subtotal": 0, "currency": "USD" }
  },
  "meta": { "request_id": "req_1a2b3c4d", "timestamp": "2026-08-26T00:00:00.000Z" }
}
```

## Hosted pay-by-link

A channel-agnostic hosted checkout URL — the fallback when `checkout-channel` returns no native option (SMS, email, Telegram, Viber, or a voice-IVR readout + QR code).

| Method | Path                          | Purpose                                            |
| ------ | ----------------------------- | -------------------------------------------------- |
| `POST` | `/payment-request`            | Mint a fresh hosted PSP-checkout link              |
| `POST` | `/payment-request/render`     | Render the link's content for one specific channel |
| `POST` | `/payment-request/transition` | Advance the link's lifecycle                       |
| `POST` | `/payment-request/reconcile`  | Match a captured PSP payment against the request   |

## In-thread channel checkout

Native checkout without the buyer leaving the conversation — WhatsApp Pay where Meta supports it (India UPI, Brazil Pix, Singapore), the hosted link everywhere else; same pattern for RCS.

| Method | Path                 | Purpose                                |
| ------ | -------------------- | -------------------------------------- |
| `POST` | `/whatsapp-checkout` | Resolve an in-thread WhatsApp checkout |
| `POST` | `/rcs-checkout`      | Resolve an in-thread RCS checkout      |

Both mint the same `PaymentRequest` snapshot the `/payment-request/reconcile` route closes out.

## Agent payment mandates

The authorization layer that lets a voice or chat agent complete a purchase under a scoped, revocable, spend-capped consent the customer (the "principal") issued once — modeled on the AP2 agent-payments pattern. A mandate carries a per-transaction cap, a total spend cap, an optional merchant/category allowlist, and a tamper-evident consent digest re-verified before every spend.

| Method | Path                           | Purpose                                                                                                          |
| ------ | ------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
| `POST` | `/agent-mandate`               | Issue a fresh mandate                                                                                            |
| `POST` | `/agent-mandate/authorize`     | Dry-run a charge against a mandate without advancing spend                                                       |
| `POST` | `/agent-mandate/charge`        | Authorize **and** commit a charge (rejects out-of-scope charges)                                                 |
| `POST` | `/agent-mandate/revoke`        | Withdraw consent — terminal                                                                                      |
| `POST` | `/agent-mandate/verify`        | Recompute the consent digest; reports tamper-evidence for auditors                                               |
| `POST` | `/agent-mandate/network-token` | Mint a card-network agent token (Mastercard Agent Pay) in place of a hosted link, where the pilot is provisioned |

```bash 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": "mnd_abc123", "agentId": "agt_shopping", "principalId": "cnt_abc123",
    "maxPerTransaction": 50, "totalCap": 200, "currency": "USD",
    "allowedMerchants": ["store.example.com"],
    "allowedCategories": ["electronics"]
  }'
```

Dry-run a charge against the mandate — the decision is returned without advancing spend:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/commerce/agent-mandate/authorize \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "mandate": { "id": "mnd_abc123", "...": "the snapshot returned by /agent-mandate" },
    "charge": {
      "amount": 29.99, "currency": "USD",
      "merchant": "store.example.com", "category": "electronics",
      "reference": "order_9f8e7d"
    }
  }'
```

200 on the allow case — in-scope merchant + category, under both caps:

```json theme={null}
{
  "data": {
    "authorization": {
      "authorized": true,
      "reason": "ok",
      "authorizationId": "mnd_abc123-auth-1",
      "amount": 29.99,
      "currency": "USD",
      "merchant": "store.example.com",
      "reference": "order_9f8e7d",
      "remainingCap": 170.01
    }
  },
  "meta": { "request_id": "req_5e6f7a8b", "timestamp": "2026-08-26T00:00:00.000Z" }
}
```

The deny case also returns 200 (spend never advances); the reason names the scope check that failed — a merchant off the allowlist reports `merchant_not_allowed`:

```json theme={null}
{
  "data": {
    "authorization": {
      "authorized": false,
      "reason": "merchant_not_allowed",
      "authorizationId": "mnd_abc123-auth-1",
      "amount": 29.99,
      "currency": "USD",
      "merchant": "other-shop.example.com",
      "reference": "order_9f8e7e",
      "remainingCap": 200
    }
  },
  "meta": { "request_id": "req_9c0d1e2f", "timestamp": "2026-08-26T00:00:00.000Z" }
}
```

Other deny reasons a caller can branch on: `revoked`, `expired`, `exhausted`, `integrity_failed` (consent-digest tamper, also reported by `/agent-mandate/verify`), `invalid_amount`, `currency_mismatch`, `category_not_allowed`, `exceeds_per_transaction`, `exceeds_total_cap`. `/agent-mandate/charge` commits the same checks and surfaces the deny as a 422 `VALIDATION_ERROR`; `/agent-mandate/network-token` is a card-network pilot surface and is not live on most deployments — expect 503 `NETWORK_TOKEN_NOT_CONFIGURED` there.

## Metered agent-to-agent sessions (MPP)

A pre-authorized, spend-capped, time-bounded session for machine-to-machine consumption of your own APIs or agent services — open a session once, then meter successive micropayments against it.

| Method | Path                  | Purpose                                                                           |
| ------ | --------------------- | --------------------------------------------------------------------------------- |
| `POST` | `/mpp-session`        | Open a pre-authorized session                                                     |
| `POST` | `/mpp-session/call`   | First-contact meter + settle: 402 payment challenge, then commit with `X-PAYMENT` |
| `POST` | `/mpp-session/meter`  | Dry-run meter a call without settling                                             |
| `POST` | `/mpp-session/revoke` | Revoke a session                                                                  |
| `POST` | `/mpp-session/verify` | Verify a session's integrity                                                      |

`/mpp-session/call` is dark unless settlement is enabled for the deployment — an ungated call returns 503 `MPP_SETTLEMENT_NOT_ENABLED`. When it is on, settlement rides the x402 payment challenge: step 1 requests without a payment header and gets a 402 challenge; step 2 retries with the `X-PAYMENT` header and commits.

Step 1 — no `X-PAYMENT` header → 402 challenge (spend untouched):

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/commerce/mpp-session/call \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "session": { "id": "mpp_abc123", "...": "the snapshot returned by /mpp-session" },
    "call": { "amount": 0.02, "resource": "search.rank-product", "reference": "tool_0192" }
  }'
```

```json theme={null}
{
  "error": "X-PAYMENT header required",
  "paymentRequirements": {
    "amount": "20000",
    "resource": "search.rank-product",
    "sessionId": "mpp_abc123"
  }
}
```

Step 2 — retry with the signed payment header → 200, spend commits only once settlement succeeds:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/commerce/mpp-session/call \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -H "X-PAYMENT: <signed x402 payment payload>" \
  -d '{
    "session": { "id": "mpp_abc123", "...": "the snapshot returned by /mpp-session" },
    "call": { "amount": 0.02, "resource": "search.rank-product", "reference": "tool_0192" }
  }'
```

```json theme={null}
{
  "data": {
    "session": { "id": "mpp_abc123", "status": "active", "spent": 0.02, "callCount": 1 },
    "meter": {
      "authorized": true,
      "reason": "ok",
      "meterId": "mpp_abc123-call-1",
      "amount": 0.02,
      "resource": "search.rank-product",
      "reference": "tool_0192",
      "remainingCap": 9.98
    },
    "settlement": { "success": true, "transaction": "0xabc123", "network": "base" }
  },
  "meta": { "request_id": "req_a4b5c6d7", "timestamp": "2026-08-26T00:00:00.000Z" }
}
```

A call over the cap returns 200 with `authorized: false` (reason `exceeds_per_call_cap` or `exceeds_session_cap`) and `settlement: null` — the dry-run `/mpp-session/meter` exposes the same decision shape without asking for payment at all.

## Direct carrier billing (DCB)

Charge to the customer's mobile carrier bill instead of a card — merchant onboarding, the charge lifecycle, and settlement reconciliation on top of the carrier-billing network API.

| Method | Path                     | Purpose                                                                                                      |
| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `POST` | `/dcb/merchant`          | Onboard / reconfigure a DCB merchant (settlement currency, category, per-transaction cap, operator coverage) |
| `POST` | `/dcb/charge`            | Validate + initiate a charge against the merchant's coverage and cap                                         |
| `POST` | `/dcb/operator-request`  | Build the operator-billing request body for an initiated charge                                              |
| `POST` | `/dcb/charge/capture`    | Capture an initiated charge                                                                                  |
| `POST` | `/dcb/charge/fail`       | Mark an initiated charge failed (terminal)                                                                   |
| `POST` | `/dcb/charge/refund`     | Refund a captured charge                                                                                     |
| `POST` | `/dcb/charge/chargeback` | Record a chargeback                                                                                          |
| `POST` | `/dcb/reconcile`         | Reconcile a settlement batch                                                                                 |

Initiate a charge — the merchant snapshot you configured at `/dcb/merchant`, plus the buyer's number. Operator coverage is tenant-owned: onboard only the country/operator pairs your deployment actually supports (entries marked `pilot` are not live coverage).

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/commerce/dcb/charge \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "merchant": { "merchantId": "mch_abc123", "...": "the snapshot returned by /dcb/merchant" },
    "charge": {
      "id": "dcb_9f8e7d",
      "phoneNumber": "+15551234567",
      "country": "US",
      "operator": "ExampleMobile",
      "amount": 4.99,
      "currency": "USD",
      "description": "Game credits pack"
    }
  }'
```

```json theme={null}
{
  "data": {
    "id": "dcb_9f8e7d",
    "merchantId": "mch_abc123",
    "country": "US",
    "operator": "ExampleMobile",
    "buyerMsisdnMasked": "+1•••••4567",
    "amount": 4.99,
    "currency": "USD",
    "description": "Game credits pack",
    "status": "initiated",
    "capturedAmount": 0,
    "refundedAmount": 0
  },
  "meta": { "request_id": "req_c8d9e0f1", "timestamp": "2026-08-26T00:00:00.000Z" }
}
```

Capture it once the operator dip (shaped at `/dcb/operator-request`) comes back confirmed:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/commerce/dcb/charge/capture \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "charge": { "id": "dcb_9f8e7d", "...": "the snapshot returned by /dcb/charge" } }'
```

```json theme={null}
{
  "data": {
    "id": "dcb_9f8e7d",
    "status": "captured",
    "capturedAmount": 4.99,
    "refundedAmount": 0
  },
  "meta": { "request_id": "req_d2e3f4a5", "timestamp": "2026-08-26T00:00:00.000Z" }
}
```

## AI-shopping-agent storefronts (ACP / UCP)

The external protocol surface a third-party AI shopping agent (e.g. ChatGPT Instant Checkout) drives against your storefront: discover your catalog, open a cart/checkout session, then pay under a signed agent-payment mandate. Pricing is always re-derived server-side against your catalog, so an agent can never check out at a price you didn't publish.

| Method | Path                         | Purpose                                                         |
| ------ | ---------------------------- | --------------------------------------------------------------- |
| `POST` | `/agentic/manifest`          | Build the Agentic Commerce Protocol (ACP) discovery manifest    |
| `POST` | `/agentic/feed`              | Normalize your catalog into the ACP product feed                |
| `POST` | `/agentic/checkout`          | Open an ACP checkout session, priced server-side                |
| `POST` | `/agentic/checkout/update`   | Re-price a session for a changed item set                       |
| `POST` | `/agentic/checkout/complete` | Complete checkout under a signed payment mandate                |
| `POST` | `/ucp/manifest`              | Build the Universal Commerce Protocol (UCP) storefront manifest |
| `POST` | `/ucp/catalog`               | Build the UCP product catalog                                   |
| `POST` | `/ucp/cart`                  | Open a UCP cart session                                         |
| `POST` | `/ucp/cart/update`           | Re-price a UCP cart session                                     |
| `POST` | `/ucp/cart/complete`         | Complete a UCP cart under a signed mandate                      |

Build the ACP discovery manifest — the JSON a shopping agent fetches to learn your catalog endpoints and accepted payment methods. `baseUrl` must be an HTTPS origin your endpoint paths join onto; categories are optional.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/commerce/agentic/manifest \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "merchantId": "storefront_abc123",
    "merchantName": "Example Storefront",
    "baseUrl": "https://commerce.example.com/agentic",
    "currency": "USD",
    "categories": ["electronics", "accessories"]
  }'
```

```json theme={null}
{
  "data": {
    "protocol": "agentic-commerce",
    "protocolVersion": "2025-09-29",
    "merchant": {
      "id": "storefront_abc123",
      "name": "Example Storefront",
      "currency": "USD",
      "categories": ["electronics", "accessories"]
    },
    "capabilities": {
      "discovery": true,
      "checkoutSessions": true,
      "delegatedPayment": true
    },
    "paymentMethods": ["delegated_mandate"],
    "endpoints": {
      "productFeed": "https://commerce.example.com/agentic/feed",
      "checkoutSessionCreate": "https://commerce.example.com/agentic/checkout",
      "checkoutSessionUpdate": "https://commerce.example.com/agentic/checkout/update",
      "checkoutSessionComplete": "https://commerce.example.com/agentic/checkout/complete"
    },
    "generatedAt": 1787520000000
  },
  "meta": { "request_id": "req_1f2a3b4c", "timestamp": "2026-08-26T00:00:00.000Z" }
}
```

The UCP manifest endpoints (`/ucp/manifest`, `/ucp/catalog`) follow the same request/response shape.

## Subscriptions

Recurring "subscribe & save" orders on top of one-time checkout — a schedule tracks the interval and next-charge date; each renewal reuses the same agent-payment-mandate authorization the customer already consented to, so there's no re-prompt per cycle.

| Method | Path                    | Purpose                                                                                               |
| ------ | ----------------------- | ----------------------------------------------------------------------------------------------------- |
| `POST` | `/subscriptions`        | Start a subscription off an existing cart, authorized against a mandate                               |
| `POST` | `/subscriptions/list`   | Annotate + sort subscriptions by next-charge date                                                     |
| `POST` | `/subscriptions/pause`  | Pause — the renewal executor refuses to charge until resumed                                          |
| `POST` | `/subscriptions/resume` | Resume (re-anchors a past-due next-charge date one interval out, never firing a missed-cycle backlog) |
| `POST` | `/subscriptions/cancel` | Cancel — terminal                                                                                     |
| `POST` | `/subscriptions/renew`  | Fire a due renewal: charge the mandate, mint the reorder's checkout link, advance the schedule        |

Fire a due renewal — the subscription snapshot, the mandate it draws on, and the checkout config (hosted link base URL + merchant). `interval` is one of `weekly`, `biweekly`, `monthly`, `quarterly`; `channel` is `whatsapp` or `rcs`.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/commerce/subscriptions/renew \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "subscription": { "id": "sub_abc123", "...": "the snapshot returned by /subscriptions" },
    "mandate": { "id": "mnd_abc123", "...": "the snapshot returned by /agent-mandate" },
    "config": {
      "hostedBaseUrl": "https://pay.example.com/checkout",
      "merchant": "store.example.com"
    }
  }'
```

Success — mandate charged for the cycle, reorder checkout link minted, schedule advanced, and a `commerce.subscription.renewed` webhook payload returned for you to dispatch:

```json theme={null}
{
  "data": {
    "subscription": {
      "id": "sub_abc123",
      "status": "active",
      "cyclesCompleted": 3,
      "nextChargeAt": 1787520000000
    },
    "mandate": { "id": "mnd_abc123", "status": "active", "spent": 149.97 },
    "checkout": {
      "checkoutId": "sub_abc123-renewal-3",
      "hostedUrl": "https://pay.example.com/checkout/sub_abc123-renewal-3"
    },
    "webhookEvent": {
      "event": "commerce.subscription.renewed",
      "subscriptionId": "sub_abc123",
      "cycleNumber": 3,
      "amount": 49.99,
      "currency": "USD",
      "channel": "whatsapp",
      "chargeReference": "sub_abc123-cycle-3"
    }
  },
  "meta": { "request_id": "req_6b7c8d9e", "timestamp": "2026-08-26T00:00:00.000Z" }
}
```

Renewing a `paused` (or not-yet-due) subscription is refused with a 422 `VALIDATION_ERROR` — the pause gate exists so a renewal can fire only an explicit, consented charge:

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "status": 422,
    "message": "cannot renew a subscription in status \"paused\""
  },
  "meta": { "request_id": "req_f0a1b2c3", "timestamp": "2026-08-26T00:00:00.000Z" }
}
```

The schedule is left untouched on any refusal, so you can retry after resume or a mandate fix.

## See also

* [Conversational commerce checkout guide](/guides/conversational-commerce-checkout) — end-to-end walkthrough of the cart → checkout flow; this reference page covers the agent-mandate, MPP, DCB, ACP/UCP, and subscriptions clusters not covered there
* [WhatsApp channel](/channels/whatsapp) and [RCS channel](/channels/rcs) — the native catalog/order capture these checkouts bind to
* [Messaging API](/api-reference/endpoints/messaging) — sending the payment link once rendered
