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

# Conto Commerce API

> Start a purchase from a product URL and task, follow it, and get a merchant-confirmed result.

Conto Commerce lets your platform's agents buy from online stores for your users. A purchase goes from a product URL and a task to a merchant-confirmed order under one purchase id. Your platform starts it and follows it with one organization API key. Conto shops, prepares checkout and pays with the buyer's own card in the background. The buyer reviews every purchase and verifies their card on a Conto page. Your platform and your agents never receive card details.

Test mode is open to every organization: start with the [quickstart](/docs/guides/commerce-quickstart). Live purchases need Conto to enable them for your organization, and live cards need a Builder or Enterprise plan. The SDK methods here need an `@conto_finance/sdk` release that includes `admin.purchasing.purchases`. Amounts are positive integers in minor units with an uppercase currency: `{ amountMinor: 1200, currency: 'USD' }` is **\$12.00**. Purchases support USD only.

## Credentials

| Who | Credential | Uses |
| - | - | - |
| Your platform | Organization API key with `ContoAdmin` | Buyers, setup links, purchases, answers to delivery and contact forms, action links |
| The buyer | A Conto page opened from a single-use link your platform creates | Limits, card, permission for the agent, purchase review, card verification |
| An agent that calls Conto itself, if any | That agent's SDK key with `Conto` | Start, read, answer, follow and cancel its own buyer's purchases |

Neither key can add a card, grant permission to shop or approve a purchase. Those happen on the buyer's Conto page. An agent key also cannot create buyers or see another buyer's purchases. A buyer's card is paid only through purchases: `conto.connectedCards.list()` leaves it out and `draw()` refuses it.

## Set up a buyer

Map one of your users to a buyer and send the buyer to the hosted setup page.

```typescript theme={null}
import { ContoAdmin } from '@conto_finance/sdk';

const admin = new ContoAdmin({ orgApiKey: process.env.CONTO_ORG_API_KEY! });

// Return links must use an origin you list here.
await admin.purchasing.settings.update({ allowedReturnOrigins: ['https://app.example.com'] });

const buyer = await admin.purchasing.buyers.create({
  externalId: 'user_8842',
  email: 'buyer@example.com',
});

const link = await admin.purchasing.paymentMethods.createSetupLink({
  buyerId: buyer.id,
  returnUrl: 'https://app.example.com/settings/purchasing',
});
// Redirect the buyer to link.url. It works once and expires in 10 minutes.
```

On the setup page the buyer confirms per-purchase and daily limits, adds a card in the card provider's secure fields, and lets their agent shop with it. That creates a **delegation**. Read it with `admin.purchasing.delegations.list(buyer.id)`. The buyer or your platform can revoke it.

A delegation covers one card and the buyer's limits. When the buyer changes their limits, the delegation is revoked and the buyer grants it again. Each purchase is still authorized for one merchant and one total, reviewed by the buyer and verified by the card.

## Start and follow a purchase

```typescript theme={null}
const purchases = admin.purchasing.purchases;

const purchase = await purchases.create(
  {
    buyerId: buyer.id,
    request: {
      url: 'https://shop.example.com/products/tee',
      task: 'Buy one black medium shirt with standard shipping',
    },
    maxCost: { amountMinor: 7500, currency: 'USD' },
  },
  { idempotencyKey: 'customer-order-1042' }
);

for await (const event of purchases.watch(purchase.id)) {
  render(event.data);
}
```

The purchase runs as the buyer's agent. Without `payment`, Conto uses the buyer's one delegation for this kind of store: a test card for a Conto test store, a live card otherwise. When the buyer has more than one, pass `payment: { methodId, delegationId }` from `admin.purchasing.delegations.list(buyer.id)`.

`create()` returns at once. Reusing the idempotency key with the same input returns the same purchase, so it is safe to retry after a lost response. A different input with the same key is refused with `IDEMPOTENCY_CONFLICT`.

`maxCost` is the most the delivered order may cost. It narrows the delegation and never widens it. When you know the exact product, pass `request.items` with each URL, title, quantity and options.

`watch()` yields every event in version order until the purchase ends. It streams server-sent events, reconnects from the last version, drops a duplicate after a reconnect, and falls back to polling where streaming is unavailable. Closing your process does not stop the purchase. Start `watch()` again with `{ after: lastVersion }` to resume.

### From an agent's own key

An agent that calls Conto itself, for example through the [MCP server](/docs/mcp/tools), uses its own SDK key. The purchase methods are the same:

```typescript theme={null}
import { Conto } from '@conto_finance/sdk';

const conto = new Conto({ apiKey: process.env.CONTO_AGENT_KEY! });
const purchase = await conto.purchases.create(input, { idempotencyKey: 'customer-order-1043' });
```

Issue the key with `admin.agents.createSdkKey(buyer.agentId, { name: 'Purchasing agent' })`. It reaches that buyer's purchases only.

## Status, phase and evidence

| `status` | Meaning |
| - | - |
| `queued` | Saved, not started yet |
| `running` | Shopping, preparing checkout or authorizing |
| `awaiting_action` | Waiting on `requiredAction` |
| `submitting` | Paying at the store |
| `reconciling` | The outcome is being confirmed. Do not start the purchase again |
| `succeeded` | The merchant confirmed the order |
| `blocked` | A spending control denied it |
| `failed` | It ended without an order, see `reasonCode` |
| `cancelled` | It stopped before payment |

`version` increases by one with every visible change. `paymentEvidence` reports what is known about card authorization, the merchant's confirmation and settlement separately. Evidence that is missing is `unknown`, never a guess. Settlement is always `unknown` here: the card provider reports issuance only.

## Actions

When `status` is `awaiting_action`, `requiredAction` says what is needed and who completes it.

| `kind` | `authority` | Completed by |
| - | - | - |
| `buyer_details` | `agent_or_buyer` | Your platform with `purchases.respond()`, or the buyer |
| `purchase_review` | `buyer` | The buyer on the Conto page |
| `card_verification` | `cardholder` | The buyer, with their card issuer |
| `merchant_signin`, `merchant_challenge` | `buyer` | The buyer, in a secure store browser |
| `organization_approval` | `organization` | A reviewer in your Conto dashboard |

Answer `buyer_details` with the fields the action lists for its `form`:

```typescript theme={null}
const action = purchase.requiredAction;
if (action?.authority === 'agent_or_buyer') {
  const ack = await purchases.respond(purchase.id, {
    actionId: action.id,
    revision: action.revision,
    fields: deliveryDetails,
  });
  // ack.status is accepted. The purchase shows the result.
}
```

The buyer sees these details on the review page and approves them with the total. An identical answer again returns the same acknowledgment. A different answer to the same action is refused with `RESPONSE_CONFLICT`. An expired or replaced action is refused with `ACTION_EXPIRED` or `ACTION_NOT_OPEN`.

For the buyer's actions, create a single-use link on your server and send the buyer there:

```typescript theme={null}
const link = await purchases.createActionLink(purchase.id, {
  returnUrl: 'https://app.example.com/orders',
});
```

`requiredAction.url` points at the same Conto page. It opens only for a buyer who entered through a link your platform created, so it is safe to show.

## Events and webhooks

| Read | Use |
| - | - |
| `purchases.events(id, { after })` | Durable history after a version |
| `purchases.watch(id)` | Stream with reconnect and polling fallback |
| Organization webhook | Each event, signed, retried |

A cursor ahead of the purchase returns `CURSOR_INVALID`. `watch()` handles it by yielding a `purchase.resync` event with the current purchase.

Webhooks go to your organization's webhook URL with the event type in `X-Conto-Event` and the event id in `X-Conto-Delivery-Id`. The id is the same on every retry, so deduplicate on it. Verify each delivery with the raw body:

```typescript theme={null}
import { verifyPurchaseWebhook } from '@conto_finance/sdk';

const delivery = verifyPurchaseWebhook({
  body: rawBody,
  signature: request.headers['x-conto-signature'],
  secret: process.env.CONTO_WEBHOOK_SECRET!,
});
```

It refuses a bad signature and a delivery older than five minutes.

## Cancel

```typescript theme={null}
const result = await purchases.cancel(purchase.id);
```

Before payment the purchase stops and `cancellationOutcome` is `cancelled` or `requested`. Once payment may have been submitted it is `too_late` and the purchase stays in reconciliation until its outcome is known. Cancelling never refunds or reverses a payment.

## Errors

Refusals carry a stable `code`, a `requestId` and whether retrying the same request can succeed later.

| `code` | Meaning |
| - | - |
| `DELEGATION_INSUFFICIENT` | `maxCost` is above what the buyer authorized |
| `DELEGATION_REVOKED` | The buyer or your platform removed the agent's permission |
| `DELEGATION_NOT_FOUND` | The buyer has not granted permission for this kind of store |
| `PAYMENT_METHOD_REQUIRED` | The buyer has more than one card for it. Pass `payment` |
| `PAYMENT_METHOD_ENVIRONMENT_MISMATCH` | A test card at a real store, or a live card at a test store |
| `CURRENCY_UNSUPPORTED` | Not USD |
| `PURCHASE_IN_PROGRESS` | The buyer already has an open purchase |
| `IDEMPOTENCY_CONFLICT` | The key was used for a different purchase |
| `ACTION_AUTHORITY` | This credential cannot complete that action |
| `CREATION_PAUSED` | New purchases are paused. Reads and cancellation still work |
| `LIVE_NOT_ENABLED` | Live purchases are not enabled for your organization yet |
| `AGENT_LIMIT_REACHED` | Your plan's agent limit is reached. Each buyer uses one |

## Test purchases

Test mode is open to every organization. Create a setup link with `environment: 'test'`. The buyer adds a test card instead of a real one. A test card pays only Conto's test stores and a live card never does:

| Store | Products |
| - | - |
| `https://supplies.conto-test.invalid` | `/products/paper-ream`, `/products/notebook-pack`, `/products/desk-lamp` |
| `https://cloud.conto-test.invalid` | `/products/compute-10`, `/products/storage-month`, `/products/compute-50` |

A test purchase runs the same limits, policies, reviews and actions as a live one, and its authorization is recorded as simulated. No card network, card provider or store is involved, and nothing is charged.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.