# Conto Documentation - Complete Reference
> Conto checks an AI agent's payments against its owner's spending limits, allowed recipients, and approval rules before the agent pays.
## Quick Reference
- Base URL: https://conto.finance
- API paths start with /api, for example https://conto.finance/api/sdk/setup
- OpenAPI Spec: https://conto.finance/api/openapi
- SDK Package: @conto_finance/sdk
- Dashboard: https://conto.finance
---
# What is Conto?
Source: https://conto.finance/docs/introduction/overview
Your agent asks Conto before it pays. Conto approves the payment, holds it for a person, or denies
it with a reason. Payments use [supported stablecoins](https://conto.finance/docs/guides/choose-your-integration#networks) or, on Builder
and Enterprise plans, connected cards.
## Start with a payment
### Make your first payment
Link: https://conto.finance/docs/quickstart/setup
Create an agent and funded Tempo Testnet wallet, then verify an onchain payment.
### Try the agent sandbox
Link: https://conto.finance/docs/quickstart/agent-sandbox
See approved and denied decisions without an account, signer, or movement of funds.
## Where Conto fits
Conto only checks payments that go through it. If your agent holds its own wallet keys, it can
still send funds without asking Conto. See [wallet ownership and custody](https://conto.finance/docs/guides/custody-modes).
---
# Quickstart
Source: https://conto.finance/docs/quickstart/setup
Create an agent, a spending policy, an SDK key, and a funded wallet on **Tempo Testnet**, a free test network.
Then send `0.01 pathUSD`, check its settlement, and see how Conto handles a payment that needs
approval and one it denies.
## Choose your first test
| Path | What you test | Funds and signer |
| ---------------------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------- |
| **This guide: Tempo Testnet** | Policy checks and an actual onchain transfer | Free faucet tokens. Conto manages signing |
| [Anonymous sandbox](https://conto.finance/docs/quickstart/agent-sandbox) | API decisions, configurable controls, and test receipts | Simulated settlement. No signer or funded wallet needed |
## 1. Create your project
You need Node.js 20.19+, npm, an interactive terminal, and a browser. Sign in or create a Conto
account when the wizard opens the browser.
```bash
mkdir conto-first-payment
cd conto-first-payment
npx @conto_finance/create-conto-agent
```
Enter an agent name and type, keep the default daily limit, and choose **Tempo Testnet** (chain `42431`). The wizard creates the
agent, managed wallet, policy, and SDK key, then funds the wallet with faucet `pathUSD`.
It writes `.env.local`, `.gitignore`, `conto.config.json`, and `example.mjs`, and installs the CLI locally.
Store the generated credentials securely. To create each resource yourself, [set up in the
dashboard instead](#set-up-in-the-dashboard-instead). For CI without a browser, use the
[agent sandbox quickstart](https://conto.finance/docs/quickstart/agent-sandbox).
## 2. Verify the wallet before sending
From the same directory:
```bash
npx @conto_finance/create-conto-agent doctor
```
Confirm the agent is active, the wallet chain is `42431`, the balance is positive, and the key has
`payments:request` and `payments:execute`. Fix any failed checks before continuing.
## 3. Request and execute one payment
```bash
npx @conto_finance/create-conto-agent pay 0x0000000000000000000000000000000000000001 0.01 --json
```
The command requests a `0.01 pathUSD` payment to a test recipient, stops if controls do not approve
it, and executes an approved request. Save the returned `requestId` and `receipt`. Each new invocation creates a
new payment intent. Use the original ID to inspect an uncertain outcome.
The response puts the transaction ID, transaction hash, and explorer URL under
`receipt`. A submitted testnet transfer has `status: "processing"` and
`receipt.settlementMode: "live"`: here **live means an onchain transfer**, including testnet.
An anonymous sandbox receipt uses `settlementMode: "test"` instead.
## 4. Confirm settlement
Run `export CONTO_API_KEY="$(grep '^CONTO_API_KEY=' .env.local | cut -d= -f2-)"`, then replace `REQUEST_ID`
with the ID returned above:
```bash
curl -sS https://conto.finance/api/sdk/payments/REQUEST_ID \
-H "Authorization: Bearer $CONTO_API_KEY"
```
Completion means `status: "completed"` and `transaction.status: "completed"`. Check `transaction.chain.id` is `"42431"`, and open `transaction.explorerUrl` to inspect the transfer on Tempo Testnet.
While it is `processing`, wait and check again. Do not repeat execution. See
[track and recover a payment](https://conto.finance/docs/guides/payment-lifecycle) for bounded polling and error handling.
## 5. See approval and denial
New organizations start with two starter policies, linked to every new agent:
- Up to 10 pathUSD: approved.
- Over 10 pathUSD: waits for a person to approve.
- Over 25 pathUSD, or over 100 pathUSD in a day: denied.
Check two larger amounts. Neither amount is approved automatically, so nothing is sent:
```bash
npx @conto_finance/create-conto-agent pay 0x0000000000000000000000000000000000000001 12 --json
npx @conto_finance/create-conto-agent pay 0x0000000000000000000000000000000000000001 30 --json
```
The first returns `REQUIRES_APPROVAL` and sends the owner a review request. Approving it sends the
payment, so deny it in **Alerts & Approvals** or let it expire. The second returns `DENIED` with
the reason in `reasons`. Edit or replace the starter policies in **Policies**.
## If the result needs attention
| Result | Next action |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DENIED` | Read `reasons`, then inspect wallet limits and applicable policies |
| Every payment `DENIED` after you edit wallet limits | A wallet limit of `0` blocks all payments through that link. On the agent detail page, set a positive cap or clear the field to remove the cap |
| `DENIED` when you expected `REQUIRES_APPROVAL` | Policies combine with AND logic. If one policy denies while another requires approval, the denial wins. Check the agent's **Permissions** tab for every assigned policy |
| `REQUIRES_APPROVAL` | Approve or deny it from the owner's email or **Alerts & Approvals**, then check the original request ID |
| Timeout or disconnect during execution | Check the original request's status before any further write |
| Insufficient balance (`INSUFFICIENT_BALANCE`) | Confirm chain and wallet, fund with the testnet faucet, then inspect request state |
| `AUTH_FAILED` | The SDK key is invalid or expired. Generate a new one from the agent detail page |
## Set up in the dashboard instead
### Create your account
Sign up at [conto.finance](https://conto.finance) with your email and a strong password, then
create your **organization**. It is the top-level container for agents, wallets, and policies.
### Create and fund a wallet
Go to **Wallets > Create Wallet**.
| Field | Value |
|---|---|
| Wallet Name | `Test Operations Wallet` |
| Blockchain Network | **EVM Chains**, then Tempo Testnet (`42431`) |
| Wallet Type | Standard Wallet (EOA) |
| Ownership | **Conto-managed**, or **Organization-controlled** if you added an owner key |
Conto assigns an onchain address when it creates the managed wallet. The wallet belongs to your
organization, and Conto controls signing. You get no separate wallet-provider account or direct
key access.
To fund it, open the wallet's menu on the **Wallets** page, choose **Deposit**, then click
**Request Testnet Tokens**. Free testnet `pathUSD` arrives in seconds. Tempo Testnet charges gas
in `pathUSD`, so you need no separate gas token.
To register a wallet your agent already signs for, use **Wallets > Import Watch-Only** instead.
For a Solana address, choose the Solana network in the import dialog. For other custody modes
(`EXTERNAL`, `SMART_CONTRACT`), see [wallet custody](https://conto.finance/docs/guides/custody-modes).
### Connect an agent
Go to **Agents > Connect Agent**. Enter a name such as `Test Payment Agent`, the type `CUSTOM`
(or your framework), and a description such as `Quickstart test agent`. Click **Connect
Agent**. The agent starts in `ACTIVE` status.
To create the agent with the API instead, use an organization API key from **Settings > API
Keys**. To choose the owner, fetch a membership ID:
```bash
curl https://conto.finance/api/organizations/me/members \
-H "Authorization: Bearer $CONTO_ORG_API_KEY"
```
Pick `members[].id` for the org member or service account that should own the agent, then
create the agent. If you omit `ownerMembershipId`, Conto assigns an organization owner.
```bash
curl -X POST https://conto.finance/api/agents \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Test Payment Agent",
"agentType": "CUSTOM",
"description": "Quickstart test agent",
"ownerMembershipId": "cmm59z8fj000l49h7bjqza11p"
}'
```
Valid `agentType` values: `OPENAI_ASSISTANT`, `ANTHROPIC_CLAUDE`, `AUTOGPT`, `CUSTOM`. Agent
statuses: `ACTIVE`, `PAUSED`, `SUSPENDED`, `REVOKED`.
### Link the wallet to the agent
On the agent detail page, open the **Wallets** tab, click **Link Wallet**, and pick the Tempo
Testnet wallet. If the organization has no wallet to link yet, click **Create testnet wallet**
in the dialog, then **Get test tokens** to fund it.
Set spending limits. Recommended for this test:
| Setting | Value |
|---|---|
| Per Transaction | `50` |
| Daily Limit | `500` |
| Weekly Limit | `2000` |
| Monthly Limit | `5000` |
Warning:
A limit of `0` blocks every payment through this wallet link. Clear the field for no cap.
[Defaults](https://conto.finance/docs/reference/defaults) lists the default limits, time windows, and other agent-wallet
link defaults.
### Generate an SDK key
On the agent detail page, open the **SDK Integration** tab and click **Generate SDK Key**. Name
it (for example `Testnet Key`) and keep the default **Standard** preset and the default
expiration of 1 year. [Keys and scopes](https://conto.finance/docs/sdk/authentication) explains the presets.
The full key is shown once. Save it to your environment immediately:
```bash
export CONTO_API_KEY="conto_agent_your_key_here"
```
### Verify the setup
```bash
curl https://conto.finance/api/sdk/setup \
-H "Authorization: Bearer $CONTO_API_KEY"
```
Check that:
- the agent is available for payment calls
- the setup summary includes your Tempo Testnet wallet and balance
- `scopes` includes `payments:request` and `payments:execute`
## Next steps
To write your own rules, see [test allow, review, and deny decisions](https://conto.finance/docs/guides/policy-testing).
To integrate it into application code, [install the SDK](https://conto.finance/docs/sdk/installation). To test an existing
agent and wallet from code, open [Developers](https://conto.finance/developers) and use **Test this
agent's integration**. Before moving real funds, complete
[production readiness](https://conto.finance/docs/guides/production-readiness).
---
# Try the sandbox
Source: https://conto.finance/docs/quickstart/agent-sandbox
Try Conto without an account, from a terminal or an autonomous agent. A new sandbox starts with
default limits of $5 per payment and $25 per day. A payment within them returns `APPROVED`, and one
above them returns `DENIED`. The sandbox setup key lets you change those limits and add policies,
including merchant and category rules and a rule that holds a payment for human approval. If an
organization owner has already invited your agent,
[register with their token](https://conto.finance/docs/quickstart/connecting-agents#register-an-agent-in-an-existing-organization)
instead.
Sandbox signup creates a test-mode organization, one agent, one Tempo Testnet wallet, an agent SDK
key, a sandbox organization API key, and a sandbox setup key, with no email verification or human
approval. Settlement is simulated: no onchain transfer occurs, a test receipt is not an explorer
receipt, and you do not need to fund the wallet.
Warning:
The anonymous sandbox is for testing only. Returned credentials are shown once and expire after
7 days.
## Discovery
Start from the agent manifest:
```bash
curl -sS https://conto.finance/.well-known/agent.json
```
Read these fields:
| Field | Purpose |
| ---------------------------------------- | ------------------------------------------------- |
| `machineReadable.agentSandboxQuickstart` | This guide |
| `machineReadable.agentSandboxSignup` | Anonymous sandbox creation endpoint |
| `machineReadable.agentSandboxHandoff` | Browser handoff for a person to claim the sandbox |
| `machineReadable.agentSandboxClaim` | Human claim endpoint |
| `machineReadable.openapi` | OpenAPI schema for request and response shapes |
| `machineReadable.llms` | Compact agent-readable docs index |
If your runtime skips manifests, use the endpoint directly:
```bash
curl -sS https://conto.finance/api/agents/sandbox
```
The descriptor includes `environment`, simulated-settlement semantics, the default wallet limits,
and a machine-readable `workflow` with setup, request, execute, status, human-claim, and
control-configuration actions. Each control action names the credential it needs. Use those links
instead of building route names from memory.
## Create a sandbox
One command creates the sandbox, writes an owner-only `.env.local`, updates `.gitignore`, and adds
`conto.config.json` and a runnable `example.mjs` to the current directory:
```bash
npx @conto_finance/create-conto-agent --sandbox --json
```
`.env.local` holds the agent SDK key and the sandbox API key. The CLI writes the setup key to a
separate owner-only file, `.conto/sandbox-setup.env`, and adds `.conto/` to `.gitignore`, so the
payment runtime that loads `.env.local` never receives it. The JSON result reports file locations
and environment-variable names (`credentials.setupKeyStoredIn` and `credentials.setupKeyEnv`) but
prints no secret. If the result has no `setupKeyStoredIn`, your CLI version predates setup keys.
Update the package, or create the sandbox with the API call below.
To test from Python 3.10+, run the CLI command above (it needs Node.js 20.19+), then use the Python
starter from the same directory. It calls Conto's HTTP API and needs no Python dependencies.
1. Download it: `curl -fsS https://conto.finance/examples/conto-sandbox.py -o conto-sandbox.py`
2. Run `python3 conto-sandbox.py` to check setup. It prints the agent name and scopes and creates
no payment.
3. Run `python3 conto-sandbox.py --execute` to make one test payment. The script executes only in a
CLI-created simulated sandbox. It creates one payment intent for `0.01`, prints its request ID, and executes only if the policy decision is `APPROVED`.
Success prints `Status: completed` and `Settlement mode: test`. The test receipt records the
workflow without transferring funds.
4. Each `--execute` run creates a new intent. After a timeout, inspect the original request with
`python3 conto-sandbox.py --status REQUEST_ID` instead of executing again. In production, store
the request ID before executing.
To create the sandbox without the CLI, call the endpoint directly:
```bash
curl -sS -X POST https://conto.finance/api/agents/sandbox \
-H "Content-Type: application/json" \
-d '{
"name": "Research Buyer Agent",
"agentType": "CUSTOM",
"description": "Tests policy-checked payments from an autonomous agent"
}' > conto-sandbox.json
```
The endpoint's response includes:
| Response field | Save it as | Notes |
| ---------------------- | ------------------------- | ------------------------------------------------------------------ |
| `credentials.sdkKey` | `CONTO_API_KEY` | Bearer token for Conto's agent SDK. The payment runtime's only key |
| `credentials.apiKey` | `CONTO_SANDBOX_API_KEY` | Read-only sandbox organization key. Used for handoff and claim |
| `credentials.setupKey` | `CONTO_SANDBOX_SETUP_KEY` | Configures this sandbox's controls. Keep it out of the agent |
| `agent.id` | `CONTO_AGENT_ID` | Created agent ID |
| `wallet.id` | `CONTO_WALLET_ID` | Created wallet ID |
| `wallet.address` | None | Placeholder address. No one holds its key |
| `wallet.custodyMode` | None | `EXTERNAL`. Sandbox execution is simulated, so no signer is needed |
| `environment` | None | `sandbox`. No production funds are used |
| `settlement.mode` | None | `simulated`. Receipts are recorded without moving funds |
| `workflow` | None | Supported next-step methods and URLs |
Example extraction into one owner-only file per role:
```bash
umask 077
jq -r '"CONTO_API_KEY=\(.credentials.sdkKey)"' conto-sandbox.json > agent.env
jq -r '"CONTO_SANDBOX_SETUP_KEY=\(.credentials.setupKey)",
"CONTO_SANDBOX_API_KEY=\(.credentials.apiKey)"' conto-sandbox.json > setup.env
export CONTO_AGENT_ID="$(jq -r '.agent.id' conto-sandbox.json)"
export CONTO_WALLET_ID="$(jq -r '.wallet.id' conto-sandbox.json)"
rm conto-sandbox.json
```
Load `agent.env` in the payment agent's runtime and `setup.env` only where you configure controls.
The commands below assume the relevant file is loaded, for example with `set -a; . ./setup.env;
set +a`.
## Credential roles
| Credential | Prefix | Held by | Can | Cannot |
| --------------- | -------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| SDK key | `conto_agent_` | The payment agent | Read its effective controls, request payments, execute approved payments, check payment status | Change controls, approve a held payment, claim the sandbox |
| Setup key | `conto_setup_` | The person or coding assistant preparing the test | Read and change this sandbox's policies, policy rules, policy assignments, and agent-wallet spend limits | Request, execute, or approve payments. Create wallets or keys. Change team, billing, or settings. Reach another organization |
| Sandbox API key | `conto_` | Whoever offers the claim link | Read the sandbox organization, create a claim handoff | Write anything |
| Human owner | Signed-in user | The person who claims the sandbox | Review held payments, edit controls in Conto | Turn the sandbox into a live organization |
All keys are stored hashed and expire with the sandbox after 7 days. A human claim revokes the
setup key. From then on, the owner changes controls in Conto.
A coding assistant can apply the test configuration a person asked for with the setup key, then run
the payment agent with only the SDK key. The setup key never belongs in the agent's environment,
prompt, or tool configuration.
## Configure controls
The setup key works only on these routes, only for its own sandbox, and only until a human claims
it. Other organization routes return `403` with `code: "SANDBOX_SETUP_SCOPE"`, and payment routes
reject it. An ID from another organization returns `404`.
| Method | Route | Purpose |
| ---------------------- | ----------------------------------------------- | ------------------------------------------------- |
| `GET`, `PATCH` | `/api/agents/{agentId}/wallets/{walletId}` | Read and change spend limits and allowed hours |
| `GET` | `/api/agents/{agentId}/wallets` | List the agent's wallet links |
| `GET`, `POST` | `/api/policies` | List and create policies |
| `GET`, `PATCH` | `/api/policies/{policyId}` | Read, rename, reprioritize, activate, deactivate |
| `GET`, `POST`, `PUT` | `/api/policies/{policyId}/rules` | Read, add, or replace a policy's rules |
| `GET`, `PATCH`, `DELETE` | `/api/policies/{policyId}/rules/{ruleId}` | Read, change, or remove one rule |
| `GET` | `/api/policies/{policyId}/versions` | Read the policy's change history |
| `GET`, `POST`, `DELETE` | `/api/agents/{agentId}/policies` | Read, assign, and unassign the agent's policies |
These are the same routes, validators, audit log, and policy history that account-backed
organizations use. See [Policy rules](https://conto.finance/docs/policies/overview) for every rule type.
### Change spend limits
```bash
curl -sS -X PATCH "https://conto.finance/api/agents/$CONTO_AGENT_ID/wallets/$CONTO_WALLET_ID" \
-H "Authorization: Bearer $CONTO_SANDBOX_SETUP_KEY" \
-H "Content-Type: application/json" \
-d '{ "spendLimitPerTx": 20, "spendLimitDaily": 60 }'
```
The setup key can send `spendLimitPerTx`, `spendLimitDaily`, `spendLimitWeekly`,
`spendLimitMonthly`, `allowedHoursStart`, `allowedHoursEnd`, `allowedDays`, and `timezone`. Limits
accept `0` through `10000`, or `null` for no limit on that window. Other fields, such as `isActive`
or `delegationType`, return `403`. Wallet limits are hard limits: an approval rule cannot override
them.
### Restrict merchants and categories
Create a policy and assign it to the agent:
```bash
curl -sS -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_SANDBOX_SETUP_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Approved merchants",
"policyType": "COUNTERPARTY",
"scope": "assigned",
"rules": [
{
"ruleType": "ALLOWED_COUNTERPARTIES",
"operator": "IN_LIST",
"value": "[{\"type\":\"address\",\"value\":\"0x1111111111111111111111111111111111111111\",\"label\":\"Merchant A\"}]",
"action": "ALLOW"
},
{
"ruleType": "BLOCKED_CATEGORIES",
"operator": "IN_LIST",
"value": "[\"gambling\"]",
"action": "DENY"
}
]
}' > policy.json
curl -sS -X POST "https://conto.finance/api/agents/$CONTO_AGENT_ID/policies" \
-H "Authorization: Bearer $CONTO_SANDBOX_SETUP_KEY" \
-H "Content-Type: application/json" \
-d "{ \"policyId\": \"$(jq -r '.id' policy.json)\" }"
```
Counterparty rules match the payment's `recipientAddress`. Category rules match the `category`
the agent sends with the request. That value is agent-supplied, not a verified merchant category. A payment that omits `category` is denied by any category rule.
### Require human approval
```bash
curl -sS -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_SANDBOX_SETUP_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Review purchases over $10",
"policyType": "APPROVAL_THRESHOLD",
"scope": "assigned",
"rules": [
{
"ruleType": "REQUIRE_APPROVAL_ABOVE",
"operator": "GREATER_THAN",
"value": "10",
"action": "REQUIRE_APPROVAL"
}
]
}'
```
Assign it the same way. A rule's `action` defaults to `ALLOW`, so set `REQUIRE_APPROVAL`
explicitly. Keep the per-payment limit above the approval threshold, or the limit denies the
payment before review.
### Change or turn off a policy
Replace a policy's rules with `PUT /api/policies/{policyId}/rules` and a `rules` array, or turn
the policy off with `PATCH /api/policies/{policyId}` and `{ "isActive": false }`. A change applies
to the next evaluation. Policy and rule changes are recorded in the policy's history and the audit
log. Limit and assignment changes are recorded in the audit log.
### Quotas and the simulated balance
A sandbox holds up to 25 policies with up to 50 rules each. Reaching a quota returns `409` with
`code: "SANDBOX_POLICY_QUOTA"` or `"SANDBOX_RULE_QUOTA"`. The wallet's $100 test balance is fixed,
and the setup key cannot add to it. A payment larger than the remaining balance is denied with
the reason `The available balance is insufficient for this payment.`, not a spending-limit
reason.
### What the agent sees
The payment agent reads its effective controls with its own SDK key: `GET /api/sdk/setup`,
`GET /api/sdk/spending-limits`, and `GET /api/sdk/policies`. It cannot change them.
## Inspect the setup
Use the SDK key returned by sandbox signup:
```bash
curl -sS https://conto.finance/api/sdk/setup \
-H "Authorization: Bearer $CONTO_API_KEY"
```
This returns the authenticated agent, available wallets, spend limits, granted scopes, and
versioned [setup diagnostics](https://conto.finance/docs/sdk/installation#setup-diagnostics). Call it as the runtime probe
before any payment operation. In a new sandbox, `diagnostics.environment.kind` is
`anonymous_sandbox`, and the `SANDBOX_UNCLAIMED` issue tells you a person has not claimed it yet.
After a claim, the kind is `claimed_sandbox`. In a CLI-created project,
`npx @conto_finance/create-conto-agent doctor --json` prints the same diagnostics.
## Run a policy-checked payment
Request a small test payment. Conto evaluates it and returns the decision in `status`:
```bash
curl -sS -X POST https://conto.finance/api/sdk/payments/request \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 3,
"recipientAddress": "0x0000000000000000000000000000000000000001",
"recipientName": "Test Vendor",
"purpose": "Sandbox API credits",
"category": "AI_SERVICES"
}' > payment-request.json
export REQUEST_ID="$(jq -r '.requestId' payment-request.json)"
```
If the status is `APPROVED`, execute it:
```bash
curl -sS -X POST "https://conto.finance/api/sdk/payments/$REQUEST_ID/execute" \
-H "Authorization: Bearer $CONTO_API_KEY"
```
Success is an execute response with `"status": "completed"` and `receipt.settlementMode: "test"`.
The receipt includes a Conto `transactionId` and omits `txHash` and the explorer URL. Status and
transaction endpoints repeat the settlement mode. `test` never proves an onchain transfer.
`GET /api/sdk/transactions` includes the transaction, which counts against the configured spend
limits. With the default limits, a request over $5, or over $25 in a day, returns `DENIED` with a
reason. A denied request never executes and never spends.
To make retries safe, send an `idempotencyKey` with each purchase. The same request with the same
key returns the original request. The same key with different input returns `409`. Give a changed
purchase, including a retry after you change a control, a new key. If an execute call ends without
a response, check `GET /api/sdk/payments/{requestId}` before doing anything else. Do not execute
again blindly.
## Human review
A payment that matches a `REQUIRE_APPROVAL` rule returns `"status": "REQUIRES_APPROVAL"` and an
`approvalRequestId`. It stays pending and does not execute. No machine credential can approve it:
the review routes refuse the SDK key, the setup key, and the sandbox API key.
To finish the review, a person claims the sandbox:
1. Create a claim link with the [handoff](#hand-off-to-a-person-from-a-remote-agent) below and send
`verificationUrl` to the person.
2. The person signs in, claims the sandbox, switches to the sandbox organization in Conto, and
opens **Alerts & Approvals**, then **Approvals**.
3. The person approves or rejects the payment.
The agent polls `GET /api/sdk/payments/{requestId}` with its SDK key. It does not call execute
while the payment waits.
- **Approved.** Conto rechecks the payment against the current controls and records a simulated
test payment. The agent sees `"status": "completed"` and `"settlementMode": "test"`.
- **Rejected.** The agent sees `"status": "declined"`. Nothing is spent.
- **Controls changed while it waited.** The recheck applies the current controls, so an approved
payment can still end as `declined`.
- **No decision.** The request expires after 24 hours.
An unclaimed sandbox has no reviewer, so a held payment stays pending until someone claims it.
## Simulation boundary
Every payment executed in a sandbox is simulated, whatever wallet it uses. No custody provider is
called, and receipts carry `settlementMode: "test"` with no transaction hash or explorer link.
Spend counters and the test balance use the same reservation and accounting as live payments.
Claiming does not change this: a sandbox organization cannot create managed wallets, set up Conto
Pay, or change plans, and connected cards are not available in a sandbox. To use real funds, create
a Conto organization. The optional external signer flow below is separate: your own wallet signs
that transfer, and Conto never executes it.
## Bring your own signer (optional)
If your agent controls a real Tempo Testnet wallet, call `POST /api/sdk/payments/approve` with
`amount`, `recipientAddress`, your wallet as `senderAddress`, and `chainId: 42431`. An approved
response returns `approvalToken` and `confirmUrl`:
```bash
curl -sS -X POST https://conto.finance/api/sdk/payments/approve \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"amount\": 1,
\"recipientAddress\": \"0x0000000000000000000000000000000000000001\",
\"senderAddress\": \"$MY_WALLET_ADDRESS\",
\"chainId\": 42431
}" > approval.json
export APPROVAL_TOKEN="$(jq -r '.approvalToken' approval.json)"
export CONFIRM_URL="$(jq -r '.confirmUrl' approval.json)"
```
Conto registers your wallet with the
[external-wallet defaults](https://conto.finance/docs/reference/defaults#external-wallet-auto-create), not the $5 and $25
sandbox limits. Send the transfer from your wallet, then post the `txHash` and `approvalToken` to
`confirmUrl`:
```bash
curl -sS -X POST "https://conto.finance$CONFIRM_URL" \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"txHash\": \"$TX_HASH\",
\"approvalToken\": \"$APPROVAL_TOKEN\"
}"
```
See the [external-wallet flow](https://conto.finance/docs/sdk/payments#external-wallet-approve-and-confirm-flow) for token
expiry and the SDK methods.
## Hand off to a person from a remote agent
Use this flow when your agent runs on a server, in CI, or on another device. It requires no localhost
callback and never places the sandbox API key in a browser link.
In a CLI-created project, one command creates the link and keeps the polling token in an
owner-only file:
```bash
npx @conto_finance/create-conto-agent sandbox claim --json
npx @conto_finance/create-conto-agent sandbox claim --wait --timeout 300 --json
```
See [sandbox handoff](https://conto.finance/docs/cli/commands#sandbox-handoff) for its exit codes. The API calls below do
the same without the CLI.
```bash
umask 077
curl --fail-with-body -sS -X POST https://conto.finance/api/agents/sandbox/handoff \
-H "Authorization: Bearer $CONTO_SANDBOX_API_KEY" > conto-handoff.json
```
Send only `verificationUrl` to the person who will own the sandbox. Treat that URL and the returned
`token` as temporary secrets: keep them out of logs and public messages. The URL expires in at most
15 minutes. The person signs in, verifies their email, reviews the
sandbox name and ownership permission, and explicitly chooses **Claim sandbox**.
Poll `statusUrl` using the handoff token (not the SDK or sandbox key). Wait at least the returned
`intervalSeconds` between checks. Stop on `claimed` or on expiry. Do not repeatedly create grants.
```bash
export CONTO_HANDOFF_TOKEN="$(jq -r '.token' conto-handoff.json)"
curl --fail-with-body -sS https://conto.finance/api/agents/sandbox/handoff \
-H "Authorization: Bearer $CONTO_HANDOFF_TOKEN"
```
`status: "claimed"` completes the handoff and revokes the setup key. It does not enable
production payments, fund a wallet, change spending limits, or extend the sandbox credentials. Delete the handoff file when done.
Review [production readiness](https://conto.finance/docs/guides/production-readiness) before using real funds.
## Agent checklist
1. Fetch `/.well-known/agent.json`.
2. Read `machineReadable.agentSandboxQuickstart` and `machineReadable.agentSandboxSignup`.
3. `POST /api/agents/sandbox`.
4. Store returned keys securely. They are shown once. Give the payment runtime only
`credentials.sdkKey`. Keep `credentials.setupKey` with the setup step.
5. Optionally configure limits and policies with the setup key.
6. Call `GET /api/sdk/setup` with `credentials.sdkKey`.
7. `POST /api/sdk/payments/request` with an `idempotencyKey`, then
`POST /api/sdk/payments/{requestId}/execute` for an `APPROVED` result.
8. Verify `status: "completed"` and `receipt.settlementMode: "test"`. Expect no hash or explorer URL in a new simulated receipt.
9. For `REQUIRES_APPROVAL`, hand off to a person and poll the payment status.
10. Before the credentials expire, `POST /api/agents/sandbox/handoff`, or run `sandbox claim` with the CLI, and send the returned `verificationUrl` to a person.
## Next steps
### Code examples
Link: https://conto.finance/docs/quickstart/connecting-agents
SDK, OpenAI, Anthropic, Python, and server examples
### Payments API
Link: https://conto.finance/docs/sdk/payments
Request, approve, execute, confirm, and inspect payment state
### Custody modes
Link: https://conto.finance/docs/guides/custody-modes
Choose managed execution or external-wallet approval flows
### OpenAPI
Link: https://conto.finance/docs/api/reference
Generate clients and inspect request and response schemas
---
# How Conto works
Source: https://conto.finance/docs/introduction/concepts
Three questions define a setup: who can spend, which wallet they can use, and which controls must
pass. The agent owner or budget holder owns that configuration through their organization. The
agent uses its own SDK key.
## Who does what
The owner can be a person, a team, or a business that runs agents for its customers. A platform
that integrates Conto for its customers keeps each customer's policies in its own workspace.
| Who | What they do |
| --------------------- | ------------------------------------------------------------------------------------------------------------ |
| You | Set budgets, allowed recipients, which wallets or cards each agent can use, and who approves. |
| Your agent | Asks Conto before it pays, with the amount, currency, recipient, and purpose. It uses its own SDK key. |
| Conto | Checks the agent's policies and limits, then approves, holds for review, or denies. Records every result. |
| Approver | A person you choose. Approves or denies held payments, within the limits you set. |
| Signer | The code that signs the payment or uses the card. It waits for Conto's approval, then reports what happened. |
| Merchant or recipient | Sends the price and payment details, takes the payment, and delivers. |
A merchant's invoice or checkout page cannot approve the payment or change your policies.
## Resource relationships
```mermaid
flowchart TD
Org[Buyer organization / budget holder] --> Agent[Agent]
Org --> Wallet[Wallet]
Org --> Policy[Policies]
Agent --> Access[Agent-wallet access and limits]
Wallet --> Access
Policy --> Decision[Payment decision]
Access --> Decision
Intent[Payment request] --> Decision
Decision --> Review[Approval request when review is needed]
Decision --> Tx[Transaction when execution starts]
```
| Resource | What it represents | What your application retains |
| ----------------- | ------------------------------------------------------------------- | ----------------------------------------------------- |
| Organization | The buyer workspace that owns resources and spending authority | Organization context for provisioning |
| Agent | A runtime identity with its own keys and status | Agent ID. Its SDK key stays in a secret store |
| Wallet | A stablecoin address and its signing/control model | Wallet ID, address, chain, and currency |
| Agent-wallet link | Permission to use a particular wallet, with limits and time windows | The assigned wallet ID and its limits |
| Policy | Rules applied at organization, agent, or wallet scope | Policy assignment and configuration for the use case |
| Counterparty | A recipient and the organization's relationship with it | Address, chain context, and review/approval decisions |
One wallet can serve multiple agents with different limits. An agent can have multiple wallets.
Use `walletId` when the payment must use a particular wallet or chain. A key authenticates the
agent. Wallet access and policy evaluation constrain its payments.
Each agent has an environment: `DEV` (the default), `STAGING`, or `PROD`. Only `PROD` agents can pay
from mainnet wallets.
## Three records, three purposes
A **payment request** is the intent and authorization decision. An **approval request** is the
human review record when a workflow needs a decision. A **transaction** records execution and
settlement progress. These IDs are not interchangeable.
For example, a research agent requests 12 pathUSD. Its policy requires review above 10 and denies
above 15. The payment request waits for review. An authorized human decides, and the application checks
the original payment request. Execution creates a transaction, which may still be processing.
The application waits for completion before treating the purchase as paid.
See [the payment lifecycle](https://conto.finance/docs/guides/payment-lifecycle) for status values and recovery steps.
## Where controls apply
Organization policies apply to every agent in the organization. Assigned policies apply to one agent
or wallet. All applicable controls must pass. A high-priority allow does not override a denial.
Priority determines evaluation order.
Wallet limits are another layer. A policy that permits 50 does not override a wallet cap of 20.
A wallet limit of `0` blocks all payments through that link, and `null` removes the cap. Omitted
limits get defaults. See [Defaults](https://conto.finance/docs/reference/defaults#agent-wallet-links) before changing caps.
[Test allow, review, and deny](https://conto.finance/docs/guides/policy-testing) to see these rules together.
## Who signs
The code that signs must get a Conto approval before it signs or uses credentials. Do not give the
model unrestricted payment keys and rely on it to follow the rules. A changed amount, recipient,
currency, or material purchase detail needs a fresh check.
- **Managed wallets** use request, then execute. Conto signs after your controls pass.
- **External wallets** use approve, your own transfer, then confirm. Your agent signs, and Conto
cannot block a transfer sent outside its flow. See [wallet custody](https://conto.finance/docs/guides/custody-modes).
- **x402 and MPP** clients call pre-authorization before they sign a payment or fund a session. An
approved grant reserves the amount against your limits for 10 minutes. The client still signs
and pays.
- **Connected cards** (Builder and Enterprise): Conto releases a single-use, merchant-locked,
amount-capped credential only after policy, limits, and the purchase authorization the
cardholder verified all allow the purchase. The agent never sees the underlying card. Cards have
their own spending controls, separate from wallet limits. See
[connected cards](https://conto.finance/docs/guides/connected-cards).
## Three testing terms
- **Anonymous sandbox:** simulated settlement, no signer or funded wallet needed.
- **Testnet:** actual blockchain transactions using test tokens.
- **Mainnet:** actual blockchain transactions using real funds.
A receipt with `settlementMode: "live"` identifies an onchain transfer and can still be a testnet
transfer. Verify its chain as well as its status. Check [flows and prerequisites](https://conto.finance/docs/guides/choose-your-integration).
## Next steps
[Run the quickstart](https://conto.finance/docs/quickstart/setup), [choose an integration](https://conto.finance/docs/guides/choose-your-integration),
or read [API conventions](https://conto.finance/docs/reference/api-conventions) for units and identifiers.
---
# Choose an integration
Source: https://conto.finance/docs/guides/choose-your-integration
Make three choices: how your agent calls Conto, who signs the transfer, and which payment method
the recipient accepts. The choices are independent. For example, an SDK integration can use an
external wallet to pay an x402 API.
## 1. Connect your runtime
| Your task | Recommended starting point |
| ------------------------------------------------------- | ------------------------------------------ |
| Build a backend or custom agent in TypeScript | [TypeScript SDK](https://conto.finance/docs/sdk/installation) |
| Build in another language or need raw HTTP | [REST API](https://conto.finance/docs/api/reference) |
| Give an MCP-compatible assistant payment and read tools | [Install MCP](https://conto.finance/docs/mcp/install) |
| Connect OpenClaw or Hermes with an existing wallet | [Agent skills](https://conto.finance/docs/sdk/skills) |
| Provision or inspect resources from a terminal or CI | [CLI](https://conto.finance/docs/cli/commands) |
| Review activity and manage controls as a human | [Dashboard assistant](https://conto.finance/docs/assistant/overview) |
For your first integration, use the [Tempo Testnet quickstart](https://conto.finance/docs/quickstart/setup).
An autonomous agent can start with the [simulated sandbox](https://conto.finance/docs/quickstart/agent-sandbox), which needs
no signer or funded wallet.
## 2. Choose who signs
Managed wallets use request, then execute. External wallets use approve, your own transfer, then
confirm. Conto cannot block a transfer your signer sends outside that flow.
Use a managed wallet if you want Conto to sign. Use an external wallet if you already run a
signer. See [wallet ownership and custody](https://conto.finance/docs/guides/custody-modes).
A contract-enforced wallet needs a deployed contract and an independently configured
authorization and signing path. Conto provides policy evaluation only. Managed `execute` cannot pay
from a smart-contract wallet.
### Hybrid integrations
In a hybrid integration, your app owns the wallet, protocol client, or settlement path, and Conto
decides before your app pays. Choose hybrid when you already run a wallet or treasury layer, handle
x402 or MPP in your own runtime, or embed paid actions inside a larger workflow.
Call Conto before your app pays. If your app pays first and reports later, Conto only records the
spend and cannot stop it.
#### Example: your own wallet
```typescript
import { Conto } from '@conto_finance/sdk';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
export async function payProvider(
amount: number,
recipientAddress: string,
// Your wallet or provider code. Returns the transaction hash.
send: (to: string, amount: number) => Promise
) {
// 1. Ask Conto before paying.
const approval = await conto.payments.approve({
amount,
recipientAddress,
senderAddress: process.env.APP_WALLET_ADDRESS!,
chainId: 42431,
purpose: 'Provider settlement',
});
if (approval.status !== 'ready_to_send' || !approval.approvalId || !approval.approvalToken) {
// declined: do not pay. review_required: pay only after a reviewer approves.
return { paid: false, status: approval.status, reasons: approval.reasons };
}
// 2. Pay through your own path.
const txHash = await send(recipientAddress, amount);
// 3. Record the settled spend.
const receipt = await conto.payments.confirm(approval.approvalId, {
txHash,
approvalToken: approval.approvalToken,
});
return { paid: true, transactionId: receipt.transactionId };
}
```
The approval token expires after 10 minutes. For x402 and MPP, the same shape uses
`x402.preAuthorize()` or `mpp.preAuthorize()`, then `x402.record()` or `mpp.record()` with the
returned grant. For a card, use `cards.approve()`, then `cards.confirm()`.
#### Hybrid checklist
- Each actor in your app maps to one Conto agent with its own SDK key, kept server-side and
encrypted at rest.
- Spending settings live in Conto policies, or sync into them. Only the owner or authorized budget
managers edit them.
- Your executor requires a Conto approval before it signs or uses credentials. A model instruction
alone does not enforce this boundary.
- Denied payments never reach the execution layer.
- Every settled payment is recorded back to Conto: `payments.confirm()` with the transaction hash,
`x402.record()` or `mpp.record()` with the grant, or `cards.confirm()`.
#### Map product controls to policies
| Product control | Conto primitive |
| ------------------------------ | --------------------------------------------------- |
| Max spend per action | `SPEND_LIMIT` |
| Budget per period or project | `BUDGET_ALLOCATION` with a `BUDGET_CAP` rule |
| Budget per x402 or MPP session | `X402_SESSION_BUDGET` or `MPP_SESSION_BUDGET` rules |
| Allowed providers or vendors | `COUNTERPARTY` or `WHITELIST` |
| Human review threshold | `APPROVAL_THRESHOLD` |
| Time-based restrictions | `TIME_WINDOW` or `BLACKOUT_PERIOD` |
## 3. Match the recipient's payment protocol
| Recipient accepts | Flow | Guide |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| A stablecoin transfer to a wallet address | Request and execute, or approve and externally send | [Payments](https://conto.finance/docs/sdk/payments) |
| An HTTP 402 challenge for a paid API | Pre-authorize, pay and retry through your x402 integration, then record settlement with the grant | [x402 API payments](https://conto.finance/docs/guides/x402-api-payments) |
| MPP charges or sessions | Pre-authorize, use your MPP integration, then record settlement with the grant | [MPP payments](https://conto.finance/docs/guides/mpp-session-payments) |
| A card checkout | A connected card: Conto releases a single-use card credential after your controls pass (Builder and Enterprise) | [Connected cards](https://conto.finance/docs/guides/connected-cards) |
| A merchant checkout your agent completes | Check the rules before Link or another payment method opens, and again before the payment is submitted | [Checkout checkpoints](https://conto.finance/docs/guides/checkout-checkpoints) |
Checkout checkpoints need no wallet or signer. For a Link checkout, see
[Conto with Stripe Link](https://conto.finance/docs/guides/stripe-link).
Pre-authorization returns a grant that the record call consumes. Your x402 or MPP client still
signs, so a payment without a grant is flagged and the owner alerted, but Conto cannot stop it.
Check the [supported networks](#networks) before selecting one.
If a Conto payment call returns `503 PAYMENTS_PAUSED`, stop the flow before signing or submitting
payment and ask the owner to check availability. See [error handling](https://conto.finance/docs/sdk/error-handling).
## Networks
Confirm the agent's wallet and network with `GET /api/sdk/setup`.
| Network | Chain ID | Primary stablecoin | Environment |
| ------------- | ---------------- | ------------------ | ----------------------------------- |
| Tempo Testnet | `42431` | `pathUSD` | Onchain testnet, quickstart default |
| Tempo | `4217` | `USDC.e` | Mainnet |
| Base | `8453` | `USDC` | Mainnet |
| Base Sepolia | `84532` | `USDC` | Testnet |
| Ethereum | `1` | `USDC` | Mainnet |
| Arbitrum One | `42161` | `USDC` | Mainnet |
| Polygon | `137` | `USDC` | Mainnet |
| Solana | `solana-mainnet` | `USDC` | Mainnet |
| Solana Devnet | `solana-devnet` | `USDC` | Testnet |
Check `GET /api/chains` for the live list.
- Managed payment requests select the wallet with `walletId` and pay on that wallet's configured
chain. The wallet must be linked and funded.
- External approval uses `chainId`. Your signer and sender wallet must be on that chain.
- x402 settles in USDC only: use Base, Ethereum, Arbitrum, Polygon, or Solana (Base Sepolia or
Solana Devnet for testing). Your x402 signer or client must support the service's challenge and
settlement network. Conto does not make the paid HTTP request.
- MPP uses Tempo. Your MPP client and the service must agree on method, network, and session terms.
## Before enabling real funds
Confirm the intended custody model is available, the wallet is provisioned and funded, and your
signer can pay the network's fees. Organization-controlled managed wallets need owner setup first.
Until then, requests for that wallet type fail. See [wallet custody](https://conto.finance/docs/guides/custody-modes).
Run a small payment on the intended path and verify the final transaction, not only the authorization.
Use separate production credentials and controls, then complete [production readiness](https://conto.finance/docs/guides/production-readiness).
If the selected path is unavailable, [contact support](mailto:support@conto.finance) before funding it.
## Continue with the same payment ID
Preserve returned IDs in every integration. Human review and HTTP 402 require a next
action. A submission timeout requires reconciliation. Follow [track and recover a payment](https://conto.finance/docs/guides/payment-lifecycle)
before treating a request as settled or creating another payment intent.
---
# Install the TypeScript SDK
Source: https://conto.finance/docs/sdk/installation
Requires Node.js 20.19+. Keep SDK keys in your server or agent runtime, never in browser code.
## Install
```bash npm
npm install @conto_finance/sdk
```
```bash pnpm
pnpm add @conto_finance/sdk
```
```bash yarn
yarn add @conto_finance/sdk
```
## Choose your client
`Conto` takes an agent SDK key (`CONTO_API_KEY`) and requests payments. `ContoAdmin` takes an
organization API key (`CONTO_ORG_API_KEY`) and provisions agents, wallets, and policies. Get keys
with the [quickstart](https://conto.finance/docs/quickstart/setup) or see [keys and scopes](https://conto.finance/docs/sdk/authentication) for the key
types. Save the key in `.env.local` and add that file to `.gitignore`.
```typescript
import { Conto, ContoAdmin } from '@conto_finance/sdk';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
const admin = new ContoAdmin({ orgApiKey: process.env.CONTO_ORG_API_KEY! });
```
Create only the client your application needs. `ContoAdmin` needs an organization API key
(`conto_...`) and rejects admin SDK keys, which are still agent keys (`conto_agent_...`).
## Verify without creating a payment
Save as `verify.mjs`. Setup uses the same agent key as the SDK and creates no payment record.
```javascript
import { Conto } from '@conto_finance/sdk';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY });
const setup = await conto.setup();
console.log('Agent:', setup.agent.name);
console.log('Wallets:', setup.wallets);
console.log('Scopes:', setup.scopes);
```
```bash
node --env-file=.env.local verify.mjs
```
Check the intended wallet, network, and payment scopes. In a CLI-created project you can also run
`npx @conto_finance/create-conto-agent doctor`.
To run the same check in the dashboard, open [Developers](https://conto.finance/developers), select
an agent and its wallet, and paste that agent's SDK key into the connection panel. **Check
connection** reads setup with the pasted key. **Inspect payment** reads a payment request by its
ID. The panel shows HTTP status, latency, scopes, and payment status, and never executes payments.
A passing connection check verifies authentication, not that a given payment will pass policy
checks.
## Setup diagnostics
`setup()` also returns `diagnostics` (version `conto.setup-diagnostics.v1`), a summary of what this
key can do now. It can be `null` when Conto could not compute it, and older servers omit the field,
so fall back to the other setup fields.
| Field | Meaning |
| ---------------------------------------- | --------------------------------------------------------------------------------------- |
| `environment.kind` | `anonymous_sandbox`, `claimed_sandbox`, or `account` |
| `environment.settlement` | `simulated` in a sandbox. `per_wallet` otherwise, so check each wallet |
| `status.canRequest`, `status.canExecute` | `yes`, `no`, or `unknown` |
| `status.ownerActionRequired` | `true` when at least one `error` issue belongs to an owner |
| `wallets` | Per wallet: execution mode, network, funding, whether limits are set, what it can do |
| `connectedCards` | Test and live card readiness, reported separately from wallets |
| `issues` | Each has a stable `code`, a `severity`, an `actor`, a `message`, and next `actions` |
Act on each issue by its `actor`. The agent can resolve an `agent` issue with credentials it holds,
such as starting a sandbox handoff with the sandbox API key for `SANDBOX_UNCLAIMED`. The agent
cannot resolve an `owner` issue, such as `SCOPE_MISSING`, where an owner replaces the key. Report it
to the owner with its `actions`: a CLI command, an API call that names the credential it needs, or a
dashboard page.
Issue codes keep their meaning, and new codes can be added: `AGENT_PAUSED`, `AGENT_FROZEN`,
`AGENT_UNAVAILABLE`, `SDK_KEY_EXPIRED`, `SDK_KEY_REVOKED`, `SCOPE_MISSING`, `NO_WALLET`,
`WALLET_PAUSED`, `WALLET_UNAVAILABLE`, `WALLET_VIEW_ONLY`, `WALLET_UNFUNDED`,
`MAINNET_REQUIRES_PROD_AGENT`, `LIVE_PAYMENTS_PAUSED`, `LIVE_PAYMENTS_UNAVAILABLE`,
`EXTERNAL_SIGNER_REQUIRED`, `SANDBOX_UNCLAIMED`, and `SANDBOX_EXPIRED`. The SDK exports the
`SetupDiagnostics`, `SetupDiagnosticIssue`, `SetupDiagnosticIssueCode`, and `SetupDiagnosticAction`
types.
A key that Conto recognizes but will not serve, such as one for a paused agent or an expired or
revoked key, gets `401` from `GET /api/sdk/setup` with the same `diagnostics` object. `setup()`
throws a `ContoError` whose optional `diagnostics` property carries that object. Setup errors for
missing scope (`403`) carry it too. Diagnostics are also available from
`conto doctor --json` and the MCP `get_setup` tool.
## Configuration
| Option | Default | Meaning |
| --------- | ----------------------- | ----------------------------------------------- |
| `apiKey` | Required | Agent SDK key. Must start with `conto_agent_` |
| `baseUrl` | `https://conto.finance` | API origin. Must use https except for localhost |
| `timeout` | `30000` | Request timeout in milliseconds |
The SDK retries eligible reads and idempotency-protected authorization. Execution is not
retried automatically. See [retry behavior](https://conto.finance/docs/sdk/error-handling#retry-strategy).
## Start building
- [Make a payment](https://conto.finance/docs/sdk/payments): request, execute, and status contracts.
- [Track and recover a payment](https://conto.finance/docs/guides/payment-lifecycle): required actions, uncertainty, and settlement.
- [Code examples](https://conto.finance/docs/quickstart/connecting-agents): Express and Lambda handlers, plus OpenAI,
Anthropic, and Python HTTP examples.
- [Provision with the admin SDK](https://conto.finance/docs/sdk/admin): organization-level automation.
---
# Keys and scopes
Source: https://conto.finance/docs/sdk/authentication
Conto SDK requests authenticate with agent-specific SDK keys:
```text
conto_agent_[64-character-hex-string]
```
Every SDK key belongs to exactly one agent and always expires: the default lifetime is **365 days** and the maximum is **730 days**.
## Choose the right credential
| Credential | Format | Best for | Notes |
| ------------------------ | ----------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------ |
| **Standard SDK key** | `conto_agent_...` | The agent payment lifecycle (request, execute, approve, confirm) plus agent-scoped reads | Works with the `Conto` client |
| **Admin SDK key** | `conto_agent_...` | Agents that also manage agents, wallets, or policies | Still an agent key. Not accepted by `ContoAdmin` |
| **Organization API key** | `conto_...` | Backend/admin automation across the whole organization | Use with `ContoAdmin` |
Info:
[`ContoAdmin`](https://conto.finance/docs/sdk/admin) requires an **organization API key** from **Settings > API Keys**.
Admin SDK keys can call elevated HTTP API endpoints, but `ContoAdmin` rejects them.
## Generate SDK keys
### Via dashboard
### Open the agent
Go to **Agents** and select the agent that will use the key.
### Create a key
Open the **SDK Integration** tab and click **Generate SDK Key**.
### Choose a preset
Select **Standard** for the payment lifecycle plus read access, or **Admin** if the agent also
manages agents, wallets, or policies. Only organization owners and admins can create admin keys.
### Set expiration
Choose 30, 90, or 180 days, 1 year (the default), or 2 years (the maximum).
### Copy the secret
Warning:
The full key is shown only once. Store it in your secrets manager before closing the dialog.
### Via API
```bash
curl -X POST https://conto.finance/api/agents/{agentId}/sdk-keys \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Key",
"expiresInDays": 90,
"keyType": "standard"
}'
```
**Response**
```json
{
"id": "cmm5d0key000l49h7entvd11p",
"key": "conto_agent_abc123def456...",
"name": "Production Key",
"keyType": "standard",
"scopes": [
"payments:request",
"payments:execute",
"payments:approve",
"payments:confirm",
"wallets:read",
"policies:read",
"transactions:read",
"counterparties:read",
"alerts:read",
"agents:read",
"analytics:read",
"network:read"
],
"message": "Save this key now! It will not be shown again."
}
```
To create an admin key with `"keyType": "admin"`, the organization API key needs the `admin` or
`team:write` scope.
## Scopes
A scope authorizes an operation. Wallet links, policies, spend limits, approvals, and resource
ownership still decide what a key can pay.
### Agent key presets
Standard is the default. x402 and MPP recording require `payments:execute`, not
`transactions:write`, so they work with a standard key.
{/* generated:agent-key-presets:start */}
| Scope | Standard | Admin | Operation |
| ---------------------- | -------- | ----- | -------------------------------------------------------------------------------------------------- |
| `payments:request` | Yes | Yes | Request authorization and protocol pre-authorization |
| `payments:execute` | Yes | Yes | Execute approved managed payments and record x402/MPP settlement |
| `payments:approve` | Yes | Yes | Authorize an external-wallet payment |
| `payments:confirm` | Yes | Yes | Confirm an externally executed payment |
| `wallets:read` | Yes | Yes | Read wallets, spending limits, and budget requests |
| `policies:read` | Yes | Yes | Read assigned policies |
| `transactions:read` | Yes | Yes | Read transactions and payment status |
| `counterparties:read` | Yes | Yes | Read counterparty relationships |
| `alerts:read` | Yes | Yes | Read alerts |
| `agents:read` | Yes | Yes | Read the agent and setup summary |
| `analytics:read` | Yes | Yes | Read analytics and protocol budget/service summaries |
| `network:read` | Yes | Yes | Read network trust |
| `audit:read` | No | Yes | Read audit logs |
| `alerts:write` | No | Yes | Respond to alerts |
| `transactions:write` | No | Yes | Retry a definitive failed transfer (also requires `payments:execute`) |
| `counterparties:write` | No | Yes | Create and update counterparties |
| `merchant:purchase` | No | Yes | Let Conto complete a card checkout for one approved cart (off unless enabled for the organization) |
| `policies:exceptions` | No | Yes | Request policy changes for review |
| `agents:write` | No | Yes | Create and update agents in the organization |
| `wallets:write` | No | Yes | Create and update wallets in the organization |
| `policies:write` | No | Yes | Create and update policies in the organization |
{/* generated:agent-key-presets:end */}
### Admin SDK keys
Admin keys add the scopes that Standard lacks: audit reads, write access to agents, wallets,
policies, counterparties, and alerts, retries of failed transfers, policy change requests, and
`merchant:purchase` (off unless Conto enables it for your organization). On organization endpoints
an admin key acts with Manager permissions for the whole organization. Admin keys do not include
team management, organization settings, or the organization `admin` super-scope, and an admin key
cannot create another admin SDK key.
### Organization API key scopes
Only Owners create organization API keys, in **Settings > API Keys** or with `POST /api/api-keys`.
Organization keys always expire: `expiresInDays` defaults to 365, which is also the maximum. The
dashboard offers three presets, and the API also accepts individual scopes.
| Preset | Scopes included |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| **Read Only** | `*:read` scopes only |
| **Standard** | Read and write for agents, wallets, policies, transactions, counterparties, and alerts, plus analytics, audit, and team read |
| **Admin** | All scopes (Owner-only) |
Available scopes: `agents`, `wallets`, `transactions`, `policies`, `counterparties`, and `alerts`
(each `:read` and `:write`), plus `analytics:read`, `audit:read`, `team:read`, `team:write`,
`org:read`, `org:write`, and `admin`.
## Revoke keys
### Via dashboard
1. Go to **Agents**
2. Open the agent
3. Open the **SDK Integration** tab
4. Click **Revoke**
### Via API
```bash
curl -X DELETE "https://conto.finance/api/agents/{agentId}/sdk-keys?keyId={keyId}" \
-H "Authorization: Bearer $CONTO_ORG_API_KEY"
```
Revocation is immediate.
## Best practices
- Store SDK keys in a secrets manager, not in source control.
- Use separate keys for development, staging, and production.
- Prefer standard keys unless the agent needs elevated management access.
- Standard keys can move funds. Limit what they can spend with policies, spend limits, and approvals.
- Rotate keys on a schedule instead of waiting for emergency revocations.
---
# Code examples
Source: https://conto.finance/docs/quickstart/connecting-agents
Every example calls the agent SDK endpoints with the agent's SDK key as `CONTO_API_KEY`. Do not use
`CONTO_ORG_API_KEY` for these `/api/sdk/*` calls. See [Keys and scopes](https://conto.finance/docs/sdk/authentication).
Info:
Before integrating, [connect your agent, link a wallet, and generate an SDK key](https://conto.finance/docs/quickstart/setup).
If you are an autonomous agent evaluating Conto without a human account,
[try the sandbox](https://conto.finance/docs/quickstart/agent-sandbox) first.
The examples use a managed wallet: request, then execute. External wallets use approve, your own
transfer, then confirm. See [wallet custody](https://conto.finance/docs/guides/custody-modes).
## SDK examples
These use `@conto_finance/sdk`. See [Install the TypeScript SDK](https://conto.finance/docs/sdk/installation).
### One-call payment
Use `payments.pay()` with a managed wallet to request approval and execute in one call. It throws if
the payment is denied or needs approval.
```typescript
import { Conto } from '@conto_finance/sdk';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
const result = await conto.payments.pay({
amount: 50,
recipientAddress: '0x0000000000000000000000000000000000000001',
recipientName: 'OpenAI',
purpose: 'API credits',
category: 'AI_SERVICES',
});
console.log(result.status, result.receipt.transactionId, result.receipt.txHash ?? 'test settlement');
```
### Approval-aware payment
Use `request()` then `execute()` when your app handles denied or review-required outcomes before
execution.
```typescript
import { Conto } from '@conto_finance/sdk';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
const request = await conto.payments.request({
amount: 100,
recipientAddress: '0x0000000000000000000000000000000000000001',
purpose: 'Infrastructure spend',
});
if (request.status === 'DENIED') {
console.log(request.reasons);
}
if (request.status === 'REQUIRES_APPROVAL') {
console.log('Awaiting approval:', request.requestId);
}
if (request.status === 'APPROVED') {
const result = await conto.payments.execute(request.requestId);
console.log(result.receipt.transactionId, result.receipt.txHash ?? 'test settlement');
}
```
### Setup check
Call `conto.setup()` on agent startup to read the agent, its wallets, spending limits, and scopes.
```typescript
import { Conto } from '@conto_finance/sdk';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
const setup = await conto.setup();
console.log(setup.agent.name);
console.log(setup.scopes);
console.log(setup.wallets);
```
## The shared payment helper
Each framework integration wraps the same two calls: request (policy check), then execute. This
helper calls the REST API directly with no SDK dependency. Define it once and call it from each
framework's tool handler. It:
- Sends the payment to `POST /api/sdk/payments/request` with an idempotency key, so Conto checks it
against your policies.
- Stops if the result is not `APPROVED` and returns the status, `reasons`, and `actionUrl`.
`pending` is `true` when the payment waits for approval.
- Calls `POST /api/sdk/payments/{requestId}/execute` only for an approved request.
- Returns the receipt, transaction hash, and explorer URL. `success` is `true` only for a
`completed` payment with a transaction ID.
- Returns `uncertain` or `checkStatus` when the outcome is unknown. It never retries.
```typescript
const CONTO_API_KEY = process.env.CONTO_API_KEY;
const CONTO_BASE_URL = process.env.CONTO_BASE_URL ?? 'https://conto.finance';
type PaymentInput = {
amount: number;
recipientAddress: string;
recipientName?: string;
purpose: string;
category?: string;
};
async function executeContoPayment(input: PaymentInput, idempotencyKey = crypto.randomUUID()) {
let requestId: string | undefined;
try {
const response = await fetch(`${CONTO_BASE_URL}/api/sdk/payments/request`, {
method: 'POST',
headers: {
Authorization: `Bearer ${CONTO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ category: 'OPERATIONS', ...input, idempotencyKey }),
});
const request = await response.json();
requestId = request.requestId;
if (!response.ok) {
return { success: false, requestId, idempotencyKey, error: request.error, uncertain: response.status >= 500 };
}
if (request.status !== 'APPROVED' || !requestId) {
return {
success: false,
pending: request.status === 'REQUIRES_APPROVAL',
requestId,
idempotencyKey,
status: request.status,
reasons: request.reasons,
actionUrl: request.actionUrl,
};
}
const execution = await fetch(
`${CONTO_BASE_URL}/api/sdk/payments/${encodeURIComponent(requestId)}/execute`,
{ method: 'POST', headers: { Authorization: `Bearer ${CONTO_API_KEY}` } }
);
const result = await execution.json();
if (!execution.ok) {
return { success: false, requestId, idempotencyKey, error: result.error, checkStatus: true };
}
return {
success: result.status === 'completed' && Boolean(result.receipt?.transactionId),
pending: result.status === 'processing',
status: result.status,
requestId,
idempotencyKey,
receipt: result.receipt,
transactionHash: result.receipt?.txHash,
amount: result.amount,
explorerUrl: result.receipt?.explorerUrl,
};
} catch {
return { success: false, uncertain: true, requestId, idempotencyKey, message: 'Check the payment status before trying again.' };
}
}
```
A `processing` result means the payment was submitted and awaits confirmation. A sandbox
`completed` result has a test receipt and does not prove an onchain transfer. Keep the request ID
and idempotency key with the operation. After a timeout or an uncertain response, check
`GET /api/sdk/payments/{requestId}` before trying again. If the request response was lost, repeat
only the authorization request with the same input and idempotency key to recover it. Do not
start a new payment. Neither helper retries automatically.
The Python equivalent needs `pip install requests`:
```python
import os
from typing import Optional
import requests
CONTO_API_KEY = os.environ.get('CONTO_API_KEY')
CONTO_BASE_URL = os.environ.get('CONTO_BASE_URL', 'https://conto.finance')
def make_payment(amount: float, recipient_address: str, purpose: str,
recipient_name: Optional[str] = None, idempotency_key: Optional[str] = None):
"""Request and execute once. Keep the returned IDs to recover an uncertain result."""
import uuid
idempotency_key = idempotency_key or str(uuid.uuid4())
request_id = None
headers = {'Authorization': f'Bearer {CONTO_API_KEY}', 'Content-Type': 'application/json'}
try:
response = requests.post(
f'{CONTO_BASE_URL}/api/sdk/payments/request', headers=headers, timeout=30,
json={'amount': amount, 'recipientAddress': recipient_address,
'purpose': purpose, **({'recipientName': recipient_name} if recipient_name else {}),
'category': 'OPERATIONS', 'idempotencyKey': idempotency_key}
)
request_data = response.json()
request_id = request_data.get('requestId')
if not response.ok:
return {'success': False, 'requestId': request_id, 'idempotencyKey': idempotency_key,
'error': request_data.get('error'), 'uncertain': response.status_code >= 500}
if request_data.get('status') != 'APPROVED' or not request_id:
return {'success': False, 'requestId': request_id, 'idempotencyKey': idempotency_key,
'pending': request_data.get('status') == 'REQUIRES_APPROVAL',
'status': request_data.get('status'), 'reasons': request_data.get('reasons'),
'actionUrl': request_data.get('actionUrl')}
execution = requests.post(f'{CONTO_BASE_URL}/api/sdk/payments/{request_id}/execute',
headers=headers, timeout=30)
result = execution.json()
if not execution.ok:
return {'success': False, 'requestId': request_id, 'idempotencyKey': idempotency_key,
'error': result.get('error'), 'checkStatus': True}
receipt = result.get('receipt') or {}
return {'success': result.get('status') == 'completed' and bool(receipt.get('transactionId')),
'pending': result.get('status') == 'processing', 'status': result.get('status'),
'requestId': request_id, 'idempotencyKey': idempotency_key, 'receipt': receipt,
'transactionHash': receipt.get('txHash'), 'amount': result.get('amount'),
'explorerUrl': receipt.get('explorerUrl')}
except (requests.RequestException, ValueError, AttributeError):
return {'success': False, 'uncertain': True, 'requestId': request_id,
'idempotencyKey': idempotency_key,
'message': 'Check the payment status before trying again.'}
```
## Framework integrations
Each framework needs only its own tool wiring around the shared helper.
### OpenAI (Responses API)
```typescript
import OpenAI from 'openai';
const openai = new OpenAI();
const tools: OpenAI.Responses.FunctionTool[] = [
{
type: 'function',
name: 'make_payment',
description: 'Make a payment to a recipient',
strict: false,
parameters: {
type: 'object',
properties: {
amount: { type: 'number', description: 'Payment amount in USD' },
recipientAddress: { type: 'string', description: 'Recipient wallet address' },
recipientName: { type: 'string', description: 'Recipient name' },
purpose: { type: 'string', description: 'Payment purpose' },
},
required: ['amount', 'recipientAddress', 'purpose'],
},
},
];
async function runAgent(userMessage: string) {
const response = await openai.responses.create({
model: 'gpt-5',
input: userMessage,
tools,
});
const outputs: OpenAI.Responses.ResponseInputItem[] = [];
for (const item of response.output) {
if (item.type === 'function_call' && item.name === 'make_payment') {
const result = await executeContoPayment(JSON.parse(item.arguments) as PaymentInput);
outputs.push({ type: 'function_call_output', call_id: item.call_id, output: JSON.stringify(result) });
}
}
if (outputs.length === 0) return response.output_text;
// Send the payment result back so the model can report it.
const followUp = await openai.responses.create({
model: 'gpt-5',
previous_response_id: response.id,
input: outputs,
tools,
});
return followUp.output_text;
}
```
### Anthropic Claude (tool use)
```typescript
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: 'make_payment',
description: 'Make a payment to a recipient using Conto',
input_schema: {
type: 'object',
properties: {
amount: { type: 'number', description: 'Payment amount in USD' },
recipientAddress: { type: 'string', description: 'Recipient wallet address' },
recipientName: { type: 'string', description: 'Recipient name' },
purpose: { type: 'string', description: 'Payment purpose' },
},
required: ['amount', 'recipientAddress', 'purpose'],
},
},
];
async function runAgent(userMessage: string) {
const response = await anthropic.messages.create({
model: 'claude-opus-5',
max_tokens: 1024,
tools,
messages: [{ role: 'user', content: userMessage }],
});
for (const block of response.content) {
if (block.type === 'tool_use' && block.name === 'make_payment') {
const result = await executeContoPayment(block.input as PaymentInput);
// Continue conversation with tool result...
}
}
}
```
### Python (any framework)
Register `make_payment` (defined above) as a tool in your framework of choice (CrewAI, AutoGen,
or a custom loop) and return its dict result to the model as the tool output.
## Server handlers
These handlers take payment requests on your own server. They are examples: add your own
application authentication and authorization, and configure the recipient allowlist before you
expose a payment route.
### Validate before paying
Do not pass an untrusted request body directly to `payments.pay()`. Put authentication, local
limits, a recipient allowlist, and a caller-supplied idempotency key in front of the SDK. Both
handlers below use this helper:
- `parsePayment` rejects the body unless the amount is positive and at most `MAX_PAYMENT_AMOUNT`
(default 50), the recipient is an EVM address on `PAYMENT_RECIPIENT_ALLOWLIST`, and the purpose
is 1 to 200 characters.
- `submitPayment` asks Conto first. A denial returns 403, a payment waiting for approval returns
202, and any other status that is not `APPROVED` returns 409.
- It executes only an approved request. After a timeout, a dropped connection, or a 408, 409, 429,
or 5xx error, it reads the payment status instead of retrying.
- `mapPaymentError` turns SDK errors into HTTP codes: 400 for validation errors, 409 for
idempotency conflicts and insufficient balance, 503 for rate limits, and 502 for anything else.
```typescript
// safe-payment.ts
import { Conto, ContoError, type PaymentRequestInput } from '@conto_finance/sdk';
const conto = new Conto({
apiKey: process.env.CONTO_API_KEY!,
timeout: 15_000,
});
const maxAmount = Number(process.env.MAX_PAYMENT_AMOUNT ?? '50');
if (!Number.isFinite(maxAmount) || maxAmount <= 0) {
throw new Error('MAX_PAYMENT_AMOUNT must be a positive number');
}
const allowedRecipients = new Set(
(process.env.PAYMENT_RECIPIENT_ALLOWLIST ?? '')
.split(',')
.map((address) => address.trim().toLowerCase())
.filter(Boolean)
);
type PaymentCommand = Pick;
export function parsePayment(value: unknown): PaymentCommand {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
throw new Error('Body must be an object');
}
const body = value as Record;
const address = typeof body.recipientAddress === 'string' ? body.recipientAddress : '';
const purpose = typeof body.purpose === 'string' ? body.purpose.trim() : '';
if (
typeof body.amount !== 'number' ||
!Number.isFinite(body.amount) ||
body.amount <= 0 ||
body.amount > maxAmount ||
!/^0x[0-9a-fA-F]{40}$/.test(address) ||
!allowedRecipients.has(address.toLowerCase()) ||
purpose.length < 1 ||
purpose.length > 200
) {
throw new Error('Invalid amount, recipient, or purpose');
}
return { amount: body.amount, recipientAddress: address, purpose };
}
export async function submitPayment(input: PaymentCommand, idempotencyKey: string) {
const request = await conto.payments.request({ ...input, idempotencyKey });
if (request.status !== 'APPROVED') {
const statusCode =
request.status === 'DENIED' ? 403 : request.status === 'REQUIRES_APPROVAL' ? 202 : 409;
return {
statusCode,
body: { requestId: request.requestId, status: request.status, reasons: request.reasons },
};
}
try {
const result = await conto.payments.execute(request.requestId);
return {
statusCode: result.status === 'processing' ? 202 : 200,
body: result,
};
} catch (error) {
const deterministicClientError =
error instanceof ContoError &&
error.status >= 400 &&
error.status < 500 &&
![408, 409, 429].includes(error.status);
if (deterministicClientError) throw error;
// Execution may have succeeded before a timeout, connection drop, 429, or 5xx.
const state = await conto.payments.status(request.requestId).catch(() => null);
const transaction = state?.transaction;
return {
statusCode:
transaction?.status === 'failed' ? 409 : transaction?.status === 'completed' ? 200 : 202,
body: {
requestId: request.requestId,
status: transaction?.status ?? 'UNKNOWN',
transaction,
message: 'Execution result was reconciled. Do not retry execute blindly.',
},
};
}
}
export function mapPaymentError(error: unknown) {
if (error instanceof ContoError) {
let statusCode = 502;
if (error.code === 'VALIDATION_ERROR') statusCode = 400;
if (['IDEMPOTENCY_CONFLICT', 'INSUFFICIENT_BALANCE'].includes(error.code)) statusCode = 409;
if (error.code === 'RATE_LIMITED') statusCode = 503;
return { statusCode, body: { error: error.code } };
}
return { statusCode: 502, body: { error: 'PAYMENT_SERVICE_ERROR' } };
}
```
Set `PAYMENT_RECIPIENT_ALLOWLIST` to comma-separated EVM addresses and `MAX_PAYMENT_AMOUNT` to your
application-level cap.
### Express
Connect the helper to an authenticated and authorized Express route. The middleware must verify a
user or service identity, enforce your application's payment-operator permission, and set
`res.locals.subject`. Replace the sample import with your implementation.
```typescript
// app.ts
import { createHash } from 'node:crypto';
import express from 'express';
import { requirePaymentOperator } from './auth';
import { mapPaymentError, parsePayment, submitPayment } from './safe-payment';
const app = express();
app.use(express.json({ limit: '16kb' }));
app.post('/payments/submit', requirePaymentOperator, async (req, res) => {
const callerKey = req.get('Idempotency-Key');
if (!callerKey || !/^[A-Za-z0-9:_-]{8,64}$/.test(callerKey)) {
return res.status(400).json({ error: 'A stable Idempotency-Key is required' });
}
let payment: ReturnType;
try {
payment = parsePayment(req.body);
} catch {
return res.status(400).json({ error: 'Invalid payment request' });
}
const subject = res.locals.subject;
if (typeof subject !== 'string' || !subject) {
return res.status(401).json({ error: 'Unauthorized' });
}
const idempotencyKey = createHash('sha256').update(`${subject}:${callerKey}`).digest('hex');
try {
const response = await submitPayment(payment, idempotencyKey);
return res.status(response.statusCode).json(response.body);
} catch (error) {
const response = mapPaymentError(error);
return res.status(response.statusCode).json(response.body);
}
});
```
### Serverless (AWS Lambda)
Put this function behind an API Gateway JWT authorizer and require your application's own
`payments:create` scope on the route. The handler also checks that scope, rejects requests without
a verified subject, validates and caps the body, requires idempotency, and uses the same recipient
allowlist and error mapping as the Express route.
```typescript
import { createHash } from 'node:crypto';
import { mapPaymentError, parsePayment, submitPayment } from './safe-payment';
export const handler = async (event: any) => {
const claims = event.requestContext?.authorizer?.jwt?.claims;
const subject = claims?.sub;
if (typeof subject !== 'string' || !subject) {
return { statusCode: 401, body: JSON.stringify({ error: 'Unauthorized' }) };
}
const scopes = typeof claims.scope === 'string' ? claims.scope.split(/\s+/) : [];
if (!scopes.includes('payments:create')) {
return { statusCode: 403, body: JSON.stringify({ error: 'Forbidden' }) };
}
const callerKey = event.headers?.['idempotency-key'] ?? event.headers?.['Idempotency-Key'];
if (typeof callerKey !== 'string' || !/^[A-Za-z0-9:_-]{8,64}$/.test(callerKey)) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'A stable Idempotency-Key is required' }),
};
}
const rawBody = event.isBase64Encoded
? Buffer.from(event.body ?? '', 'base64').toString('utf8')
: (event.body ?? '');
if (rawBody.length > 16_384) {
return { statusCode: 413, body: JSON.stringify({ error: 'Request body too large' }) };
}
let payment: ReturnType;
try {
payment = parsePayment(JSON.parse(rawBody));
} catch {
return { statusCode: 400, body: JSON.stringify({ error: 'Invalid payment request' }) };
}
const idempotencyKey = createHash('sha256').update(`${subject}:${callerKey}`).digest('hex');
try {
const response = await submitPayment(payment, idempotencyKey);
return { statusCode: response.statusCode, body: JSON.stringify(response.body) };
} catch (error) {
const response = mapPaymentError(error);
return { statusCode: response.statusCode, body: JSON.stringify(response.body) };
}
};
```
## Register an agent in an existing organization
If an organization owner has already invited your agent, register it with the owner's
registration token instead of creating a sandbox. A GET request lists the required fields:
```bash
curl -sS https://conto.finance/api/agents/register
```
Post the token to join the organization and receive a scoped SDK key:
```bash
curl -sS -X POST https://conto.finance/api/agents/register \
-H "Content-Type: application/json" \
-d '{
"registrationToken": "registration_token_from_human_owner",
"name": "Research Buyer Agent",
"agentType": "CUSTOM",
"externalId": "research-buyer-prod",
"callbackUrl": "https://agent.example.com/webhooks/conto"
}'
```
The key has two scopes. With `payments:request` the agent calls `POST /api/sdk/payments/request`,
which returns a policy decision. With `agents:read` it reads its own setup with `GET /api/sdk/setup`
and `GET /api/sdk/agents/me`. Setup includes the agent's wallet balances, limits, and policy rules
even though the key has no `wallets:read` or `policies:read`. The key has no `payments:execute`,
`payments:approve`, or `payments:confirm`, and it cannot change policies, create keys, or read other
agents. An organization owner issues keys with more scopes. See [Keys and scopes](https://conto.finance/docs/sdk/authentication).
Read the agent's setup with the new key:
```bash
curl -sS https://conto.finance/api/sdk/setup \
-H "Authorization: Bearer $CONTO_API_KEY"
```
`externalId` is optional but recommended: it makes retries idempotent and gives your system a
durable reconciliation key. The registration response confirms the agent status, granted scopes,
credential expiration, and the SDK endpoints that credential can call. It shows the SDK key only on
the first successful registration. Registering the same `externalId` again returns the existing
agent and credential reference without revealing the key. A retry never changes the key's scopes.
A key registered before `agents:read` was included has only `payments:request`. For that key,
`GET /api/sdk/setup` returns `403` with `code: "INSUFFICIENT_SCOPE"`, `requiredScope: "agents:read"`,
`actor: "owner"`, and a `nextAction`. A registration retry returns the same fields in `remediation`
and leaves `setup` out of `endpoints`. Conto never adds scopes to an existing key. An organization
owner generates a new key for the agent and revokes the old one.
## Handle errors
HTTP errors carry a `code` such as `VALIDATION_ERROR`, `AUTH_FAILED`, `INSUFFICIENT_SCOPE` or
`RATE_LIMITED`. A `5xx` or a timeout leaves the outcome unknown, so look up the request status
before retrying execution or creating another payment. See
[error handling](https://conto.finance/docs/sdk/error-handling) for every code.
Policy denials are not HTTP errors. `/api/sdk/payments/request` returns `200` with
`status: "DENIED"` and a `reasons` array of fixed messages. Rule names and limits appear only in the
dashboard.
## Next steps
### Send a payment
Link: https://conto.finance/docs/sdk/payments
Every payment method and response shape
### Track and recover a payment
Link: https://conto.finance/docs/guides/payment-lifecycle
Check status and recover an uncertain result
### Webhooks
Link: https://conto.finance/docs/guides/webhooks
Receive approval and payment events
### Recipes
Link: https://conto.finance/docs/guides/recipes
Curl snippets for payments, policies, x402, MPP, and monitoring
---
# Recipes
Source: https://conto.finance/docs/guides/recipes
Each recipe assumes you have an active agent with an SDK key. For initial setup, see
[Quickstart](https://conto.finance/docs/quickstart/setup). To create agents, wallets, and SDK keys yourself, see
[Set up in the dashboard instead](https://conto.finance/docs/quickstart/setup#set-up-in-the-dashboard-instead), or the
[Admin SDK](https://conto.finance/docs/sdk/admin) for the API. For the same calls in TypeScript, see
[Send a payment](https://conto.finance/docs/sdk/payments) and [Code examples](https://conto.finance/docs/quickstart/connecting-agents).
Run payment recipes in the agent's runtime with its SDK key. Policy and alert recipes use an
organization API key.
```bash
# Agent SDK key (for payment operations)
export CONTO_API_KEY="conto_agent_your_key_here"
# Org API key (for policy and alert recipes)
export CONTO_ORG_API_KEY="conto_your_org_key_here"
```
---
## Payments
### Request and execute a payment
Two calls: request (policy check) → execute (onchain transfer).
```bash
# Step 1: Request
REQUEST=$(curl -s -X POST https://conto.finance/api/sdk/payments/request \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 10,
"recipientAddress": "0x0000000000000000000000000000000000000001",
"purpose": "Service payment"
}')
REQUEST_ID=$(echo "$REQUEST" | jq -r '.requestId')
echo "$REQUEST" | jq '.status'
# Step 2: Execute the approved request
curl -X POST "https://conto.finance/api/sdk/payments/$REQUEST_ID/execute" \
-H "Authorization: Bearer $CONTO_API_KEY"
```
---
### Check payment status
```bash
curl https://conto.finance/api/sdk/payments/REQUEST_ID \
-H "Authorization: Bearer $CONTO_API_KEY"
```
Returns one of `approved`, `declined`, `review_required`, `processing`, `reconciliation_required`,
`completed`, `failed`, or `expired`. For `declined` and `review_required`, `reasons` says why.
---
### List recent transactions
```bash
curl https://conto.finance/api/sdk/transactions \
-H "Authorization: Bearer $CONTO_API_KEY"
```
---
### x402 and MPP endpoint calls
Each protocol's API reference documents the pre-authorize, record, and budget/services calls with
full request and response shapes:
- [x402 API reference](https://conto.finance/docs/guides/x402-api-payments#api-reference): pre-authorize, record (including batch), budget, services
- [MPP API reference](https://conto.finance/docs/guides/mpp-session-payments#api-reference): pre-authorize, record settlements, budget, services
For both protocols, record endpoints use flat top-level settlement fields and optional `batchItems`
for per-call detail. Pass the `grantId` and `grantSignature` from the approved pre-authorization to
the record call. A record without a valid grant is flagged as a policy bypass and alerts the owner.
With an [OpenClaw or Hermes skill](https://conto.finance/docs/sdk/skills), the helper covers both x402 calls and requires
the grant when recording:
```bash
bash skills/conto/conto-check.sh x402 0.05 0xPayee https://api.example.com/v1 "" pay_01J...
bash skills/conto/conto-check.sh x402-record 0.05 0xPayee https://api.example.com/v1 pay_01J... 0xTxHash GRANT_ID GRANT_SIGNATURE
```
To set the guardrails those endpoints check, start from the
[x402](https://conto.finance/docs/guides/x402-api-payments#step-1-set-up-x402-policies) or
[MPP](https://conto.finance/docs/guides/mpp-session-payments#step-1-set-up-mpp-policies) starter policy. The
[rule reference](https://conto.finance/docs/policies/advanced#x402-protocol-rules) has the JSON for every x402 and MPP rule.
---
### Approve and confirm an external wallet payment
Use this flow when your agent holds its own keys and Conto should only authorize the spend.
```bash
# Step 1: Ask Conto for approval
APPROVAL=$(curl -s -X POST https://conto.finance/api/sdk/payments/approve \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 50,
"recipientAddress": "0x0000000000000000000000000000000000000001",
"senderAddress": "0x1a2b3c4d5e6f...",
"purpose": "Vendor payout",
"chainId": 42431
}')
echo $APPROVAL
# Step 2: Agent executes the transfer with its own wallet tools
# Step 3: Confirm the onchain transfer back to Conto
curl -X POST https://conto.finance/api/sdk/payments/APPROVAL_ID/confirm \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"txHash": "0xabc123...",
"approvalToken": "APPROVAL_TOKEN"
}'
```
---
## Policies
A policy created without `scope` or `agentIds` applies to every agent in the organization. The
recipes below limit each policy to one agent, either with `"scope": "assigned"` and a separate
assignment or with `"agentIds": ["AGENT_ID"]`. Remove both only when the rule should cover every
agent.
### Limit an agent to $50 per day
```bash
curl -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Daily Cap: $50",
"scope": "assigned",
"policyType": "SPEND_LIMIT",
"rules": [
{
"ruleType": "DAILY_LIMIT",
"operator": "LTE",
"value": "50",
"action": "ALLOW"
}
]
}'
```
Then assign it to the agent:
```bash
curl -X POST https://conto.finance/api/agents/AGENT_ID/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "policyId": "POLICY_ID" }'
```
---
### Restrict payments by agent identity
Use identity rules to require a production, low-risk agent with a finance tag and hardware
attestation. Missing identity context fails closed for these allow rules.
```bash
curl -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Finance Agents",
"agentIds": ["AGENT_ID"],
"policyType": "COMPOSITE",
"rules": [
{
"ruleType": "AGENT_ENVIRONMENT",
"operator": "IN_LIST",
"value": "{\"environments\":[\"PROD\"]}",
"action": "ALLOW"
},
{
"ruleType": "AGENT_RISK_TIER",
"operator": "IN_LIST",
"value": "{\"riskTiers\":[\"LOW\",\"MEDIUM\"]}",
"action": "ALLOW"
},
{
"ruleType": "AGENT_OWNER_ROLE",
"operator": "IN_LIST",
"value": "{\"roles\":[\"OWNER\",\"ADMIN\"]}",
"action": "ALLOW"
},
{
"ruleType": "AGENT_TAG",
"operator": "IN_LIST",
"value": "{\"tags\":[\"finance\"]}",
"action": "ALLOW"
},
{
"ruleType": "ATTESTATION_LEVEL",
"operator": "IN_LIST",
"value": "{\"attestationModes\":[\"HARDWARE\"]}",
"action": "ALLOW"
}
]
}'
```
---
### Require approval above $100
```bash
curl -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Approval Above $100",
"agentIds": ["AGENT_ID"],
"policyType": "APPROVAL_THRESHOLD",
"rules": [
{
"ruleType": "REQUIRE_APPROVAL_ABOVE",
"operator": "GREATER_THAN",
"value": "100",
"action": "REQUIRE_APPROVAL"
}
]
}'
```
---
### Block payments outside business hours
```bash
curl -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Business Hours Only",
"agentIds": ["AGENT_ID"],
"policyType": "TIME_WINDOW",
"rules": [
{
"ruleType": "TIME_WINDOW",
"operator": "BETWEEN",
"value": "{\"start\": \"09:00\", \"end\": \"17:00\"}",
"action": "ALLOW"
},
{
"ruleType": "DAY_OF_WEEK",
"operator": "IN_LIST",
"value": "[\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\"]",
"action": "ALLOW"
}
]
}'
```
---
### Allowlist specific recipients
```bash
curl -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Approved Vendors Only",
"agentIds": ["AGENT_ID"],
"policyType": "COUNTERPARTY",
"rules": [
{
"ruleType": "ALLOWED_COUNTERPARTIES",
"operator": "IN_LIST",
"value": "[\"0x1234567890abcdef1234567890abcdef12345678\", \"0xabcdef1234567890abcdef1234567890abcdef12\"]",
"action": "ALLOW"
}
]
}'
```
---
### Restrict MPP to allowed services
```bash
curl -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "MPP Service Allowlist",
"agentIds": ["AGENT_ID"],
"policyType": "COMPOSITE",
"rules": [
{
"ruleType": "MPP_ALLOWED_SERVICES",
"operator": "IN_LIST",
"value": "[\"api.service.com\", \"streaming.provider.io\"]",
"action": "ALLOW"
}
]
}'
```
---
## Approvals and trust
### Look up trust for a counterparty address
```bash
curl https://conto.finance/api/sdk/network/trust/0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18 \
-H "Authorization: Bearer $CONTO_API_KEY"
```
Requires the `network:read` scope. Returns normalized trust information, a relationship summary,
aggregate transaction history, and an active risk-signal count.
---
### Notify Slack about large payments
1. Create an approval threshold rule:
```bash
curl -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Approval Above $500",
"agentIds": ["AGENT_ID"],
"policyType": "APPROVAL_THRESHOLD",
"rules": [
{
"ruleType": "REQUIRE_APPROVAL_ABOVE",
"operator": "GREATER_THAN",
"value": "500",
"action": "REQUIRE_APPROVAL"
}
]
}'
```
2. Add a Slack notification channel with the `approval.requested` event.
Slack posts each request with a **Review in Conto** link. Approvers decide in the dashboard or from
the email link. See [approval notifications](https://conto.finance/docs/guides/approval-workflows#notify-approvers) for channel
setup.
---
## Monitoring
### Get agent spending summary
```bash
curl "https://conto.finance/api/sdk/analytics?period=month" \
-H "Authorization: Bearer $CONTO_API_KEY"
```
---
### Get wallet balance
```bash
curl https://conto.finance/api/sdk/wallets \
-H "Authorization: Bearer $CONTO_API_KEY"
```
Returns all linked wallets with current balances.
---
### List active alerts
```bash
curl "https://conto.finance/api/alerts?status=open" \
-H "Authorization: Bearer $CONTO_ORG_API_KEY"
```
---
# Connect an MCP client
Source: https://conto.finance/docs/mcp/install
## What is MCP?
MCP (Model Context Protocol) is an open protocol for connecting AI agents to external tools. Conto's MCP server gives Claude and other MCP clients tools to check balances, request payments, read transactions, and query analytics. Conto checks the agent's policies before any payment moves. The same `@conto_finance/mcp-server` package works in **Claude Desktop**, **Claude Code**, and other MCP clients.
When a user asks for a payment, the client calls a Conto tool. Conto approves the request, denies it, or sends it to a person for approval. Conto sends an approved payment from a managed wallet, and the client reports the receipt. Sandbox payments are simulated. External wallets use approve, your own transfer, then confirm. See [wallet custody](https://conto.finance/docs/guides/custody-modes).
See [MCP tools](https://conto.finance/docs/mcp/tools) for every tool.
## MCP vs dashboard assistant vs Conto Pay
Conto has three natural-language surfaces:
| Surface | Who acts | Reach it via | Auth | Onchain execution |
| --------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------- | --------------------------------- | --------------------------------------------------------------------------- |
| **MCP server** | Your own external AI agent (Claude Desktop, Claude Code, other MCP clients) | An MCP client wired to your agent | Agent SDK key (`conto_agent_...`) | Yes, with custody-aware policy checks before execution |
| **[Dashboard assistant](https://conto.finance/docs/assistant/overview)** (Conto AI) | You, working in the dashboard | Dashboard chat panel | Dashboard login | Yes, when you confirm a final approval on a managed wallet or a Conto Pay payment |
| **[Conto Pay](https://conto.finance/docs/conto-pay/overview)** | A Conto-hosted payment agent | Dashboard chat workspace | Dashboard login | Yes, through the normal request, approval, and execution flow |
## Prerequisites
- An MCP client, such as [Claude Desktop](https://claude.ai/download) or [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
- A Conto agent SDK key (`conto_agent_...`). In the dashboard, open **Agents**, select the agent,
open the **SDK Integration** tab, and choose **Generate SDK Key**.
- Node.js 20.19+, with Node.js 22 requiring 22.12+ and Node.js 23 requiring 23.2+, because the server runs through `npx`
Warning:
The MCP server always uses `CONTO_API_KEY=conto_agent_...`. Do not use an organization API key
(`conto_...`) or `CONTO_ORG_API_KEY` here.
Tip:
Already ran the CLI wizard? Use `npx @conto_finance/create-conto-agent mcp` as the server command
in the configs below and drop the `env` block. It reads the SDK key and base URL the wizard saved.
## Add the server
In each config below, replace `conto_agent_xxx...` with your SDK key.
### Claude Code
Add Conto as an MCP server in one command:
```bash
claude mcp add conto \
--env CONTO_API_KEY=conto_agent_xxx... \
--env CONTO_BASE_URL=https://conto.finance \
-- npx @conto_finance/mcp-server
```
Environment flags must come before the `--`. Everything after `--` is the command Claude Code runs for the server, so flags placed there would be passed to `npx` instead.
### Verify
Start a new Claude Code session:
```
> What are my wallet balances?
```
Claude calls the `get_wallets` tool.
### Manage the server
```bash
claude mcp list # list configured MCP servers
claude mcp remove conto
```
### Claude Desktop
Add Conto to your Claude Desktop config file:
```json
{
"mcpServers": {
"conto": {
"command": "npx",
"args": ["@conto_finance/mcp-server"],
"env": {
"CONTO_API_KEY": "conto_agent_xxx...",
"CONTO_BASE_URL": "https://conto.finance"
}
}
}
}
```
Config file: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or
`%APPDATA%\Claude\claude_desktop_config.json` (Windows).
### Verify
1. Restart Claude Desktop.
2. Look for the tools icon in the chat input. Conto's tools should appear in the list.
3. Ask: "What are my wallet balances?"
## Configuration reference
| Env var | Default | Required |
| ------------------------------- | ----------------------- | ---------------------------------------------------------------------------- |
| `CONTO_API_KEY` | none | Yes. Agent SDK key, format `conto_agent_...`. |
| `CONTO_BASE_URL` | `https://conto.finance` | No. API base URL. |
| `CONTO_BROWSER_RUNTIME_URL` | none | No. HTTPS endpoint for a trusted guarded-browser runtime. |
| `CONTO_BROWSER_RUNTIME_TOKEN` | none | Yes when the browser runtime URL is configured. |
| `CONTO_BROWSER_OBSERVATION_URL` | none | No. Same-origin endpoint that enables `observe_browser_session`. |
| `CONTO_BROWSER_SESSIONS_URL` | none | No. Same-origin endpoint that enables the session create and release tools. |
| `CONTO_LINK_WALLET` | none | No. Set to `link-cli` so `checkout_checkpoint_cancel` can cancel a Link request. |
| `CONTO_LINK_WALLET_TEST` | none | No. Set to `1` for Link test mode. |
Leave the `CONTO_BROWSER_*` settings unset unless you run a guarded browser runtime. See
[guarded browser checkout](https://conto.finance/docs/mcp/tools#guarded-browser-checkout-optional) for the tools they add.
The `CONTO_LINK_*` settings turn on the [Stripe Link card integration](https://conto.finance/docs/guides/stripe-link-cards). The tool
appears only when `link-cli` is also on the server's PATH. Log it in on that machine with
`link-cli auth login`.
### Guarded browser runtime settings
`CONTO_BROWSER_RUNTIME_URL` and `CONTO_BROWSER_RUNTIME_TOKEN` enable `guarded_browser_checkout`.
Set both or neither. With only one set, the server exits at startup. The token must contain at
least 32 characters and be a dedicated service credential. Conto API keys are rejected. The
observation and sessions URLs must be on the same origin as the runtime URL and distinct from it.
The browser runtime must own the page and must be the agent's only route to payment-sensitive
click or submit actions. Use `createGuardedPlaywrightCheckoutHttpHandler` from
`@conto_finance/sdk/playwright` to mount the authenticated endpoint the MCP server calls around
your guarded Playwright runtime.
## Next steps
### MCP tools
Link: https://conto.finance/docs/mcp/tools
Full tool list, parameters, and responses
### Set up with the CLI
Link: https://conto.finance/docs/cli/commands
Create an agent, wallet, and SDK key in one command
---
# CLI commands
Source: https://conto.finance/docs/cli/commands
The CLI is the npm package `@conto_finance/create-conto-agent`. It provides the
`create-conto-agent` and `conto` commands. Run it with `npx` to start the setup wizard:
```bash
npx @conto_finance/create-conto-agent
```
Or install it globally:
```bash
npm install -g @conto_finance/create-conto-agent
create-conto-agent
```
The examples on this page use `conto`, which the global install provides. Without a global
install, run `npx @conto_finance/create-conto-agent `, or `npx conto ` inside a
project the CLI set up. The unscoped npm package named `conto` is not the Conto CLI.
## Init wizard
```bash
create-conto-agent # Run the setup wizard (interactive)
conto init # Same as above
# Non-interactive with flags:
CONTO_ORG_API_KEY=conto_xxx create-conto-agent --base-url https://conto.finance --name my-agent --type ANTHROPIC_CLAUDE --limit 500 --chain 42431 --json
# Mainnet requires an explicit chain and real-funds acknowledgement:
CONTO_ORG_API_KEY=conto_xxx create-conto-agent --chain 4217 --confirm-mainnet
# Skip adding the CLI as a dev dependency:
create-conto-agent --no-install
# Finish an interrupted organization setup, or discard its record and start over:
CONTO_ORG_API_KEY=conto_xxx create-conto-agent --resume --json
CONTO_ORG_API_KEY=conto_xxx create-conto-agent --restart --json
```
Info:
`--chain` expects the chain ID returned by `GET /api/chains`, for example `42431`, `4217`, `8453`,
or `solana-mainnet`. If it is omitted in a headless run, setup uses Tempo Testnet (`42431`). `--api-key` is also
accepted, but `CONTO_ORG_API_KEY` avoids placing the key in process arguments.
The interactive wizard needs a terminal and a browser. For CI, scripts, and autonomous agents, use a
[noninteractive mode](#headless-and-agent-runs) instead. The wizard runs these steps:
### Configure your agent
Choose your agent name, type, daily spending limit, and network.
### Authenticate in the browser
The CLI opens a browser window to `conto.finance/cli-auth`. Sign in with your Conto account (or
create one). The browser sends the token and its expiration time back to the CLI automatically. Browser-issued
organization credentials expire after seven days. Run `conto login` again to renew one.
### Create the agent and wallet
The wizard creates a wallet on your chosen chain, registers the agent, links the wallet with
spend limits, creates a default spending policy, and generates an SDK key.
### Save your project files
The wizard writes or updates four files in your current directory:
- `.env.local`: SDK key and configuration (owner-only permissions)
- `.gitignore`: updated to ignore `.env.local`
- `conto.config.json`: agent metadata
- `example.mjs`: runnable payment example
It also adds `@conto_finance/create-conto-agent` as a dev dependency, creating a minimal
`package.json` if the directory has none. Pass `--no-install` to skip this.
### Supported networks
| Network | Currency | Chain ID | Notes |
| ----------------- | -------- | -------------- | ---------------------------------------------- |
| **Tempo Testnet** | pathUSD | 42431 | Use for evaluation, auto-funded via faucet |
| Tempo | USDC.e | 4217 | Mainnet. Moving real funds needs confirmation |
| Base | USDC | 8453 | Mainnet. Moving real funds needs confirmation |
| Solana | USDC | solana-mainnet | Mainnet. Moving real funds needs confirmation |
The wizard lists every chain returned by `/api/chains`, including Ethereum, Arbitrum, Polygon,
Base Sepolia, and Solana Devnet. The first run defaults to Tempo Testnet, so evaluation cannot move
real funds.
Warning:
Choosing any mainnet prompts for confirmation. Headless setup must include `--confirm-mainnet`
together with an explicit mainnet `--chain`, for example `--chain 4217 --confirm-mainnet`.
### Run the example
```bash
node example.mjs # Check setup + request a test payment (no funds move)
node example.mjs --resume REQUEST_ID # Continue the request printed above
```
The example prints your agent, key scopes, and wallet from `GET /api/sdk/setup`, then requests a
$0.01 payment through the policy engine. Without `--execute` it stops after the policy decision. Use `--resume REQUEST_ID` to continue
that request, or `--execute` on the first run to request and execute a new payment together.
### Headless and agent runs
For CI, scripts, and autonomous agents, use a noninteractive mode:
```bash
# Anonymous test-mode sandbox: no account, no browser, no prompts.
# Expires after 7 days. Test payments do not submit an onchain transfer.
npx @conto_finance/create-conto-agent --sandbox --json
# Existing organization: authenticate with an org API key instead of the browser.
export CONTO_ORG_API_KEY=conto_xxx
npx @conto_finance/create-conto-agent --name my-agent --json
```
Both modes write the same files as the wizard. Sandbox mode also writes the sandbox setup key,
which configures that sandbox's policies and spend limits, to `.conto/sandbox-setup.env`. Keep that
file out of the agent's runtime. `--json` prints a machine-readable result to stdout with credential
file and environment-variable names, never the credential values.
Without a TTY and without one of these modes, the CLI exits with an error that lists them instead
of hanging on a prompt. `--help` and `--version` work in any terminal, TTY or not.
After a headless run:
```bash
# Hand the sandbox to the person who will own it. Send them only the verificationUrl.
npx @conto_finance/create-conto-agent sandbox claim --json
# See what the agent can do now, and who acts on each remaining issue.
npx @conto_finance/create-conto-agent doctor --json
# Finish an organization setup that stopped with "status": "partial".
npx @conto_finance/create-conto-agent --resume --json
```
### Resume and exit codes
Organization setup records each step in `.conto/init-checkpoint.json` before it creates anything.
The record holds resource IDs and the requested configuration, never a key. When a step fails,
setup stops with `"status": "partial"`, the failed step, the IDs it created, and a resume command.
It writes no agent credentials until every step succeeds. `--resume` checks each recorded resource
and creates only what is missing. It refuses a different base URL, organization, or configuration.
`--restart` discards the record. Resources the old setup created stay in the organization.
| Exit code | Meaning |
| --------- | -------------------------------------------------------------------- |
| 0 | Setup complete |
| 1 | Setup failed before anything was created |
| 2 | Refused: unfinished setup, mismatched resume, or rejected credential |
| 3 | Partial setup, run the resume command |
| 4 | Reconciliation or owner action needed first, see the output |
## Global options
All commands support these flags:
| Flag | Description |
| ----------- | ----------------------------------- |
| `--json` | Output in JSON format for scripting |
| `--help` | Show help for any command |
| `--version` | Show CLI version |
## Agent and status
```bash
conto doctor # Diagnose setup: auth, scopes, wallet, chain, balance, base URL
conto doctor --json # One JSON document with the server's setup diagnostics
conto status # Agent info, wallet balances, spending progress
conto balance # Wallet balance table
conto config # Show current configuration
conto config # Update a config value
```
Info:
`conto doctor` calls `GET /api/sdk/setup` with your SDK key and reports each check, including
whether the key has `payments:request` and `payments:execute` and whether the wallet can execute
real transfers. When the server returns [setup diagnostics](https://conto.finance/docs/sdk/installation#setup-diagnostics),
those decide the result, and each issue says whether the agent or an owner acts next.
For anonymous sandbox setups created with `create-conto-agent --sandbox`, `conto doctor` treats
simulated execution as healthy and reports the sandbox expiry instead of warning that the wallet
is manual-only.
`conto doctor --json` prints exactly one JSON document with `ok`, `exitCode`, `baseUrl`,
`diagnosticsSource` (`server` or `cli`), `checks`, `issues`, `diagnostics`, and `setup`.
| Exit code | Meaning |
| --------- | -------------------------------------------------------- |
| 0 | No action needed |
| 1 | Action required. Each issue names who acts and the next step |
| 2 | The check could not complete, for example Conto was unreachable |
## Sandbox handoff
Hand an anonymous sandbox to the person who will own it, from a remote or headless shell:
```bash
conto sandbox claim # Create or reuse a claim link for the owner
conto sandbox claim --wait --timeout 300 # Wait until the owner claims it
conto sandbox claim-status # pending, claimed, or expired
```
`sandbox claim` uses only the sandbox organization key, `CONTO_SANDBOX_API_KEY`, from the
environment or `.env.local`. It returns a `verificationUrl`. Send that URL only to the person who
will own the sandbox, because it holds a temporary secret. The polling token stays in the
owner-only `.conto/sandbox-claim.json` and is never printed. Running `sandbox claim` again reuses a
pending link. `claim-status` never prints the link.
A claim makes that person the owner and revokes the sandbox setup key. The sandbox stays in test
mode. A claim does not fund a wallet, change spending limits, grant scopes, or enable live
payments. See [hand off to a person](https://conto.finance/docs/quickstart/agent-sandbox#hand-off-to-a-person-from-a-remote-agent).
| Exit code | Meaning |
| --------- | ---------------------------------------------------------- |
| 0 | Link issued or reused, or the sandbox was claimed |
| 1 | Request failed |
| 2 | Missing or wrong credential, or unusable local claim state |
| 3 | The link or the sandbox expired |
| 4 | `--wait` timed out and the link is still valid |
| 5 | Rate limited, see `retryAfterSeconds` |
| 130 | Interrupted while waiting, the link is still valid |
## Payments
```bash
conto pay # Request and execute payment
conto pay --dry-run # Validate input and show the request, send nothing
conto pay --purpose "reason" # Add a purpose string
conto pay --wallet # Use a specific wallet
conto pay --category "API" # Tag with spend category
```
Info:
`--dry-run` makes no API calls. It checks the address, amount, purpose, and category locally
against the same rules the server uses, then prints the `POST /api/sdk/payments/request` it would
send. It runs no policy evaluation, so a valid dry run does not mean the payment will be
approved. Run `conto limits` to see the agent's current limits.
## Transactions
```bash
conto transactions # List recent transactions (default: 10)
conto transactions --limit 50 # List more transactions
conto transactions # View transaction details
```
Add `--json` for the full record, including source wallet, authorization decision, and block details.
## Wallets
```bash
conto wallets # List all wallets with balances
conto fund # Show chain-aware funding instructions
conto fund --faucet # Request testnet funds when the configured chain supports it
```
## Spending limits
```bash
conto limits # Daily/weekly/monthly limits with progress bars
```
## Alerts
```bash
conto alerts # List all alerts
conto alerts --unread # Active alerts only (alias for --active)
```
## Policies
Run policy commands from a project the CLI set up. They use the organization API key saved during
setup. Lists show `[organization]` for mandatory baselines and the effective assignment path, such
as `[agent]` or `[wallet]`, for assigned policies.
```bash
conto policies # List effective policies and their scope/assignment path
conto policies all # List all org policies
conto policies create # Interactive policy builder
conto policies update # Update name, priority, active status
conto policies delete # Delete with confirmation
conto policies assign # Assign policy to current agent
conto policies unassign # Remove policy from agent
conto policies add-rule # Add a rule to a policy
conto policies remove-rule # Remove a rule
```
`conto policies all` lists every policy in the organization, not only your agent's, with each
policy's ID, type, priority, rules, and active status.
Warning:
Organization policies cannot be assigned or unassigned because they already apply to every
payment. To stop one, deactivate or delete it. Assigned policies do nothing until linked to an
agent, wallet, or card.
All policy commands support `--json`:
```bash
conto policies --json
conto policies all --json
conto policies create --json # Returns created policy as JSON
```
### Create a policy
`conto policies create` walks you through each step with descriptions. It prompts for:
1. **Policy name**. A descriptive label.
2. **Policy type**. One of the types below, each with an explanation.
3. **Scope**. Apply to the entire organization or only explicitly assigned targets.
4. **Priority**. Higher numbers are evaluated first. Use 0-100. Default is 50.
5. **Rules**. Optionally add the first rule immediately.
6. **Assignment**. Assigned policies can be linked to the current agent.
| Type | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------- |
| Spend Limit | Cap per-transaction, daily, weekly, or monthly totals |
| Approval Threshold | Require human approval above a dollar amount |
| Counterparty | Allow or block addresses, or check trust score and trust level. Use this for most payee rules |
| Category | Restrict which spend categories are permitted |
| Geographic | Restrict payments by recipient country |
| Budget Allocation | Set a total budget cap for a time period |
| Expiration | Deny payments outside a date range |
| Merchant | The same allow and block lists, labeled as merchants. Use this to keep merchant lists apart from other payees |
| Time Window | Restrict transactions to specific hours or days |
| Velocity | Rate limit: max transactions per time period |
| Whitelist | One allow list. Use this when only listed addresses may be paid |
| Composite | Mix rule types in one policy. Rules always combine with AND |
| Contract Allowlist | Restrict to approved smart contract addresses |
| Blackout Period | Block all transactions during specific time windows |
The type only sets which rules the builder offers. Conto evaluates each rule by its rule type, so an
allow list works the same under Counterparty, Merchant, or Whitelist.
### Add rules
`conto policies add-rule ` prompts for:
- **Rule type**: context-aware options based on the policy type
- **Operator**: `<=`, `>=`, `in list`, `not in list`, `between`, etc.
- **Value**: with placeholders that show the format (dollar amounts for limits, JSON arrays for
lists, country codes for geographic rules)
- **Action**: Allow, Deny, or Require Approval
| Rule Type | Example Value | Format |
| ---------------------- | ------------------------------------------- | ------------------------------------------------ |
| Daily Limit | `500` | Dollar amount |
| Allowed Counterparties | `["0xabc...","0xdef..."]` | JSON array of addresses |
| Geographic Restriction | `["US","CA","GB"]` | JSON array of country codes |
| Trust Score | `0.7` | Number (0.0-1.0) |
| Date Range | `{"start":"2026-10-01","end":"2026-12-31"}` | JSON with ISO dates |
| Velocity Limit | `{"maxCount":5,"period":"HOUR"}` | JSON with `maxCount` or `maxAmount` and `period` |
| Time Window | `{"start":"09:00","end":"17:00","timezone":"America/New_York"}` | JSON with 24-hour times |
| Blackout Period | `{"windows":[{"start":"02:00","end":"06:00"}]}` | JSON with a `windows` list |
A bare number for Velocity Limit is a dollar amount per hour, not a transaction count. Use
`maxCount` to limit the number of transactions or `maxAmount` to limit spend. `period` is `HOUR`,
`DAY`, `WEEK`, or `MONTH`.
## Authentication
```bash
conto login # Re-authenticate (opens browser)
conto login --base-url # Authenticate against a specific server
conto logout # Clear stored credentials
```
## MCP server
```bash
conto mcp # Start the Conto MCP server for Claude
```
## Documentation
```bash
conto docs # Open the CLI quickstart docs
conto docs commands # Open the commands reference
conto docs policies # Open the policy docs
conto docs sdk # Open the SDK installation guide
conto docs api # Open the API reference page
```
## JSON mode
Every command supports `--json` for machine-readable output:
```bash
conto status --json | jq '.agent.name'
conto pay --dry-run 0x... 10 --json | jq '.valid'
conto policies --json | jq '.[].name'
```
## Configuration files
| File | Location | Purpose |
| ---------------------- | -------- | ------------------------------------- |
| `~/.conto/config.json` | Global | API keys, agent ID, server URL |
| `.env.local` | Project | SDK key, chain config, wallet address |
| `conto.config.json` | Project | Agent metadata (generated by init) |
| `.conto/sandbox-setup.env` | Project | Sandbox setup key, kept out of the agent runtime |
| `.conto/sandbox-claim.json` | Project | Pending sandbox claim link and polling token |
| `.conto/init-checkpoint.json` | Project | Progress of an unfinished organization setup, no keys |
Configuration precedence is process environment, project `.env.local`, project
`conto.config.json`, then global `~/.conto/config.json`. Generated credential files use owner-only
permissions, and init adds `.env.local` to the project `.gitignore`. The CLI creates `.conto/`
with owner-only permissions and adds it to `.gitignore` when it first writes a file there.
## Local webhook testing
```bash
conto webhooks test http://localhost:8080/webhooks/conto --json
```
Set `CONTO_WEBHOOK_SECRET` to your local handler's signing secret. The CLI sends a synthetic signed
payment event, reports response status and latency, and exits nonzero if delivery fails. This command
requires no Conto credentials and accepts only loopback destinations. See [webhook testing](https://conto.finance/docs/guides/webhooks#test-a-local-handler).
## Continue a generated payment example
Save the request ID printed by `example.mjs`. After owner review or an interrupted run,
check or resume that same request instead of starting another payment:
```bash
node example.mjs --status REQUEST_ID
node example.mjs --resume REQUEST_ID
```
Resume checks the current status and executes only an approved request. A completed,
declined, or pending-review request is reported without another execution. Running the
example without either option starts a new payment intent.
---
# OpenClaw and Hermes skills
Source: https://conto.finance/docs/sdk/skills
The Conto skill adds spending-policy checks to supported payment flows built on [OpenClaw](https://github.com/openclaw/openclaw) or [Nous Hermes](https://hermes-agent.nousresearch.com/). It checks payments routed through the skill against your policies before the connected flow executes them. An external signer that can send outside this path can bypass the check.
Both frameworks use the same wrapper script (`conto-check.sh`) and the same Conto REST API. Only the install command and the config file location differ.
Info:
In the examples below, `pathUSD` refers to Tempo Testnet. For production wallets on Tempo Mainnet,
use `USDC.e`.
## How it works
### The agent wants to pay
For example, 50 pathUSD to `0xabc...`.
### The skill asks Conto
The skill calls the matching Conto payment endpoint.
### Conto checks your policies
- `APPROVED`: the agent makes the payment.
- `DENIED`: the agent stops and reports the reason.
- `REQUIRES_APPROVAL`: the agent waits for a person to sign off.
Quick setup registers your wallet as `EXTERNAL`, so the skill uses approve, the agent's own
transfer, then confirm. An approval token expires after 10 minutes. Managed wallets use request,
then execute. See [wallet custody](https://conto.finance/docs/guides/custody-modes).
Info:
The default **Standard SDK key** preset includes the payment scopes these flows need:
`payments:execute`, `payments:approve`, and `payments:confirm`. Only the
[policy management](#policy-management) commands need an Admin SDK key.
## Install
Requirements: `conto-check.sh` uses `curl`, `jq`, and `python3`. Install `jq` via your package
manager if missing (`brew install jq`, `apt install jq`). `python3` handles the temporary browser
callback during authentication.
### OpenClaw
Install from [ClawHub](https://clawhub.ai/kwattana/conto):
```bash
npx clawhub install conto
```
You can inspect the raw skill manifest at [`conto.finance/skill.md`](https://conto.finance/skill.md).
Use ClawHub for installation so the helper script is installed with the skill.
Warning:
Do not run `npm install @conto_finance/sdk`. The OpenClaw skill calls the Conto REST API directly through `conto-check.sh`, which ClawHub installs. `@conto_finance/sdk` is a separate TypeScript SDK that this skill does not need.
### Nous Hermes
Install from the well-known endpoint:
```bash
hermes skills install well-known:https://conto.finance/.well-known/skills/conto --force
```
This fetches `SKILL.md` and `conto-check.sh` and installs them into `~/.hermes/skills/conto/`.
Hermes may require explicit confirmation for networked finance skills. Use `--force` only after
you review that this skill talks to `https://conto.finance` and writes the SDK key you approve
into `~/.hermes/.env`.
## Choose how to start
| Situation | Start with |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| No Conto account, evaluating Conto | An [anonymous sandbox](https://conto.finance/docs/quickstart/agent-sandbox). Save `credentials.sdkKey` as `CONTO_SDK_KEY`. Sandbox payments are simulated |
| The owner has a Conto organization | [Quick setup](#quick-setup) below, or [register with the owner's token](https://conto.finance/docs/quickstart/connecting-agents#register-an-agent-in-an-existing-organization) |
| A person needs to own the sandbox, for example to review a held payment | [Hand off the sandbox](https://conto.finance/docs/cli/commands#sandbox-handoff), then continue with the same SDK key |
| The agent needs to move real funds | [Setup diagnostics](https://conto.finance/docs/sdk/installation#setup-diagnostics), then give the owner the actions they list |
Claiming a sandbox does not enable live payments. The skill's own instructions carry the same
choices in their Choose How to Start section.
## Quick setup
After installing, run setup with your agent name and wallet address:
```bash
bash skills/conto/conto-check.sh setup "my-agent" "0xYourWalletAddress" EVM 42431
```
This opens your browser for Conto login. After you approve, setup provisions:
- An agent record linked to your organization
- Your wallet registered as `EXTERNAL` custody
- Default spend limits ($100/tx, $500/day)
- An SDK key written to the framework's config file (see below)
| Argument | Default | Description |
| ---------------- | -------- | -------------------------------------------------------------- |
| `agent_name` | required | Name for your agent |
| `wallet_address` | required | Your wallet address (`0x...` for EVM, base58 for Solana) |
| `chain_type` | `EVM` | `EVM` or `SOLANA` |
| `chain_id` | `42431` | Common: `8453` (Base), `42431` (Tempo Testnet), `1` (Ethereum) |
Verify it works:
```bash
bash skills/conto/conto-check.sh budget
```
For Hermes installs, use `bash ~/.hermes/skills/conto/conto-check.sh ...` instead of
`bash skills/conto/conto-check.sh ...`.
You can adjust spend limits, add policies, and manage the agent in the [Conto dashboard](https://conto.finance).
## Config file locations
The skill writes the SDK key to a framework-specific path:
### OpenClaw
`~/.openclaw/openclaw.json`:
```json
{
"skills": {
"entries": {
"conto": {
"env": {
"CONTO_SDK_KEY": "conto_agent_your_key_here",
"CONTO_API_URL": "https://conto.finance"
}
}
}
}
}
```
Warning:
This must be valid JSON. Trailing commas or missing braces will make every OpenClaw command fail. Validate with `cat ~/.openclaw/openclaw.json | jq .`.
### Nous Hermes
`~/.hermes/.env`:
```bash
CONTO_SDK_KEY=conto_agent_your_key_here
CONTO_API_URL=https://conto.finance
```
`CONTO_SDK_KEY` holds the same agent SDK key (`conto_agent_...`) that the TypeScript SDK, CLI, and
MCP server read from `CONTO_API_KEY`. `CONTO_API_URL` is the skill's name for the API origin that
they read from `CONTO_BASE_URL`.
## Manual setup
If browser-based setup doesn't work, configure manually:
1. **Connect your agent in Conto.** Sign in to the [Conto dashboard](https://conto.finance) and create the agent record.
2. **Link your wallet.** Register the address under **Wallets > Import Watch-Only**. Then go to **Agents > your agent > Wallets > Link Wallet**, pick the wallet, and set initial spending limits.
3. **Generate an SDK key.** Go to **Agents > your agent > SDK Integration > Generate SDK Key**. Pick
**Standard** for payment flows. The preset includes the request, execute, approve, and confirm
scopes this skill uses. Pick **Admin** only if you also want the skill to manage policies,
agents, or wallets.
4. **Save the key** to the config path for your framework (above).
## Finding your wallet address
**Existing MCP wallet.** Ask your agent or run its balance or account-listing tool. Copy the address for the chain you want to use.
**Create a wallet in Conto.** Dashboard > **Wallets > Create Wallet** > choose `MANAGED` > select a chain. Conto creates the wallet and shows its address.
**Your own external wallet (hardware, MetaMask, etc.).** Register the address in Conto as `EXTERNAL` custody. Your agent handles the onchain transfer itself.
Warning:
`EXTERNAL` custody keeps full key control in your wallet stack. Conto can approve, deny, record,
and alert on payments routed through Conto, but it cannot cryptographically block a direct
transfer signed outside Conto.
## Usage
```
/conto list my policies
/conto create a $200 per-transaction limit
Send 50 pathUSD to 0x0000000000000000000000000000000000000001 on Tempo
```
CLI (OpenClaw example):
```bash
openclaw agent --agent main -m "Send 50 pathUSD to 0x0000000000000000000000000000000000000001 on Tempo"
```
### x402 payments
When the skill records x402 or MPP protocol payments, it sends the aggregate settlement fields at
the top level and per-call details in `batchItems`. Do not wrap protocol records in a top-level
`payments` array. It also sends back the `grantId` and `grantSignature` from the approved
pre-authorization. A record without a valid grant is flagged as a policy bypass and alerts the
owner. The helper takes the grant as a required argument:
```bash
bash skills/conto/conto-check.sh x402 0.05 0xPayee https://api.example.com/v1 "" pay_01J...
# Pay the x402 request with your wallet, then record it with the returned grant:
bash skills/conto/conto-check.sh x402-record 0.05 0xPayee https://api.example.com/v1 pay_01J... 0xTxHash GRANT_ID GRANT_SIGNATURE
```
Reuse the same `paymentId` for both calls. A retried pre-authorization with that `paymentId`
returns the same live grant.
## Policy management
Policy commands need an [admin SDK key](https://conto.finance/docs/sdk/authentication#admin-sdk-keys). With one, manage
policies in natural language:
```
/conto create a policy that limits each transaction to 200 pathUSD
/conto create a policy that only allows API_PROVIDER and CLOUD categories
/conto block address 0xbad... from receiving payments
/conto create a policy that requires approval for payments over 500 pathUSD
/conto delete the blocklist policy
```
With a standard key, create policies under **Policies > Create Policy** in the dashboard.
## Supported rules
Rules cover spend caps, categories, counterparties, schedules, velocity, approval thresholds, agent
identity, geography, contracts, and x402 and MPP limits. See [Policy overview](https://conto.finance/docs/policies/overview)
for the full list.
## End-to-end example: pay a vendor on Tempo Testnet
This walks the external-wallet flow on Tempo Testnet (chain ID `42431`). Fund the wallet first
with the [Tempo testnet faucet](https://docs.tempo.xyz/quickstart/faucet).
### Step 1. Install the skill
Run the install command for your framework from [Install](#install).
### Step 2. Run setup
```bash
bash skills/conto/conto-check.sh setup "my-agent" "0xYourWalletAddress" EVM 42431
```
Setup links the wallet with a $100 per-payment and $500 daily limit.
### Step 3. Request a payment
```
Send 50 pathUSD to 0x0000000000000000000000000000000000000001 on Tempo
```
The skill calls:
```bash
bash skills/conto/conto-check.sh approve 50 0x0000000000000000000000000000000000000001 0xYourWalletAddress 42431 "Vendor payment"
```
If approved immediately, the response includes `decision: "approved"`, `status: "ready_to_send"`,
an `approvalId`, a short-lived `approvalToken`, the network, and a `nextAction` with the confirmation
URL. If review is required, save `approvalRequestId` and follow the returned `statusUrl` instead of
sending funds.
The agent then sends `pathUSD` with its own wallet tool. The Conto skill does not sign or send
transfers.
### Step 4. Confirm back to Conto
After the onchain transfer succeeds:
```bash
bash skills/conto/conto-check.sh confirm APPROVAL_ID TX_HASH APPROVAL_TOKEN
```
Conto records the payment, updates spend counters, and the transaction appears in the dashboard.
The confirmation receipt includes the `requestId`, `transactionId`, normalized `status`, network,
explorer URL, and transaction `statusUrl`.
### Step 5. Verify
Open **Transactions** in the dashboard and follow the explorer link.
### What happens when a policy blocks the payment?
```
Send 300 pathUSD to 0x0000000000000000000000000000000000000001 on Tempo
```
The $100 per-payment limit from setup denies it. The response carries a fixed denial reason and no
transfer happens. The denied attempt appears under **Alerts & Approvals** in the dashboard.
```json
{
"approved": false,
"decision": "declined",
"status": "declined",
"approvalId": "payment_request_id",
"reasons": ["This payment exceeds a configured spending limit."],
"reasonCodes": ["SPENDING_LIMIT"],
"requiresHumanApproval": false,
"statusUrl": "/api/sdk/payments/payment_request_id",
"nextAction": null
}
```
### Review a pending payment in OpenClaw
The OpenClaw skill can list and decide payment reviews for the human owner assigned to the agent:
```bash
bash skills/conto/conto-check.sh pending-approvals
bash skills/conto/conto-check.sh approve-request APPROVAL_REQUEST_ID HUMAN_ACTION_TOKEN "Approved"
# Or deny it:
bash skills/conto/conto-check.sh deny-request APPROVAL_REQUEST_ID HUMAN_ACTION_TOKEN "Reason"
```
The helper uses `approvalRequestId`, not `approvalId`. `approvalId` is the payment request ID.
`approvalRequestId` is a different ID for the human review. The workflow still enforces eligible
roles and users, sequential approval order, required approval count, and expiry. The skill must show
the amount, currency, recipient, purpose, and approval progress. It submits a decision only after
the human gives an explicit decision and the matching one-time token from their approval email.
Never retrieve that token from the human's email or messaging account.
On the final approval, managed wallets execute automatically and the response includes `receiptUrl`.
If OpenClaw controls an external wallet, the response instead includes an `EXECUTE_EXTERNALLY`
handoff with the exact transfer fields. After the wallet tool returns a hash, confirm without a token
within 24 hours of the approval:
```bash
bash skills/conto/conto-check.sh confirm PAYMENT_REQUEST_ID TX_HASH
```
`PAYMENT_REQUEST_ID` is the `approvalId` from the original `approve` response, the same ID as
`APPROVAL_ID` in Step 4. Do not pass the `approvalRequestId` here.
## Rate limits
On `429`, the response body includes `retryAfter` in seconds. `conto-check.sh` does not retry. It
exits with the error, so wait that long before calling again. See [Defaults](https://conto.finance/docs/reference/defaults)
for the per-agent limits.
## Troubleshooting
### Skill can't reach Conto (connection refused or timeout)
Verify `CONTO_API_URL` is correct. For the hosted platform, use `https://conto.finance`. Test:
```bash
curl https://conto.finance/api/sdk/setup \
-H "Authorization: Bearer $CONTO_SDK_KEY"
```
A valid JSON response means the URL is reachable.
### 401 AUTH_FAILED
SDK keys are scoped to a single agent. Check that:
- The key starts with `conto_agent_` (not `conto_`)
- The key has not been revoked on the agent's **SDK Integration** tab
- You're using the correct key for the correct agent
Generate a new key under **Agents > your agent > SDK Integration > Generate SDK Key**.
### Payment denied unexpectedly
The denial response includes fixed `reasons`. Common causes:
- No usable wallet is linked to this agent. Link an active wallet on the agent's page.
- Spend limit exceeded. Check the daily, weekly, and monthly counters on the agent's **Wallets** tab.
- Counterparty not on allowlist. If you have an `ALLOWED_COUNTERPARTIES` policy, the recipient must be listed.
- Outside time window. `TIME_WINDOW` and `DAY_OF_WEEK` policy rules take an optional IANA `timezone` and use UTC without one.
- Category mismatch. Both category rules, `ALLOWED_CATEGORIES` and `BLOCKED_CATEGORIES`, deny a payment with no `category`. Send `category` on every payment when you use category rules.
### Payment approved but no onchain transfer (external wallet)
In external wallet mode, Conto only enforces policy. The agent must transfer funds itself. If `approve` succeeds but no transfer happens:
- Check the agent has enough `pathUSD` in its wallet.
- Check the agent logs for transfer errors.
- Ensure the wallet address in Conto matches the agent's actual wallet.
If the transfer succeeded but Conto doesn't show it, the `confirm` call may have failed. Run it
again with the same hash:
```bash
bash skills/conto/conto-check.sh confirm APPROVAL_ID TX_HASH APPROVAL_TOKEN
```
### Admin commands fail with 403
Policy management requires an Admin SDK key. The default Standard preset covers the payment
lifecycle (`payments:request`, `payments:execute`, `payments:approve`, `payments:confirm`) and
reads, but not management scopes like `policies:write`. Check the key type on the agent's
**SDK Integration** tab.
---
# Payments
Source: https://conto.finance/docs/sdk/payments
## Overview
The payment flow has two steps:
1. **Request** - Request authorization and policy evaluation
2. **Execute** - Execute the approved payment onchain
Or use `autoExecute: true` to request and execute in a single API call. You can also use the SDK convenience method `pay()` to do both in one call.
## Choose the right flow
Managed wallets use request, then execute. External wallets use approve, your own transfer, then
confirm. An exported private key can sign transfers outside Conto. See
[custody modes](https://conto.finance/docs/guides/custody-modes) before exporting a key.
x402 and MPP service payments use `conto.x402` and `conto.mpp` instead. Their `preAuthorize()`
returns a grant (`grantId`, `grantSignature`) that `record()` must send back, or fixed `reasons`
and `reasonCodes` when the payment is declined or held. A record without a valid grant is flagged
as a policy bypass and alerts the owner. See [x402
payments](https://conto.finance/docs/guides/x402-api-payments#grants-and-policy-bypass) and [MPP
payments](https://conto.finance/docs/guides/mpp-session-payments#grants-and-policy-bypass).
To buy from a store with a buyer's card, use `conto.purchases` instead. Conto shops, checks out
and pays in the background, and the buyer reviews each purchase. See [Purchases](https://conto.finance/docs/sdk/purchases).
## payments.request()
Request authorization for a payment. This evaluates policies without executing.
```typescript
const request = await conto.payments.request({
amount: 100,
recipientAddress: '0x0000000000000000000000000000000000000001',
recipientName: 'OpenAI',
purpose: 'GPT-4 API credits',
category: 'AI_SERVICES',
});
```
### Parameters
| Parameter | Type | Required | Description |
| ----------------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `amount` | number | Yes | Positive amount with at most 2 decimal places, in the wallet chain stablecoin |
| `recipientAddress` | string | Yes | Wallet address. `0x` + 40 hex chars for EVM, or base58 (32-44 chars) for Solana. |
| `recipientName` | string | No | Human-readable name |
| `purpose` | string | No | Why this payment is needed. Shown to reviewers and recorded in the audit trail |
| `category` | string | No | Free-form label of up to 50 characters. Category rules and workflow triggers compare it to the categories they name, so reuse the values your policies use |
| `context` | object | No | Optional `invoice`, `department`, `project`, and `recipientCountry` (two-letter ISO code). Other keys are rejected |
| `walletId` | string | No | Specific wallet to use |
| `urgency` | string | No | `LOW`, `NORMAL` (default), `HIGH`, or `CRITICAL`. A label stored with the request and shown on the transaction receipt. It does not change evaluation or execution order |
| `autoExecute` | `boolean` | No | If `true`, automatically execute the payment when approved. Returns the transaction result directly instead of requiring a separate `execute()` call. |
| `targetContractAddress` | string | No | Smart contract address for contract interaction policy evaluation |
| `functionSelector` | string | No | 4-byte function selector (e.g., `0xa9059cbb`) for contract allowlist rules |
| `idempotencyKey` | string | No | Client-supplied key that makes retries safe. Reusing the same key with the same request returns the original request. Reusing it with different request parameters returns a conflict. |
Note:
You can include invoice details in `context.invoice`. Supported fields are `vendorId`,
`vendorAddress`, `id`, `hash`, `sourceUrl`, `payload`, `expectedAmount`, `currency`, and
`dueDate`. These fields are included in relevant payment lifecycle webhooks.
```typescript
context: {
invoice: { id: 'invoice_1234', expectedAmount: 100, currency: 'USDC' },
},
```
Note:
If an approval workflow matches any non-denied request, Conto opens the approval request
immediately and returns `REQUIRES_APPROVAL`. A production agent's first payment to a payee Conto
has not seen is held the same way. Keep the returned `requestId` and check its status before
taking another payment action.
### Response
```typescript
interface PaymentRequestResult {
requestId: string;
status: 'APPROVED' | 'DENIED' | 'REQUIRES_APPROVAL' | 'PROCESSING' | 'COMPLETED';
amount: number;
currency: string;
recipient: string;
custodyMode?: CustomerCustodyMode;
expiresAt?: string;
reasons: string[];
approvalRequestId?: string;
statusUrl: string;
actionUrl?: string;
receipt?: {
transactionId: string;
txHash?: string;
settlementMode: 'live' | 'test';
explorerUrl?: string;
};
}
```
Info:
**Currency depends on chain:** The `currency` field reflects the stablecoin used on the wallet's
blockchain. On Base, Ethereum, Arbitrum, Polygon, and Solana, payments use `USDC`. On Tempo
Testnet, it is `pathUSD`. On Tempo Mainnet, it is `USDC.e`. The amount is always denominated in
the stablecoin configured for the payment.
### Idempotent retries
The SDK sets a new `idempotencyKey` on each `request()` call and reuses it for its own retries. Pass your own key, stored with the business action, so a retry after a crash or restart returns the original request.
- Same `idempotencyKey` + same request payload: returns the original request using the same public response shape
- Same `idempotencyKey` + different request payload: returns HTTP `409` with `code: "IDEMPOTENCY_CONFLICT"`
- Different `idempotencyKey`: creates a new payment request
```typescript
const request = await conto.payments.request({
amount: 100,
recipientAddress: '0x0000000000000000000000000000000000000001',
purpose: 'Top up API credits',
idempotencyKey: 'payment-req-2026-04-17-001',
});
```
### Example
```typescript
const request = await conto.payments.request({
amount: 50,
recipientAddress: '0x0000000000000000000000000000000000000001',
purpose: 'API credits',
});
switch (request.status) {
case 'APPROVED':
console.log('Payment approved');
console.log('Expires:', request.expiresAt);
break;
case 'DENIED':
console.log('Payment denied:', request.reasons);
break;
case 'REQUIRES_APPROVAL':
// For example, the payee needs review. Approval in Conto executes the payment.
console.log('Needs manual approval:', request.reasons);
break;
case 'COMPLETED':
console.log('Already completed. Read the receipt at:', request.actionUrl);
break;
case 'PROCESSING':
console.log('Check status:', request.actionUrl);
break;
}
```
## payments.execute()
Execute an approved payment request.
```typescript
const result = await conto.payments.execute(requestId);
```
Info:
`payments.execute()` authenticates the SDK key, verifies request ownership, enforces rate limits,
and requires the `payments:execute` scope. Conto rechecks applicable spend limits and payment
controls before sending and prevents the same request from being executed twice.
An approval from `request` expires after 5 minutes. Check `expiresAt` before you call `execute()`,
and request the payment again if it has passed.
If approval in Conto already executed the payment, `execute()` returns the
existing receipt with `alreadyExecuted: true` and sends nothing. Other states return a specific
error instead of a receipt:
| Code | HTTP | Meaning |
| ---------------------------- | ---- | ------------------------------------------------------------------- |
| `APPROVAL_PENDING` | 409 | Still waiting for approval. Approval in Conto executes the payment. |
| `POLICY_DENIED_AT_EXECUTION` | 403 | Payment controls declined it at execution. Do not retry. |
| `POLICY_REAPPROVAL_REQUIRED` | 403 | Payment controls now require approval. Request a new payment. |
| `PAYMENT_DENIED` | 403 | The payment was declined earlier. Do not retry. |
| `PAYMENT_PROCESSING` | 409 | Another execution is in flight. Check `payments.status()`. |
| `EXPIRED` | 400 | The approval window ended. Request the payment again. |
Denials and approval holds include `reasons`, for example
`This payment exceeds a configured spending limit.` See
[payment reasons](https://conto.finance/docs/sdk/error-handling#payment-reasons).
### Parameters
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | ---------------------------------- |
| `requestId` | string | Yes | The requestId from payment request |
### Response
```typescript
interface PaymentExecuteResult {
requestId: string;
alreadyExecuted?: boolean;
status: 'processing' | 'completed';
amount: number;
currency: string;
recipient: string;
receipt: {
transactionId: string;
txHash?: string;
settlementMode: 'live' | 'test';
explorerUrl?: string;
};
}
```
Info:
A `processing` response means the payment was submitted and should be checked with
`payments.status(requestId)`. Every successful execution includes a stable Conto transaction ID.
Check `settlementMode` before treating the hash or explorer link as evidence of live settlement.
### Example
```typescript
const request = await conto.payments.request({
amount: 50,
recipientAddress: '0x0000000000000000000000000000000000000001',
});
if (request.status === 'APPROVED') {
const result = await conto.payments.execute(request.requestId);
console.log('Conto transaction:', result.receipt.transactionId);
console.log('Settlement mode:', result.receipt.settlementMode);
console.log('Transaction hash:', result.receipt.txHash);
if (result.receipt.explorerUrl) console.log('Explorer:', result.receipt.explorerUrl);
}
```
## payments.pay()
Convenience method that requests and executes in one call.
```typescript
const result = await conto.payments.pay({
amount: 50,
recipientAddress: '0x0000000000000000000000000000000000000001',
purpose: 'API credits',
});
```
### Behavior
- If **approved**: Executes immediately and returns result
- If **denied**: Throws `ContoError` with code `PAYMENT_DENIED` and `reasons`
- If **processing**: Throws `ContoError` with code `PAYMENT_PROCESSING`. Check `payments.status(requestId)`
- If **requires approval**: Throws `ContoError` with code `REQUIRES_APPROVAL`, the `requestId`,
and `reasons`. Approval in Conto executes the payment. Afterwards, call
`payments.execute(requestId)` or `payments.status(requestId)` for the receipt. Do not call
`pay()` again for the same purchase.
### Example
```typescript
try {
const result = await conto.payments.pay({
amount: 50,
recipientAddress: '0x0000000000000000000000000000000000000001',
purpose: 'API credits',
});
console.log('Payment state:', result.status);
if (result.receipt?.txHash) console.log('Transaction:', result.receipt.txHash);
} catch (error) {
if (error instanceof ContoError && error.code === 'PAYMENT_DENIED') {
console.log('Payment denied:', error.message);
} else if (error.code === 'REQUIRES_APPROVAL') {
console.log('Payment needs manual approval');
}
}
```
## autoExecute flag
`autoExecute: true` requests authorization and executes an approved payment in one API call.
Warning:
Requires both `payments:request` and `payments:execute` scopes. The standard SDK key preset
includes both. If a key lacks `payments:execute` (for example, a legacy custom-scoped key), the
flag is silently ignored and the response is a normal APPROVED status that you must execute
separately.
### How it works
- If **APPROVED** + `autoExecute: true`: Starts execution and returns `status: "PROCESSING"`. A
public receipt with the stable transaction ID, settlement mode, and hash is included when
execution has produced one
- If **DENIED** or **REQUIRES_APPROVAL**: `autoExecute` is ignored, normal response returned
- If execution has an ambiguous result: Returns `PROCESSING` with the payment status URL in
`actionUrl`. Check that URL before deciding whether any further action is safe.
- If execution could not start: Returns `APPROVED` with the execute URL in `actionUrl`. Nothing was sent
- If payment controls decline it at execution: Returns `DENIED` with `reasons`
### Example
```typescript
const paymentJobId = 'invoice-1234'; // stable per logical payment, reused on retries
const response = await fetch('https://conto.finance/api/sdk/payments/request', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CONTO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 50,
recipientAddress: '0x0000000000000000000000000000000000000001',
purpose: 'API credits',
autoExecute: true,
idempotencyKey: paymentJobId,
}),
});
const result = await response.json();
if (!response.ok) throw new Error(`Payment request failed: ${result.code}`);
// PROCESSING: follow result.statusUrl. Never call execute again to repair an ambiguous result.
console.log(result.status, result.receipt?.transactionId);
```
## payments.status()
Check the status of a payment request.
```typescript
const status = await conto.payments.status(requestId);
```
### Response
```typescript
interface PaymentStatusResult {
requestId: string;
settlementMode: 'live' | 'test' | 'unknown';
status:
| 'approved'
| 'declined'
| 'review_required'
| 'reconciliation_required'
| 'processing'
| 'completed'
| 'failed'
| 'expired';
amount: number;
currency: string;
recipient: string;
recipientName: string | null;
purpose: string | null;
category: string | null;
expiresAt: string | null;
createdAt: string;
reasons?: string[];
action: null;
transaction: {
id: string;
settlementMode: 'live' | 'test' | 'unknown';
txHash: string | null;
status: 'processing' | 'completed' | 'failed';
confirmedAt: string | null;
blockNumber: number | null;
chain: {
type: 'evm' | 'solana';
id: string;
};
explorerUrl: string | null;
detailsUrl: string;
} | null;
}
```
### Example: polling for confirmation
Use [bounded status polling](https://conto.finance/docs/guides/payment-lifecycle#bounded-polling) so a declined or expired
request does not leave your application waiting indefinitely. Stop on terminal states, surface
review or required actions, and retain the request ID if the deadline passes.
## Status reference
See [payment statuses](https://conto.finance/docs/guides/payment-lifecycle#the-lifecycle) for each status and what to do
next. For `declined` and `review_required`, `reasons` says why, for example
`This payee needs review before payments to it can proceed automatically.`
## External wallet approve and confirm flow
If your agent holds the signing keys, use the external-wallet flow instead of `payments.execute()`:
1. Call `POST /api/sdk/payments/approve` to evaluate policy and receive an `approvalToken`
2. Submit the onchain transfer through your own signer or wallet integration
3. Call `POST /api/sdk/payments/{requestId}/confirm` with the final `txHash`, plus the
`approvalToken` when the original `/approve` response returned one
The TypeScript SDK provides `conto.payments.approve(input)` and
`conto.payments.confirm(approvalId, { txHash, approvalToken })` for these endpoints.
`approve` accepts `amount`, `senderAddress`, `recipientAddress`, numeric `chainId`, and optional
`recipientName`, `purpose`, `category`, and `context`. It returns `approved`, `decision`, and
`requiresHumanApproval`. A successful approval also supplies `approvalId`, `approvalToken`, and
`expiresAt`. An early budget denial may have no ID. `approvalId` is the payment request ID, so pass
it to `confirm` and `status`. `approvalRequestId`, returned when a person must review the payment,
is a different ID for that review.
Only `PROD` agents can pay from mainnet wallets. If the agent is `DEV` or `STAGING` and `chainId` is
a mainnet, `approve` declines with `NO_WALLET` and the reason "This agent's environment is DEV. Set
it to PROD to pay from a mainnet wallet." No wallet is created. See the
[go-live checklist](https://conto.finance/docs/guides/production-readiness).
For a production agent, the first `approve` to a payee Conto has not seen holds the payment for
counterparty review (`requiresHumanApproval: true`), the same as `request`. If Conto cannot record
that review, `approve` returns HTTP 503 with `COUNTERPARTY_REVIEW_UNAVAILABLE` and no approval.
Retry later.
An approval reserves the amount against the agent's spend limits, budgets, velocity limits, and
per-counterparty limits, then Conto checks policy again before it returns a token. If that check fails, `approve` declines and
releases the reservation. Two approvals sent at once can both decline this way, so retry one. If
Conto cannot run the check, `approve` returns HTTP 503 `POLICY_REVALIDATION_UNAVAILABLE`. An
unconfirmed approval releases its reservation when the token expires.
Wallet approvals share the agent's daily, weekly, and monthly policy allowance with live-card
spending. Reserving an approval rechecks hard period limits under the same agent lock used by
card reservations. Individual wallet limits remain additional restrictions, and synthetic test-card
spending does not reduce this shared allowance. See [Spend limits](https://conto.finance/docs/policies/spend-limits).
When `approve` returns `requiresHumanApproval: true`, nothing is reserved until a person approves.
The final approval reserves the amount and checks policy again. If a spend limit or policy no longer
allows the payment, the approval is recorded but the payment is declined. Otherwise send the
transfer and call `confirm` without a token within 24 hours. `status` returns the deadline as
`expiresAt`. A lapsed approval releases its reservation once, and a late `confirm` returns
`EXPIRED`.
`confirm` counts a payment against the agent's spend limits exactly once. A payment its approval
already reserved is not counted again. If the transfer fails onchain, Conto releases the amount it
counted.
Only send through your signer when approval is granted and the token has not expired. Tokens expire
after 10 minutes. Persist the approval ID and the transfer hash before calling `confirm`.
Confirmation returns `accepted: true`, `confirmed: false`, and `status: "processing"`. Use
`conto.payments.status(approvalId)` to follow settlement. Neither helper retries automatically. A
confirmation conflict must be reconciled against the original transfer. Never send again to repair
confirmation.
Conto does not check an external wallet balance. If the wallet is short, the transfer reverts
onchain and confirmation records the payment as failed.
Note:
If the `senderAddress` has not been seen before, Conto auto-creates an external wallet record for
that address and links it to the agent's organization. That record still counts toward the
organization's wallet limit, so approval can fail with `WALLET_LIMIT_REACHED` when the org is
already at capacity.
## Connected-card credential delivery
Use the [connected-card checkout guide](https://conto.finance/docs/sdk/connected-cards) to request a single-use credential,
acknowledge delivery, report the outcome, and handle approvals or uncertain outcomes. Start with
[card setup](https://conto.finance/docs/guides/connected-cards).
## Checkout checkpoints
Use `conto.checkout` when your agent completes a checkout itself, such as a Link checkout. Call
`begin()` right before the payment method opens and `confirm()` right before the payment is
submitted, then report the outcome. When the agent's rules require approval, `begin()` returns
`AWAITING_APPROVAL` and `waitForApproval()` waits for the owner's decision. See
[checkout checkpoints](https://conto.finance/docs/guides/checkout-checkpoints) and [Conto with Stripe Link](https://conto.finance/docs/guides/stripe-link).
`checkout.getLinkCard()` asks Conto first, and asks Stripe Link for a card only after Conto
approves the purchase. Stripe Link issues the card. See
[Stripe Link cards for agents](https://conto.finance/docs/guides/stripe-link-cards).
## Next steps
### Error handling
Link: https://conto.finance/docs/sdk/error-handling
Handle payment errors
### Examples
Link: https://conto.finance/docs/quickstart/connecting-agents#sdk-examples
See complete integration examples
---
# Track and recover a payment
Source: https://conto.finance/docs/guides/payment-lifecycle
A payment request is an intent to spend. Authorization permits the next step. It does not prove
funds moved. A transaction records execution. Retain the request ID before executing, then use it
to reconcile the outcome.
## The lifecycle
### Request authorization
Conto approves the payment, sends it for review, or denies it. If it is denied, stop and show
the reason.
### Wait for review if needed
If a person must review it, keep the request ID. After they decide, read the request status
again before you continue.
### Execute
Once the request is approved, execute it through the managed wallet or send it from your own
wallet.
### Track the result
The payment moves to `processing`, then to `completed` or `failed`.
Use `GET /api/sdk/payments/{requestId}` or `conto.payments.status(requestId)` with the same agent
key. Status reads require `transactions:read`. The status endpoint returns lowercase values,
`request` returns uppercase values, and the external-wallet `approve` returns `ready_to_send` when
approved. Do not compare them interchangeably. This table maps each response `status` to the status
read right after it.
| Response `status` | Returned by | Status read |
| ------------------- | ------------------------------------ | ----------------------------------------------------------------- |
| `APPROVED` | `request` | `approved` |
| `REQUIRES_APPROVAL` | `request` | `review_required` |
| `DENIED` | `request` | `declined` |
| `PROCESSING` | `request` once execution has started | `processing`, `completed`, `failed`, or `reconciliation_required` |
| `ready_to_send` | `approve` (external wallet) | `approved` |
| `review_required` | `approve` (external wallet) | `review_required` |
| `declined` | `approve` (external wallet) | `declined` |
What each status read means:
| Status read | Meaning | What your application does |
| ------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `approved` | Authorization permits the next payment step | Check expiry and your stored execution state before sending |
| `review_required` | Waiting for a human decision | Retain the ID and wait for an authorized reviewer |
| `reconciliation_required` | The transfer may have been sent and Conto must review the outcome | Retain the request and transaction IDs, and investigate before another write |
| `processing` | Execution or reconciliation is in progress | Poll with a deadline or consume webhooks. Do not send again |
| `completed` | The payment flow completed | Inspect the transaction and the original execution receipt |
| `declined` | Controls or review stopped the request | Explain the result. Do not execute |
| `failed` | The payment flow failed | Reconcile transaction evidence before deciding on a new intent |
| `expired` | Authorization expired | Do not execute this authorization. Decide whether a new request is needed |
## What proves settlement?
Execution returns `receipt.transactionId` and `receipt.settlementMode`. `live` means an onchain
transfer, including testnet. `test` means simulated settlement, with no transaction hash, explorer
URL, block number, or confirmation time. Status and transaction reads repeat `settlementMode`.
`unknown` means Conto has no execution evidence for the request. `completed` alone does not prove
funds moved onchain. Preserve the receipt and request ID.
For an onchain flow, wait for `transaction.status: "completed"` and check the intended chain.
A hash alone, an approval, or HTTP 200 is not confirmation.
## Bounded polling
Call this helper after submission or an uncertain result. It only reads state. Any state other
than `processing`, including review, expiry, and denial, returns control to your application. Also
configure the SDK request timeout: an in-flight HTTP call can finish after the polling deadline.
```typescript
import { Conto, type PaymentStatusResult } from '@conto_finance/sdk';
export async function waitForPayment(
conto: Pick,
requestId: string,
timeoutMs = 60_000
): Promise {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const state = await conto.payments.status(requestId);
if (state.status !== 'processing') return state;
await new Promise((resolve) => setTimeout(resolve, 2_000));
}
throw new Error(`Still processing: retain ${requestId} and check again later`);
}
```
If a read fails with 429, honor `Retry-After` and resume later with the same request ID. A polling
deadline or read failure does not cancel the payment. See [rate limits](https://conto.finance/docs/guides/rate-limits).
## Recovery recipes
### Execution timed out
1. Read the original request ID. A timeout is not proof that execution failed.
2. If there is a transaction, follow that transaction to completion or failure.
3. If the state is unresolved, retain it and investigate. Do not automatically repeat execution,
even if the authorization still reads `approved`.
4. Create a new intent only after establishing the original outcome and confirming the business
action still needs payment. Preserve the association between both intents.
A repeated execute never sends twice. After completion it returns the existing receipt with
`alreadyExecuted: true`. While another attempt is in flight, it returns `409 PAYMENT_PROCESSING`.
Reconcile the original request before any other write.
Durably persist your business action ID, idempotency key, Conto request ID, execution attempt, and
receipt so a restarted worker can discover an earlier execution attempt.
### A human must approve
Keep `REQUIRES_APPROVAL` requests pending in your application. An authorized reviewer decides in
the configured [approval workflow](https://conto.finance/docs/guides/approval-workflows). For a managed wallet, approving the
payment in Conto executes it. Check the same request ID after review: `payments.status()` shows the
transaction, and `payments.execute()` returns the existing receipt with `alreadyExecuted: true`
instead of sending again. Before approval, `execute()` returns `409 APPROVAL_PENDING`. A declined
payment returns `403 PAYMENT_DENIED` with `reasons`.
### External confirmation returns a conflict
Send through your external signer only after approval. Confirm using the request ID, final hash,
and the approval token when one was issued. After human workflow approval, the token can be omitted.
Repeated confirmation does not duplicate recorded spend but can return `409 ALREADY_CONFIRMED`.
Compare the returned `txHash` with your saved transfer and follow `statusUrl`. If the hashes differ,
stop and investigate. Never send a second transfer to resolve a confirmation error.
### The wallet has insufficient funds
Inspect the intended wallet's address, network, currency, and available balance. Funding another
wallet or chain does not repair this request. After funding, re-read the request and check expiry
and prior execution evidence before you resume or request new authorization.
### A webhook is missing or repeated
Reconcile with the payment status endpoint. Verify webhook signatures and persist accepted events
before returning success. Handle duplicate or out-of-order delivery without creating a new payment.
Let the current resource state decide your next action. Follow
[webhook delivery and deduplication](https://conto.finance/docs/guides/webhooks).
## Next steps
Use [server integration examples](https://conto.finance/docs/quickstart/connecting-agents#server-handlers) to connect recovery to an authenticated
application route, and exercise these paths with [payment tests](https://conto.finance/docs/guides/policy-testing).
---
# Wallet custody
Source: https://conto.finance/docs/guides/custody-modes
Before you link a wallet, choose a custody mode. It answers two separate questions:
1. **Custody**. Who can sign the transfer?
2. **Enforcement**. Can Conto actually stop the transfer, or only govern the path that goes
through Conto?
## Quick comparison
| Mode | Signing control | Typical flow | Can Conto block a direct spend signed outside Conto? | Best for |
| -------------------------------------- | ------------------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| **Managed** (`MANAGED`) | Your selected root-control model plus Conto | `request -> execute` | **Conto-managed:** yes. **Organization-controlled:** no, the owner key can sign outside Conto. | Teams that want Conto-orchestrated execution |
| **External (watch-only)** (`EXTERNAL`) | You, your agent, or your wallet stack | `approve -> transfer -> confirm` | **No.** Conto only controls payments routed through Conto. | Self-custody, MPC wallets, existing wallet tools |
## What policies mean in each mode
### Managed wallets
The wallet provider holds the private key for a managed wallet, and Conto never stores it. Conto
signs only after your policies and approvals pass, and you choose who holds root control when you
create the wallet.
- Conto evaluates the payment.
- If approved, Conto orchestrates execution.
- If denied, the payment does not execute through Conto.
- The agent calls `POST /api/sdk/payments/request`, then `POST /api/sdk/payments/{id}/execute`, or
passes `autoExecute: true` on the request.
Create a managed wallet in the dashboard or use `POST /api/wallets` with
`custodyMode=MANAGED`. Set `controlModel` to `ORGANIZATION_CONTROLLED` or `CONTO_MANAGED`. If you omit it, the wallet is organization-controlled when your organization has registered an owner key, and Conto-managed otherwise.
The selection is fixed for the new wallet. Changing the organization default never migrates an
existing wallet.
### Managed control models
**Organization-controlled** (`ORGANIZATION_CONTROLLED`)
- Your organization's owner key is the root owner. Only your organization holds it.
- Conto is a separate signer and signs only after your policies and approvals pass. On EVM, the
wallet provider also limits what Conto's signer can do.
- Your owner can manage the wallet and approve a key export.
- Conto cannot stop everything. The owner key can authorize actions outside Conto, and an exported
key can sign outside Conto.
**Conto-managed** (`CONTO_MANAGED`)
- A Conto authorization key is the root owner.
- Conto is the only signer and signs only after your policies and approvals pass.
- There is no self-service key export.
- There is no customer signing path outside Conto.
For organization-controlled creation, an organization owner first registers a base64 SPKI P-256
public key in **Settings → Wallet Control** and keeps the matching private key in the customer's own
key manager. Conto never receives or stores that private key. Your owner key alone forms the owner
quorum that holds root control, and Conto is added separately as an additional signer.
Organization-controlled wallets are EVM only. For Solana, create the wallet with
`controlModel: "CONTO_MANAGED"`.
## External (watch-only) wallets
When you import a wallet, Conto registers it in `EXTERNAL` mode. You keep the keys, and your
agent or wallet stack signs the transaction.
- Conto can still evaluate policies before the transfer.
- Conto can still require approval, record the payment, and keep the audit trail.
- The agent calls `POST /api/sdk/payments/approve`, sends the transfer with its own signer or
wallet, then calls `POST /api/sdk/payments/{id}/confirm`.
- Conto **cannot cryptographically block** a direct transfer signed outside Conto.
You can register an external wallet directly with `custodyMode=EXTERNAL` plus `address`, or use
the watch-only import flow in the dashboard. Without a chain, the API records the address on its
default chain (Tempo Testnet). For a Solana address, also send `chainType: "SOLANA"` and `chainId`
(`solana-mainnet` or `solana-devnet`). The dashboard import detects a Solana address and asks for
the network.
## How to choose
- Pick **Organization-controlled managed** if you need customer root administration or an export
path while keeping Conto as the normal signer after Conto policies and approvals.
- Pick **Conto-managed** if you want Conto to remain the only product signing path and do not need
customer self-service export.
- Pick **External** if you already have a wallet stack, MPC signer, or agent-held wallet and want
to keep that setup.
## Export a wallet key
An organization Owner can export the private key of an organization-controlled managed wallet. The
export is owner-authorized and end-to-end encrypted. Conto relays the signed request and returns
only HPKE ciphertext, and never receives the customer owner private key, the HPKE recipient private
key, or the plaintext wallet key. This flow is not available for `CONTO_MANAGED` wallets.
### Before you start
You need two customer-controlled P-256 keys with different purposes:
1. The **owner authorization key** registered in **Settings → Wallet Control**. Keep its private
half in your KMS or offline signing environment.
2. A new **HPKE recipient key** for this export. Keep its private half outside Conto. Conto receives
only its base64 SPKI public key.
Warning:
Anyone with the decrypted wallet key can sign outside Conto and bypass Conto policies. Treat the
export as a high-risk governance action and secure the resulting key accordingly.
### Export from the dashboard
1. Sign in as the organization **Owner**.
2. Open **Wallets**, open the organization-controlled wallet menu, and select **Export encrypted
key**.
3. Paste the HPKE recipient public key and choose **Prepare owner authorization**.
4. Copy and base64-decode the authorization payload. Sign the decoded bytes outside Conto with the
registered owner authorization private key using ECDSA P-256 with SHA-256 and DER encoding.
5. Paste the base64 DER signature and download the encrypted export before the displayed expiry.
6. Decrypt `ciphertext` using `encapsulatedKey`, the HPKE recipient private key, and this suite:
`DHKEM(P-256, HKDF-SHA256) / HKDF-SHA256 / ChaCha20-Poly1305`.
For a base64 PKCS8 owner authorization private key, this TypeScript snippet prints the signature
the dialog expects. Save it as `sign.ts` and run `npx tsx sign.ts` with both environment variables
set:
```ts
import { createPrivateKey, sign } from 'node:crypto';
const payload = Buffer.from(process.env.CONTO_AUTHORIZATION_PAYLOAD!, 'base64');
const ownerKey = createPrivateKey({
key: Buffer.from(process.env.CONTO_OWNER_PRIVATE_KEY!, 'base64'),
format: 'der',
type: 'pkcs8',
});
const signature = sign('sha256', payload, ownerKey).toString('base64');
console.log(signature);
```
Conto does not keep the ciphertext or the key. A successful export sets the wallet's `keyAccess`
to `EXPORTED` and writes an audit event. If Conto cannot confirm the result, `keyAccess` shows
`EXPORT_RECONCILIATION_REQUIRED`, and the next export request checks the provider's export record
before it continues.
### Operational boundaries
- The authorization payload covers this wallet, this recipient key, and this request, and expires
after 10 minutes.
- Only an authenticated organization Owner can prepare or submit an export.
- Conto's additional-signer key cannot authorize export or administer the customer owner quorum.
- Conto can continue signing through its normal policy and approval path after export, but the
exported key also permits signing outside Conto.
## Related guides
### Choose your integration
Link: https://conto.finance/docs/guides/choose-your-integration
Compare SDK, OpenClaw, Hermes, x402, and MPP
### Payments API
Link: https://conto.finance/docs/sdk/payments
See the managed and external payment flows
---
# Pay for APIs with x402
Source: https://conto.finance/docs/guides/x402-api-payments
When an AI agent calls a paid API, the API returns HTTP `402 Payment Required` with a payment challenge. The [x402 protocol](https://www.x402.org/) standardizes this flow. The agent checks each payment with Conto before it pays.
Note:
x402 endpoints enforce explicit API-key scopes. Pre-authorization requires `payments:request`,
managed signing (`pay`) and recording execution require `payments:execute`, and budget or service analytics require
`analytics:read`.
## Prerequisites
An active agent with an SDK key ([Quickstart](https://conto.finance/docs/quickstart/setup))
A funded wallet with `USDC` on the network the API settles on (Base for production, or Base Sepolia for
testing). x402 is not offered on Tempo: pre-authorize and pay return `422 UNSUPPORTED_NETWORK` for a
wallet on a network with no USDC.
An x402-enabled API to call (or a test service that returns an x402 challenge)
## How x402 works
```
Agent calls API → HTTP 402 → Conto pre-authorizes → Agent pays & retries → Conto records
```
Which path enforces depends on who holds the key:
| Wallet | Call | What Conto does |
| ---------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------- |
| External (agent's key) | `pre-authorize`, then `record` | Checks the payment before the agent signs and detects a payment that skipped the check when it is recorded |
| Managed (EVM) | `pay`, then `record` | Enforces: Conto signs only the approved payment, for exactly that payee, amount, and USDC contract |
See [Conto-signed payments](#conto-signed-payments-managed-wallets) for the managed path.
## Step 1: Set up x402 policies
Without x402-specific policies, only your general spend limits apply. The [policy rule reference](https://conto.finance/docs/policies/advanced) lists every x402 rule type and value format.
Warning:
x402 rules apply to every payment the agent makes, not only x402 calls. `X402_MAX_PER_REQUEST`
caps all of its payments, and `X402_MAX_PER_SERVICE` denies any payment without an x402 service.
Assign x402 policies only to an agent that pays for APIs.
### Recommended starter policy
The two rules below belong to different x402 policy types, so this example uses `COMPOSITE`.
### Create the policy
Go to **Policies > Create Policy**.
| Field | Value |
|-------|-------|
| Name | x402 API Guardrails |
| Policy Type | COMPOSITE |
### Add rules
**Rule 1: Cap per API call**
| Field | Value |
|-------|-------|
| Rule Type | X402_MAX_PER_REQUEST |
| Operator | LTE |
| Value | 0.50 |
| Action | ALLOW |
No single x402 payment can exceed $0.50.
**Rule 2: Budget per service**
| Field | Value |
|-------|-------|
| Rule Type | X402_MAX_PER_SERVICE |
| Operator | LTE |
| Value | `{"maxAmount": 50}` |
| Action | ALLOW |
Spend with any one API domain cannot exceed $50 in a rolling 24 hours.
### Assign to agent
Go to the agent's **Permissions** tab and assign the policy.
Info:
You can also add `X402_ALLOWED_SERVICES` with a JSON array value such as `["api.example.com",
"data.provider.io"]`. This is the strongest guardrail: the agent can pay only the API domains you
list.
## Step 2: Pre-authorize a payment
When your agent receives an HTTP 402, send the payment details to `POST /api/sdk/x402/pre-authorize`.
Conto evaluates your x402 policies plus your general spend limits. An approved payment returns a
single-use `grantId` and `grantSignature` and reserves the amount for 10 minutes. The agent signs the
payment and retries the API call. A declined payment returns `reasons` and `reasonCodes`, and the
agent skips the call. See [Pre-authorization](#pre-authorization).
If Conto returns `503 PAYMENTS_PAUSED`, no grant was issued. Stop before signing or retrying the
resource request. Ask the owner to check payment availability, then retry with the same payment
identity after service resumes. `LIVE_PAYMENTS_PAUSED` and the `LIVE_PAYMENT_CAP_*` codes are
separate mainnet controls. See [error handling](https://conto.finance/docs/sdk/error-handling).
## Step 3: Record the transaction
After the payment settles onchain, record it with `POST /api/sdk/x402/record` and include the
`grantId` and `grantSignature` from Step 2. See [Recording transactions](#recording-transactions).
Warning:
A record without a valid, unexpired grant is treated as a policy bypass. Conto records the
payment, flags it for review, and alerts the account owner. See [Grants and policy bypass](#grants-and-policy-bypass).
## Step 4: Monitor spending
Open **Machine Spend** in the dashboard for spend, calls, and average cost per service and endpoint.
Conto also raises [anomaly alerts](https://conto.finance/docs/sdk/machine-spend#anomaly-alerts) on recorded payments. From
the API, use [Budget tracking](#budget-tracking) and [Querying services](#querying-services).
## Full integration example
This example uses an external wallet: your agent signs, and Conto checks the payment first and
records it after. It follows the x402 wire format:
- The API answers `402`. x402 v1 puts the payment requirements in the response body. x402 v2 puts
them, base64 encoded, in the `PAYMENT-REQUIRED` header.
- The agent retries with a signed payload in the `X-PAYMENT` header (v1) or `PAYMENT-SIGNATURE`
header (v2).
- The API returns the settlement, including the transaction hash, in the `X-PAYMENT-RESPONSE`
header (v1) or `PAYMENT-RESPONSE` header (v2).
Conto takes networks in CAIP-2 form, such as `eip155:8453` for Base. v1 challenges use names like
`base`, so convert them first.
```typescript
import { randomUUID } from 'node:crypto';
import { Conto } from '@conto_finance/sdk';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
// Your own signer. On this path Conto checks the payment but never signs it.
declare const wallet: {
getAddress(): Promise;
signX402Payment(input: { x402Version: 1 | 2; requirement: any }): Promise; // base64
};
// x402 v1 network names in CAIP-2 form. v2 challenges already use CAIP-2.
const CAIP2: Record = { base: 'eip155:8453', 'base-sepolia': 'eip155:84532' };
const toCaip2 = (network: string) => (network.includes(':') ? network : CAIP2[network]);
const decode = (value: string) => JSON.parse(Buffer.from(value, 'base64').toString('utf8'));
async function callPaidApi(url: string, sessionId: string) {
// Step 1: Call the API
const first = await fetch(url);
if (first.status !== 402) return first.json();
// Step 2: Read the payment requirements (v2 header, or v1 body)
const requiredHeader = first.headers.get('PAYMENT-REQUIRED');
const challenge = requiredHeader ? decode(requiredHeader) : await first.json();
const x402Version: 1 | 2 = challenge.x402Version === 2 ? 2 : 1;
const requirement = challenge.accepts[0]; // pick the entry your wallet can pay
const baseUnits = requirement.amount ?? requirement.maxAmountRequired; // v2 ?? v1
const amount = Number(baseUnits) / 1e6; // USDC uses 6 decimals
const network = toCaip2(requirement.network);
const payerAddress = await wallet.getAddress();
const paymentId = randomUUID(); // persist it: a retry with the same ID returns the same grant
// Step 3: Check the payment against your Conto policies
const auth = await conto.x402.preAuthorize({
amount,
recipientAddress: requirement.payTo,
resourceUrl: url,
scheme: requirement.scheme,
sessionId,
paymentId,
payerAddress,
network,
});
if (!auth.authorized) {
throw new Error(`${auth.decision}: ${auth.reasons?.join(' ') ?? auth.message}`);
}
// Step 4: Sign the payload and retry the API call
const signed = await wallet.signX402Payment({ x402Version, requirement });
const paid = await fetch(url, {
headers: { [x402Version === 2 ? 'PAYMENT-SIGNATURE' : 'X-PAYMENT']: signed },
});
// Step 5: Record the settlement with the grant from Step 3
const receiptHeader = paid.headers.get(
x402Version === 2 ? 'PAYMENT-RESPONSE' : 'X-PAYMENT-RESPONSE'
);
if (!receiptHeader) throw new Error(`Payment not settled: HTTP ${paid.status}`);
const settlement = decode(receiptHeader); // { success, transaction, network, payer }
// record() is not retried automatically. On SETTLEMENT_PENDING, call it again later
// with the same fields before auth.grantExpiresAt. A record after that is flagged as a bypass.
await conto.x402.record({
amount,
recipientAddress: requirement.payTo,
txHash: settlement.transaction,
resourceUrl: url,
paymentId,
sessionId,
payerAddress,
network,
grantId: auth.grantId,
grantSignature: auth.grantSignature,
});
return paid.json();
}
```
With a managed EVM wallet, replace Steps 3 and 4 with `conto.x402.pay()`. Pass the same
fields plus `x402Version` and `paymentRequirements: requirement`, then retry the API call with the
returned `paymentHeaderName` header set to `xPaymentHeader`. Record the settlement the same way.
## Troubleshooting
### Pre-authorize returns denied but I haven't set x402 policies
Your general spend limits still apply to x402 payments. If the x402 amount plus today's spend
exceeds your daily limit, it's denied. Check your wallet-level and agent-level spend limits.
### Budget shows less spend than expected
Conto counts x402 spend only from recorded payments. Record every payment after it settles.
### X402_ALLOWED_SERVICES blocks everything
The allowlist is strict. Only listed domains can receive x402 payments. Add the exact domain
(for example `api.example.com`, not `example.com` or `www.api.example.com`).
### Can I reuse a pre-authorization?
No. Each grant covers one payment and is used up when it is recorded, so every paid call needs
its own pre-authorization.
## API reference
### Pre-authorization
With the SDK, call `conto.x402.preAuthorize()` with the same fields.
```bash
curl -X POST https://conto.finance/api/sdk/x402/pre-authorize \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 0.05,
"recipientAddress": "0xPayToAddress",
"resourceUrl": "https://api.example.com/data",
"sessionId": "research-run-42",
"facilitator": "https://facilitator.example.com",
"scheme": "exact",
"paymentId": "pay_01J...",
"payerAddress": "0x1111111111111111111111111111111111111111",
"network": "eip155:8453"
}'
```
Conto derives `serviceDomain` from `resourceUrl`, so you do not need to send it. Send the
facilitator, scheme, payment ID, and session ID when available so matching policies evaluate the
actual x402 challenge.
Send `payerAddress` and `network` together. Use the exact signing address and the CAIP-2 network
from the selected x402 requirement. This binds policy evaluation to the wallet that will pay. The
binding is required when an agent has more than one active wallet.
**Response (Approved):**
```json
{
"authorized": true,
"decision": "approved",
"message": "Payment is approved.",
"grantId": "cmm59z8fj000l49h7bjqza11p",
"grantSignature": "pg1.3kq...",
"grantExpiresAt": "2026-09-18T12:10:00.000Z",
"x402": {
"serviceDomain": "api.example.com",
"amount": 0.05
}
}
```
An approved decision issues a single-use grant for the exact wallet, recipient, and amount, and
reserves that amount against the wallet's spend limits. The grant expires after 10 minutes. If it
is never recorded, Conto releases the reservation. Retrying pre-authorization with the same
`paymentId` returns the same live grant instead of reserving the amount twice.
**Response (Denied):**
```json
{
"authorized": false,
"decision": "declined",
"message": "Payment was declined by your controls.",
"reasons": ["This payment exceeds a configured spending limit."],
"reasonCodes": ["SPENDING_LIMIT"],
"x402": {
"serviceDomain": "api.example.com",
"amount": 0.05
}
}
```
A declined or review-required decision includes `reasons` and `reasonCodes`. Conto does not queue a
`review_required` payment for a person, so handle it like a decline. x402 decisions use the shared
[payment reasons](https://conto.finance/docs/sdk/error-handling#payment-reasons). x402 price, endpoint, service and session
limits return `SPENDING_LIMIT`. Blocked or unlisted services and facilitators return
`RECIPIENT_NOT_ALLOWED`.
### Recording transactions
With the SDK, call `conto.x402.record()`.
```bash
curl -X POST https://conto.finance/api/sdk/x402/record \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 0.05,
"recipientAddress": "0xPayToAddress",
"txHash": "0xabc123...",
"resourceUrl": "https://api.example.com/data",
"paymentId": "pay_01J...",
"sessionId": "research-run-42",
"payerAddress": "0x1111111111111111111111111111111111111111",
"network": "eip155:8453",
"grantId": "cmm59z8fj000l49h7bjqza11p",
"grantSignature": "pg1.3kq...",
"context": {
"department": "engineering",
"project": "research"
}
}'
```
`txHash` is required. Before recording spend, Conto verifies a successful settlement on the
wallet's configured chain with the exact sender, recipient, asset, and amount. A pending receipt
returns `SETTLEMENT_PENDING`. A mismatched transfer returns `SETTLEMENT_MISMATCH`. An unavailable RPC
returns a temporary service error. Only verified settlements are stored as confirmed.
Repeat the same `payerAddress` and `network` used for pre-authorization. Conto resolves the pair to
one active wallet and uses that wallet for settlement verification and spend tracking.
#### Grants and policy bypass
Send the `grantId` and `grantSignature` from the approved pre-authorization. Conto checks the
onchain transfer against the grant's recipient and amount, not the values in the record request,
then marks the grant used. The reserved amount becomes actual spend, so it is counted once.
| Record | Result |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Live grant, transfer matches | `201`, response includes `grantId` |
| Live grant, amount or recipient differs | `422 SETTLEMENT_MISMATCH`. The grant stays unused and a corrected record can still use it |
| Grant already used | `409 GRANT_ALREADY_CONSUMED` |
| Same `paymentId` already recorded | `409`, nothing recorded again |
| No grant, an unknown grant, or an expired grant | `201` with `policyBypass: true`, `bypassReason`, and `agentFrozen` |
A record without a valid grant means the payment settled without an approval from Conto. Conto
still records it, because the money has already moved, but it flags the transaction for review and
raises a critical alert to the account owner. `bypassReason` is `GRANT_MISSING`,
`GRANT_INVALID`, or `GRANT_EXPIRED`. `agentFrozen` is true when Conto froze the agent for this
bypass. A frozen agent's API key is rejected until an owner unfreezes the agent from the dashboard.
While that alert is still open, further bypasses by the same agent update it (occurrence count and
recent transactions) instead of sending another email.
Note:
The grant is a check, not a signing control. An agent that holds its own signing key can still pay
without asking Conto. Conto detects that when the payment is recorded. To stop the payment before
it is signed, keep the key behind a signer that requires a Conto approval.
#### Batch recording
To record several calls settled together, send one record with the per-call details in `batchItems`:
```bash
curl -X POST https://conto.finance/api/sdk/x402/record \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 0.03,
"recipientAddress": "0xPayToAddress",
"txHash": "0xsettlement123...",
"resourceUrl": "https://api.example.com/batch",
"paymentId": "batch_01J...",
"payerAddress": "0x1111111111111111111111111111111111111111",
"network": "eip155:8453",
"grantId": "cmm59z8fj000l49h7bjqza11p",
"grantSignature": "pg1.3kq...",
"batchItems": [
{ "amount": 0.01, "resourceUrl": "https://api.example.com/v1", "paymentId": "pay_1" },
{ "amount": 0.02, "resourceUrl": "https://api.example.com/v2", "paymentId": "pay_2" }
]
}'
```
The top-level fields describe the one settlement, and `txHash` is the transaction that paid for every
item. The record endpoint does not accept a top-level `payments` array. The sum of all `batchItems`
must equal the top-level amount, and the grant must come from a pre-authorization for that same
total.
Use optional `context.department` and `context.project` values during pre-authorization and
recording to apply scoped budgets and persist those dimensions on the transaction.
### Conto-signed payments (managed wallets)
When the agent's wallet is a managed EVM wallet, the agent holds no key. Conto approves and signs
the payment. Call `/api/sdk/x402/pay` (`conto.x402.pay()` in the SDK) instead of pre-authorize, with
the same fields plus the accepted entry from the 402 challenge:
```bash
curl -X POST https://conto.finance/api/sdk/x402/pay \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 0.05,
"recipientAddress": "0xPayToAddress",
"resourceUrl": "https://api.example.com/data",
"paymentId": "pay_01J...",
"paymentRequirements": {
"scheme": "exact",
"network": "base",
"maxAmountRequired": "50000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xPayToAddress",
"maxTimeoutSeconds": 60,
"extra": { "name": "USD Coin", "version": "2" }
}
}'
```
Conto makes the same decision as pre-authorize. A declined or review-required payment returns the
same body, and nothing is signed. An approved payment is signed as an EIP-3009
`TransferWithAuthorization` for exactly the approved payee, amount, USDC contract, and chain:
```json
{
"authorized": true,
"decision": "approved",
"message": "Payment is approved and signed.",
"grantId": "cmm59z8fj000l49h7bjqza11p",
"grantSignature": "pg1.3kq...",
"grantExpiresAt": "2026-09-18T12:10:00.000Z",
"paymentHeaderName": "X-PAYMENT",
"xPaymentHeader": "eyJ4NDAyVmVyc2lvbiI6MS...",
"authorization": {
"from": "0x1111111111111111111111111111111111111111",
"to": "0xPayToAddress",
"value": "50000",
"validAfter": "0",
"validBefore": "1789733460",
"nonce": "0x3f..."
},
"x402": { "serviceDomain": "api.example.com", "amount": 0.05 }
}
```
Retry the resource request with the `paymentHeaderName` header set to `xPaymentHeader`, then call
`/api/sdk/x402/record` with the grant and the settlement `txHash` as usual. Send `"x402Version": 2`
for a v2 challenge. Conto then returns a `PAYMENT-SIGNATURE` header.
| Result | Response |
| --------------------------------------------------------------- | -------------------------------------------------------------------- |
| Approved | `200` with the payment header |
| Declined or needs review | `200`, `authorized: false`, nothing signed |
| `payTo`, amount, asset, network, scheme, or token domain differ | `422 REQUIREMENTS_MISMATCH` with `field`. Nothing reserved or signed |
| Wallet's network has no USDC (Tempo) | `422 UNSUPPORTED_NETWORK`. Nothing reserved or signed |
| Wallet is external, Solana, or not provisioned | `409 MANAGED_SIGNING_UNAVAILABLE`. Use pre-authorize and record |
| Payment already signed | `409 GRANT_ALREADY_SIGNED` |
| Signing failed | `502 SIGNING_FAILED`. Nothing was paid |
| Payments paused | `503 PAYMENTS_PAUSED`. Nothing reserved or signed. Stop and check availability |
Managed signing covers only USDC, the stablecoin that supports EIP-3009. The authorization is valid until
the earlier of the grant expiry and `maxTimeoutSeconds` from now. Each grant is signed once, so
`pay` is not retried automatically. If the payment is never recorded, Conto checks the
authorization nonce onchain after it expires: an unused authorization releases the reserved
amount, and a used one is recorded as a confirmed payment.
Note:
If `/api/sdk/x402/pay` returns `404`, managed signing is off. Use pre-authorize and record
instead. After a wallet's key is exported, whoever holds the exported key can sign without Conto.
### Querying services
List the x402 services this agent has paid. Optional `period` is `24h`, `7d` (default), `30d`, or
`90d`. Optional `limit` defaults to 50, max 100. With the SDK, call
`conto.x402.services({ period, limit })`.
```bash
GET /api/sdk/x402/services?period=7d&limit=50
```
### Budget tracking
Check the agent's spend, limits, and burn rate. With the SDK, call `conto.x402.budget({ sessionId })`.
```bash
GET /api/sdk/x402/budget?sessionId=research-run-42
```
The response contains the authenticated agent's aggregate daily, weekly, and monthly spend,
effective limits, and usage trend. Those periods use UTC calendar boundaries, include confirming
transactions, and count each item in a recorded batch as one call. When `sessionId` is supplied,
`spend.session` also reports the call count and amount recorded with that customer-defined ID.
---
# Pay with MPP sessions
Source: https://conto.finance/docs/guides/mpp-session-payments
The Machine Payments Protocol (MPP) supports session-based micropayments on Tempo. Your agent opens a session with a deposit, makes many requests against it, and the session settles when it closes. Conto checks both MPP charges and sessions against your policies before the agent pays.
Note:
MPP endpoints enforce explicit API-key scopes. Pre-authorization requires `payments:request`,
recording settlement requires `payments:execute`, and budget or service analytics require
`analytics:read`.
## Prerequisites
An active agent with an SDK key ([Quickstart](https://conto.finance/docs/quickstart/setup))
A funded Tempo wallet with `pathUSD` on testnet or `USDC.e` on mainnet
An MPP-enabled service to call (or an MPP test service)
## MPP vs x402
| | x402 | MPP |
| ----------------- | ----------------------------------------------------------- | ---------------------------------------------- |
| **Payment model** | Pay-per-request | Session with deposit |
| **Best for** | Occasional API calls | Streaming, multi-request workflows |
| **Chain** | USDC on Base, Ethereum, Arbitrum, Polygon, or Solana | Tempo (USDC.e on mainnet, pathUSD on testnet) |
| **Settlement** | Immediate per call | On session close |
| **Unused funds** | N/A | Returned to agent |
Use x402 when each API call is independent. Use MPP when your agent will make many requests to the same service in a session, such as streaming data, iterative processing, or chat APIs.
## How MPP works
```
Pre-authorize → Open session (deposit) → Make requests → Close session → Record in Conto
```
## Step 1: Set up MPP policies
Send `"intent": "charge"` or `"intent": "session"` on every MPP pre-authorization. Session rules such
as `MPP_MAX_SESSION_DEPOSIT` deny a request with no intent. They also apply to every payment the
agent makes, so assign this policy only to an agent that pays for MPP services. The
[policy rule reference](https://conto.finance/docs/policies/advanced) lists every MPP rule type and value format.
The two rules below belong to different MPP policy types, so this example uses `COMPOSITE`.
### Create the policy
Go to **Policies > Create Policy**.
| Field | Value |
|-------|-------|
| Name | MPP Session Guardrails |
| Policy Type | COMPOSITE |
### Add rules
**Rule 1: Cap the session deposit**
| Field | Value |
|-------|-------|
| Rule Type | MPP_MAX_SESSION_DEPOSIT |
| Operator | LTE |
| Value | 25 |
| Action | ALLOW |
No session deposit or one-time charge can exceed $25.
**Rule 2: Budget per service**
| Field | Value |
|-------|-------|
| Rule Type | MPP_MAX_PER_SERVICE |
| Operator | LTE |
| Value | `{"maxAmount": 50}` |
| Action | ALLOW |
Spend with any one MPP service cannot exceed $50 in a rolling 24 hours.
### Assign to agent
Go to that agent's **Permissions** tab and assign the policy.
## Step 2: Pre-authorize the session
Before your agent opens an MPP session, check it with `POST /api/sdk/mpp/pre-authorize`. Send the
deposit amount and `"intent": "session"`. Conto evaluates your MPP policies plus your general spend
limits.
If pre-authorization returns `503 PAYMENTS_PAUSED`, do not open the session: no grant or spend
reservation was created. Ask the owner to check payment availability, then retry after service
resumes. See [error handling](https://conto.finance/docs/sdk/error-handling) for the distinct mainnet payment controls.
An approved session returns a `grantId` and `grantSignature` and reserves the amount against your
spend limits. The grant is a ceiling that your records draw down until `grantExpiresAt`: the
shortest `MPP_MAX_SESSION_DURATION` that applies, or 24 hours. A record after that is flagged as a
policy bypass. Proceed with the wallet already configured for your MPP client.
A declined session returns `reasons` and `reasonCodes`. `SPENDING_LIMIT` means a price, deposit,
session budget, or rate limit applied, so request a smaller session. See
[Pre-authorization](#pre-authorization).
## Step 3: Run the session
Open the session with your MPP client, make requests against the deposit, then close it. The service settles the charged total onchain and returns the rest of the deposit.
## Step 4: Record in Conto
Record the amount the service charged with `POST /api/sdk/mpp/record`. Send a stable
`credentialId`, `"intent": "session"`, the same `sessionId`, and the `grantId` and `grantSignature`
from Step 2. Record before `grantExpiresAt`, and send `closeSession: true` on the final record so
Conto closes the session and releases the unused reservation (`@conto_finance/sdk` 0.5.3 or later).
If the service charged nothing, still send the final record with `amount: 0`, `closeSession: true`,
and the grant. Leave out `txHash`, because a session record with `txHash` returns `422`. See
[Recording transactions](#recording-transactions).
Warning:
Record the amount the service charged, not the deposit. Recording the deposit overcounts spend
against your limits.
## Step 5: Monitor spending
Open **Machine Spend** in the dashboard for spend per service and endpoint. See
[Machine spend analytics](https://conto.finance/docs/sdk/machine-spend) for combined x402 and MPP service spend. From the
API, use [Budget tracking](#budget-tracking) and [Querying services](#querying-services).
## Full integration example
This example uses `conto.mpp` from `@conto_finance/sdk`. `mppClient` stands for your MPP client
library.
```typescript
import { randomUUID } from 'node:crypto';
import { Conto } from '@conto_finance/sdk';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
// Your MPP client library. Conto does not provide one.
declare const mppClient: {
// Reads the service's 402 session challenge.
getChallenge(url: string): Promise<{ id: string; recipient: string }>;
openSession(input: { url: string; deposit: string }): Promise<{ channelId: string }>;
request(input: { channelId: string; url: string; body: unknown }): Promise<{ data: unknown }>;
// Resolves with the total the service charged, in dollars.
closeSession(input: { channelId: string }): Promise<{ charged: number }>;
};
const DEPOSIT = 10;
async function runMppSession(serviceUrl: string, queries: string[]) {
const sessionId = randomUUID(); // your ID for this session in Conto
const challenge = await mppClient.getChallenge(serviceUrl);
// 1. Check the session deposit against your Conto policies
const auth = await conto.mpp.preAuthorize({
amount: DEPOSIT,
depositAmount: DEPOSIT,
intent: 'session',
sessionId,
challengeId: challenge.id,
recipientAddress: challenge.recipient,
resourceUrl: serviceUrl,
});
if (!auth.authorized) {
throw new Error(`${auth.decision}: ${auth.reasons?.join(' ') ?? auth.message}`);
}
// 2. Open the session, make requests, and close it
const session = await mppClient.openSession({ url: serviceUrl, deposit: DEPOSIT.toFixed(2) });
const results: unknown[] = [];
for (const query of queries) {
const result = await mppClient.request({
channelId: session.channelId,
url: `${serviceUrl}/query`,
body: { query },
});
results.push(result.data);
}
const { charged } = await mppClient.closeSession({ channelId: session.channelId });
// 3. Record the charged amount (0 when nothing was charged) before auth.grantExpiresAt and
// close the session.
// No txHash: the service settles a session from the Tempo escrow channel.
await conto.mpp.record({
amount: charged,
recipientAddress: challenge.recipient,
resourceUrl: serviceUrl,
credentialId: session.channelId,
intent: 'session',
depositAmount: DEPOSIT,
sessionId,
challengeId: challenge.id,
grantId: auth.grantId,
grantSignature: auth.grantSignature,
closeSession: true,
});
return { results, charged };
}
```
## Troubleshooting
### Pre-authorize declined after several sessions
If you added `MPP_MAX_CONCURRENT_SESSIONS`, the agent gets `PAYMENT_CONTROLS` once it reaches the
limit. Conto counts a session as open from its approved pre-authorize until a record sends
`closeSession: true`, or until its `MPP_MAX_SESSION_DURATION` (24 hours without one) passes.
Another pre-authorize for a `sessionId` that is already open does not count as a new session.
Close sessions on the final record, even when nothing was charged, or raise
`MPP_MAX_CONCURRENT_SESSIONS`.
### Session deposit higher than wallet balance
The deposit is locked onchain when the session opens. If your wallet has $15 and you try to open
a $20 session, it fails. Check your wallet balance and request a smaller deposit.
### Settled amount doesn't match expected charges
The service determines final settlement. If charges seem wrong, check the session details with
the service provider. Record the total the service reports when the session closes. Conto
rejects a record whose recipient or currency differs from the grant, or whose amount is more
than what is left of it.
### Can I use MPP on Base?
No. Conto supports MPP only on Tempo (see [Supported
chain](#supported-chain) for chain IDs, currencies, and explorers). For paid
APIs on Base or Ethereum, use the [x402 protocol](https://conto.finance/docs/guides/x402-api-payments) instead.
## API reference
### Pre-authorization
Check a session or a one-time charge before the agent pays. With the SDK, call
`conto.mpp.preAuthorize()` with the same fields.
```bash
curl -X POST https://conto.finance/api/sdk/mpp/pre-authorize \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 10.00,
"recipientAddress": "0xServiceAddress",
"resourceUrl": "https://api.service.com/stream",
"intent": "session",
"depositAmount": 10.00,
"sessionId": "stream-session-42",
"scheme": "tempo",
"credentialId": "cred_01J...",
"challengeId": "challenge_01J...",
"paymentMethod": "tempo"
}'
```
Conto derives `serviceDomain` from `resourceUrl`, so you do not need to send it. Always send
`intent` (`charge` or `session`). Session-lifecycle rules deny a pre-authorization without it. Send
the scheme, credential, challenge, payment method, and stable session ID when available so MPP
service, method, and session-lifecycle controls have the context they require. `sessionId` is
required when `intent` is `session`.
**Response (Approved):**
```json
{
"authorized": true,
"decision": "approved",
"message": "Payment is approved.",
"grantId": "cmm59z8fj000l49h7bjqza11p",
"grantSignature": "pg1.3kq...",
"grantExpiresAt": "2026-09-18T12:10:00.000Z",
"mpp": {
"serviceDomain": "api.service.com",
"amount": 10.0
}
}
```
An approved decision issues a grant and reserves `amount` against the wallet's spend limits until
`grantExpiresAt`. A `charge` grant covers one payment of exactly that amount and expires after 10
minutes. A `session` grant is a ceiling: several records for the same `sessionId` may draw it down
until the total reaches `amount` or the grant expires. It lasts for the session's maximum duration:
the shortest `MPP_MAX_SESSION_DURATION` that applies, or 24 hours by default. A record after
`grantExpiresAt` is a policy bypass. When a grant expires, Conto releases the part of the reservation
that was not recorded. Retrying pre-authorization with the same `challengeId` (or `credentialId`)
returns the same live grant.
**Response (Denied):**
```json
{
"authorized": false,
"decision": "declined",
"message": "Payment was declined by your controls.",
"reasons": ["This payment exceeds a configured spending limit."],
"reasonCodes": ["SPENDING_LIMIT"],
"mpp": {
"serviceDomain": "api.service.com",
"amount": 10.0
}
}
```
A declined or review-required decision includes `reasons` and `reasonCodes`. Conto does not queue a `review_required` payment for a person, so handle it like a decline. The codes name the kind of
control without rule values or policy names. `SPENDING_LIMIT` covers price ceilings, per-endpoint
and per-service limits, session budgets and deposits, velocity, and period limits.
`RECIPIENT_NOT_ALLOWED` covers blocked or unlisted services. Concurrent-session, session-duration,
session-intent and payment-method rules return `PAYMENT_CONTROLS`. See
[payment reasons](https://conto.finance/docs/sdk/error-handling#payment-reasons) for the full list.
### Recording transactions
After the service charges the agent, record the charged amount. With the SDK, call
`conto.mpp.record()`.
```bash
curl -X POST https://conto.finance/api/sdk/mpp/record \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 3.50,
"recipientAddress": "0xServiceAddress",
"resourceUrl": "https://api.service.com/stream",
"credentialId": "cred_01J...",
"sessionId": "stream-session-42",
"scheme": "tempo",
"challengeId": "challenge_01J...",
"paymentMethod": "tempo",
"intent": "session",
"depositAmount": 10.00,
"paymentReceipt": "eyJzdGF0dXMiOiJzdWNjZXNzIiw...",
"grantId": "cmm59z8fj000l49h7bjqza11p",
"grantSignature": "pg1.3kq...",
"context": {
"department": "engineering",
"project": "streaming"
}
}'
```
When `paymentReceipt` is supplied, Conto rejects unsuccessful or challenge-mismatched receipts
with `400` and amount mismatches with `409`. Because the receipt is caller supplied, passing its
structural and consistency checks does not confirm settlement. A record without `txHash` stays
`PENDING`.
When `txHash` is supplied, Conto confirms the record only after the onchain transfer matches the
wallet sender, recipient, amount, and currency. Pending transfers return `409` and mismatches
return `422`. Send `txHash` for a `charge`. A session settles from the Tempo escrow channel, not
from the agent wallet, so a session record with `txHash` returns `422`. Record a session without
it.
Reuse the same `credentialId` and `sessionId` on retries. A session ID cannot be reused by another
agent. Send `closeSession: true` on a session's final record. Conto marks the session closed, stops
counting it toward `MPP_MAX_CONCURRENT_SESSIONS`, and releases the unused part of the grant. When
the session charged nothing, send `amount: 0` with `closeSession: true` and the grant. Conto closes
the session and releases the whole reservation without recording a transaction. Repeating the close
returns the same `sessionClosed: true` response.
#### Grants and policy bypass
Grants and policy bypass work as they do for [x402](https://conto.finance/docs/guides/x402-api-payments#grants-and-policy-bypass),
with these differences:
- A `charge` record must be the grant's exact amount and use its grant within 10 minutes. A
`session` grant is a ceiling that lasts for the session: each record must fit within what is left
of it.
- A record must use the grant's `sessionId`. When the record sends `challengeId`, it must match the
grant's.
- The recipient and currency must match the grant. When `txHash` is supplied, the onchain check uses
the grant's recipient.
| Record | Result |
| ----------------------------------------------------- | ------------------------------------------------------------------ |
| Live grant, record matches | `201`, response includes `grantId` |
| Live grant, record does not match | `422 SETTLEMENT_MISMATCH`. The grant is unchanged |
| Charge grant already used, or session ceiling used up | `409 GRANT_ALREADY_CONSUMED` |
| No grant, an unknown grant, or an expired grant | `201` with `policyBypass: true`, `bypassReason`, and `agentFrozen` |
A record without a valid grant is recorded, flagged, and alerted as a policy bypass.
`agentFrozen` is true when Conto froze the agent for this bypass.
Use optional `context.department` and `context.project` values during pre-authorization and
recording to apply scoped budgets. Conto persists the same dimensions on the aggregate settlement
transaction.
#### Batch recording
To record several calls settled together, put the total in the top-level fields and each call in
`batchItems`. The items must add up to `amount` and use unique `credentialId` values:
```bash
curl -X POST https://conto.finance/api/sdk/mpp/record \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 3.50,
"recipientAddress": "0xServiceAddress",
"resourceUrl": "https://api.service.com/stream",
"credentialId": "settlement_01J...",
"sessionId": "stream-session-42",
"intent": "session",
"grantId": "cmm59z8fj000l49h7bjqza11p",
"grantSignature": "pg1.3kq...",
"batchItems": [
{ "amount": 1.00, "resourceUrl": "https://api.service.com/stream?chunk=1", "credentialId": "cred_1" },
{ "amount": 2.50, "resourceUrl": "https://api.service.com/stream?chunk=2", "credentialId": "cred_2" }
]
}'
```
### Querying services
List the MPP services this agent has paid. Optional `period` is `24h`, `7d` (default), `30d`, or
`90d`. Optional `limit` defaults to 50, max 100. With the SDK, call
`conto.mpp.services({ period, limit })`.
```bash
GET /api/sdk/mpp/services?period=7d&limit=50
```
### Budget tracking
Check the agent's MPP spend and limits. With the SDK, call `conto.mpp.budget({ sessionId })`.
```bash
GET /api/sdk/mpp/budget?sessionId=stream-session-42
```
The response has the same shape as the [x402 budget](https://conto.finance/docs/guides/x402-api-payments#budget-tracking). When
`sessionId` is supplied, `spend.session` reports the call count and amount recorded with that ID.
### Supported chain
Conto supports MPP payments on the **Tempo** blockchain, on testnet and mainnet. The examples on this page use Tempo Testnet.
| Property | Tempo Testnet | Tempo Mainnet |
| -------- | ----------------------------------- | --------------------------- |
| Chain ID | `42431` | `4217` |
| Currency | `pathUSD` | `USDC.e` |
| Explorer | `https://explore.testnet.tempo.xyz` | `https://explore.tempo.xyz` |
---
# Agent-to-agent payments
Source: https://conto.finance/docs/sdk/a2a-payments
One registered agent requests a payment from another. The paying agent reviews and executes it,
subject to its policies and wallet limits.
Note:
A2A endpoints enforce explicit API-key scopes: `payments:request` to create, `payments:approve` to
approve or reject, `payments:execute` to settle, `transactions:read` to list or inspect,
`analytics:read` for statistics, and `wallets:read` to resolve an address.
## How it works
```text
Requesting agent -> creates request -> target agent reviews -> target agent executes
```
1. Agent A calls `POST /api/sdk/a2a/request`
2. Agent B either receives a `payment_request.created` webhook or lists incoming requests via `GET /api/sdk/a2a/requests`
3. Agent B approves via `POST /api/sdk/a2a/requests/{id}/approve` or rejects via `POST /api/sdk/a2a/requests/{id}/reject`. If Agent B's policy requires approval, a person in Agent B's organization must approve it instead
4. Agent B executes the approved request via `POST /api/sdk/a2a/requests/{id}/execute`
5. The transfer still runs through Conto policy evaluation before settlement
## Create a request
Identify the paying agent by `targetAgentId` or `targetWalletAddress`.
```bash
curl -X POST https://conto.finance/api/sdk/a2a/request \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"targetAgentId": "cmm5a0agt001m49h7bjqza22q",
"amount": 50,
"currency": "USDC",
"purpose": "Reimbursement for API credits",
"invoiceId": "INV-2026-001",
"dueDate": "2026-08-01T00:00:00.000Z"
}'
```
**Response**
```json
{
"success": true,
"paymentRequest": {
"id": "cmm5h1a2a001m49h7irxzh22q",
"status": "review_required",
"direction": "outgoing",
"requestingAgent": {
"id": "cmm59z8fj000l49h7bjqza11p",
"name": "Purchasing Agent"
},
"requestedFromAgent": {
"id": "cmm5a0agt001m49h7bjqza22q",
"name": "DevOps Agent"
},
"amount": 50,
"currency": "USDC",
"purpose": "Reimbursement for API credits",
"invoiceId": "INV-2026-001",
"dueDate": "2026-08-01T00:00:00.000Z",
"expiresAt": "2026-08-07T00:00:00.000Z",
"links": {
"details": "/api/sdk/a2a/requests/cmm5h1a2a001m49h7irxzh22q"
}
},
"message": "Payment request sent to DevOps Agent"
}
```
Info:
If you pass `targetWalletAddress`, Conto resolves it to a registered agent first. Requests to
unknown addresses are rejected.
## List requests
```bash
GET /api/sdk/a2a/requests?direction=incoming&status=review_required&limit=50&offset=0
```
Lists the authenticated agent's incoming and outgoing requests. The `direction` filter accepts
`incoming`, `outgoing`, or `all`.
List responses and detail responses (`GET /api/sdk/a2a/requests/{id}`) use these request statuses:
| Status | Meaning |
| ----------------- | -------------------------------------- |
| `review_required` | Waiting for the paying agent, or for a person when policy requires approval |
| `approved` | Approved and ready to execute |
| `declined` | Declined or cancelled |
| `processing` | Payment is in progress |
| `completed` | Payment completed successfully |
| `failed` | Payment processing failed |
| `expired` | The request expired |
When a transaction is present, its status is `processing`, `completed`, or `failed`. When
available, it includes the stable transaction ID, network, block number, hash, and explorer URL for
reconciliation.
```json
{
"requests": [
{
"id": "cmm5h1a2a001m49h7irxzh22q",
"status": "review_required",
"direction": "incoming",
"amount": 50,
"currency": "USDC",
"transaction": null,
"links": {
"details": "/api/sdk/a2a/requests/cmm5h1a2a001m49h7irxzh22q",
"approve": "/api/sdk/a2a/requests/cmm5h1a2a001m49h7irxzh22q/approve",
"reject": "/api/sdk/a2a/requests/cmm5h1a2a001m49h7irxzh22q/reject",
"execute": null
}
}
],
"total": 1,
"limit": 50,
"offset": 0
}
```
## Webhook-first automation
If the paying organization has a webhook URL, or the paying agent has a callback URL, Conto emits
`payment_request.created` as soon as a new request is created. The payload targets the paying agent
and includes:
- `paymentRequestId`
- `requestingAgentId` and `requestingAgentName`
- `amount`, `currency`, `purpose`, `invoiceId`
- `approveUrl` and `rejectUrl`
## Approve or reject a request
The paying agent can approve a pending request before execution when its policies allow the
payment outright:
```bash
curl -X POST https://conto.finance/api/sdk/a2a/requests/{id}/approve \
-H "Authorization: Bearer $CONTO_API_KEY"
```
The response includes the request ID, `approved` status, and `links.details` and `links.execute` for
the next call.
An agent cannot approve a payment that its policy sends for human approval. In that case the
request stays pending and the endpoint returns `403`:
```json
{
"success": false,
"error": "The payer policy requires human approval for this payment. The request stays pending until an authorized member of the paying organization approves it while signed in to Conto.",
"code": "HUMAN_APPROVAL_REQUIRED",
"requestId": "cmm5h1a2a001m49h7irxzh22q",
"status": "pending_human_approval",
"links": { "details": "/api/sdk/a2a/requests/cmm5h1a2a001m49h7irxzh22q" }
}
```
An owner, admin, or manager of the paying organization then approves the request while signed in
to Conto: open the paying agent in the dashboard, go to **Transactions**, and select **Approve** on
the request under **Payment requests**. After a person approves, the agent can call the execute
endpoint. The `payments:approve` scope does not let an agent clear a human approval step, and nobody can approve
a payment that policy denies.
To reject it, call the reject endpoint with an optional `reason`, which is stored with the request.
```bash
curl -X POST https://conto.finance/api/sdk/a2a/requests/{id}/reject \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "This invoice is missing the deployment reference"
}'
```
## Execute a request
The paying agent executes the approved request:
```bash
curl -X POST https://conto.finance/api/sdk/a2a/requests/{id}/execute \
-H "Authorization: Bearer $CONTO_API_KEY"
```
The response includes the A2A request ID, transaction ID, customer-facing transaction status, and
links to both records. Policy, including spend limits, approval rules, and counterparty rules, is
checked again at execution. If it requires approval and no authorized person has approved the
request, execution returns `403` with `code: "HUMAN_APPROVAL_REQUIRED"`.
## Resolve an agent from a wallet address
Returns `isAgent`, and when true, the agent's `id` and `name` and the organization `name`.
```bash
curl "https://conto.finance/api/sdk/a2a/resolve?address=0x1234567890abcdef..." \
-H "Authorization: Bearer $CONTO_API_KEY"
```
## A2A statistics
```bash
GET /api/sdk/a2a/stats
```
## API reference
| Endpoint | Method | Description |
| ------------------------------------ | ------ | -------------------------------------- |
| `/api/sdk/a2a/request` | POST | Create an A2A payment request |
| `/api/sdk/a2a/requests` | GET | List A2A requests |
| `/api/sdk/a2a/requests/{id}` | GET | Get one request |
| `/api/sdk/a2a/requests/{id}/approve` | POST | Approve (policy must allow it) |
| `/api/sdk/a2a/requests/{id}/reject` | POST | Reject a pending request |
| `/api/sdk/a2a/requests/{id}/execute` | POST | Execute an approved request |
| `/api/sdk/a2a/resolve` | GET | Resolve an agent from a wallet address |
| `/api/sdk/a2a/stats` | GET | View A2A statistics |
---
# Budget requests
Source: https://conto.finance/docs/sdk/budget-requests
Info:
Budgets are optional. Once an agent has one approved budget, every later payment needs an active
budget. When it expires or runs out, request a new one. Both the budget and policies must pass for
a payment to go through.
## How it works
### The agent requests a budget
The agent calls `request_budget` with an amount and a purpose. The request appears on the
Budgets tab in the dashboard.
### A person approves or declines
The reviewer can adjust the amount before approving. The agent calls `get_budget_request` to
check whether it was approved.
### The agent pays as usual
Standard payment requests, x402 pre-authorizations, and MPP pre-authorizations are all checked
against the budget. Spend is deducted after each payment executes.
### The budget runs out
Once the budget is spent, further payments are blocked. The dashboard shows spend progress.
## Lifecycle
| Status | Description |
| ----------------- | ----------------------------------------------------------------------------- |
| `review_required` | Awaiting human review. Expires after 24 hours if it is not reviewed. |
| `approved` | Approved and available until `expiresAt`, 24 hours after approval by default. |
| `declined` | Not approved by a reviewer. |
| `expired` | Timed out before review, or the approved budget window elapsed. |
| `used` | The approved amount has been spent. |
Each agent can have one active request at a time. Creating another while one is active returns HTTP
`409`.
## SDK API
### Create a budget request
```bash
curl -X POST https://conto.finance/api/sdk/budget-request \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 50,
"purpose": "Research 200 companies and generate reports",
"category": "research"
}'
```
**Parameters:**
| Parameter | Type | Required | Description |
| ---------- | ------ | -------- | ----------------------------------------------- |
| `amount` | number | Yes | Requested budget in USDC |
| `purpose` | string | Yes | What this budget is for (shown to the reviewer) |
| `category` | string | No | Customer-defined spend category |
**Response (201):**
```json
{
"requestId": "cmm59z8k40001abcd1234efgh",
"status": "review_required",
"amount": 50.0,
"purpose": "Research 200 companies and generate reports",
"category": "research",
"createdAt": "2026-04-07T12:00:00.000Z"
}
```
**Error (409), active request already exists:**
```json
{
"error": "A budget request is already active",
"requestId": "cmm59z8k40001abcd1234efgh",
"status": "review_required"
}
```
### Check budget request status
```bash
curl https://conto.finance/api/sdk/budget-request?status=approved \
-H "Authorization: Bearer $CONTO_API_KEY"
```
**Query parameters:**
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | --------------------------------------------------------------- |
| `requestId` | string | No | Specific budget request ID |
| `status` | string | No | `review_required`, `approved`, `declined`, `expired`, or `used` |
**Response (200):**
```json
{
"requests": [
{
"requestId": "cmm59z8k40001abcd1234efgh",
"status": "approved",
"amount": 50.0,
"approvedAmount": 25.0,
"spent": 12.5,
"remaining": 12.5,
"purpose": "Research 200 companies and generate reports",
"category": "research",
"currency": "USDC",
"expiresAt": "2026-04-08T12:00:00.000Z",
"approvedAt": "2026-04-07T12:05:00.000Z",
"createdAt": "2026-04-07T12:00:00.000Z"
}
]
}
```
## MCP tools
### request_budget
Request a spending budget for payments. Submits a request for human approval. Only one active budget request per agent at a time. Applies to all payment types.
| Parameter | Type | Required | Description |
| ---------- | ------ | -------- | ------------------------------------------- |
| `amount` | number | Yes | Requested budget in USDC |
| `purpose` | string | Yes | What this budget is for (shown to reviewer) |
| `category` | string | No | Customer-defined spend category |
### get_budget_request
Check status and remaining balance of your budget requests. Use to poll for approval and monitor remaining budget.
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | --------------------------------------------------------------- |
| `requestId` | string | No | Specific request ID |
| `status` | string | No | `review_required`, `approved`, `declined`, `expired`, or `used` |
## Payment flow integration
Once an agent has had a budget approved, every payment flow checks it:
**Pre-execution (blocking):**
- `payments/request`: checks budget before policy evaluation
- `payments/approve`: checks budget for external wallet payments
- `x402/pre-authorize`: checks budget before authorizing x402 payments
- `mpp/pre-authorize`: checks budget before authorizing MPP payments
**Post-execution (decrement):**
- `payments/execute`: holds the amount against the budget before sending and records it as spend when the payment succeeds
- `x402/record`: decrements budget when recording x402 payments
- `mpp/record`: decrements budget when recording MPP payments
In-flight managed payments hold budget until they settle or fail. `remaining` subtracts those
holds and `spent` counts recorded spend. A failed onchain transfer returns its amount to the budget.
x402 and MPP pre-authorizations do not hold budget, so a grant does not guarantee budget remains
when you record the payment.
If a payment would exceed the remaining budget, x402 and MPP pre-authorization return the response below. `payments/request` returns HTTP 403 with `code: "PAYMENT_DENIED"`, and `payments/approve` returns `approved: false` with `reasonCodes: ["BUDGET_INSUFFICIENT"]`.
```json
{
"authorized": false,
"decision": "declined",
"message": "Payment is outside the available budget.",
"reasons": ["The available payment budget is insufficient."],
"reasonCodes": ["BUDGET_INSUFFICIENT"]
}
```
## Dashboard
Budget requests appear on the **Budgets** tab of **Alerts & Approvals**.
**Requests that need review** show the agent name, purpose, requested amount, and category.
**Approved requests** show:
- Spend progress bar showing spent and approved amounts
- Remaining balance and expiry countdown
- Whether the amount was adjusted from the original request
**Completed requests** show whether they were declined, expired, or fully used.
### Approving a request
Open the request on the **Budgets** tab and choose **Approve** or **Do not approve**. To approve a
different amount than requested, enter it in the optional amount field.
## Audit trail
Budget requests, review decisions, spend, and expiry are recorded in the organization's audit
history using the same labels shown in the dashboard.
## Example: agent workflow
```typescript
import { Conto } from '@conto_finance/sdk';
const CONTO_API_BASE = 'https://conto.finance';
const apiKey = process.env.CONTO_API_KEY;
if (!apiKey) throw new Error('CONTO_API_KEY is required');
const conto = new Conto({ apiKey });
// 1. Request a budget
const createResponse = await fetch(`${CONTO_API_BASE}/api/sdk/budget-request`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 50,
purpose: 'Research 200 companies via paid APIs',
category: 'research',
}),
});
const budget = await createResponse.json();
if (!createResponse.ok) throw new Error(`Budget request failed: ${budget.error}`);
console.log('Budget request:', budget.requestId); // review_required
// 2. Poll until the request leaves review
let req;
while (true) {
const statusResponse = await fetch(
`${CONTO_API_BASE}/api/sdk/budget-request?requestId=${encodeURIComponent(budget.requestId)}`,
{ headers: { Authorization: `Bearer ${apiKey}` } }
);
if (!statusResponse.ok) throw new Error(`Status check failed: ${statusResponse.status}`);
req = (await statusResponse.json()).requests[0];
if (req.status !== 'review_required') break;
await new Promise((r) => setTimeout(r, 5000));
}
// declined, expired, and used are final. A new budget needs a new request.
if (req.status !== 'approved') throw new Error(`Budget ${req.status}`);
console.log(`Approved: $${req.approvedAmount} budget, $${req.remaining} remaining`);
// 3. Make payments normally. The budget is enforced automatically.
await conto.payments.pay({
amount: 0.05,
recipientAddress: '0x0000000000000000000000000000000000000001',
purpose: 'Paid research API call',
});
// Recorded spend reduces the remaining budget by $0.05
```
---
# Connected cards
Source: https://conto.finance/docs/guides/connected-cards
Connect a card you already have to an agent. Card details are entered in secure hosted fields and tokenized. You set the agent’s spending limits and authorize purchases, and Conto checks each credential request against them before it releases a single-use checkout credential to your integration.
Connected cards are available on the Builder and Enterprise plans. Your application runs the agent, the browser automation, and the checkout. On any plan, you can [try the same controls with a test card](#try-it-with-a-test-card) first.
Using a card saved in Link is a separate integration path. See [Conto with Stripe Link](https://conto.finance/docs/guides/stripe-link) for where policy checks belong and how its checkout flow differs from connected-card credential requests.
[Watch the captioned walkthrough](https://conto.finance/videos/connected-card-walkthrough.mp4) (80 seconds, demo data).
## Before you begin
Use a Builder or Enterprise organization, an existing agent, and an Owner or Admin account. Have the cardholder available to verify purchase authorizations. Purchases are in USD, and each credential works for one checkout.
## Set up your card
### 1. Choose the card and cardholder
Open your agent’s **Connected Cards** tab and choose **Connect a card**. Select **I own it** or **A teammate owns it**. Enter a new card through the secure hosted fields, or choose **Use a saved card** to select a card belonging to that cardholder.
Saving a card does not let the agent spend.
### 2. Set the agent’s limits
For this example, set **$25 per transaction**, **$100 daily**, and **$250 monthly**. Agent limits must fit within the card’s limits, which apply to every agent on the card. A card you enter here starts at $500 a purchase and $1,000 a day, and saving raises those to match any higher limit you set for its first agent. A saved card shows its limits, and an agent limit above them is rejected.
Leave **Set up a purchase authorization now (optional)** off, choose **Review card connection**, review the cardholder and limits, and choose **Save card**. You can save the card without knowing what the agent will buy.
### 3. Authorize a purchase
A **mandate** is the verified purchase authorization. It must be active before your agent can request a credential.
Choose **Authorize purchase** on the connected card. Set **Purchase type**, **Merchant name**, **Merchant URL**, **Country**, **Amount (USD)** and **Expires in**. **Merchant category code** is optional. Choose **One purchase** for a single checkout, or **Multiple purchases** for repeated draws against the amount until the authorization expires. The same fields appear during setup when you turn on **Set up a purchase authorization now (optional)**.
For this example, use **Example Software**, **https://software.example**, **US**, **$25**, and **1 hour**.
Save the authorization. The cardholder then chooses **Verify authorization** and completes the provider’s secure verification flow. For a teammate’s card, Conto emails them the verification link, and you can copy it with **Copy verification link**. They sign in to the same organization to use it. Conto confirms the authorization’s status with the provider before it can be used.
Only the cardholder can verify their own authorization, whatever their role in the organization. Owners and admins cannot verify it for them.
## Make a purchase
Your agent prepares the purchase with your computer-use tools. Your backend uses the [connected-card SDK](https://conto.finance/docs/sdk/connected-cards) to request a credential against the verified authorization, using a stable idempotency key for that purchase. Conto checks the merchant, amount, current limits, and any review requirement. Keep credentials out of model prompts, tool results, and logs.
If the request needs review, Conto alerts the organization owners by email and the request appears in **Needs attention** on the agent’s **Connected Cards** tab. An Owner, Admin, or Manager chooses **Approve** or **Reject**. After approval, the integration retries the original request with the same input and key, and Conto rechecks the current rules. Approval itself does not submit a payment. A denied request must stop without requesting a replacement under a new key.
An approved request returns the credential once. See [decisions and retries](https://conto.finance/docs/sdk/connected-cards#decisions-and-retries) for uncertain outcomes and confirming payment.
## Manage authorizations
Keep the Conto request ID and the merchant’s non-sensitive order reference. Confirm payment using the merchant receipt and card activity. A credential being issued, received, or reported used is not proof of settlement.
From the agent’s **Connected Cards** tab, complete pending verification, review requests, or revoke a mandate. Revoking blocks new credential requests and asks the provider to cancel the allowance. Use **Refresh** on the revoked authorization if provider cancellation is pending. Revocation does not freeze the original card, refund purchases, or guarantee an already issued credential is cancelled.
To take a card away from an agent, choose **Remove card** on the card. Every live authorization on it is revoked and the agent can no longer request card details with it. A purchase already in flight can still complete, and a credential issued before the removal may still work, so check the card statement if one was in progress. The card stays removed until an Owner or Admin chooses **Add back** on it, on the agent’s tab or under **Use a saved card**, and sets the agent’s limits again. Revoked authorizations do not come back, so the cardholder verifies a new one before the agent can buy anything. A card removed because its agent was deleted cannot be added back.
A connected card also has its own limits, which apply to every agent on it. An Owner or Admin can change them with **Edit card limits** on the card in the agent’s tab, or by opening the card from **Cards**. An agent’s limits never exceed the card’s, so Conto rejects card limits below any agent’s limits on that card and names those agents. Lower the agent limits first.
Raising an agent limit does not change an existing mandate’s amount cap or expiration. The agent limits on a card count purchases on that card. A spending policy assigned to the agent or to your organization counts the agent’s purchases across all of its cards: with a $100 daily policy, an agent with two cards can spend $100 a day in total, not $100 on each card. For a denied request, check those limits, merchant scope, verification, and expiration. If a denial, cancellation, or purchase outcome stays unresolved, contact support with the request ID and masked evidence, never full card details.
## Try it with a test card
A test card lets you and your agent try card controls without a real card. It is available on every plan, including Free. Purchases are simulated at Conto's test merchants: no card is charged and no funds move.
1. Open your agent's **Connected Cards** tab and choose **Add test card**. Conto adds a card labelled **Test card: no real charges**. There are no card details to enter and nothing to verify. The agent starts with limits of $50 a purchase and $100 a day, which you can change with **Edit limits**.
2. Choose **Authorize test purchase**. Pick a test merchant, **One purchase** or **Multiple purchases**, an amount, and an expiry. The authorization is saved with a **Simulated verification** instead of a cardholder step, and nothing claims a cardholder verified it.
3. Give your agent its standard SDK key. It lists the test card, requests a single-use test credential, and completes checkout at the test merchant. See [test cards in the SDK guide](https://conto.finance/docs/sdk/connected-cards#test-cards).
Each request goes through the agent's real limits and your organization's policies, including rules that require approval. Test purchases are totaled apart from purchases on real cards, so trying a policy on a test card does not use up the agent's limits on its real cards. A request that needs review appears under **Card purchases awaiting review** on **Alerts & Approvals** and under **Needs attention** on the agent's tab, labelled as a test card, and is not emailed. Approve or reject it there. Change a limit or a policy and the next test purchase shows the new result.
Receipts appear under the test card on the agent's tab and as labelled rows in **Transactions**. Test purchases are left out of spend volume and pending totals, and a test card does not count toward your plan's card limit. Revoke a test authorization or choose **Remove card** the same way as for a real card.
### Set up a test card from code
To set up a test card without the dashboard, for example from a coding agent, use an organization API key with the `wallets:write` scope, such as the key `conto login` creates. Your agent's own SDK key cannot do any of this, and only a person can approve a purchase that needs review.
| Method | Endpoint | What it does |
| ------- | ------------------------------------------------------- | --------------------------------------- |
| `POST` | `/api/connected-cards/test` | Add a test card to an agent (`agentId`) |
| `POST` | `/api/connected-cards/test/{referenceId}/mandates` | Authorize test purchases |
| `PATCH` | `/api/connected-cards/test/{referenceId}/limits` | Change the agent's limits |
| `POST` | `/api/connected-cards/test/mandates/{mandateId}/revoke` | Revoke a test authorization |
| `POST` | `/api/connected-cards/test/{referenceId}/disconnect` | Remove the test card |
Changes made with a key are recorded in the audit log under that key.
Test cards and connected cards stay separate. A test authorization only works with a test card, a test credential only works at the test checkout, and upgrading your plan does not turn a test card into a real one. To use a real card, [connect one on Builder or Enterprise](#set-up-your-card).
## Track card activity
The **Cards** page lists your organization’s cards and the card activity recorded in Conto. Conto stores the card name, brand, and last four digits, never the full card number. A connected card is managed from the agent’s **Connected Cards** tab and cannot be paused or refreshed from **Cards**.
To track a card an agent does not use, choose **Add card** and enter a card name, the last four digits, brand, and card type. A manual record does not give an agent card access or connect Conto to the card processor. **Pause in Conto**, **Resume in Conto**, and **Archive record in Conto** change the record in Conto only, not the card at its processor.
Open a card to see its tracked totals and recent transactions, with the date, merchant, amount, and status of each. Tracked totals count only activity recorded in Conto. Your card processor keeps the official record, so reconcile against its statement.
---
# Connected-card checkout
Source: https://conto.finance/docs/sdk/connected-cards
Connected cards are available on the Builder and Enterprise plans. [Connect a card and verify its authorization](https://conto.finance/docs/guides/connected-cards) first. Install `@conto_finance/sdk` 0.4.0 or later. A standard agent SDK key has the `transactions:read` and `payments:approve` scopes these calls need. See [authentication](https://conto.finance/docs/sdk/authentication).
## Request and use a credential
Call `conto.connectedCards.list()`. Each entry in `cards` has a `cardId` and the active `mandates` (verified authorizations) the agent can use. Pass `cardId` and a mandate `id` to `draw()`. A credential request is called a **draw**. Use a stable 8–128 character idempotency key per draw. Amounts are in USD cents: `2500` means **$25.00**.
```typescript
import { Conto } from '@conto_finance/sdk';
import { completeAuthorizedCheckout } from './trusted-checkout';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
const draw = await conto.connectedCards.draw({
cardId: process.env.CONTO_CARD_ID!,
mandateId: process.env.CONTO_MANDATE_ID!,
idempotencyKey: 'office-supplies-order-1042',
amountMinor: 2500,
currency: 'USD',
merchant: {
merchantName: 'Office Supply Store',
merchantCountry: 'US',
},
});
if (draw.reconciliationRequired) {
throw new Error('Review the existing card request before checkout');
}
if (draw.decision !== 'APPROVED' || !draw.credential) {
throw new Error('No fresh checkout credential is available');
}
if (!draw.credentialExpiresAt || Date.parse(draw.credentialExpiresAt) <= Date.now()) {
throw new Error('Checkout credential has expired');
}
await conto.connectedCards.acknowledgeDelivery(draw.requestId);
// Your checkout integration; keep the credential in memory only.
const receipt = await completeAuthorizedCheckout({
requestId: draw.requestId,
credential: draw.credential,
amountMinor: 2500,
currency: 'USD',
});
await conto.connectedCards.reportOutcome(draw.requestId, { outcome: 'USED' });
```
`completeAuthorizedCheckout` is your application’s function. It must use the authorized merchant and checkout origin, enforce the purchase amount, and prevent duplicate checkout. Never persist credentials or expose them in prompts, tool results, logs, traces, or screenshots.
## Check a purchase first
Before you ask the cardholder to authorize a purchase, you can check it against the agent's card limits and policies. The check reserves nothing, creates no authorization, and returns no credential.
```typescript
const response = await fetch('https://conto.finance/api/sdk/connected-cards/preview', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CONTO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
cardId: process.env.CONTO_CARD_ID,
amountMinor: 2500,
merchant: {
name: 'Office Supply Store',
countryCode: 'US',
checkoutOrigin: 'https://shop.example.com',
},
}),
});
const { decision, reasons } = await response.json();
```
`decision` is `APPROVED`, `DENIED`, or `REQUIRES_APPROVAL`, and `reasons` explains a denial. Send the store details the authorization will record. All `merchant` fields are optional: `name`, `providerMerchantId`, `categoryCode` (four-digit MCC), `countryCode` (two-letter code), and `checkoutOrigin` (HTTPS).
A preview is a check, not a decision on the purchase. Limits, spend, and policies can change before the draw. The draw decides again, using the store details on the verified authorization.
## Decisions and retries
| Result | What to do |
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `APPROVED` with a fresh credential | Check expiry, acknowledge delivery, then complete checkout. |
| `DENIED` | Stop and inspect `reasons`. |
| `REQUIRES_APPROVAL` | Wait for approval under **Needs attention** on the agent’s **Connected Cards** tab, then retry the original input and key. Conto checks the request again. |
| Replay without a credential, or `reconciliationRequired` | Investigate the existing request before another issuance or checkout attempt. |
Credentials are returned only once. The SDK does not automatically retry `draw()`, and reusing a key cannot retrieve a lost credential. Do not generate a new key to recover an uncertain result. A failed checkout does not return the amount to the verified authorization, even after the hold on the agent's limits is released.
If the card provider refuses to issue a credential, the draw returns `DENIED` with the reason. Nothing was charged, the hold is released, and a single-purchase authorization stays usable, so you can retry with a new idempotency key.
`acknowledgeDelivery()` is safe to retry for the same request. It records receipt of the credential, **not payment settlement**. Keep the request ID and non-sensitive order references, and confirm the purchase against the merchant receipt and card activity. Contact support if the outcome remains unclear.
## Report the outcome
The draw's amount stays held against the agent's limits until Conto knows what happened. After checkout, call `reportOutcome()`:
```typescript
// Charged the full amount. This closes the draw.
await conto.connectedCards.reportOutcome(draw.requestId, { outcome: 'USED' });
// Charged less than the approved amount, in cents.
await conto.connectedCards.reportOutcome(draw.requestId, { outcome: 'USED', amountMinor: 1850 });
// Never used the credential.
await conto.connectedCards.reportOutcome(draw.requestId, { outcome: 'NOT_USED' });
```
A report never releases held spend by itself. `USED` at the full amount closes the draw. `NOT_USED`, or a smaller amount, goes under **Needs attention** on the agent's **Connected Cards** tab with your report shown. A person checks the card's activity and confirms, and only then is the rest of the hold released. An unused credential is shown there once it expires. The result has `closed` and `reviewRequired` so your agent knows which happened.
`reportOutcome()` is safe to retry with the same report. A different second report returns 409. If the agent reports nothing, the draw also goes under **Needs attention** an hour after its credential expires.
## Test cards
A [test card](https://conto.finance/docs/guides/connected-cards#try-it-with-a-test-card) works on every plan and uses the same agent key and scopes. Draws go through the agent's limits, your policies, and human review like a live card, and the same retry rules apply. The differences:
- Test cards have their own endpoints. A live card or authorization ID is not found there, and a test ID is not found on the live endpoints.
- An approved draw returns `testCredential`, an opaque token that starts with `ctc_test_`, instead of card fields. It is returned once. Handle it like a live credential: keep it out of prompts, tool results, and logs.
- The purchase completes at Conto's test checkout, the only place the token works. Checkout returns a receipt with `environment: "test"`, `simulated: true`, and `settlementMode: "test"`. Retrying checkout with the same draw and token returns the same receipt with `replayed: true`.
- Test purchases are simulated. No card is charged and no funds move.
```typescript
const base = 'https://conto.finance';
const headers = {
Authorization: `Bearer ${process.env.CONTO_API_KEY}`,
'Content-Type': 'application/json',
};
const { cards, merchants } = await (
await fetch(`${base}/api/sdk/connected-cards/test`, { headers })
).json();
const card = cards[0];
const mandate = card.mandates[0];
const draw = await (
await fetch(`${base}/api/sdk/connected-cards/test/draws`, {
method: 'POST',
headers,
body: JSON.stringify({
cardId: card.cardId,
mandateId: mandate.id,
idempotencyKey: 'test-order-1042',
amountMinor: 1200,
currency: 'USD',
merchant: { merchantName: mandate.merchant.name },
}),
})
).json();
if (draw.decision === 'APPROVED' && draw.testCredential) {
const receipt = await (
await fetch(`${base}/api/sdk/connected-cards/test/checkout`, {
method: 'POST',
headers,
body: JSON.stringify({
requestId: draw.requestId,
credential: draw.testCredential.token,
items: [{ sku: 'notebook-pack', quantity: 1 }],
}),
})
).json();
console.log(receipt.orderNumber, receipt.settlementMode);
}
```
`merchants` lists each test merchant's catalog. Leave out `items` to buy one line for the authorized amount. An order above the authorized amount, or an item the merchant does not sell, is declined with 409 and leaves the credential usable until it expires. A credential expires 15 minutes after it is issued.
## HTTP endpoints
| Method | Endpoint | Agent scope |
| ------ | -------------------------------------------------------- | ------------------- |
| `GET` | `/api/sdk/connected-cards` | `transactions:read` |
| `POST` | `/api/sdk/connected-cards/preview` | `payments:approve` |
| `POST` | `/api/sdk/connected-cards/draws` | `payments:approve` |
| `POST` | `/api/sdk/connected-cards/draws/{requestId}/acknowledge` | `payments:approve` |
| `POST` | `/api/sdk/connected-cards/draws/{requestId}/outcome` | `payments:approve` |
Test cards:
| Method | Endpoint | Agent scope |
| ------ | ------------------------------------------------------------- | ------------------- |
| `GET` | `/api/sdk/connected-cards/test` | `transactions:read` |
| `POST` | `/api/sdk/connected-cards/test/preview` | `payments:approve` |
| `POST` | `/api/sdk/connected-cards/test/draws` | `payments:approve` |
| `POST` | `/api/sdk/connected-cards/test/draws/{requestId}/acknowledge` | `payments:approve` |
| `POST` | `/api/sdk/connected-cards/test/checkout` | `payments:approve` |
| `GET` | `/api/sdk/connected-cards/test/orders/{orderId}` | `transactions:read` |
Use bearer authentication. On a draw, all `merchant` fields are optional: `merchantName`, `merchantId`, `merchantCategory` (four-digit MCC), and `merchantCountry` (two- or three-letter code). Any field you send must match the verified mandate exactly, or the request fails with 400. `amountMinor` cannot exceed the mandate amount.
---
# Checkout checkpoints
Source: https://conto.finance/docs/guides/checkout-checkpoints
When your agent completes a checkout itself, call Conto at two points: right before it opens the
payment method, and right before it submits the payment. Conto checks the agent's rules at both,
counts the amount toward its limits in between, and records the outcome your agent reports.
You need an agent and a standard SDK key, which has every scope these calls use. No wallet is
needed.
```bash
npm install @conto_finance/sdk
```
## The two checkpoints
| When | Call | Continue only if |
| --------------------------------------------------- | ---------------------------- | ---------------------------------- |
| Before Link, a card form, Shop Pay, or PayPal opens | `checkout.begin()` | The stage is `APPROVED` |
| While the owner reviews the purchase | `checkout.waitForApproval()` | It returns the approved checkpoint |
| Right before the payment is submitted | `checkout.confirm()` | The stage is `CONFIRMED` |
| After the payment | `checkout.reportOutcome()` | Always report it |
New organizations start with these limits: $25 per transaction, $100 a day, and owner approval
above $10. The $15 purchase below fits under the cap and goes to the owner for approval. See
[starter guardrails](https://conto.finance/docs/policies/overview#starter-guardrails-for-new-organizations).
```typescript
import { Conto } from '@conto_finance/sdk';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
let checkpoint = await conto.checkout.begin({
idempotencyKey: order.id,
merchant: { id: 'acme-retail', name: 'ACME Retail', checkoutUrl: page.url() },
amountMinor: 1500,
purpose: 'Phone charger the shopper chose',
lineItems: [{ name: 'USB-C phone charger', quantity: 1, unitAmountMinor: 1500 }],
});
if (checkpoint.stage === 'AWAITING_APPROVAL') {
// The owner reviews the purchase. Throws if they reject it or the request expires.
checkpoint = await conto.checkout.waitForApproval(checkpoint, { timeoutMs: 15 * 60_000 });
}
// Open Link. The buyer signs in and the final total appears.
const confirmed = await conto.checkout.confirm(checkpoint, {
amountMinor: finalTotal,
totals: finalTotals,
});
// Submit the payment.
await conto.checkout.reportOutcome(confirmed.id, { outcome: 'PAID', reference: orderNumber });
```
If any call throws, stop the checkout. A denial at either checkpoint throws an error, and the
amount stops counting toward the agent's limits.
## When the owner must approve
When the agent's rules require approval, `begin()` returns the stage `AWAITING_APPROVAL` with
`nextAction: WAIT_FOR_APPROVAL`. Conto holds the spend against the agent's limits and sends the
owner an approval request in **Alerts & Approvals**. The organization's
[approval workflow](https://conto.finance/docs/guides/approval-workflows) sets who can approve it. Do not open the payment
method while the checkpoint waits.
The checkpoint carries the request:
```json
{
"stage": "AWAITING_APPROVAL",
"nextAction": "WAIT_FOR_APPROVAL",
"expiresAt": "2026-10-05T15:00:00.000Z",
"approval": {
"requestId": "cmg8x2k1p0004l70a9c3d7f2e",
"status": "PENDING",
"expiresAt": "2026-10-05T15:00:00.000Z"
}
}
```
`approval.status` is `PENDING`, `APPROVED`, `DENIED`, `EXPIRED`, or `CANCELLED`. While the
checkpoint waits, `expiresAt` is when the owner's decision window closes. The request expires when
its approval workflow does, and no later than 24 hours after `begin()`.
`waitForApproval(checkpoint, options)` reads the checkpoint until the owner decides. It returns the
approved checkpoint, with stage `APPROVED`. The 30 minutes to confirm start at the approval.
| Option | Default | Meaning |
| ---------------- | ---------- | -------------------------------------------------- |
| `timeoutMs` | 10 minutes | Stop waiting after this long. The spend stays held |
| `pollIntervalMs` | 5 seconds | Time between reads. The minimum is 1 second |
| `signal` | None | An `AbortSignal` that stops the wait |
### Errors
Each error has a `checkpoint` property with the checkpoint as Conto last returned it.
| Error | When | What to do |
| ------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `CheckoutApprovalDeniedError` | The owner rejected the purchase | Do not pay. Conto released the spend |
| `CheckoutApprovalExpiredError` | The request expired before the owner decided | Do not pay. Conto released the spend |
| `CheckoutApprovalTimeoutError` | `timeoutMs` passed before the owner decided | Wait again, or cancel the checkpoint to release the spend |
| `CheckoutApprovalPendingError` | The checkpoint still needs approval when you call `confirm()` | Call `waitForApproval(error.checkpoint)`, then confirm the checkpoint it returns |
| `CheckoutCheckpointClosedError` | The checkpoint is already denied, cancelled, expired, or not paid | Stop this checkout. Start a new purchase with a new idempotency key |
`CheckoutApprovalDeniedError` and `CheckoutApprovalExpiredError` extend
`CheckoutCheckpointClosedError`. `begin()` throws `CheckoutCheckpointClosedError` when you retry it
with the key of a checkpoint that already closed.
### Reject, expire, or cancel
- **The owner rejects the purchase.** The stage becomes `DENIED`, and Conto releases the spend.
- **The request expires.** The stage becomes `EXPIRED`, and Conto releases the spend.
- **The agent cancels while it waits.** Conto withdraws the approval request and releases the
spend. The stage becomes `CANCELLED`.
Until the owner approves, the confirm endpoint returns `409 APPROVAL_REQUIRED` and the SDK's
`confirm()` throws `CheckoutApprovalPendingError`. Retrying `begin()` with the same key returns the
same waiting checkpoint.
## The merchant is the checkout domain
Conto names the merchant by the registrable domain of `checkoutUrl`. Case and a leading `www.` do
not matter. `https://WWW.Example.com/cart` and `https://shop.example.com/checkout` are both the
merchant `example.com`. Stores on a shared store platform's domain each count as their own
merchant.
- Allow and block lists, payee status, and velocity rules with `RECIPIENT` scope use that domain.
- A list entry for `example.com` also covers `shop.example.com`.
- [Store rules](https://conto.finance/docs/policies/advanced#connected-card-store-rules) match the host of `checkoutUrl` the
same way.
- `merchant.id` and `merchant.name` are labels the owner sees. Write list entries as domains.
- `checkoutUrl` must use the store's domain name. `begin()` refuses an IP address or a single-word
host such as `localhost` with `400`.
See [checkout purchases](https://conto.finance/docs/policies/evaluation#checkout-purchases) for how rules count them.
## HTTP endpoints
If your integration calls the HTTP API directly, use an agent SDK key with the scope for each step.
All paths below begin with `/api/sdk` and return `{ "checkpoint": { ... } }` with an `id`, `stage`,
`nextAction`, and `expiresAt`.
| Step | Method and path | Scope |
| ---- | --------------- | ----- |
| Begin | `POST /checkout-checkpoints` | `payments:request` |
| Read | `GET /checkout-checkpoints/{id}` | `transactions:read` |
| Confirm | `POST /checkout-checkpoints/{id}/confirm` | `payments:execute` |
| Report outcome | `POST /checkout-checkpoints/{id}/outcome` | `payments:confirm` |
| Cancel before confirm | `POST /checkout-checkpoints/{id}/cancel` | `payments:execute` |
Begin with a stable `idempotencyKey`, merchant ID, name and HTTPS checkout URL, amount in USD cents,
purpose, and a cart with line items and totals. Continue only after `APPROVED` with
`CONFIRM_BEFORE_PAYING`.
When begin returns `200` with `AWAITING_APPROVAL` and `WAIT_FOR_APPROVAL`, read the checkpoint
every few seconds until the stage changes. Continue after `APPROVED`. Stop after `DENIED`,
`CANCELLED`, or `EXPIRED`.
Confirm with the final `amountMinor`, `currency`, and, when available, the final cart and
`pageUrl`. The raw HTTP confirm refuses a changed total. Cancel the old checkpoint, begin a new one
at the final total with a new idempotency key, and confirm that one. The SDK `checkout.confirm()`
helper performs that replacement automatically. Submit payment only after `CONFIRMED` with
`PAY_THEN_REPORT_OUTCOME`.
Confirm and report outcome also accept an optional `paymentReference`, such as
`{ "provider": "LINK", "id": "lsrq_..." }` for the Link request behind a
[Stripe Link card](https://conto.finance/docs/guides/stripe-link-cards). The checkpoint keeps the first reference it receives and
returns `409` for a different one. Conto refuses a reference that contains a card number with `400`.
After submission, report `PAID`, `NOT_PAID`, or `UNKNOWN` on the same checkpoint. An outcome sent
before `CONFIRMED` returns `409`. Read its status before retrying after a timeout. Never submit the
payment twice. A reported outcome has `settlementVerified: false` and is not proof of settlement.
If Conto returns `503 PAYMENTS_PAUSED`, stop checkout and ask the owner to check payment
availability. See [error handling](https://conto.finance/docs/sdk/error-handling).
## When the total changes
Tax and shipping often appear only after the buyer signs in and enters an address. The final total
still has to pass the agent's rules. If it differs from the approved total, `confirm()` replaces the
approval with one for the final total before it confirms. If the final total breaks a rule, the
payment stops there.
If the final total needs the owner's approval, the owner gets a new request for it. `confirm()`
throws `CheckoutApprovalPendingError`:
```typescript
import { CheckoutApprovalPendingError, type CheckoutCheckpoint } from '@conto_finance/sdk';
const final = { amountMinor: finalTotal, totals: finalTotals };
let confirmed: CheckoutCheckpoint;
try {
confirmed = await conto.checkout.confirm(checkpoint, final);
} catch (error) {
if (!(error instanceof CheckoutApprovalPendingError)) throw error;
const approved = await conto.checkout.waitForApproval(error.checkpoint);
confirmed = await conto.checkout.confirm(approved, final);
}
```
Pass `pageUrl` to `confirm()` to check that the payment is submitted on the merchant's site or the
payment method's own pages.
## Report the outcome
- `PAID` keeps the amount counted toward the agent's limits.
- `NOT_PAID` releases it.
- `UNKNOWN` keeps it counted until you report again or the owner records what happened.
Report an outcome only after `confirm()` succeeds. To stop before that, cancel the checkpoint.
Checkout spend counts toward the agent's daily, weekly, and monthly limits at agent or organization
scope, together with its wallet and card spend.
An approved checkpoint that is never confirmed is released when its approval expires, 30 minutes
after `begin()` or after the owner's approval. When a confirmed checkpoint has no outcome, the owner
gets an alert to check the order with the merchant.
## What the owner sees
Each checkout appears in **Transactions**, with its status, amount, payment method, agent, purpose,
order number, and steps. A checkout that waits for approval links to its request in
**Alerts & Approvals**.
When a confirmed checkout has no outcome, or the agent reported `UNKNOWN`, its page shows
**Mark as paid** and **Mark as not paid**. Owners, Admins, and Managers can use them. Mark as paid
keeps the amount counted toward the agent's limits. Mark as not paid releases it.
## MCP tools
An MCP agent uses the same flow with five tools: `checkout_checkpoint_begin`,
`checkout_checkpoint_confirm`, `checkout_checkpoint_report_outcome`, `checkout_checkpoint_cancel`,
and `checkout_checkpoint_status`.
When `checkout_checkpoint_begin` returns `AWAITING_APPROVAL`, its result tells the agent not to
open the payment method. The agent calls `checkout_checkpoint_status` every few seconds until the
stage is `APPROVED`, then continues. It stops if the stage becomes `DENIED`, `CANCELLED`, or
`EXPIRED`. `checkout_checkpoint_confirm` can return `AWAITING_APPROVAL` for a changed total. The
agent waits the same way, then calls confirm again. See [MCP tools](https://conto.finance/docs/mcp/tools#checkout-checkpoints-5).
## How strong this control is
Checkpoints are the lightweight way to add Conto to a checkout. Conto decides, and your checkout
acts on the answer. Conto does not see the card or the charge, so it does not check a payment your
agent makes without calling the checkpoints, and it records the outcome you report.
With Stripe Link, the [Stripe Link card integration](https://conto.finance/docs/guides/stripe-link-cards) goes further. The
agent asks Conto before it asks Stripe Link for a card, and Stripe Link is asked only after Conto
approves the purchase.
For control at the moment of payment, pay through Conto: a
[managed wallet](https://conto.finance/docs/guides/custody-modes), or a [connected card](https://conto.finance/docs/guides/connected-cards) whose
single-use credential Conto releases only after the controls pass.
---
# Conto with Stripe Link
Source: https://conto.finance/docs/guides/stripe-link
Stripe Link is Stripe's payment wallet. Conto integrates with it at two points in a Link checkout:
right before Link opens, and right before your agent submits the payment. Conto checks the purchase against the owner's rules at both points. The
agent opens Link and pays only after Conto approves. Then it reports whether the payment went
through.
Conto decides, and your agent's platform enforces the decision. The buyer pays with their own Link
account. Nobody connects Link to Conto.
Try the [Stripe Link demo](https://conto.finance/demos/claude/retail) to shop with Claude and see
Conto check a purchase before Link checkout.
## Add the checkpoints
You need an agent and its SDK key. Create the agent in the dashboard, then choose
**Generate SDK Key** on its **SDK Integration** tab.
```bash
npm install @conto_finance/sdk
```
```typescript
import { Conto } from '@conto_finance/sdk';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
// 1. Right before Link opens.
let checkpoint = await conto.checkout.begin({
idempotencyKey: order.id,
merchant: { id: 'acme-retail', name: 'ACME Retail', checkoutUrl: page.url() },
amountMinor: 1500,
purpose: 'Phone charger the shopper chose',
lineItems: [{ name: 'USB-C phone charger', quantity: 1, unitAmountMinor: 1500 }],
});
if (checkpoint.stage === 'AWAITING_APPROVAL') {
// The owner must approve first. Do not open Link yet.
checkpoint = await conto.checkout.waitForApproval(checkpoint);
}
// Open Link. The buyer signs in and the final total appears.
// 2. Right before the payment is submitted.
const confirmed = await conto.checkout.confirm(checkpoint, {
amountMinor: finalTotal,
totals: finalTotals,
pageUrl: page.url(),
});
// Submit the payment.
// 3. Report what happened.
await conto.checkout.reportOutcome(confirmed.id, { outcome: 'PAID', reference: orderNumber });
```
If any call throws, stop the checkout and do not pay.
`checkoutMethod` defaults to `LINK`, so a Link checkout can leave it out. `checkoutUrl` is the
store's checkout page. Conto names the merchant by its domain. See
[the merchant is the checkout domain](https://conto.finance/docs/guides/checkout-checkpoints#the-merchant-is-the-checkout-domain).
Reuse the same `idempotencyKey` when you retry `begin()`. After a timeout, read the checkpoint with
`conto.checkout.get()` before you retry. Never submit the payment twice.
[Checkout checkpoints](https://conto.finance/docs/guides/checkout-checkpoints) covers every stage, the HTTP endpoints, and
the MCP tools.
## When the owner must approve
New organizations start with these limits: $25 per transaction, $100 a day, and owner approval
above $10. The $15 purchase above goes to the owner for approval. See
[starter guardrails](https://conto.finance/docs/policies/overview#starter-guardrails-for-new-organizations).
When a rule requires approval, `begin()` returns the stage `AWAITING_APPROVAL`. Conto holds the $15
against the agent's limits and sends the owner an approval request. Do not open Link while the
checkpoint waits.
`waitForApproval()` returns the checkpoint once the owner approves it. The agent then has 30
minutes to confirm. If the owner rejects the purchase or the request expires, `waitForApproval()`
throws and Conto releases the spend. See
[when the owner must approve](https://conto.finance/docs/guides/checkout-checkpoints#when-the-owner-must-approve).
## Report the outcome
After the agent submits the payment, it reports one outcome on the confirmed checkpoint:
- `PAID` keeps the amount counted toward the agent's limits.
- `NOT_PAID` releases it.
- `UNKNOWN` keeps it counted until the agent reports again or the owner records what happened.
Report an outcome only after `confirm()` succeeds. To stop before that, cancel the checkpoint with
`conto.checkout.cancel()`.
## What the owner sees
- **Approvals.** A purchase that needs approval appears in **Alerts & Approvals** with the
merchant, the checkout site, the payment method, the items, the purpose, and the agent. The owner
chooses **Approve purchase** or **Reject purchase**.
- **Transactions.** Each checkout appears in **Transactions**. Its page shows the status, the
amount, the agent, the order number, and each step.
- **Missing outcomes.** When a confirmed checkout has no outcome, the owner gets an alert. After
checking the order with the merchant, an Owner, Admin, or Manager chooses **Mark as paid** or
**Mark as not paid** on the checkout page.
## Ask Conto before Stripe Link issues a card
With the [Stripe Link card integration](https://conto.finance/docs/guides/stripe-link-cards), the agent asks Conto before it
asks Stripe Link for a card. Stripe Link is asked only after Conto approves the purchase, for
exactly the approved amount at that store. The person still approves the card in the Link app, and Conto does not receive
the card number. Use it when the agent fills in a store's card form with a Link card.
## Stronger control with connected cards
Checkpoints rely on your agent's checkout to call Conto and follow the answer. Conto does not see
the Link payment, so it records the outcome your checkout reports.
For control at the moment of payment, use a [connected card](https://conto.finance/docs/guides/connected-cards). Conto
releases a single-use card credential only after the owner's controls pass. Connecting a card to
Conto does not connect a Link account.
---
# Stripe Link cards for agents
Source: https://conto.finance/docs/guides/stripe-link-cards
Stripe Link is Stripe's payment wallet. With Link CLI, Stripe's command-line tool, an agent can ask
Stripe Link for a virtual card for one purchase. The person approves each card in their Link app,
and Stripe Link issues it.
Conto integrates with Link CLI so the agent asks Conto first. Conto checks the purchase against the
owner's rules. Only after Conto approves does Link CLI ask Stripe Link for a card, for exactly the
approved amount at that store.
If Conto does not approve, Stripe Link is never asked, so no card exists.
## How it works
1. At the payment step, once the final total is known, the agent's code calls
`conto.checkout.getLinkCard()`.
2. Conto checks the purchase against the agent's rules and holds the amount against its limits.
When a rule requires it, the owner approves in Conto first.
3. After Conto approves, Stripe's Link CLI on your machine asks Stripe Link for a card for that amount at that
store. The person approves it in the Link app.
4. Your code gets the card and fills in the store's card form. Keep the card out of any model
prompt, tool result, or log.
5. Right before paying, the agent calls `confirm()`. After paying, it reports the outcome.
Stripe Link sends the card to your machine. Conto does not receive the card number or security code.
Conto stores only the Link request ID, so the owner can see which Link request paid.
## Set up
Install Stripe's Link CLI on the machine where your agent runs, and log in with the Link account that pays:
```bash
npm i -g @stripe/link-cli
link-cli auth login
npm install @conto_finance/sdk
```
The person who approves needs the Link app. Nobody connects Link to Conto, and there is no Link
setup in the Conto dashboard. You also need an agent and its standard SDK key, as for
[checkout checkpoints](https://conto.finance/docs/guides/checkout-checkpoints).
## Get a card with the SDK
```typescript
import { Conto } from '@conto_finance/sdk';
import { linkCliWallet } from '@conto_finance/sdk/link-wallet';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
const wallet = linkCliWallet({ test: true });
// At the payment step, once the final total with shipping and tax is known.
const result = await conto.checkout.getLinkCard({
idempotencyKey: order.id,
merchant: { id: 'acme-retail', name: 'ACME Retail', checkoutUrl: page.url() },
amountMinor: 1500,
purpose: 'Phone charger the shopper chose',
lineItems: [{ name: 'USB-C phone charger', quantity: 1, unitAmountMinor: 1500 }],
wallet,
});
// Fill in result.card in the store's card form.
// Right before paying.
const confirmed = await conto.checkout.confirm(result.checkpoint, {
amountMinor: 1500,
paymentReference: result.paymentReference,
});
// Pay, then report what happened.
await conto.checkout.reportOutcome(confirmed.id, {
outcome: 'PAID',
reference: orderNumber,
paymentReference: result.paymentReference,
});
```
`getLinkCard()` takes the same purchase details as `checkout.begin()`, plus the wallet. The
checkout method is always `LINK`. It waits for the owner when a rule requires approval, then for
the person in Link. Two optional settings limit each wait, and both default to 10 minutes:
`ownerApprovalTimeoutMs` and `linkApprovalTimeoutMs`. Pass `signal` to stop early.
Retrying `getLinkCard()` with the same input resumes the same checkpoint and the same Link
request, so the person is not asked twice.
### The card
| Field | Meaning |
| --------------- | ---------------------------------- |
| `number` | Card number |
| `securityValue` | The card's CVC |
| `expMonth` | Expiry month |
| `expYear` | Expiry year, four digits |
| `validUntil` | When Link stops accepting the card |
| `last4` | Last four digits |
| `brand` | Card brand, when Link reports it |
The card stays in your agent's process. Printing or serializing the result shows only the last
four digits, the brand, and the expiry. Use the card only in the store's card form for this
purchase.
`linkCliWallet()` runs Link CLI without a shell and reads the card from a private temporary file
that it deletes before the call returns. Its options are `binary` (defaults to `link-cli` on PATH),
`test`, `paymentMethodId` (defaults to the person's default payment method), `timeoutSeconds`, and
`env`.
## When the total changes
Call `getLinkCard()` at the payment step, when the total includes shipping and tax. The card
covers the amount Conto approved, so `confirm()` refuses a different total for it with
`LINK_CARD_TOTAL_CHANGED`.
If the total changes anyway, get a new card for the final total:
```typescript
const replacement = await conto.checkout.replaceLinkCard(result, {
wallet,
amountMinor: finalTotal,
});
```
`replaceLinkCard()` cancels the old Link request and checkpoint, checks the final total against
the agent's rules, and gets a new card. Confirm `replacement.checkpoint` with the final total.
## When something goes wrong
If any call throws, do not pay.
| Error | When | What Conto did |
| ------------------------------ | ---------------------------------------------------------- | --------------------------------------------------------- |
| `ContoError` | The agent's rules denied the purchase | Held nothing. Link was never asked |
| `CheckoutApprovalDeniedError` | The owner rejected the purchase | Released the spend. Link was never asked |
| `CheckoutApprovalExpiredError` | The owner did not decide in time | Released the spend. Link was never asked |
| `CheckoutApprovalTimeoutError` | `ownerApprovalTimeoutMs` passed first | Kept the spend held. Retry with the same input, or cancel |
| `LinkCardDeniedError` | The person declined the card in Link | Cancelled the checkpoint and released the spend |
| `LinkCardExpiredError` | The Link request expired before the person approved it | Cancelled the checkpoint and released the spend |
| `LinkCardTimeoutError` | `linkApprovalTimeoutMs` passed first | Cancelled the Link request and the checkpoint |
| `LinkWalletUnavailableError` | Link CLI is not installed or not logged in on this machine | Cancelled the checkpoint and released the spend |
The four Link errors extend `LinkCardError`, which also covers other Link failures. Each has
`holdReleased`, which is `false` when Conto could not cancel the checkpoint. Cancel it with
`conto.checkout.cancel(error.checkpoint.id)` in that case. Error messages never contain card data.
A `LinkCardError` with code `LINK_CARD_NOT_CANCELLED` means Conto could not cancel the Link
request, so Link could still issue its card. Conto keeps the spend held and returns the
`linkRequestId`. Retry with the same input, or cancel the request with
`wallet.cancel(error.linkRequestId)` before `conto.checkout.cancel(error.checkpoint.id)`. The
same code without a `linkRequestId` means the wallet failed while asking Link for the card, so Conto
cannot tell whether Link created the request. Retry with the same input to resume it.
## Test mode
`linkCliWallet({ test: true })` creates test-mode Link requests. Link returns test card data and
charges nothing. The person still approves each request in the Link app. Conto checks and records
test purchases like any other purchase.
## MCP
The Conto MCP server does not issue Link cards. A tool result goes into the model's context and
often into the MCP host's logs, so a card number and CVC must never be in one. Get the card with
`conto.checkout.getLinkCard()` in code the model does not see, and pass the model only the
`linkRequestId` and the card's last four digits.
The MCP checkpoint tools still take a `linkRequestId`. `checkout_checkpoint_confirm` and
`checkout_checkpoint_report_outcome` send it to Conto as the payment reference. With
`CONTO_LINK_WALLET` set to `link-cli`, `checkout_checkpoint_cancel` with a `linkRequestId` also
cancels that Link request, and keeps the spend held until Link cancels it or reports it closed. See [MCP tools](https://conto.finance/docs/mcp/tools#stripe-link-cards).
## What this protects
This integration makes Conto's approval part of how the agent pays, as long as Conto is the agent's
only way to get a Link card. It stops:
- **An agent going off script.** Link is asked for a card only for a purchase Conto approved.
- **Overspending.** The card is limited to the approved amount, and the amount counts toward the
agent's limits.
- **The wrong store.** Conto checks the store's domain against the owner's rules before any card
exists, and the person sees the store in Link.
- **Too many purchases.** Count and velocity rules apply to each card request.
It does not stop a builder who also gives the agent raw Link access. An agent that can run Link CLI
itself, use a shell, or call Link with your credentials can get a card without asking Conto. Give
the agent the Conto SDK or the MCP tool, not Link CLI or a shell.
## What the owner sees
Each purchase appears in **Transactions** like any checkout. Its page shows the Link request ID
next to the order number, and each step that reported it. A purchase that needs approval appears
in **Alerts & Approvals** first.
---
# Conto Commerce quickstart
Source: https://conto.finance/docs/guides/commerce-quickstart
Conto Commerce lets your platform's agents buy from online stores for your users. Your server starts a purchase from a product URL and a task. Conto shops, checks out and pays with the buyer's own card, and your user reviews each purchase on a Conto page. Your platform never handles card details.
Test mode is open to every organization. A test card pays only Conto's test stores, so this whole page runs without a real card, a real store or a charge.
## 1. Create a key
Create an organization API key in **Settings > API Keys**. The **Standard** preset covers buyers, setup and purchases. Setting allowed return origins needs `org:write`, which the **Admin** preset includes. Keep the key on your server.
Purchases with one organization key need an `@conto_finance/sdk` release that includes `admin.purchasing.purchases`.
## 2. Set up a test buyer
Map one of your users to a buyer and send them to Conto's setup page.
```typescript
import { ContoAdmin } from '@conto_finance/sdk';
const admin = new ContoAdmin({ orgApiKey: process.env.CONTO_ORG_API_KEY! });
await admin.purchasing.settings.update({ allowedReturnOrigins: ['http://localhost:4100'] });
const buyer = await admin.purchasing.buyers.create({
externalId: 'test-user-1',
email: 'buyer@example.com',
});
const link = await admin.purchasing.paymentMethods.createSetupLink({
buyerId: buyer.id,
environment: 'test',
returnUrl: 'http://localhost:4100/',
});
console.log(link.url);
```
Open `link.url` in a browser. It works once and expires in 10 minutes. On the page, confirm the spending limits, add the test card and let the agent shop with it.
The same `externalId` returns the same buyer, so reuse it while you test. Each buyer uses one agent in your plan.
## 3. Buy from a test store
```typescript
const purchases = admin.purchasing.purchases;
const purchase = await purchases.create(
{
buyerId: buyer.id,
request: {
url: 'https://supplies.conto-test.invalid/products/notebook-pack',
task: 'Buy one pack of notebooks with standard shipping',
},
maxCost: { amountMinor: 2000, currency: 'USD' },
},
{ idempotencyKey: `order-${Date.now()}` }
);
for await (const event of purchases.watch(purchase.id)) {
const { status, requiredAction } = event.data;
console.log(event.version, status);
if (requiredAction?.kind === 'buyer_details') {
await purchases.respond(purchase.id, {
actionId: requiredAction.id,
revision: requiredAction.revision,
fields: {
name: 'Sample Buyer',
email: 'buyer@example.com',
phone: '4155550100',
address: '1 Market Street',
city: 'San Francisco',
region: 'CA',
postalCode: '94105',
country: 'US',
},
});
}
if (requiredAction?.kind === 'purchase_review') {
const review = await purchases.createActionLink(purchase.id);
console.log('Open to approve:', review.url);
}
}
```
The store asks for delivery details, which your server answers. If the buyer saved their delivery details on Conto's page before, Conto uses those and skips this step. The buyer then opens the review link, checks the items, total and delivery details, and approves.
## 4. Read the result
```typescript
const done = await purchases.get(purchase.id);
console.log(done.status, done.result?.merchantReference);
// succeeded TEST-3BE45BD829
```
`succeeded` means the store confirmed the order. `paymentEvidence` reports authorization, the store's confirmation and settlement separately.
## Go live
Live purchases need Conto to enable them for your organization, a Builder or Enterprise plan, and the buyer's own card. Until then, live setup links and live purchases are refused with `LIVE_NOT_ENABLED`. `admin.purchasing.capabilities()` reports `environments.live.available` for your organization.
Your code does not change for live: create the setup link without `environment: 'test'` and buy from a real store URL. For events in production, use [webhooks](https://conto.finance/docs/sdk/purchases#events-and-webhooks) instead of holding a stream open.
## Build it with a coding agent
Paste this into your coding agent from your app's repository:
```text
Add Conto Commerce to this app with @conto_finance/sdk and an organization API key from
CONTO_ORG_API_KEY, on the server only. Follow https://conto.finance/docs/guides/commerce-quickstart
and https://conto.finance/docs/sdk/purchases.
1. Map each signed-in user to a buyer with admin.purchasing.buyers.create({ externalId: user.id, email }).
2. Add a "Set up purchasing" button that redirects to a test setup link from
admin.purchasing.paymentMethods.createSetupLink({ buyerId, environment: 'test', returnUrl }).
3. Start purchases with admin.purchasing.purchases.create({ buyerId, request: { url, task }, maxCost })
and a stable idempotency key per order.
4. Answer buyer_details actions with the user's saved address, and send the user to
purchases.createActionLink(id) for review and card verification.
5. Receive events by webhook and verify each with verifyPurchaseWebhook.
Never handle card numbers, and never approve a review on the user's behalf.
```
## Next
- [Conto Commerce API reference](https://conto.finance/docs/sdk/purchases): statuses, actions, events, webhooks, errors and test stores.
- [MCP tools](https://conto.finance/docs/mcp/tools): the purchase tools for agents that call Conto themselves.
---
# Conto Commerce API
Source: https://conto.finance/docs/sdk/purchases
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](https://conto.finance/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
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.
### Pay with a company card
If your platform pays for its users' purchases and bills them itself, add one company card and let each buyer draw from it. Buyers then need no setup page, card or review of their own. You set two kinds of limits:
| Limits | Set with | What they cap |
| --------- | -------------------------------------------------------- | -------------------------------------------------------------- |
| The card | `admin.purchasing.companyCards.create()` and `.update()` | One order, and all buyers together in a month |
| One buyer | `admin.purchasing.buyers.setCompanyCard()` | One order, one day and one month, so no buyer uses up the card |
```typescript
const card = await admin.purchasing.companyCards.create({
environment: 'test',
limits: { perPurchaseMinor: 15000, monthlyMinor: 500000 },
});
await admin.purchasing.buyers.setCompanyCard(buyer.id, {
companyCardId: card.id,
limits: {
perPurchaseMinor: 10000,
dailyMinor: 20000,
monthlyMinor: 60000,
reviewAboveMinor: 8000,
},
});
```
Each purchase still gets its own single-use authorization, for one store and one total. Purchases at or below the buyer's `reviewAboveMinor` go ahead without a review. Above it, the purchase waits for your `purchase_review` answer (see [Actions](#actions)). A purchase over any of the buyer's limits or the card's caps is refused or ends unpaid with `POLICY_DENIED`, and `limitReached` says which: `buyer_daily`, `buyer_monthly`, `buyer_daily_count`, `store` or `card_or_policy`. Several buyers can buy at the same time.
Company cards are test cards for now, and need Conto to enable them for your organization. A buyer with a card of their own cannot also use a company card.
## Start and follow a purchase
```typescript
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. If your organization has store order checkout, a purchase with exact items uses it at stores that support it. Other purchases use browser checkout. Conto chooses once, when the purchase is created. Either way, answer the `buyer_details` form the purchase asks for.
`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.
### Grocery lists
For groceries, pass `request.groceryList` instead of `items`, with an Instacart store link as `url`. Conto searches the store for each item, adds it to the cart and checks out. It never substitutes a different product. If it cannot find an item, the purchase stops before payment.
```typescript
const purchase = await purchases.create(
{
buyerId: buyer.id,
request: {
url: 'https://www.instacart.com/store/walmart/storefront',
groceryList: {
items: [
{ label: 'Bananas', query: 'bananas', quantity: 1, unit: 'bunch', exclude: ['organic'] },
{ label: 'Whole milk', query: 'whole milk', quantity: 1, size: '1 gallon' },
{ label: 'Eggs', query: 'large eggs', quantity: 2, size: '1 dozen' },
],
},
},
maxCost: { amountMinor: 4000, currency: 'USD' },
},
{ idempotencyKey: 'grocery-order-2210' }
);
```
| Field | Required | What |
| ---------- | -------- | ----------------------------------------------------------- |
| `label` | Yes | A short name, such as `Bananas`. Up to 60 characters |
| `query` | Yes | Plain search words. No prices, quantities or package sizes |
| `quantity` | Yes | 1 to 20 |
| `size` | No | A package size, such as `1 pint` or `half gallon` |
| `unit` | No | How the item is sold, such as `bunch`, `bag`, `dozen`, `lb` |
| `brand` | No | A brand the item must be |
| `exclude` | No | Up to 8 words the item must not match, such as `organic` |
A list holds up to 20 items. A list at any other store is refused with `MERCHANT_UNSUPPORTED`. Live grocery purchases need Conto to enable them for your organization. In test mode, use Conto Test Grocery (see [Test purchases](#test-purchases)).
### From an agent's own key
An agent that calls Conto itself, for example through the [MCP server](https://conto.finance/docs/mcp/tools), uses its own SDK key. The purchase methods are the same:
```typescript
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.
### Estimate and maximum
Once the store's checkout is read, `quote` has the `estimate` (the store's own total) and the `maximum` (the most the order can come to). Grocery orders can come to up to about 15% more than the estimate, for weighed items and refunds, so their maximum is above the estimate. For an exact total the two are the same. Every limit, `maxCost` and the card authorization are held to the maximum. The purchase is never paid above `maxCost`. Before the card is charged, an update with `phase` `authorization` or `payment` carries both, so you can tell the buyer "About $82, at most $94". `paymentEvidence.merchant.amount` reports the amount charged.
If the maximum is above `maxCost`, nothing is authorized. The purchase waits with a `budget_increase` action whose `budget` has the estimate, the maximum and the current `maxCost`. Answer it with a new `maxCost` that covers the maximum, or decline:
```typescript
if (action?.kind === 'budget_increase') {
await purchases.respond(purchase.id, {
actionId: action.id,
fields: { decision: 'raise', maxCostMinor: action.budget!.maximum.amountMinor },
});
}
```
The answer applies at once. A new `maxCost` above what the buyer authorized for one purchase is refused with `DELEGATION_INSUFFICIENT`. Declining ends the purchase with `OVER_BUDGET`.
## 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 |
| `purchase_review` | `organization` | Your platform with `purchases.respond()`, on a company card |
| `card_verification` | `cardholder` | The buyer, with their card issuer |
| `merchant_signin`, `merchant_challenge` | `buyer` | The buyer, in a secure store browser |
| `budget_increase` | `agent_or_buyer` | Your platform with `purchases.respond()` |
| `organization_approval` | `organization` | A reviewer in your Conto dashboard |
If the buyer saved their delivery details on Conto's page, Conto answers the delivery form with them and the buyer approves them at review. Pass `buyerProfileId: null` when creating the purchase to answer it yourself.
Answer `buyer_details` with the fields the action lists for its `form`:
```typescript
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.
}
```
On a company card, answer the review with the total you saw, or decline it:
```typescript
if (action?.kind === 'purchase_review' && action.authority === 'organization') {
await purchases.respond(purchase.id, {
actionId: action.id,
fields: { decision: 'approve', totalMinor: purchase.quote!.total.amountMinor },
});
}
```
A review answer applies at once. If the total changed, approval is refused with `RESPONSE_CONFLICT`. A declined purchase ends with `DECLINED_BY_ORGANIZATION`.
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
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.
Conto also emails the buyer when a live purchase needs their review, card verification or a store sign-in, and again when the store confirms the order. Each email links to the step and works until the step expires. To reach buyers yourself, turn the emails off:
```typescript
await admin.purchasing.settings.update({ buyerEmails: false });
```
## 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
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
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 |
| `MERCHANT_UNSUPPORTED` | Conto cannot buy this request at this store |
| `COMPANY_CARDS_UNAVAILABLE` | Company cards are not enabled for your organization |
| `COMPANY_CARD_NOT_FOUND` | No such company card, or it was removed |
| `BUYER_HAS_OWN_CARD` | The buyer pays with their own card |
## 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` |
| `https://grocery.conto-test.invalid` | A grocery list. See below |
Conto Test Grocery takes `request.groceryList` and no product links. It finds each item by its `query` words and leaves out products with an `exclude` word. It ignores `brand`, `size` and `unit`. It sells bananas, organic bananas, Gala apples, strawberries, avocados, baby spinach, whole milk, large eggs, butter, cheddar cheese, Greek yogurt, whole wheat bread, spaghetti, white rice, chicken breast, ground coffee, orange juice and chocolate chip ice cream. A list with an item it does not sell fails, and the reason names the item. Like Test Supplies, it asks for delivery details.
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.
---
# Test allow, review, and deny
Source: https://conto.finance/docs/guides/policy-testing
Build a three-tier test: one payment is approved, one waits for approval, and one is denied.
[More scenarios](#more-scenarios) covers blocked recipients and time windows.
## Prerequisites
A Conto account and organization
An active agent with a linked, funded wallet and an SDK key in `CONTO_API_KEY`. The
[Quickstart](https://conto.finance/docs/quickstart/setup) walks through creating the wallet, connecting the agent, and
generating the key
## Keep the test isolated
Use a dedicated test agent and its Tempo Testnet wallet. Other organization policies, approval
workflows, or wallet caps can change the result. Make sure the wallet's
[per-transaction cap](https://conto.finance/docs/policies/spend-limits#wallet-level-limits) is at least 20 and its remaining
daily budget covers the test. Pass its `walletId` when multiple wallets exist.
Authorization tests create request records and send no funds unless you execute. The starter
policies on a new agent ($25 per payment, review above $10) do not change these results. Use a `DEV`
agent, because a production agent holds its first payment to a new address for payee review.
| Test | Spend cap: 15 | Review above: 10 | Expected decision | Permitted next action |
| ---- | ------------- | -------------------- | ------------------- | ------------------------------------------------- |
| 5 | Pass | Not triggered | `APPROVED` | May execute before expiry |
| 12 | Pass | Triggered | `REQUIRES_APPROVAL` | Wait for review, then read the same request |
| 20 | Fail | Also above threshold | `DENIED` | Stop. Review cannot override a failed cap |
Conto re-checks controls before a managed payment executes, so a policy change can stop a request
that was approved earlier. Read [lifecycle and recovery](https://conto.finance/docs/guides/payment-lifecycle) before resuming.
## Step 1: Verify your setup
Confirm the agent is configured before you create policies:
```bash
curl https://conto.finance/api/sdk/setup \
-H "Authorization: Bearer $CONTO_API_KEY"
```
Check that the setup summary shows:
- the agent is available for payment calls
- at least one wallet is available
If either check fails, go back to the [Quickstart](https://conto.finance/docs/quickstart/setup).
## Step 2: Create policies
### Policy A: Spend limit
### Create the policy
Go to **Policies > Create Policy**.
- **Policy Name**: "Manual Spend Test"
- **Applies to**: **Only assigned agents, wallets, or cards**
- **Policy Type**: **Spend Limit**
- **Max Transaction ($)**: `15`
- **Description**: "Deny transactions over $15"
### Check the rule
The form creates this rule:
| Field | Value |
|---|---|
| Rule Type | `MAX_AMOUNT` |
| Operator | `LTE` |
| Value | `15` |
| Action | `ALLOW` |
This allows transactions up to $15. Anything above is denied.
Info:
**How rule actions work:** an `ALLOW` rule is a required condition, so a transaction that does
not match it is denied. A `DENY` or `REQUIRE_APPROVAL` rule triggers when its condition matches.
`DENY` + `GREATER_THAN 15` has the same effect as this rule. `REQUIRE_APPROVAL` +
`GREATER_THAN 10` requires manual approval when the amount is over $10. See
[how policies are evaluated](https://conto.finance/docs/policies/evaluation#evaluation-semantics).
### Policy B: Approval threshold
### Create the policy
Go to **Policies > Create Policy**.
- **Policy Name**: "Manual Approval Test"
- **Applies to**: **Only assigned agents, wallets, or cards**
- **Policy Type**: **Approval Threshold**
- **Require Approval Above ($)**: `10`
- **Description**: "Require approval for transactions over $10"
### Check the rule
The form creates this rule:
| Field | Value |
|---|---|
| Rule Type | `REQUIRE_APPROVAL_ABOVE` |
| Operator | `GREATER_THAN` |
| Value | `10` |
| Action | `REQUIRE_APPROVAL` |
Transactions over $10 require manual approval in the dashboard before they can execute.
## Step 3: Assign policies to the agent
### Open the agent detail page
Navigate to your agent's detail page.
### Go to the Permissions tab
Click the **Permissions** tab.
### Assign both policies
Assign "Manual Spend Test" and "Manual Approval Test" to the agent. All assigned policies are
evaluated with AND logic. The most restrictive rule wins.
To confirm, re-run the `GET /api/sdk/setup` call from Step 1 and check that the payment-control
summary reflects the assigned policies.
## Step 4: Run test transactions
Use `curl` or any HTTP client. With the SDK, call
`conto.payments.request({ amount, recipientAddress, walletId, idempotencyKey })` with amounts 5, 12,
and 20. Use a different key for each test, and reuse a key only to retry the same test. See
[Payments](https://conto.finance/docs/sdk/payments).
### Test 1: $5 payment (expect APPROVED)
```bash
curl -X POST https://conto.finance/api/sdk/payments/request \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 5,
"recipientAddress": "0x0000000000000000000000000000000000000001",
"purpose": "Test - under all limits",
"category": "SAAS"
}'
```
Expected response:
```json
{
"requestId": "cmm...",
"status": "APPROVED",
"amount": 5,
"currency": "pathUSD",
"recipient": "0x0000000000000000000000000000000000000001",
"reasons": []
}
```
### Test 2: $12 payment (expect REQUIRES_APPROVAL)
```bash
curl -X POST https://conto.finance/api/sdk/payments/request \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 12,
"recipientAddress": "0x0000000000000000000000000000000000000001",
"purpose": "Test - over approval threshold"
}'
```
Expected response:
```json
{
"requestId": "cmm...",
"status": "REQUIRES_APPROVAL",
"amount": 12,
"currency": "pathUSD",
"recipient": "0x0000000000000000000000000000000000000001",
"reasons": ["This payment requires approval before it can proceed."]
}
```
### Test 3: $20 payment (expect DENIED)
```bash
curl -X POST https://conto.finance/api/sdk/payments/request \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 20,
"recipientAddress": "0x0000000000000000000000000000000000000001",
"purpose": "Test - over max amount"
}'
```
Expected response:
```json
{
"requestId": "cmm...",
"status": "DENIED",
"amount": 20,
"currency": "pathUSD",
"recipient": "0x0000000000000000000000000000000000000001",
"reasons": ["This payment exceeds a configured spending limit."]
}
```
## Step 5: Execute an approved payment
If Test 1 returned `APPROVED`, you can execute it onchain:
```bash
curl -X POST https://conto.finance/api/sdk/payments/REQUEST_ID/execute \
-H "Authorization: Bearer $CONTO_API_KEY"
```
The response includes `receipt.txHash`, `receipt.transactionId`, and `receipt.settlementMode`. Submission is not confirmation. Follow [Track and recover a payment](https://conto.finance/docs/guides/payment-lifecycle) to verify the final state.
Held and denied requests appear in **Alerts & Approvals** and **Audit Logs**, not on
**Transactions**, which lists executed payments. Each audit entry shows which policies were
evaluated, which rules matched, and the final decision.
## More scenarios
Both scenarios use the same agent and a $5 payment, so only the new policy can change the result.
Create each policy in **Policies > Create Policy** with **Applies to** set to **Only assigned
agents, wallets, or cards**, then assign it on the agent's **Permissions** tab.
### Blocked counterparty
| Field | Value |
| ---------------------- | -------------------------------------------- |
| Policy Name | Test: Blocked Address |
| Policy Type | Counterparty |
| Blocked Counterparties | `0x000000000000000000000000000000000000dEaD` |
```bash
curl -X POST https://conto.finance/api/sdk/payments/request \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 5,
"recipientAddress": "0x000000000000000000000000000000000000dEaD",
"purpose": "Test: blocked recipient"
}'
```
Expect `DENIED` with the reason `This recipient is not allowed by the current payment controls.`
The amount does not matter. The address itself is blocked.
### Time window
| Field | Value |
| ----------- | ------------------------- |
| Policy Name | Test: Business Hours Only |
| Policy Type | Time Window |
| Start Time | 09:00 |
| End Time | 17:00 |
Send a $5 payment outside 9am to 5pm UTC, or set the window to a range that excludes the current
UTC time. Expect `DENIED` with the reason `This payment is outside the permitted schedule.` Remove
the policy afterward so it does not block your other tests.
Info:
Time rules without a `timezone` are evaluated in UTC. To test a local schedule, create the rule
over the API with an IANA `timezone` in its value. See [time windows](https://conto.finance/docs/policies/time-windows).
## Troubleshooting
For setup issues such as a lost SDK key or an unfunded wallet, see
[If the result needs attention](https://conto.finance/docs/quickstart/setup#if-the-result-needs-attention) in the Quickstart.
### Every test returns APPROVED
Check the **Permissions** tab on the agent detail page. Assigned policies apply only to the
agents they are assigned to, and only while they are active. Also check that each policy has
rules.
### Payment denied but I expected REQUIRES_APPROVAL
Every applicable policy is evaluated with AND logic, whatever its priority. If one policy denies
while another would require approval, the denial wins. For example, if both an approval
threshold (`>$10`) and a spend limit (`>$15`) match, DENY wins over REQUIRES_APPROVAL. Priority
changes sort order only. It does not override a failing rule. Check which policies are assigned
on the **Permissions** tab.
### A starter policy caps my agent lower than my policy
Every applicable policy must pass, so the strictest cap wins. New agents are linked to the
starter policies ($25 per payment, $100 per day, review above $10). If the starter cap is $25
and your policy allows $100, the $25 cap wins. Edit or unassign the starter policy on the
**Permissions** tab.
## Next steps
### SDK reference
Link: https://conto.finance/docs/sdk/payments
Full SDK payment methods and options
### Go live checklist
Link: https://conto.finance/docs/guides/production-readiness
Checks before you enable live funds
---
# Policies
Source: https://conto.finance/docs/policies/overview
The agent owner sets policies for each agent. Conto checks them before the agent pays.
## What is a policy?
A policy is a set of rules that decides whether a payment can proceed. Every applicable policy must
pass, and the payment gets one of three outcomes:
| Outcome | Condition |
| --------------------- | -------------------------------------------------------------- |
| **DENIED** | Any check denies |
| **REQUIRES_APPROVAL** | No denial, and at least one rule or workflow requires approval |
| **APPROVED** | All checks pass |
[How policies are evaluated](https://conto.finance/docs/policies/evaluation) covers scope, evaluation order, and how rules
combine. The [policy rule reference](https://conto.finance/docs/policies/advanced) lists every rule type.
## Starter guardrails for new organizations
New human organizations start with two active starter policies:
- **Starter guardrails**: `$25` maximum per transaction and `$100` maximum per day
- **Starter approval threshold**: payments above `$10` require explicit approval
The starter policies are ordinary assigned policies visible in the dashboard. Active starters are
linked to new agents automatically and combine with organization baselines and any policies assigned
to the agent or wallet. You can edit, deactivate, or delete them.
Approval requests go to the owner's email first. See
[Notification Channels](https://conto.finance/docs/integrations/notification-channels) to change where reviews are sent.
## Policy types
The policy type groups rules in the dashboard. The rules decide the outcome.
- [Spend limits](https://conto.finance/docs/policies/spend-limits): per-payment, daily, weekly, and monthly caps
- [Time windows](https://conto.finance/docs/policies/time-windows): allowed hours and days, and blackout periods
- [Counterparties](https://conto.finance/docs/policies/counterparties): recipient allowlists, blocklists, and trust rules
- [Policy rule reference](https://conto.finance/docs/policies/advanced): every rule type, including identity, category,
geographic, contract, budget, x402, and MPP rules
- [Policy testing](https://conto.finance/docs/guides/policy-testing): build a policy that approves, holds, and denies test payments
- [Version history](https://conto.finance/docs/policies/versioning): view, compare, and restore earlier versions of a policy
## Creating policies
### Via dashboard
### Open the form
Go to **Policies > Create Policy**.
### Configure the policy
| Field | Description |
| --------------- | --------------------------------------------------------------------------- |
| **Policy Name** | A label for the policy |
| **Priority** | 0 to 100. Higher runs first. Priority never overrides a failing rule |
| **Applies to** | **Entire organization** or **Only assigned agents, wallets, or cards** |
| **Policy Type** | Chooses which rule fields the form shows |
| **Description** | Optional. What this policy does |
### Add rules
Set the rule values for the chosen type.
### Assign if needed
For assigned scope, link the policy to agents, wallets, or cards.
### Via API
```bash
curl -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Daily Spend Limit",
"description": "Limits daily spending to $1000",
"rules": [{ "ruleType": "DAILY_LIMIT", "operator": "LTE", "value": "1000", "action": "ALLOW" }],
"policyType": "SPEND_LIMIT",
"scope": "organization",
"priority": 50,
"isActive": true
}'
```
In an [anonymous sandbox](https://conto.finance/docs/quickstart/agent-sandbox#configure-controls), use the sandbox setup key
as the bearer token for these policy routes and for agent-wallet spend limits. It works only on
that sandbox, only until a human claims it, and never lets the agent's SDK key change a policy.
## Policy properties
| Property | Type | Description |
| ------------- | ------- | ---------------------------------------------------------------------------------------------------------- |
| `name` | string | Human-readable name |
| `description` | string | Detailed description |
| `policyType` | enum | Type of policy |
| `scope` | enum | `organization` baseline or `assigned` targets. When omitted, it is `organization` unless `agentIds` is set |
| `agentIds` | array | Agents to assign the policy to when it is created. Only valid with `assigned` scope |
| `priority` | number | Evaluation order (0-100), not override precedence |
| `isActive` | boolean | Whether policy is enforced |
| `rules` | array | Specific rules for this policy |
## Assigning policies
Assign a policy to specific agents, wallets, or cards. Only policies with `scope: "assigned"` can be
assigned. Organization policies apply automatically.
### Assign to agent
```bash
curl -X POST https://conto.finance/api/agents/{agentId}/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-d '{
"policyId": "cmm5c0pol000l49h7dmsuc11p"
}'
```
## Recommended setup
Keep the starter guardrails ($25 per payment, $100 per day, review above $10) until the agent has a
track record. Then raise limits by giving each agent its own policies, in this order. Create each
policy with `"scope": "assigned"` and the agent's ID, or assign it on the agent's **Permissions**
tab. The starter policies still apply until you edit or unassign them, because every applicable
policy must pass. The numbers below are an example for a payments agent and a separate API agent.
1. **Cap spend.** `MAX_AMOUNT` 50 and `DAILY_LIMIT` 200 deny any payment over $50 and any payment
that would take the day's spend over $200. See [Spend limits](https://conto.finance/docs/policies/spend-limits).
2. **Send the middle band to review.** `REQUIRE_APPROVAL_ABOVE` 25 holds payments from $25 to $50 on
the **Approvals** tab of **Alerts & Approvals** for a person to review. The agent gets
`REQUIRES_APPROVAL` and can poll or use webhooks to check status. Keep the threshold below
`MAX_AMOUNT`: smaller payments go through, the middle band waits for review, and larger payments
are denied. See [Approval workflows](https://conto.finance/docs/guides/approval-workflows).
3. **Limit recipients.** `ALLOWED_COUNTERPARTIES` pays only the addresses you list. To allow every
recipient except known bad ones, use `BLOCKED_COUNTERPARTIES` instead. To also deny payees whose
network score drops below 0.5, add `TRUST_SCORE` `GTE` 0.5. It still allows new payees. See
[Counterparty policies](https://conto.finance/docs/policies/counterparties).
4. **Limit hours.** `TIME_WINDOW` 09:00 to 18:00 and `DAY_OF_WEEK` Mon to Fri deny overnight and
weekend payments. Set a `timezone` on each rule over the API, or it runs in UTC. Add
`BLACKOUT_PERIOD` for holidays and maintenance windows. See [Time windows](https://conto.finance/docs/policies/time-windows).
5. **Give x402 spend its own agent.** If the agent pays for APIs with
[x402](https://conto.finance/docs/guides/x402-api-payments), assign x402 rules only to a separate API agent, because they
apply to every payment of the agents they cover. `X402_MAX_PER_REQUEST` 0.10 and
`X402_MAX_PER_SERVICE` `{"maxAmount": 20}` cap each call at $0.10 and each service at $20 over a
rolling 24 hours. `X402_ALLOWED_SERVICES` pays only the domains you list. See
[x402 protocol rules](https://conto.finance/docs/policies/advanced#x402-protocol-rules).
6. **Email owners about serious alerts.** Conto raises alerts on **Alerts & Approvals**
automatically, including policy violations and [x402 anomalies](https://conto.finance/docs/sdk/machine-spend#anomaly-alerts).
On **Alerts & Approvals**, select **Configure alerts**, turn on **Email owners about high-severity
alerts**, and set **Severity to email** to **High and critical** or **Critical only**. Lower
severities stay in the dashboard.
7. **Verify before you go live.** Call `GET /api/sdk/setup` with the agent's SDK key and check that
the payment-control summary reflects the policies you assigned. Then
[test allow, review, and deny](https://conto.finance/docs/guides/policy-testing).
## Denial explanations
When a payment is denied, the response includes a plain-language reason that can be shown to an
operator or end user:
```json
{
"status": "DENIED",
"reasons": ["This payment exceeds a configured spending limit."]
}
```
Reasons come from a fixed set, such as `No usable wallet is linked to this agent.` or
`This payee needs review before payments to it can proceed automatically.` They never include rule
values or policy names. See [payment reasons](https://conto.finance/docs/sdk/error-handling#payment-reasons).
An authorized operator can review the configured controls in the dashboard's policy and wallet
views. Dashboard alerts for a denied payment show the full evaluation reason.
## Test a policy before you enable it
`POST /api/policies/simulate` replays a candidate policy against your real recent transactions and
reports what it would have decided. It creates no policy, runs no approval, and moves no money. Use
it to see which currently allowed payments a new limit would block before you turn it on.
```bash
curl -X POST https://conto.finance/api/policies/simulate \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"candidatePolicy": {
"name": "Cap vendor payments",
"policyType": "SPEND_LIMIT",
"rules": [{ "ruleType": "MAX_AMOUNT", "operator": "LTE", "value": "500", "action": "ALLOW" }]
},
"sampleSize": 200
}'
```
The response returns a per-transaction `wouldBe` decision (`ALLOW`, `DENY`, `REQUIRE_APPROVAL`) and a
`summary` with `newlyBlocked` and `newlyAllowed` counts.
The simulation evaluates the rule types a single historical transaction fully determines (amount,
category, counterparty, agent identity, x402/MPP service). Rule types whose decision depends on
state that is not on a payment row (cumulative daily/weekly/monthly limits, velocity, live trust
scores, geography, and time-of-day windows) are not replayed and are listed under
`summary.contextDependentRuleTypes`.
---
# Spend limits
Source: https://conto.finance/docs/policies/spend-limits
## Configuration
Create a complete `SPEND_LIMIT` policy with its rules in one request:
```bash
curl -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Spend Limits",
"policyType": "SPEND_LIMIT",
"priority": 50,
"isActive": true,
"rules": [
{"ruleType": "MAX_AMOUNT", "operator": "LTE", "value": "500", "action": "ALLOW"},
{"ruleType": "DAILY_LIMIT", "operator": "LTE", "value": "2000", "action": "ALLOW"},
{"ruleType": "WEEKLY_LIMIT", "operator": "LTE", "value": "10000", "action": "ALLOW"},
{"ruleType": "MONTHLY_LIMIT", "operator": "LTE", "value": "30000", "action": "ALLOW"}
]
}'
```
## Rule types
| Rule Type | Description | Operator | Value |
| --------------- | ------------------------------ | -------- | ------------------------------------ |
| `MAX_AMOUNT` | Maximum per single transaction | `LTE` | Amount string, such as `"500"` |
| `DAILY_LIMIT` | Maximum total spend per day | `LTE` | Amount string |
| `WEEKLY_LIMIT` | Maximum total spend per week | `LTE` | Amount string |
| `MONTHLY_LIMIT` | Maximum total spend per month | `LTE` | Amount string |
| `BUDGET_CAP` | Budget with period | `LTE` | `{"amount": N, "period": "MONTHLY"}` |
`value` is always a string, such as `"500"`, or a JSON-encoded object for `BUDGET_CAP`. A bare number
fails validation. See [Budget allocations](https://conto.finance/docs/policies/advanced#budget-allocations) for `BUDGET_CAP`
periods and scopes.
## How it works
1. **Per-Transaction**: Checked against the requested amount
2. **Daily/Weekly/Monthly**: Checked against cumulative spend + requested amount
Daily, weekly, and monthly policy limits assigned to an agent or to the organization count the
agent's combined spend across its wallets and live cards. With a $100 daily limit, a $60 wallet
payment leaves $40 available for live-card payments that day. Concurrent wallet and card
reservations share the same agent lock and recheck hard period limits before recording spend.
Synthetic test-card spending stays separate from wallet and live-card totals.
A policy assigned only to a wallet counts the agent's spend through that wallet. A policy linked
to one card counts that card's spend. Limits set on an individual agent-wallet or agent-card link
remain additional restrictions. Scoped `BUDGET_CAP` and velocity rules use their own transaction
histories, not the combined period counters. See
[Budget allocations](https://conto.finance/docs/policies/advanced#budget-allocations).
### Example evaluation
```
Agent: Operations Agent
Daily Limit: $1,000
Spent Today: $750
Request: $300 payment
Evaluation:
- Per-tx check: $300 < limit (pass)
- Daily check: $750 + $300 = $1,050 > $1,000 (FAIL)
Result: DENIED
Reason: "This payment exceeds a configured spending limit."
```
## Wallet-level limits
Set limits on the agent-wallet link when you link it with `POST /api/agents/{agentId}/wallets`. To
change them later, send the limit fields without `walletId` to
`PATCH /api/agents/{agentId}/wallets/{walletId}`:
```json
{
"walletId": "cmm5b2wal002n49h7ckrtb33r",
"spendLimitPerTx": 100,
"spendLimitDaily": 1000,
"spendLimitWeekly": 5000,
"spendLimitMonthly": 15000
}
```
These are evaluated **first**, before policy rules. Wallet-level limits count spend through that
one agent-wallet link only.
To edit them in the dashboard:
1. Open the agent detail page.
2. On the **Overview** or **Wallets** tab, click the **pencil icon** next to the wallet.
3. Update the per-transaction, daily, weekly, or monthly limits and click **Save Changes**. To
remove a cap, clear the field instead of entering `0`.
Info:
**Auto-creation defaults:** When an external wallet is auto-created, Conto applies per-transaction
$100, daily $1,000, weekly $5,000, and monthly $20,000. You can override these when linking a
wallet to an agent. On update, `null` removes a cap. A limit of `0` blocks all payments through
that link. Omission on creation applies endpoint defaults (per-transaction $100, daily $1,000).
Omission on update preserves the existing value. See
[Defaults](https://conto.finance/docs/reference/defaults#agent-wallet-links).
## Tracking spend
Conto tracks spend automatically:
- **spentToday** - Resets at midnight UTC
- **spentThisWeek** - Resets Monday midnight UTC
- **spentThisMonth** - Resets 1st of month midnight UTC
---
# Counterparty policies
Source: https://conto.finance/docs/policies/counterparties
## Configuration (API)
Create a complete `COUNTERPARTY` policy with its rules in one request:
```bash
curl -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Trusted Counterparties",
"policyType": "COUNTERPARTY",
"priority": 50,
"isActive": true,
"rules": [
{
"ruleType": "ALLOWED_COUNTERPARTIES",
"operator": "IN_LIST",
"value": "[\"0x1234567890abcdef1234567890abcdef12345678\", \"0xabcdef1234567890abcdef1234567890abcdef12\"]",
"action": "ALLOW"
},
{
"ruleType": "TRUST_SCORE",
"operator": "GTE",
"value": "0.5",
"action": "ALLOW"
}
]
}'
```
## Rule types
### ALLOWED_COUNTERPARTIES
Only allow payments to specific wallet addresses (allowlist):
```json
{
"ruleType": "ALLOWED_COUNTERPARTIES",
"operator": "IN_LIST",
"value": "[\"0x1234...\", \"0x5678...\"]",
"action": "ALLOW"
}
```
Any recipient not in the list is denied. Address matching is case-insensitive.
### BLOCKED_COUNTERPARTIES
Block payments to specific addresses:
```json
{
"ruleType": "BLOCKED_COUNTERPARTIES",
"operator": "IN_LIST",
"value": "[\"0xdead...\"]",
"action": "DENY"
}
```
### TRUST_SCORE
`TRUST_SCORE` compares the payee's Conto network trust score, from 0 to 1. An address with no
network history scores 0.5, and so does every address when network sharing is off. A threshold
above 0.5 therefore denies every first payment to a new payee. This rule allows new payees and
denies payees whose network score has dropped below 0.5:
```json
{
"ruleType": "TRUST_SCORE",
"operator": "GTE",
"value": "0.5",
"action": "ALLOW"
}
```
The network score weighs success rate (30%), transaction volume (20%), number of organizations
(20%), flags and blocks (20%), and account age (10%). The per-organization score on the
counterparty page uses different weights and is not what `TRUST_SCORE` reads. See
[Trust Scoring](https://conto.finance/docs/guides/trust-scoring).
Info:
Conto looks up both values during evaluation. `TRUST_SCORE` reads the network score and
`COUNTERPARTY_STATUS` reads your organization's trust level for the payee. The payment request
does not need to include either value.
### COUNTERPARTY_STATUS
Require a specific trust level (`TRUSTED`, `VERIFIED`, `UNKNOWN`, `SUSPICIOUS`, or `BLOCKED`):
```json
{
"ruleType": "COUNTERPARTY_STATUS",
"operator": "EQUALS",
"value": "{\"status\": \"TRUSTED\"}",
"action": "ALLOW"
}
```
See [how trust levels are assigned](https://conto.finance/docs/guides/trust-scoring#how-trust-levels-are-assigned).
## Card payments
A card payment has no wallet address. On a card, an `ALLOWED_COUNTERPARTIES` or
`BLOCKED_COUNTERPARTIES` entry matches the merchant by the card network's merchant ID, by the
merchant name (case and extra spaces are ignored), or by domain. A domain covers the store's
checkout domain and every subdomain of it, so `example.com` covers `checkout.example.com`. Wallet
addresses, EINs, DUNS numbers, and LEIs never match a card merchant, so a list of only those does
not apply to card payments. `TRUST_SCORE`, `COUNTERPARTY_STATUS`, and `COUNTERPARTY_APPROVAL_STATUS`
do not apply to card payments.
Your counterparty records also apply to a card purchase when a counterparty's domain is the store's
checkout domain or a parent of it, or when its name matches the merchant name. A `BLOCKED`
counterparty denies the purchase. A counterparty that is `PENDING_REVIEW`, `MONITORED`, or `QUARANTINED`, or `DISCOVERED`
when the agent is a production agent, sends it to approval, and so does a relationship with the
agent that requires approval or has expired. Relationship spend limits do not apply to card
payments.
## Relationship spend limits
Each agent's relationship with a counterparty can carry its own limits. Set them on the
counterparty detail page: open the **Agents** tab and click **Controls** next to the agent.
| Field | Description |
| ----------- | --------------------------------------------------- |
| Per payment | Maximum amount per transaction to this counterparty |
| Daily | Maximum total spend per day to this counterparty |
| Monthly | Maximum total spend per month to this counterparty |
Leave a field empty for no cap. A limit of `0` blocks every payment to that counterparty.
These limits are checked after the trust and block checks and before the final approval decision.
If a counterparty has a $500/day limit, the evaluator adds up the agent's confirming and confirmed
transactions to that address today, across all of its wallets, payments to it that are executing or
approved but unconfirmed, and [checkout](https://conto.finance/docs/guides/checkout-checkpoints) purchases on its domain or a
subdomain while their spend is held or after they are paid. It blocks the payment if the new amount
would exceed the limit.
`POST /api/relationships` accepts a signed-in dashboard session, not an API key.
## Managing counterparties
### Add a counterparty
```bash
curl -X POST https://conto.finance/api/counterparties \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-d '{
"name": "Amazon Web Services",
"type": "VENDOR",
"address": "0x1234...",
"domain": "aws.amazon.com",
"category": "INFRASTRUCTURE",
"lifecycleStatus": "TRUSTED"
}'
```
### Block a counterparty
Block a counterparty from its page in the dashboard. The status endpoint accepts a signed-in
session, not an API key.
## Network intelligence & external providers
Conto's network intelligence shares risk signals across organizations. Solana wallets with no Conto
network history get their [Fairscale](https://fairscale.xyz) onchain reputation score.
See [external enrichment and compliance signals](https://conto.finance/docs/guides/trust-scoring#external-enrichment-and-compliance-signals)
for all available providers.
## Best practices
### Start with known vendors
Begin with a small list of trusted vendors:
```json
{
"policyType": "WHITELIST",
"rules": [{
"ruleType": "ALLOWED_COUNTERPARTIES", "operator": "IN_LIST", "value": "[\"0x1234...\", \"0x5678...\"]", "action": "ALLOW"
}]
}
```
### Gradually expand trust
As you verify new vendors, move them through the lifecycle:
1. New vendor: `DISCOVERED`. A production agent's payments to it wait for review.
2. After review: `APPROVED`
3. After history: `TRUSTED`
---
# Time windows
Source: https://conto.finance/docs/policies/time-windows
Time window policies restrict payments by hour and day. Set an IANA `timezone` in the value of every
policy rule. Without one, the rule is evaluated in UTC, not your local time.
[Wallet-level time windows](#wallet-level-time-windows) use the agent-wallet link's own `timezone`.
## Configuration (API)
Create a complete `TIME_WINDOW` policy with its rules in one request:
```bash
curl -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Business Hours",
"policyType": "TIME_WINDOW",
"priority": 50,
"isActive": true,
"rules": [
{
"ruleType": "TIME_WINDOW",
"operator": "BETWEEN",
"value": "{\"start\": \"09:00\", \"end\": \"17:00\", \"timezone\": \"America/New_York\"}",
"action": "ALLOW"
},
{
"ruleType": "DAY_OF_WEEK",
"operator": "IN_LIST",
"value": "{\"days\": [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\"], \"timezone\": \"America/New_York\"}",
"action": "ALLOW"
}
]
}'
```
## Rule types
### TIME_WINDOW (hours)
Restrict transactions to specific hours of the day:
| Property | Description |
| ---------- | ------------------------------------------------------------------------------------------------ |
| `ruleType` | `TIME_WINDOW` |
| `operator` | `BETWEEN` matches inside the window, including the end minute. `NOT_BETWEEN` matches outside it. |
| `value` | JSON string: `{"start": "HH:MM", "end": "HH:MM", "timezone": "Area/Location"}` |
| `action` | `ALLOW`, `DENY`, or `REQUIRE_APPROVAL` |
`09:00` to `17:00` matches through 17:00 and stops matching at 17:01. Wallet-level hours work
differently: their end hour is excluded. See [Wallet-level time windows](#wallet-level-time-windows).
### DAY_OF_WEEK (days)
Restrict transactions to specific days of the week:
| Property | Description |
| ---------- | --------------------------------------------------------------------------------- |
| `ruleType` | `DAY_OF_WEEK` |
| `operator` | `IN_LIST` matches the listed days. `NOT_IN_LIST` matches every other day. |
| `value` | JSON string: `{"days": ["Mon", "Tue"], "timezone": "Area/Location"}` |
| `action` | `ALLOW`, `DENY`, or `REQUIRE_APPROVAL` |
Valid day values: `Mon`, `Tue`, `Wed`, `Thu`, `Fri`, `Sat`, `Sun`
### BLACKOUT_PERIOD
Block transactions during maintenance windows or holidays:
```bash
curl -X POST https://conto.finance/api/policies \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Maintenance Windows",
"policyType": "BLACKOUT_PERIOD",
"priority": 50,
"isActive": true,
"rules": [{
"ruleType": "BLACKOUT_PERIOD",
"operator": "BETWEEN",
"value": "{\"timezone\": \"America/New_York\", \"windows\": [{\"start\": \"02:00\", \"end\": \"06:00\", \"reason\": \"Maintenance\", \"recurring\": true}]}",
"action": "DENY"
}]
}'
```
For a date range such as a holiday, give each window full timestamps, for example
`{"windows":[{"start":"2026-12-24T00:00:00Z","end":"2026-12-26T23:59:59Z"}]}` with operator
`BETWEEN` and action `DENY`.
## Wallet-level time windows
Time windows can also be set on the agent-wallet link. Send this body to
`POST /api/agents/{id}/wallets` when linking, or the same fields without `walletId` to
`PATCH /api/agents/{id}/wallets/{walletId}`:
```json
{
"walletId": "cmm5b2wal002n49h7ckrtb33r",
"allowedHoursStart": 9,
"allowedHoursEnd": 17,
"allowedDays": ["Mon", "Tue", "Wed", "Thu", "Fri"],
"timezone": "America/New_York"
}
```
Info:
`allowedHoursStart`, `allowedHoursEnd`, and `allowedDays` are evaluated in the link's IANA
`timezone`, which defaults to `UTC`. Invalid IANA names are rejected with a 400. Hours are whole
numbers on a 24-hour clock, with `allowedHoursStart` inclusive and `allowedHoursEnd` exclusive:
`9` to `18` allows 09:00 through 17:59 and blocks 18:00 onward. Midnight is hour `0`, so `0` to
`24` covers the full day, which is the default. This differs from a `TIME_WINDOW` rule, where the
end minute is included.
## Use cases
### Business hours
Only allow transactions during working hours
```json
{
"allowedHoursStart": 9,
"allowedHoursEnd": 18,
"allowedDays": ["Mon", "Tue", "Wed", "Thu", "Fri"]
}
```
### Extended hours
Allow transactions in extended support hours
```json
{
"allowedHoursStart": 7,
"allowedHoursEnd": 22,
"allowedDays": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
}
```
### Weekends only
For agents that operate on weekends
```json
{
"allowedHoursStart": 0,
"allowedHoursEnd": 24,
"allowedDays": ["Sat", "Sun"]
}
```
## Error response
When a transaction is blocked by time window:
```json
{
"status": "DENIED",
"reasons": ["This payment is outside the permitted schedule."]
}
```
## Best practices
### Set the wallet timezone
Set the link's `timezone` to your organization's operating timezone (for example,
`America/New_York`) instead of converting hours to UTC by hand. With the timezone set,
`allowedHoursStart` and `allowedHoursEnd` read as local business hours, Conto applies daylight
saving time, and `allowedDays` matches the local calendar day.
### Require approval after hours
```json
{
"ruleType": "TIME_WINDOW",
"operator": "NOT_BETWEEN",
"value": "{\"start\": \"09:00\", \"end\": \"17:00\", \"timezone\": \"America/New_York\"}",
"action": "REQUIRE_APPROVAL"
}
```
Every payment outside 09:00 to 17:00 New York time waits for review. Payments inside the window
follow your other limits.
---
# Trust scoring and counterparty controls
Source: https://conto.finance/docs/guides/trust-scoring
Conto scores each payee from its payment history and shared network signals. Policies and reviewers
use the score to decide whether an agent can pay that address.
## Use the right signal
| Signal | What it tells you | What it does not establish |
| --------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------- |
| Counterparty approval | Your organization has reviewed or allowed a recipient | Independent identity verification, or that later payments settle |
| Conto trust score | An assessment of history and available evidence on a 0–1 scale | A guarantee that the next payment is safe |
| Sanctions screening | Whether the available screening evidence blocks this payment | A replacement for identity or relationship review |
Recipient names, purposes, and categories supplied by your agent are caller-provided context, not
independent evidence. Do not treat them as verified identity. Trust enrichment can use cached data.
An unavailable enrichment source does not establish that the recipient is trusted. When the trust
score or level a rule needs is missing, the payment is denied, whatever the rule's action. A
baseline score can still exist for a recipient with little history, so inspect the evidence and
confidence as well.
Card payments never carry a trust score or trust level. On a card payment, `DENY` and
`REQUIRE_APPROVAL` rules of type `TRUST_SCORE` or `COUNTERPARTY_STATUS` are skipped, but an `ALLOW`
rule of either type denies the payment. To keep card payments working, write trust rules as `DENY`
rules, such as deny below 0.5 rather than allow at 0.5 and above. Or put `ALLOW` trust rules in a
policy assigned only to wallets, since organization policies and policies assigned to an agent or
card also run on card payments.
## Two layers of trust
- **Per-organization trust**: your organization's view of a counterparty, based on your own
transaction history with it.
- **Network trust**: a cross-organization view of a counterparty across all Conto customers.
`TRUST_SCORE` rules read the network score. `COUNTERPARTY_STATUS` rules read your organization's
trust level for the payee.
## The counterparty assessment
Conto combines both trust layers, your review state, and any policy or compliance gates into one
counterparty assessment. It answers five separate questions:
1. **Trust**: how much positive evidence supports paying this counterparty. The
assessment shows your organization's score as `0` to `100`, where high is good.
`TRUST_SCORE` rules do not read this number. When there is not enough evidence to
score, trust reads as `Not scored`, not a made-up middle number. A brand-new
payee is unscored, not low trust.
2. **Risk**: which adverse signals create risk. This is a category, not one minus
trust. When evidence is insufficient, risk stays `Risk unknown`. Conto does not
turn "no history" into "high risk".
3. **Confidence**: how much data backs the assessment, independent of trust and
risk. A high-trust counterparty with only a few payments is still low
confidence.
4. **Hard stops**: independent policy, compliance, identity, or operational
gates. A hard stop such as a sanctions hit or a network-wide block stops
release on its own, without inflating the risk score. A counterparty can be
low risk and still hard-stopped.
5. **Recommended vs enforced action**: Conto shows what it recommends (block,
review, approve with controls, or automate) next to what your policies enforce
today (block, require approval, or allow). When policy is looser than the
recommendation, the assessment flags the gap. Review the counterparty controls
before relying on automation.
Operators can open the assessment from the Trust tab on any counterparty.
## Evidence behind the trust score
The counterparty detail response and dashboard show the evidence behind an assessment:
- payment count and confirmed payment volume
- successful-settlement rate
- failed and policy-flagged payment counts
- relationship age and time since last activity
- your team's verification state
- whether shared network evidence is available
The assessment also groups evidence into payment history, reliability, activity, and verification,
with strength labels and recommended actions.
A new counterparty can improve over time through successful activity, while a once-safe
counterparty can degrade if failure or alert signals appear.
## How trust levels are assigned
Policies and payment checks read three separate values:
| Value | What it is | Read by |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| Network score | A cross-organization score from `0` to `1`. An address with no network history scores `0.5` | `TRUST_SCORE` rules |
| Trust level | `UNKNOWN`, `VERIFIED`, `TRUSTED`, `SUSPICIOUS`, or `BLOCKED`. Set from your organization's score and by the lifecycle status | `COUNTERPARTY_STATUS` rules |
| Lifecycle status | The review state you set on the counterparty in the dashboard, such as `APPROVED` or `MONITORED`. It can block payments or hold them for approval | Every payment to that payee |
Conto calculates your organization's score from `0.0` to `1.0` and maps it to a trust level. The
assessment shows the same score as `0` to `100`.
| Score range | Trust level | Notes |
| ------------ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `< 0.2` | `UNKNOWN` or `BLOCKED` | Counterparties with low scores only become `BLOCKED` when there are actual negative signals, such as failed or flagged transactions |
| `0.2 - 0.49` | `UNKNOWN` | Limited history or weak signal quality |
| `0.5 - 0.74` | `VERIFIED` | Reasonable confidence |
| `>= 0.75` | `TRUSTED` | High-confidence recipient. Requires manual verification, otherwise the level stays `VERIFIED` |
## Lifecycle statuses
Trust level is an evidence signal. Lifecycle status is the operator control state that Conto stores
on each counterparty and maps back into policy-compatible fields.
| Lifecycle status | Sets `trustLevel` | Sets `approvalStatus` | Payments |
| ---------------- | ----------------- | --------------------- | --------------------------------------------------------- |
| `DISCOVERED` | `UNKNOWN` | `PENDING` | Need approval for production agents |
| `PENDING_REVIEW` | `UNKNOWN` | `PENDING` | Need approval |
| `APPROVED` | `VERIFIED` | `APPROVED` | Proceed within policy |
| `TRUSTED` | `TRUSTED` | `APPROVED` | Proceed within policy |
| `MONITORED` | `VERIFIED` | `APPROVED` | Need approval, even though `approvalStatus` is `APPROVED` |
| `QUARANTINED` | `SUSPICIOUS` | `PENDING` | Need approval |
| `BLOCKED` | `BLOCKED` | `BLOCKED` | Blocked |
Counterparty APIs return `lifecycleStatus` together with the matching `trustLevel` and
`approvalStatus`. Use `MONITORED` for a payee you have approved but still want a person to review
each payment to.
Once a payee is `APPROVED`, `TRUSTED`, `MONITORED`, `QUARANTINED`, or `BLOCKED`, its trust level
stays the value in this table. Rescoring after a payment updates only the score.
## External enrichment and compliance signals
### Conto network intelligence
Conto aggregates anonymized risk signals across organizations to improve counterparty evaluation.
Addresses that several organizations flag or block raise alerts and lower the network score.
The **Network** page in the dashboard shows entity details, trust lookups, alerts, and network-wide
totals.
### Fairscale for Solana
For Solana addresses with no existing network trust score, Conto can enrich the counterparty with
[Fairscale](https://fairscale.xyz) wallet reputation data. Fairscale scores onchain behavior, token
holdings, transaction patterns, staking activity, and social connections from 0 to 100.
- Used for cold-start reputation on Solana
- Normalized into Conto's `0.0 - 1.0` trust scale and used as the network score that
`TRUST_SCORE` rules read
- Reputation enrichment supplements the trust information already available for the address
- Skipped for EVM addresses, and when no enrichment result is available
### Sanctions screening
Sanctions and compliance checks are separate from trust scoring. Conto screens wallet addresses
against sanctions lists on every payment, including known sanctioned addresses such as Tornado Cash,
Lazarus Group, and Garantex. Screening can block a payment independently of the trust score, so a
counterparty can be high-trust from a behavior standpoint and still be blocked for compliance
reasons.
| Provider | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **Local OFAC** (default) | Built in for every organization. OFAC SDN wallet addresses, refreshed on a schedule. No configuration or API key needed. |
| **Chainalysis** | Blockchain risk scoring via the [Chainalysis KYT API](https://www.chainalysis.com/). Identifies sanctions exposure through cluster analysis. |
| **TRM Labs** | Blockchain intelligence and compliance via the [TRM Labs Screening API](https://www.trmlabs.com/). Screens addresses for sanctions risk indicators. |
Chainalysis or TRM Labs can replace the local list. Contact
[support@conto.finance](mailto:support@conto.finance) about either.
Note:
Sanctions screening fails closed. If the selected provider is unavailable, or the local OFAC list
has not refreshed recently, the payment is denied.
Conto also blocks OFAC-sanctioned countries whenever the payment request includes
`context.recipientCountry`. To allow only the countries you permit, see
[geographic restrictions](https://conto.finance/docs/policies/advanced#geographic-restrictions-ofac).
Warning:
Always consult legal counsel for compliance requirements. This is not legal advice.
## How trust becomes a control
Trust data feeds into policy evaluation. Common trust-aware controls:
- `TRUST_SCORE` thresholds
- Counterparty status and trust-level rules
- New-recipient approval workflows
- Alerts when trust drops sharply
See [counterparty policies](https://conto.finance/docs/policies/counterparties).
## Look up trust programmatically
Use the SDK network trust endpoint to inspect any wallet address:
```bash
curl https://conto.finance/api/sdk/network/trust/0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18 \
-H "Authorization: Bearer $CONTO_API_KEY"
```
The response includes `trust.globalScore` (0 to 1), `isVerified`, `riskLevel`,
`networkRelationships`, `riskSignalCount`, your agent's relationship with the address, and your
transaction history with it. Standard SDK keys include the required `network:read` scope.
## Network sharing modes
Choose how your organization participates in network intelligence in **Settings**.
| Mode | What you share | What you receive |
| -------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------- |
| `FULL` | Transaction telemetry, shared volume patterns, local trust hints, and manual risk signals | Full trust lookups, alerts, and network stats |
| `PARTIAL` | Transaction telemetry and manual signals, but not shared volume or local trust scores | Full trust lookups, alerts, and network stats |
| `RECEIVE_ONLY` | No transaction telemetry, no volume sharing, and no manual signal contribution | Trust lookups, alerts, and network stats |
| Disabled | Nothing | No shared trust lookups or network analytics |
If sharing is disabled, network lookups return an `UNKNOWN` baseline score of 0.5 for every
address, so a `TRUST_SCORE` rule compares every payee against 0.5.
## Related guides
### Approval workflows
Link: https://conto.finance/docs/guides/approval-workflows
Use trust levels as a review trigger
### Recommended policy setup
Link: https://conto.finance/docs/policies/overview#recommended-setup
Build a layered risk model for agents that spend
### Recipes
Link: https://conto.finance/docs/guides/recipes
Copy-paste trust lookup and policy setup commands
---
# Approvals
Source: https://conto.finance/docs/guides/approval-workflows
An approval workflow holds matching agent payments until the required reviewers approve them.
Payments that match no workflow follow your policies.
The dashboard review shows the full destination address, the network, the paying wallet, and the policy rule that held the payment. A recipient name comes from the agent and is shown as an unverified label. Approving asks you to confirm the amount and destination address before Conto records the decision.
Each approval records the reviewer’s decision. If more approvals are needed, Conto shows how many remain. The final approval submits a managed payment, which still needs confirmation. A simulated payment is reported as a completed test. If a decision response is lost, refresh the request to check its status before trying again.
Connected-card credential requests use a separate single-reviewer flow on the agent’s Connected Cards tab. The routing, quorum, sequential approval, and self-approval rules on this page do not apply to them. See [connected-card review](https://conto.finance/docs/guides/connected-cards#make-a-purchase) before choosing that payment path.
## How the flow works
### Conto looks for a matching workflow
When an agent requests a payment, Conto runs your policies and looks for a matching approval
workflow. If none matches, the policy decision stands.
### Conto creates an approval request
Conto notifies the workflow's channels: Slack, email, Telegram, WhatsApp, or a webhook.
### Reviewers decide
Reviewers approve or reject in the dashboard or from the email link. The request stays pending
until it has the required approvals. If a workflow sets `escalation` and the request is still
pending after `escalation.afterHours`, its escalation targets can decide it too.
### The payment goes ahead or stops
With the required approvals, Conto approves the request and submits the managed payment. Any
rejection denies it. Either way, Conto writes the audit log, sends a webhook, and records the
channel.
## How Conto matches a workflow
Conto evaluates active workflows in priority order and picks the first one whose trigger conditions
match the payment context. Matching runs for every payment that policy does not deny, so a matching
workflow can hold a policy-approved payment until the required approvers decide.
If a policy requires approval and no custom workflow matches, Conto creates a one-review request for
an Owner, Admin, or Manager. The person or agent that started the payment cannot approve it. The
payment response includes the approval request ID and status URL so your integration can track the
review to completion.
### Supported trigger conditions
| Trigger | What it matches |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `minimumAmount` | Amounts at or above a threshold |
| `currency` | Currency-specific review |
| `categories` | Category-based review, such as vendor or infrastructure spend |
| `agentIds` | A specific set of agent IDs inside the organization |
| `agentTypes` | `OPENAI_ASSISTANT`, `ANTHROPIC_CLAUDE`, `AUTOGPT`, or `CUSTOM` |
| `newRecipients` | Payments to an address your organization has not paid before. Applies to production and non-production agents |
| `counterpartyTrustLevels` | `TRUSTED`, `VERIFIED`, `UNKNOWN`, `SUSPICIOUS`, or `BLOCKED` |
## Workflow settings that matter
| Setting | What it does |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `priority` | Higher-priority workflows match first |
| `approvals.required` | Approvals needed, 1 to 10. Defaults to `2` |
| `expiresAfterHours` | Hours a request stays pending, 1 to 168. Defaults to `24` |
| `approvals.allowRequester` | Whether the requester can approve their own request. Defaults to `false` |
| `approvals.approvers.roles` | With `approvers.type: "roles"`. Defaults to `OWNER` and `ADMIN`. Approval-capable roles only: `OWNER`, `ADMIN`, or `MANAGER`. Read-only roles (`VIEWER`, `MEMBER`) are rejected |
| `approvals.approvers.membershipIds` | Explicit organization members allowed to approve. Each must hold an approval-capable role |
| `approvals.order` | Uses `sequential` for ordered approval |
| `escalation.afterHours` | Hours a pending request waits before it escalates. Must be less than `expiresAfterHours` |
| `escalation.roles` | Roles that can also decide the request after it escalates: `OWNER`, `ADMIN`, or `MANAGER` |
| `escalation.membershipIds` | Members who can also decide the request after it escalates |
Info:
Any rejection ends the request. When the final approval is recorded, Conto executes a
managed-wallet payment, and the review shows the transaction hash and explorer URL. If execution
fails, the approval stays recorded and the request shows that execution needs attention. Conto
does not submit payments from external or smart-contract wallets. The agent sends those transfers
and confirms them. For an external wallet, the final approval reserves the amount and rechecks
policy, then the agent sends the transfer and confirms it within 24 hours. See
[wallet custody](https://conto.finance/docs/guides/custody-modes).
When a decision is final, Conto sends `approval.approved` or `approval.denied` to your organization
event webhook (see [two kinds of webhooks](https://conto.finance/docs/guides/webhooks#two-kinds-of-webhooks)). Notification
channels subscribed to `approval.decided` get that event instead. Before a managed wallet sends
funds, Conto rechecks current policy and stops the payment if the check denies it or cannot run.
### Escalation
Conto checks pending requests every 15 minutes. A request that is still pending
`escalation.afterHours` after it was created escalates once.
- The escalation roles and members can then approve or reject it in the dashboard, from the
approval email, or with the assistant. The workflow's own approvers can still decide too.
- In a sequential workflow, an escalation target can decide out of turn. Their decision counts as
the next step.
- Escalation targets follow the same rules as every approver. They must be members of your
organization with the `OWNER`, `ADMIN`, or `MANAGER` role, and the requester cannot approve their
own request unless `approvals.allowRequester` is `true`.
- Escalation does not extend the request. It still expires `expiresAfterHours` after it was
created.
- Channels subscribed to `approval.escalated` send the request again to every approver who has not
decided, including the escalation targets. To email a target their own Approve and Reject links,
add their sign-in email to an email channel's recipients.
## Configure workflows through the API
Create and manage workflows with this API or by asking the Conto assistant. The dashboard has no
workflow form. For a single review above an amount, set **Require Approval Above** on a policy rule
instead.
| Method | Endpoint | Purpose |
| -------- | -------------------------------------- | ----------------------------------------- |
| `GET` | `/api/approval-workflows` | List workflows and filter by `enabled` |
| `POST` | `/api/approval-workflows` | Create a workflow |
| `GET` | `/api/approval-workflows/{workflowId}` | Read one workflow |
| `PATCH` | `/api/approval-workflows/{workflowId}` | Update supported workflow settings |
| `DELETE` | `/api/approval-workflows/{workflowId}` | Delete a workflow with no pending reviews |
Create a sequential two-person review:
```bash
curl -X POST https://conto.finance/api/approval-workflows \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Large treasury payments",
"description": "Two reviewers for large transfers",
"triggers": {
"minimumAmount": 5000,
"currency": "USDC"
},
"approvals": {
"required": 2,
"order": "sequential",
"allowRequester": false,
"approvers": {
"type": "specific_members",
"membershipIds": ["cmm59z8k40001abcd1234efgh", "cmm59z9m20002abcd5678ijkl"]
}
},
"expiresAfterHours": 24,
"escalation": {
"afterHours": 4,
"roles": ["OWNER"],
"membershipIds": []
},
"priority": 50,
"enabled": true
}'
```
The response returns the stable workflow ID, normalized triggers, reviewer selection, ordered
`steps` for sequential review, timeout, escalation targets, enabled state, usage count, and
timestamps.
Use `GET /api/organizations/me/members` to find reviewer membership IDs. A sequential workflow
requires specific members, and the `membershipIds` order defines the approval steps. A role-based
workflow uses `order: "any_order"`.
`PATCH` updates only the fields you send, except `triggers`, which replaces the current trigger set.
Deletion returns `WORKFLOW_HAS_PENDING_REQUESTS` until the workflow's active reviews are resolved.
## Notify approvers
Conto sends each approval request to every active notification channel subscribed to
`approval.requested`. New organizations start with an **Owner email** channel. Owners and Admins
add channels in **Settings > Event Delivery**. Email links can approve or reject. Slack, Telegram,
WhatsApp, and webhook messages link to the review in Conto. See
[notification channels](https://conto.finance/docs/integrations/notification-channels) for channel setup, a test approval,
and troubleshooting.
Subscribe channels to `approval.requested`, plus `approval.escalated` if a workflow sets
`escalation`. On a webhook notification channel, also enable `approval.decided` and
`approval.expired`, plus `payment.executed` if your system marks invoices as paid.
Warning:
Email and dashboard decisions follow the same rules. The deciding member must hold `OWNER`,
`ADMIN`, or `MANAGER`. `VIEWER` and `MEMBER` cannot clear a payment even if a workflow lists
them, and the initiator cannot self-approve unless a custom workflow sets `allowRequester`.
## Recommended patterns
### Pattern 1: single approval for large payments
| Setting | Example |
| ------------------ | --------------------------------------------------------- |
| Trigger | `minimumAmount = 100` |
| Required approvals | `1` |
| Approver roles | `OWNER`, `ADMIN` |
| Best for | Day-to-day spend that only needs review above a threshold |
### Pattern 2: review first-time recipients
| Setting | Example |
| ------------------ | ------------------------------------------------------------------------ |
| Trigger | `newRecipients = true` |
| Required approvals | `1` |
| Approver roles | `ADMIN` |
| Best for | Agents paying addresses your organization has not paid before |
### Pattern 3: dual control for sensitive transfers
| Setting | Example |
| ------------------ | ---------------------------------------------------- |
| Trigger | `minimumAmount = 5000` |
| Required approvals | `2` |
| Self approval | `false` |
| Best for | Treasury, vendor onboarding, or high-value transfers |
### Pattern 4: sequential approval for finance and security
| Setting | Example |
| ------------------- | ----------------------------------------------------------------- |
| Specific approvers | `finance lead`, then `security lead` |
| Order | `sequential` |
| Best for | Controls that require ordered sign-off from multiple stakeholders |
### Pattern 5: agent-specific review in a shared organization
| Setting | Example |
| ------------------ | --------------------------------------------------------------- |
| Trigger | `minimumAmount = 0`, `agentIds = ["cmm5a7agt008t49h7bjqza99x"]` |
| Required approvals | `1` |
| Best for | Giving one agent a dedicated approval path |
## Related guides
### Notification channels
Link: https://conto.finance/docs/integrations/notification-channels
Channel fields, payloads, and signatures
### Trust scoring
Link: https://conto.finance/docs/guides/trust-scoring
Use counterparty trust as an approval trigger
### Recommended policy setup
Link: https://conto.finance/docs/policies/overview#recommended-setup
See where approvals fit in a layered policy model
### Wallet custody
Link: https://conto.finance/docs/guides/custody-modes
Diagrams of the managed and external wallet payment flows
---
# Automatic agent protection
Source: https://conto.finance/docs/guides/auto-freeze
Automatic protection freezes an agent and its wallets when its payment activity crosses a
threshold. It is off for new agents. While it is off, a crossed threshold creates a dashboard alert
and the agent keeps paying. A frozen agent cannot start new payments until a team member restores
it.
Use it as a backstop alongside payment policies and approval rules. Policies decide whether each
payment is allowed. Automatic protection can stop the agent when a pattern of activity needs review.
## Protection settings
Read the current settings with `GET /api/agents/{id}/freeze-config`. A new agent returns
`"enabled": false`:
```json
{
"enabled": false,
"thresholds": {
"maxConsecutivePolicyViolations": 5,
"maxConsecutivePaymentFailures": 5,
"spendVelocityMultiplier": 3,
"largePaymentMultiplier": 10,
"minimumTrustScore": 0.2,
"trustScoreDropThreshold": 0.3,
"maxNewCounterpartiesPerHour": 10
}
}
```
| Threshold | What it controls |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `maxConsecutivePolicyViolations` | Consecutive policy-denied payments that trigger protection |
| `maxConsecutivePaymentFailures` | Consecutive failed payments that trigger protection |
| `spendVelocityMultiplier` | How far recent payment activity may rise above the agent's established rate |
| `largePaymentMultiplier` | How large one payment may be relative to the agent's established payment size |
| `minimumTrustScore` | Lowest acceptable average counterparty trust score, from `0` to `1` |
| `trustScoreDropThreshold` | Largest acceptable drop in average counterparty trust within 24 hours, as a fraction of the earlier score (`0.3` = 30%) |
| `maxNewCounterpartiesPerHour` | Number of different recipients, new or known, the agent may pay within one hour |
The large-payment check needs 10 confirmed payments of history, and the velocity check ignores
hours with less than $50 of spend. The velocity check counts the agent's confirmed wallet payments,
its card purchases, and its [checkout](https://conto.finance/docs/guides/checkout-checkpoints) purchases together, and runs
after each wallet payment, each connected-card purchase, and each new checkout. A checkout counts
while its spend is held and after it is paid. Test card purchases are not counted. Consecutive failures and policy violations protect a new agent
from its first payment.
## Update settings
Send only the values you want to change. Omitted values keep their current setting. Set
`"enabled": true` to turn protection on.
```bash
curl -X PATCH https://conto.finance/api/agents/AGENT_ID/freeze-config \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"thresholds": {
"maxConsecutivePolicyViolations": 3,
"maxConsecutivePaymentFailures": 3,
"maxNewCounterpartiesPerHour": 5
}
}'
```
The API key needs the `agents:write` scope. The dashboard has no control for these settings. The
agent's **Safety** tab shows freeze status and history.
## Freeze or restore an agent manually
`reason` is optional and appears in the freeze history.
```bash
curl -X POST https://conto.finance/api/agents/AGENT_ID/freeze \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Reviewing an unexpected supplier payment",
"freezeWallets": true
}'
```
```bash
curl -X POST https://conto.finance/api/agents/AGENT_ID/unfreeze \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Supplier details verified",
"unfreezeWallets": true
}'
```
## Review status history
Use `GET /api/agents/{id}/freeze-history` to reconcile when and why access changed:
```json
{
"items": [
{
"id": "freeze_01JZ...",
"action": "FROZEN",
"actionLabel": "Frozen",
"source": "AUTOMATIC_PROTECTION",
"sourceLabel": "Automatic protection",
"reason": "Several payments failed in succession.",
"occurredAt": "2026-07-15T18:24:00.000Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}
```
Use each event `id` as a stable reconciliation or support reference. Automatic events include a
plain-language reason. Team actions include the optional `reason` supplied with the freeze or
restore request.
---
# Policy version history
Source: https://conto.finance/docs/policies/versioning
Each change to a policy saves a numbered version. Old versions are never edited. Any version can be
viewed, compared, or restored.
## What a version captures
A version is a snapshot of what the policy enforces: its name, description, type, scope, priority,
active state, and full rule set. Assignments (which agents, wallets, or cards a policy applies to)
are tracked separately and are not part of a version.
Conto appends a new version whenever you:
- Create a policy (`version` 1).
- Change its metadata (name, description, priority, scope, or active state).
- Add, edit, replace, or remove any rule.
- Roll back to an earlier version (the rollback itself is recorded as a new version).
Versions are numbered per policy, starting at 1 and increasing by one.
## List the history
```bash
curl https://conto.finance/api/policies/POLICY_ID/versions \
-H "Authorization: Bearer $CONTO_ORG_API_KEY"
```
Returns versions newest first, each with its `changeType`, who made the change, when, and a rule
count. Use `limit` and `offset` to page.
## Read one version
```bash
curl https://conto.finance/api/policies/POLICY_ID/versions/2 \
-H "Authorization: Bearer $CONTO_ORG_API_KEY"
```
Returns the full snapshot for that version, including every rule.
## Compare two versions
```bash
curl "https://conto.finance/api/policies/POLICY_ID/versions/compare?from=2&to=5" \
-H "Authorization: Bearer $CONTO_ORG_API_KEY"
```
The `diff` reports metadata `fields` that changed and the rules that were added or removed. Rules are
compared by content, so a rule that only changed position is not reported, and an edited rule shows
up as one removed plus one added.
## Roll back
```bash
curl -X POST https://conto.finance/api/policies/POLICY_ID/versions/2/rollback \
-H "Authorization: Bearer $CONTO_ORG_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "note": "revert the vendor cap change" }'
```
Rollback restores the policy's definition and rules from the target version and appends a new
`ROLLED_BACK` version. The policy type is fixed for the life of a policy and is never changed by a
rollback.
Note:
Rollback is a change to a live control, so it runs through the same governance approval workflow as
any other policy edit when your organization requires it. See
[Require a second approver for changes](https://conto.finance/docs/guides/roles-permissions#require-a-second-approver-for-changes).
If the target version is organization-scoped but the policy currently has assignments, rollback is
refused until you remove the assignments, because organization policies apply automatically and
cannot be assigned.
## Audit trail
Version history is for reviewing and restoring policy state. It is separate from the tamper-evident
audit log, which independently records every policy change in a per-organization hash chain. See the
audit integrity section on the [security page](https://conto.finance/security).
---
# Webhooks
Source: https://conto.finance/docs/guides/webhooks
Conto POSTs signed JSON events to your HTTPS endpoint.
## Two kinds of webhooks
Conto has two webhook systems. This page covers event webhooks.
| | Event webhook (this page) | [Notification channel](https://conto.finance/docs/integrations/notification-channels) webhook |
| --------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Set up in | **Settings** > **Event Delivery** > **Organization webhook**, or an agent `callbackUrl` | **Settings** > **Event Delivery** > **Notification Channels** > **Add Channel**, or `POST /api/notification-channels` |
| Use it for | Every event to one endpoint, for your backend or audit feed | Approval requests and other events you subscribe the channel to |
| Events | All [event types](#event-types), including `approval.approved` and `approval.denied` | Events you pick, such as `approval.requested` and `approval.decided` |
| Signature input | The raw body, with the secret Conto generates | `timestamp.rawBody`, with the `secret` you set on the channel |
| Headers | `X-Conto-Signature` | `X-Conto-Signature` and `X-Conto-Timestamp` |
Both sign with HMAC-SHA256 and send `X-Conto-Signature`, but they sign different input, so each needs its own verifier.
## Test a local handler
With the Conto CLI installed, use the same development signing secret in your handler and terminal:
```bash
export CONTO_WEBHOOK_SECRET="your-local-development-secret"
npx @conto_finance/create-conto-agent webhooks test http://localhost:8080/webhooks/conto --json
```
This sends one signed synthetic `payment.confirmed` event with `data.test: true` and a `test_`
payment ID. It reports the HTTP status and latency, creates no Conto payment, and requires no API key.
The command accepts only loopback URLs and does not follow redirects. It tests your handler's
signature and payload processing, not whether hosted delivery can reach your endpoint.
If you installed the package globally, `conto webhooks test ` runs the same command.
## Setup
1. Go to **Settings** > **Event Delivery** in the dashboard
2. Enter your HTTPS endpoint URL
3. Copy the generated **signing secret**. Your handler uses it to verify payloads
Warning:
Webhook URLs must use HTTPS. HTTP, loopback hosts, and private IP ranges are blocked for security
(SSRF protection).
## Event types
| Event | Fires when |
| ----------------------------- | --------------------------------------------------------------- |
| `payment.requested` | A `payments/request` call is evaluated (external-wallet `approve` sends no `payment.requested`, `approved`, or `denied` events) |
| `payment.approved` | A payment passes policy evaluation or its approval is granted |
| `payment.denied` | A payment is blocked by policy or rejected in review |
| `payment.executed` | A managed payment is submitted onchain or a test payment is simulated |
| `payment.confirmed` | A payment is confirmed onchain |
| `payment_request.created` | An agent-to-agent payment request is created |
| `agent.frozen` | An agent is frozen |
| `agent.unfrozen` | An agent is restored to active status |
| `policy.created` | A policy is created |
| `policy.updated` | A policy is updated |
| `policy.deleted` | A policy is deleted |
| `approval.approved` | A payment approval is granted |
| `approval.denied` | A payment approval is denied |
| `governance.approval.decided` | A sensitive change (separation of duties) is approved or denied |
Alerts are not sent to this webhook. To receive `HIGH` and `CRITICAL` alerts off the dashboard,
subscribe a webhook [notification channel](https://conto.finance/docs/integrations/notification-channels#alert-webhooks) to
`alert.raised`.
### Governance events for SIEM and GRC
`policy.*`, `approval.*`, and `governance.approval.decided` stream policy and approval-decision
activity to your webhook for your own audit trail. Payloads differ by event family:
- **Policy events** (`policy.created` / `updated` / `deleted`) carry `policyId`, `name`, and
`policyType`, never the underlying rule values. `policy.created` also includes `scope`, `priority`,
and `rulesCount`. `policy.updated` includes `isActive`.
- **Approval events** (`approval.approved` / `approval.denied`) carry `approvalRequestId`,
`paymentRequestId`, `amount`, and `decision` (`APPROVED` or `DENIED`). Agent callback deliveries add `agentId`.
- **`governance.approval.decided`** (separation-of-duties decisions on sensitive changes) carries
`governanceApprovalRequestId`, `action`, `resource`, `resourceId`, and `decision`.
They are delivered best-effort after the change commits, so a delivery failure never blocks the
change itself.
## Payload format
Every webhook POST includes these headers and a JSON body:
### Headers
| Header | Description |
| -------------------------- | ---------------------------------------------------- |
| `X-Conto-Event` | Event type (e.g., `payment.confirmed`) |
| `X-Conto-Timestamp` | ISO 8601 timestamp of the event |
| `X-Conto-Signature` | Hex HMAC-SHA256 of the raw body |
| `X-Conto-Delivery-Id` | Stable ID for this delivery, the same on every retry |
| `X-Conto-Delivery-Attempt` | Attempt number (1, 2, or 3) |
### Body
```json
{
"event": "payment.confirmed",
"timestamp": "2026-04-07T12:00:00.000Z",
"data": {
"paymentId": "cmm5h0req000l49h7irxzh11p",
"amount": 50,
"transactionHash": "0xabc...",
"transactionId": "cmm5g0txn000l49h7hqwyg11p",
"settlementMode": "live",
"organizationId": "cmm5i0org000l49h7jsyai11p"
},
"deliveryId": "5b1f7c2e-8d4a-4f3b-9a61-2c7e0d9f4b18"
}
```
Organization webhook deliveries add `organizationId` to `data`. Agent callback deliveries add
`agentId` instead.
## Verifying signatures
Conto signs every payload with your webhook secret using HMAC-SHA256. Verify the signature to confirm the request came from Conto.
**These event webhooks sign the raw request body only.** Notification-channel webhooks sign different input, so they need their own verifier. See [two kinds of webhooks](#two-kinds-of-webhooks).
```typescript
import express from 'express';
import { createHmac, timingSafeEqual } from 'crypto';
function verifyWebhook(rawBody: string, signature: string | undefined, secret: string): boolean {
if (!signature) return false;
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const signatureBuffer = Buffer.from(signature, 'hex');
const expectedBuffer = Buffer.from(expected, 'hex');
if (signatureBuffer.length !== expectedBuffer.length) {
return false;
}
return timingSafeEqual(signatureBuffer, expectedBuffer);
}
const app = express();
// In your handler
app.post('/webhooks/conto', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.header('x-conto-signature');
const rawBody = req.body.toString('utf8');
if (!verifyWebhook(rawBody, signature, process.env.CONTO_WEBHOOK_SECRET!)) {
return res.status(401).send('Invalid signature');
}
const { event, data } = JSON.parse(rawBody);
switch (event) {
case 'payment.confirmed':
console.log('Payment confirmed:', data.transactionHash);
break;
case 'payment.denied':
console.log('Payment denied:', data.reasons);
break;
}
res.status(200).send('OK');
});
```
The same rule applies in other frameworks: verify the exact raw request body before you parse it.
## Retry behavior
Each delivery gets up to **3 attempts** in total. An attempt fails if your endpoint returns a non-2xx
status or doesn't respond within **10 seconds**.
Conto retries only failures that can succeed on replay: a network error, a timeout, or an HTTP `408`,
`429`, or `5xx` response. Any other status (for example `400`, `401`, or `404`) ends the delivery
after that attempt.
| Attempt | Timing |
| ------- | ------------------------------------- |
| 1 | Immediate |
| 2 | About 1 second after attempt 1 fails |
| 3 | About 2 seconds after attempt 2 fails |
Every attempt carries the same `X-Conto-Delivery-Id` (also `deliveryId` in the body). Use it to
dedupe a delivery you already processed.
## Agent callback URLs
An agent can also have its own `callbackUrl`, set with `POST /api/agents` and changed or cleared
with `PATCH /api/agents/{id}`. Events for that agent then go to both the organization URL and the
callback URL, with the same payload format and signature headers. Policy and governance events go
only to the organization webhook.
Agent callbacks are signed with the same organization signing secret as the organization webhook.
The first time you configure a callback URL (or an organization webhook URL), the response includes
`webhookSecret` once. Store it to verify the `X-Conto-Signature` on callback deliveries. If you did
not capture it, rotate it from **Settings > Event Delivery** and update your receiver.
Authenticated agent reads return `webhookConfigured` and the configured `callbackUrl`. Responses
replace query and fragment values with `REDACTED` because callback URLs commonly carry credentials.
Send the full URL again when rotating those values.
## Settlement mode in payment events
The `payment.executed` payload includes `settlementMode`: `test` for simulated execution or `live` for an onchain transfer, including testnet. Simulated events omit `transactionHash`, so use the payment ID to read status. A `payment.confirmed` event represents onchain confirmation and reports `live`. Do not treat a simulated execution event as proof that funds moved.
---
# Notification channels
Source: https://conto.finance/docs/integrations/notification-channels
Notification channels send approval requests and other events to email, Slack, Telegram, WhatsApp,
or your own webhook. Only email links and the Conto dashboard, including the dashboard assistant,
decide payment approvals. Decisions also fire `approval.approved` and `approval.denied`
[event webhooks](https://conto.finance/docs/guides/webhooks).
## How it works
When a payment needs approval, Conto sends `approval.requested` to every active channel subscribed
to it. Channels deliver independently, and temporary delivery failures are retried. Email and
dashboard decisions follow the same approval rules and appear in the same audit history. For how
requests are matched, decided, and executed, see [Approvals](https://conto.finance/docs/guides/approval-workflows).
New organizations start with an **Owner email** channel that sends approval requests to the owner.
Change it under **Settings** > **Event Delivery**.
## Supported channels
| Channel | Delivery | How approvers act |
| -------- | -------------------------- | -------------------------------------------------- |
| Email | HTML email | Approve/Reject links to a hosted confirmation page |
| Slack | Block Kit message | **Review in Conto** button opens the dashboard |
| Telegram | Message with a link button | **Review in Conto** button opens the dashboard |
| WhatsApp | Text message | **Review in Conto** link opens the dashboard |
| Webhook | Signed JSON POST | Your system sends reviewers to `dashboardUrl` |
Each approval message shows the agent, amount, destination address, and why the payment was held.
Email shows the full address. Chat messages shorten it and link to the review.
## Setting up channels
Go to **Settings** > **Event Delivery** > **Notification Channels** in the Conto dashboard, or use
the [REST API](#rest-api).
### Choose a channel type
Click **Add Channel** and select the platform: Email, Slack, Telegram, WhatsApp, or Webhook.
### Configure credentials
| Channel | Required config |
|---------|----------------|
| Email | Recipient email addresses (an array in the API) |
| Slack | Bot token (`xoxb-...`) and channel ID |
| Telegram | Bot token, chat ID, and webhook secret token |
| WhatsApp | Phone number ID, access token, recipient numbers |
| Webhook | HTTPS URL and a signing secret (`secret` in config) |
Webhook URLs must use HTTPS and resolve to a public address. Loopback, private, link-local, `.internal`, and `.local` hosts are rejected.
### Select event types
Choose which events trigger notifications:
| Event | When it fires | Notes |
|-------|--------------|-------|
| `approval.requested` | New payment needs approval | All channel types |
| `approval.decided` | Someone approved or rejected | All channel types |
| `approval.escalated` | Request is still pending after the workflow's `escalation.afterHours` | All channel types |
| `approval.expired` | Request expired without resolution | All channel types |
| `governance.approval.requested` | Protected governance action needs review | `WEBHOOK` channels only |
| `governance.approval.decided` | Protected governance action is approved or rejected | `WEBHOOK` channels only |
| `payment.executed` | Payment is submitted onchain | `WEBHOOK` channels only |
| `alert.raised` | An alert is raised at `HIGH` or `CRITICAL` severity. See [alert webhooks](#alert-webhooks) | `WEBHOOK` channels only |
| `conto_pay.request.received` | A hosted Conto Pay request is sent to the payer | `WEBHOOK` channels only |
| `conto_pay.request.paid` | A hosted Conto Pay request is paid | `WEBHOOK` channels only |
| `conto_pay.request.rejected` | A hosted Conto Pay request is rejected | `WEBHOOK` channels only |
| `conto_pay.request.expired` | A hosted Conto Pay request expires | `WEBHOOK` channels only |
| `conto_pay.request.cancelled` | The payee cancels a hosted Conto Pay request | `WEBHOOK` channels only |
| `conto_pay.request.reminder` | The payee sends a reminder for a hosted Conto Pay request | `WEBHOOK` channels only |
On email, Slack, Telegram, and WhatsApp, `approval.decided` and `approval.expired` send a short
status update with a link to the request. They have no Approve or Reject actions.
`approval.escalated` sends the approval request again to approvers who have not decided,
including the workflow's escalation targets, only while the request is still pending and
unexpired. Escalation targets can then decide the request. See
[escalation](https://conto.finance/docs/guides/approval-workflows#escalation).
### Test the channel
Click the send-test button on the channel. A test message confirms delivery only. It creates no
approval request, so test emails have no Approve or Reject buttons.
### Run a real approval
To test the full decision flow, request a payment that requires approval, such as one above
your approval threshold:
```bash
curl -X POST https://conto.finance/api/sdk/payments/request \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "amount": 500, "recipientAddress": "0x0000000000000000000000000000000000000001", "purpose": "Test approval" }'
```
Approve from the email confirmation page or the dashboard. **Audit Logs** records who decided,
when, and through which channel, for example `EMAIL`.
## Channel configuration details
### Email
Each eligible approver receives their own HTML email with unique Approve and Reject buttons.
**Config fields:**
- `recipients` - List of email addresses. Enter members' sign-in emails. Conto sends approval requests only to addresses that belong to members who can approve the request. After a request escalates, that includes its escalation targets. Other addresses, such as a shared inbox, get no approval requests. Every listed address gets test messages and, if the channel subscribes to them, `approval.decided` and `approval.expired` status updates.
Clicking a button opens a browser, validates the token, and opens the hosted confirmation page.
The page shows the amount, network, full destination address, agent, and purpose. A recipient
name supplied by the agent is labeled as not verified. Opening the page, or a mail scanner or
link preview fetching the link, does not use the token. The decision is recorded only after the
approver selects **Approve payment** or **Reject payment**.
### Slack
Requires a Slack app with the `chat:write` bot scope. Messages use Block Kit with the payment details and a **Review in Conto** button that opens the request in the dashboard.
**Config fields:**
- `botToken` - Your Slack app's bot token (`xoxb-...`)
- `channelId` - The Slack channel ID to post messages to
**Setup:**
1. Create a Slack app at [api.slack.com/apps](https://api.slack.com/apps)
2. Add the `chat:write` bot scope
3. Install the app to your workspace
4. Invite the bot to the target channel
### Telegram
Uses the Telegram Bot API to send the payment details with a **Review in Conto** link button.
**Config fields:**
- `botToken` - Your Telegram bot token from [@BotFather](https://t.me/BotFather)
- `chatId` - The chat or group ID to send messages to
- `secretToken` - Any random string. Required when you save the channel.
**Setup:**
1. Create a bot via @BotFather
2. Add the bot to your group chat
3. Copy the chat ID into the channel config
### WhatsApp
Uses the WhatsApp Cloud API to send each recipient a free-form text message with the payment details and a
**Review in Conto** link.
**Config fields:**
- `phoneNumberId` - Your WhatsApp Business phone number ID
- `accessToken` - Permanent access token from Meta
- `recipientNumbers` - Phone numbers in international format (e.g., `+1234567890`)
**Setup:**
1. Register at [developers.facebook.com](https://developers.facebook.com)
2. Create a WhatsApp Business app
3. Copy the phone number ID and a permanent access token into the channel config
### Webhook
Sends a signed JSON payload to any HTTPS endpoint. Approval payloads include a `dashboardUrl`
that opens the request in Conto. They carry no approve or reject tokens, so an authenticated
Conto approver makes the decision.
A webhook channel is separate from the organization event webhook. See [two kinds of webhooks](https://conto.finance/docs/guides/webhooks#two-kinds-of-webhooks) for how their events and signatures differ.
**Config fields:**
- `url` - Your HTTPS endpoint URL
- `secret` - Signing secret for HMAC verification (required)
Every delivery carries two headers:
| Header | Value |
|--------|-------|
| `X-Conto-Timestamp` | ISO 8601 time the delivery was signed |
| `X-Conto-Signature` | Hex HMAC-SHA256 of `${X-Conto-Timestamp}.${rawBody}` using your channel `secret` |
Verify the signature before trusting the payload. This signed input differs from Conto's
[event webhooks](https://conto.finance/docs/guides/webhooks), which sign the raw body only. Use this verifier for every
notification-channel delivery (approval, payment execution, governance, alert, and Conto Pay
events).
```typescript
import { createHmac, timingSafeEqual } from 'crypto';
function verifyChannelWebhook(rawBody: string, timestamp: string, signature: string, secret: string) {
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
const a = Buffer.from(signature, 'hex');
const b = Buffer.from(expected, 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}
```
**Approval payload** (`approval.requested`, `approval.decided`, `approval.escalated`, `approval.expired`):
```json
{
"event": "approval.requested",
"approvalRequestId": "cmm59z8k40001abcd1234efgh",
"paymentDetails": {
"amount": 500,
"currency": "USDC",
"recipientAddress": "0x5678...",
"agentName": "Treasury Agent",
"purpose": "Invoice INV-2041",
"approvalReason": "This payment exceeds a per-payment limit and requires review."
},
"dashboardUrl": "https://conto.finance/alerts?tab=approvals&id=cmm59z8k40001abcd1234efgh",
"timestamp": "2026-04-06T12:00:00.000Z"
}
```
`purpose` is omitted when the agent did not supply one.
**Payment execution payload** (`payment.executed`): `event`, `paymentRequestId`,
`paymentDetails` (`amount`, `currency`, `recipientAddress`, `agentName`, `purpose`), `transaction` (`transactionId`, `transactionHash`,
`explorerUrl`, `executedAt`), and `timestamp`.
## Conto Pay lifecycle webhooks
`conto_pay.request.received` is delivered to the payer organization. Terminal outcomes (`paid`,
`rejected`, and `expired`), cancellations, and reminders are delivered to both the payer and payee
organizations when they have matching active webhook channels.
`links.requestUrl` opens the request review page and `links.payerActionUrl` opens the payer's action
page. They match the links shown in the Conto Pay inbox.
```json
{
"event": "conto_pay.request.paid",
"request": {
"id": "cmm6b2c9x0004ipr7k3mdq1zt",
"status": "PAID",
"amount": 250,
"currency": "pathUSD",
"purpose": "Invoice INV-100",
"invoiceId": "INV-100",
"dueDate": "2026-06-30T00:00:00.000Z",
"expiresAt": "2026-07-01T00:00:00.000Z",
"createdAt": "2026-06-14T12:00:00.000Z",
"updatedAt": "2026-06-14T12:05:00.000Z",
"direction": "incoming"
},
"payer": {
"agentName": "Buyer AP Agent",
"agentSlug": "accounts-payable",
"organizationName": "Buyer Inc",
"organizationSlug": "buyer-inc",
"handle": "@buyer-inc/accounts-payable"
},
"payee": {
"agentName": "Vendor AR Agent",
"agentSlug": "accounts-receivable",
"organizationName": "Vendor LLC",
"organizationSlug": "vendor-llc",
"handle": "@vendor-llc/accounts-receivable"
},
"links": {
"requestUrl": "https://conto.finance/pay-requests/cmm6b2c9x0004ipr7k3mdq1zt",
"payerActionUrl": "https://conto.finance/conto-pay?request=cmm6b2c9x0004ipr7k3mdq1zt"
},
"transaction": {
"id": "cmm6b3f1a0007txn8w2hpe5kq",
"txHash": "0xabc123",
"status": "CONFIRMED"
},
"timestamp": "2026-06-14T12:05:00.000Z"
}
```
Use `request.direction` to render the event from the receiving organization's point of view:
`incoming` means the organization is the payer, while `outgoing` means the organization created the
request and is waiting on the payer.
## Governance approval webhooks
Webhook channels can subscribe to `governance.approval.requested` and
`governance.approval.decided` for protected organization changes. Use them when an external audit,
compliance, or ticketing system needs a durable record of sensitive configuration changes such as
policy updates, policy rule changes, API key lifecycle actions, or freeze configuration changes.
Governance approval webhooks only notify. Owners and Admins approve or reject governance requests
in the Conto dashboard. For setup, protected-action selection, and the review flow, see
[require a second approver for changes](https://conto.finance/docs/guides/roles-permissions#require-a-second-approver-for-changes).
```json
{
"event": "governance.approval.decided",
"approvalRequest": {
"id": "cmm6c1g7r0002gov4n8xtb3wa",
"action": "POLICY_UPDATE",
"resource": "policy",
"resourceId": "cmm6c0p2d0001pol9v6kqz4re",
"status": "APPROVED",
"reason": "Update policy High value spend",
"requestPayload": "{\"isActive\":false}",
"requestedByActorType": "USER",
"requestedByActorId": "cmm5u1req0003usr2j7mfa8ny",
"requestedByUserId": "cmm5u1req0003usr2j7mfa8ny",
"expiresAt": "2026-07-14T00:00:00.000Z",
"approvedUntil": "2026-07-13T22:15:00.000Z",
"decidedAt": "2026-07-13T22:00:00.000Z",
"decidedByUserId": "cmm5u2adm0005usr6c1wdh3pk",
"decisionComment": "Reviewed with security policy owner",
"createdAt": "2026-07-13T21:55:00.000Z",
"updatedAt": "2026-07-13T22:00:00.000Z"
},
"requestedBy": {
"id": "cmm5u1req0003usr2j7mfa8ny",
"name": "Requester",
"email": "requester@example.com"
},
"decidedBy": {
"id": "cmm5u2adm0005usr6c1wdh3pk",
"name": "Admin",
"email": "admin@example.com"
},
"organizationId": "cmm5i0org000l49h7jsyai11p",
"timestamp": "2026-07-13T22:00:00.000Z"
}
```
## Alert webhooks
Webhook channels can subscribe to `alert.raised` to receive organization alerts outside the
dashboard. Conto sends every alert raised at `HIGH` or `CRITICAL` severity. `LOW`, `MEDIUM`, and
`WARNING` alerts stay in the dashboard. Approval requests arrive as `approval.requested`, not
`alert.raised`. Conto sends at most five alerts of one type per organization in each 15-minute
window. Subscribing the channel is the opt-in, so alert email preferences do not affect it.
Alert types include:
| `alert.type` | Raised when |
| ---------------------------------------------------------------- | ------------------------------------------------------------------- |
| `TRANSACTION_FAILED` | A payment fails onchain |
| `PAYMENT_NEEDS_RECONCILIATION` | A payment outcome is uncertain and needs reconciliation |
| `PAYMENT_FAILED` | A plan invoice payment fails |
| `AGENT_AUTO_FROZEN` | Conto freezes an agent automatically |
| `FREEZE_TRIGGER_DETECTED` | A freeze threshold fires while auto-freeze is off for the agent |
| `FREEZE_TRIGGER_ERROR` | Conto cannot evaluate an agent's freeze triggers |
| `PAYMENT_POLICY_BYPASS` | An x402 or MPP payment settled without Conto authorization |
| `POLICY_VIOLATION` | A payment breaks a policy |
| `AGENT_MANUALLY_FROZEN` | Someone freezes an agent |
| `CONNECTED_CARD_APPROVAL_REQUIRED` | A connected-card request needs review |
| `CONNECTED_CARD_MINT_RECONCILIATION` | A connected-card request stalled after a credential may have issued |
| `API_CALLS_WARNING`, `TRANSACTIONS_WARNING`, `VOLUME_WARNING` | Plan usage reaches 80% of a limit |
| `API_CALLS_CRITICAL`, `TRANSACTIONS_CRITICAL`, `VOLUME_CRITICAL` | Plan usage reaches a limit |
Conto does not retry a failed alert delivery. The failure is recorded on the channel as its last
error. The same alert can arrive more than once, so dedupe on `alert.id`.
```json
{
"event": "alert.raised",
"alert": {
"id": "cmm6d4h8s0006alt3p9rvc2xm",
"type": "AGENT_AUTO_FROZEN",
"severity": "CRITICAL",
"title": "Agent \"Treasury Agent\" has been frozen",
"message": "Agent was automatically frozen. Reason: ...",
"agentId": "cmm5a7agt008t49h7bjqza99x",
"transactionId": null,
"createdAt": "2026-07-13T22:00:00.000Z"
},
"organizationId": "cmm5i0org000l49h7jsyai11p",
"timestamp": "2026-07-13T22:00:01.000Z"
}
```
## REST API
Manage channels with an organization API key: `policies:read` to list, `policies:write` to create, update, test, or delete.
```bash
# List channels
curl https://conto.finance/api/notification-channels \
-H "Authorization: Bearer $API_KEY"
# Create a channel
curl -X POST https://conto.finance/api/notification-channels \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channelType": "SLACK",
"name": "Treasury Channel",
"config": { "botToken": "xoxb-...", "channelId": "C012345" },
"eventTypes": ["approval.requested"]
}'
# Create a governance approval webhook channel
curl -X POST https://conto.finance/api/notification-channels \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channelType": "WEBHOOK",
"name": "Governance Audit Stream",
"config": { "url": "https://example.com/conto/webhooks", "secret": "whsec_..." },
"eventTypes": ["governance.approval.requested", "governance.approval.decided"]
}'
# Test a channel
curl -X POST https://conto.finance/api/notification-channels/{id}/test \
-H "Authorization: Bearer $API_KEY"
# Delete a channel
curl -X DELETE https://conto.finance/api/notification-channels/{id} \
-H "Authorization: Bearer $API_KEY"
```
## Email action token security
Action tokens secure the Approve and Reject links in approval emails.
- **32 bytes of cryptographic randomness**, base64url-encoded
- **Protected at rest**. Plaintext tokens are not retained after issuance
- **One-time use**. Tokens cannot be reused after a decision is recorded
- **Time-limited**. Tokens expire when the approval request expires (default 24 hours)
- **Per-user, per-action**. Each approver gets separate Approve and Reject tokens
- **Full audit trail**. Every decision records the channel, token ID, IP address, and user agent
## Troubleshooting
### Notifications not arriving
1. Check that the channel is **active** in **Settings > Event Delivery**
2. Review the delivery notice shown for the channel
3. Verify your bot token or credentials
4. For Slack, confirm the bot is invited to the target channel
5. For Telegram, confirm the bot is in the chat that matches the configured chat ID
### Email link expired or already used
Email action tokens are one-time use and expire with the approval request. Once the request is
resolved, by another approver or from the dashboard, its links stop working.
### No approve button in Slack, Telegram, or WhatsApp
This is expected. Chat messages link to the request with **Review in Conto**. Approve or reject
in the dashboard or from the approval email.
## Next steps
### Approval workflows
Link: https://conto.finance/docs/guides/approval-workflows
Configure multi-approval workflows with escalation and sequential approvals
### Recommended policy setup
Link: https://conto.finance/docs/policies/overview#recommended-setup
Set up spending limits and approval thresholds for your agents
---
# Audit logs
Source: https://conto.finance/docs/guides/audit-logs
Conto records agent, payment, policy, freeze, and team changes in an append-only audit log. Each
entry is linked to the previous one by a SHA-256 hash, so an edited or deleted entry fails
verification.
## Viewing logs
### Dashboard
Go to **Audit Logs** in the sidebar. Search by user, agent, or resource, or filter by action,
resource, actor type, agent, or date range.
### Export
Owners and admins on Builder or Enterprise can export up to 100,000 entries as CSV from
**Audit Logs**. Select **Export**. The export uses the filters currently applied.
### SDK
The SDK route returns entries performed by or affecting the calling agent. It needs the
`audit:read` scope, which only admin SDK keys have.
```bash
curl "https://conto.finance/api/sdk/audit-logs?resource=payment_request&limit=20" \
-H "Authorization: Bearer $CONTO_API_KEY"
```
## What gets logged
### Payment decision receipts
Conto records a receipt for every `PaymentRequest`, `Transaction`, and `ApprovalRequest` lifecycle
change, including successful payments and intermediate or terminal decisions such as approval
required, approved, denied, executing, confirmed, failed, cancelled, and expired.
Each receipt can correlate the payment request, transaction, and approval request, and records:
- What was requested: amount, currency, recipient, purpose, category, agent, and wallet
- The decision at that point: status, policy result, approval requirement, reasons, violations, and
denial reason
- The controls in force: assigned policy versions and rules, wallet limits, counterparty posture,
and the approval workflow configuration
- Who acted and when: user, agent, API credential, or Conto system attribution with timestamp
- Execution evidence: transaction ID, chain transaction hash, block, confirmation, failure, and
review status when available
Database triggers capture each lifecycle change, whichever service, worker, payment rail, or retry
performed the write. Updates that change nothing are not recorded.
### Actions
| Action | Description |
| ------------------------------------------- | ---------------------------------------------- |
| `CREATE` | Resource created (agent, wallet, policy, etc.) |
| `UPDATE` | Resource modified |
| `DELETE` | Resource removed |
| `APPROVE` / `REJECT` | Approval decision made |
| `AGENT_FROZEN` / `AGENT_UNFROZEN` | Agent frozen or unfrozen |
| `FUND` / `WITHDRAW` / `TRANSFER` | Wallet balance changes |
| `PAYMENT_EXECUTED` | Payment submitted onchain |
| `PAYMENT_REQUEST` | Payment requested and controls snapshotted |
| `PAYMENT_APPROVED` | Payment approved |
| `PAYMENT_EXECUTING` | Payment execution started |
| `PAYMENT_CONFIRMED` | Payment confirmed onchain |
| `PAYMENT_DENIED` | Payment blocked by policy |
| `PAYMENT_FAILED` | Payment transaction failed |
| `PAYMENT_CANCELLED` / `PAYMENT_EXPIRED` | Payment closed without execution |
| `APPROVAL_REQUESTED` | Approval workflow receipt created |
| `APPROVAL_STATUS_CHANGED` | Approval decision or progress recorded |
| `EXTERNAL_PAYMENT_APPROVAL` | External wallet payment approved |
| `EXTERNAL_PAYMENT_CONFIRMED` | External wallet payment confirmed |
| `POLICY_VIOLATION` | Policy rule triggered |
| `ASSIGN_POLICY` / `REMOVE_POLICY` | Agent policy assignment changed |
| `LINK_WALLET` / `UNLINK_WALLET` | Agent wallet connection changed |
| `TRANSACTION_RETRY` | Payment retry requested |
| `TRUST_SCORE_UPDATE` | Counterparty trust assessment refreshed |
| `ALERT_CREATED` / `ALERT_RESOLVED` | Alert lifecycle |
| `SETTINGS_CHANGED` | Organization settings modified |
| `PERMISSION_GRANTED` / `PERMISSION_REVOKED` | Role or scope changes |
| `LOGIN` / `LOGOUT` | User session events |
| `INVITE_MEMBER` / `UPDATE_ROLE` | Team access changed |
| `PLAN_CHANGED` / `INVOICE_PAID` | Billing lifecycle |
| `AUDIT_LOG_EXPORTED` | Audit log data exported |
| `AUDIT_CHAIN_BASELINED` | Historical evidence baseline attested |
### Resources
Logs track actions on agents, wallets, transactions, policies, organization members, API and SDK
keys, alerts, counterparties, approval workflows, payment and budget requests, billing, and supported
x402 or MPP payment records.
## Log entry fields
Audit responses and exports include these fields for reconciling an event:
| Field | Description |
| ------------------- | ------------------------------------------------------------ |
| `id` | Stable audit entry reference |
| `action` | Action name, for example `PAYMENT_APPROVED` |
| `resource` | Resource type, for example `agent` |
| `actorType` | Who performed it: USER, API, AGENT, or SYSTEM |
| `createdAt` | When the event occurred |
| `resourceReference` | Supported resource ID, dashboard label, and link |
| `changes` | Before and after values for supported fields |
| `transaction` | Related payment reference and status, when the event has one |
| `paymentEvidence` | Request, decision reasons, applied policies, and workflow |
`resourceReference.id` is present when the affected record has a supported reference ID. Use it to
match an audit entry to API records or a support request. Authentication sessions and generic
settings records return a null reference ID.
```json
{
"id": "log_01JZ...",
"action": "AGENT_FROZEN",
"resource": "agent",
"actorType": "USER",
"createdAt": "2026-07-15T18:00:00.000Z",
"resourceReference": {
"id": "agent_123",
"label": "Procurement Agent",
"href": "/agents/agent_123"
},
"changes": [
{
"field": "status",
"label": "Status",
"before": "Active",
"after": "Suspended"
}
],
"transaction": null
}
```
## Change detection
When a resource is updated, `changes` includes only documented fields such as status, name, role,
limits, and alert resolution. Arbitrary metadata, network request details, authentication data, and
unsupported state fields are not returned in audit responses or exports.
## Tamper-evident hash chain
Each entry's SHA-256 hash covers the actor, organization, payment and transaction correlation,
request and decision states, network attribution, idempotency key, timestamp, and the previous
entry's hash. Modifying an earlier entry makes a later verification check fail.
Owners, admins, managers, and viewers can verify chain integrity for a selected date range. Go to
**Audit Logs** and select **Verify**. The response looks like this:
```json
{
"verified": true,
"totalLogs": 1247,
"verifiedLogs": 1247,
"verifiedAt": "2026-08-12T08:14:22.000Z",
"historicalCoverage": {
"status": "complete"
},
"externalAnchor": {
"configured": false,
"status": "not_configured",
"receipt": null
}
}
```
If part of an organization's history is missing from before the current chain began,
`historicalCoverage.status` is `attested_gap` and the response lists the missing ranges.
### Daily integrity check
Conto verifies every organization's chain daily. If a check fails, Conto raises a critical **Audit
trail integrity check failed** alert on **Alerts & Approvals**. The alert resolves after a later
check passes.
### External checkpoints
External checkpoints are not enabled by default. When they are off, `externalAnchor.configured` is
`false` and **Verify** shows "External immutable storage is not configured." The hash chain still
detects edited and deleted entries.
When external checkpoints are enabled, Conto writes a signed checkpoint of each verified chain head
to S3 Object Lock storage in `COMPLIANCE` mode. Conto rejects a checkpoint that S3 does not store
with a version ID and `COMPLIANCE` retention. Before each checkpoint, Conto compares the newest
stored checkpoint with the database head, so a database rollback is detectable. `externalAnchor.status`
is then `pending`, `verified`, `behind`, or `invalid`, and `receipt` shows the latest checkpoint's
manifest hash, covered sequence, and retention deadline. The receipt does not expose the storage
bucket, object key, or credentials.
## IP attribution
Audit entries record the client IP address when one is available.
---
# Machine Spend
Source: https://conto.finance/docs/sdk/machine-spend
Machine Spend reports an agent's recorded x402 and MPP spend and alerts in one view.
## What it includes
- total spend and call volume
- service-level breakdowns
- endpoint-level breakdowns
- x402 vs MPP split
- count of payments under $0.10
- recent x402 and MPP [anomaly alerts](#anomaly-alerts)
Both endpoints take the agent's SDK key and need the `analytics:read` scope.
## Get a summary
Returns a top-level summary plus recent machine-spend alerts for the authenticated agent.
```bash
curl "https://conto.finance/api/sdk/service-spend/summary?period=30d&protocol=all" \
-H "Authorization: Bearer $CONTO_API_KEY"
```
### Query parameters
| Parameter | Type | Default | Description |
| ---------- | ------ | ------- | --------------------------------- |
| `period` | string | `30d` | One of `24h`, `7d`, `30d`, `all` |
| `protocol` | string | `all` | One of `all`, `x402`, `mpp` |
| `limit` | number | `10` | Recent alerts to return, max 25 |
### Example response
```json
{
"filters": {
"period": "30d",
"protocol": "all",
"agentId": "cmm5a1agt002n49h7bjqza33r",
"startDate": "2026-03-18T00:00:00.000Z"
},
"summary": {
"totalSpend": 14.73,
"totalCalls": 219,
"activeServices": 6,
"activeAgents": 1,
"activeAlerts": 1,
"micropaymentTransactions": 188,
"x402Spend": 4.12,
"x402Calls": 173,
"mppSpend": 10.61,
"mppCalls": 46
},
"recentAlerts": [
{
"protocol": "x402",
"label": "Price change detected",
"state": "needs_review",
"stateLabel": "Needs review",
"createdAt": "2026-04-16T19:02:11.000Z",
"organizationName": "Acme",
"agentName": "research-agent"
}
]
}
```
## List services and endpoints
Returns the top service domains and endpoints for the authenticated agent.
```bash
curl "https://conto.finance/api/sdk/service-spend/services?period=7d&protocol=x402&limit=20" \
-H "Authorization: Bearer $CONTO_API_KEY"
```
### Query parameters
| Parameter | Type | Default | Description |
| ---------- | ------ | ------- | ---------------------------------------- |
| `period` | string | `30d` | One of `24h`, `7d`, `30d`, `all` |
| `protocol` | string | `all` | One of `all`, `x402`, `mpp` |
| `limit` | number | `10` | Services and endpoints to return, max 50 |
### Example response
```json
{
"filters": {
"period": "7d",
"protocol": "x402",
"agentId": "cmm5a1agt002n49h7bjqza33r",
"startDate": "2026-04-10T00:00:00.000Z"
},
"services": [
{
"protocol": "x402",
"serviceDomain": "api.example.com",
"totalSpend": 2.41,
"totalCalls": 120,
"avgPricePerCall": 0.0201,
"minPricePerCall": 0.01,
"maxPricePerCall": 0.05,
"firstActivityAt": "2026-04-11T03:44:52.000Z",
"lastActivityAt": "2026-04-16T21:08:17.000Z"
}
],
"endpoints": [
{
"protocol": "x402",
"resourceUrl": "https://api.example.com/search",
"serviceDomain": "api.example.com",
"totalSpend": 1.84,
"totalCalls": 92,
"avgPricePerCall": 0.02,
"lastActivityAt": "2026-04-16T21:08:17.000Z"
}
]
}
```
## Anomaly alerts
Conto runs these checks each time an x402 or MPP payment is recorded. Thresholds are fixed.
| Alert | Raised when |
| -------------- | -------------------------------------------------------------------------------------------- |
| Price spike | A payment is more than 50% above the agent's average for that service, after 5 or more calls |
| High frequency | The agent records more than 60 paid calls in 5 minutes |
| New service | The agent pays a service for the first time |
| Budget burn | At the current rate, today's spend will pass 90% of the wallet's daily limit |
Budget burn needs a daily limit on the wallet. Alerts appear in **Alerts & Approvals** in the
dashboard and in `recentAlerts` from the summary endpoint.
## When to use Machine Spend vs protocol-specific endpoints
Use **Machine Spend** for a unified service view across both protocols.
Use **protocol-specific endpoints** for details unique to one rail:
- `/api/sdk/x402/services` for x402 service-specific stats
- `/api/sdk/x402/budget` for x402 burn rate and policy limit views
- `/api/sdk/mpp/services` for MPP service-specific stats
- `/api/sdk/mpp/budget` for MPP session and budget views
## Dashboard view
The **Machine Spend** page in the dashboard shows the same data:
- filter by protocol and time window
- compare top services and endpoints
- compare top agents
- review recent x402 and MPP anomaly alerts
---
# Roles and permissions
Source: https://conto.finance/docs/guides/roles-permissions
## Roles
| Role | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Owner** | Full access, including organization settings, API key creation and revocation, and admin API keys. An organization can have multiple Owners. The last Owner cannot leave without transferring ownership first. |
| **Admin** | Manages agents, wallets, policies, billing, and members below Admin. Can view API keys but not create or revoke them. Cannot change organization settings. |
| **Manager** | Can create and update agents, manage counterparties, approve transactions, and manage alerts. Cannot create wallets or policies. |
| **Viewer** | Read-only access to agents, wallets, policies, transactions, counterparties, alerts, and analytics. |
| **Member** | Limited access. Can view wallets but cannot modify anything. |
## Permission matrix
| Permission | Owner | Admin | Manager | Viewer | Member |
| ---------------------- | ----- | ----- | ------- | ------ | ------ |
| **Agents** | | | | | |
| View agents | Yes | Yes | Yes | Yes | -- |
| Create agents | Yes | Yes | Yes | -- | -- |
| Update agents | Yes | Yes | Yes | -- | -- |
| Delete agents | Yes | Yes | -- | -- | -- |
| Suspend/freeze agents | Yes | Yes | Yes | -- | -- |
| **Wallets** | | | | | |
| View wallets | Yes | Yes | Yes | Yes | Yes |
| Create wallets | Yes | Yes | -- | -- | -- |
| Fund wallets | Yes | Yes | -- | -- | -- |
| **Policies** | | | | | |
| View policies | Yes | Yes | Yes | Yes | -- |
| Create/edit policies | Yes | Yes | -- | -- | -- |
| **Transactions** | | | | | |
| View transactions | Yes | Yes | Yes | Yes | -- |
| Execute transactions | Yes | Yes | Yes | -- | -- |
| Approve transactions | Yes | Yes | Yes | -- | -- |
| **Counterparties** | | | | | |
| View counterparties | Yes | Yes | Yes | Yes | -- |
| Manage counterparties | Yes | Yes | Yes | -- | -- |
| **Alerts** | | | | | |
| View alerts | Yes | Yes | Yes | Yes | -- |
| Acknowledge/resolve | Yes | Yes | Yes | -- | -- |
| **Analytics** | | | | | |
| View analytics | Yes | Yes | Yes | Yes | -- |
| Export data | Yes | Yes | -- | -- | -- |
| **Settings** | | | | | |
| View settings | Yes | Yes | -- | -- | -- |
| Modify settings | Yes | -- | -- | -- | -- |
| **Team** | | | | | |
| View members | Yes | Yes | Yes | Yes | -- |
| Invite/remove members | Yes | Yes | -- | -- | -- |
| **API Keys** | | | | | |
| View API keys | Yes | Yes | -- | -- | -- |
| Create/revoke API keys | Yes | -- | -- | -- | -- |
## Managing members
### Invite a member
Go to **Settings** > **Team** > **Invite Member**. Enter the email and select a role.
### Change a role
Owners and Admins can change roles. You can change or remove only members whose role is strictly
below your own. An Admin can manage Managers, Members, and Viewers, but not another Admin. Owners
can manage anyone, including other Owners, and only an Owner can promote a member to Owner. The last
Owner cannot be demoted or removed without transferring ownership first.
## Require a second approver for changes
An Owner can protect sensitive changes. When someone attempts a protected change, Conto holds it and
creates a governance approval request. A second Owner or Admin must approve the request before the
change applies. Managers, Members, and Viewers cannot configure governance approvals or decide
requests.
You can protect:
- API key creation, rotation, and revocation
- Policy creation, update, and deletion
- Policy-rule creation, update, and deletion
- Automatic freeze settings updates for an agent
Pending requests expire after 24 hours. The requester cannot approve their own request, so a
single-owner organization needs a second Owner or Admin.
### Set up protected changes
### Open the Governance tab
As an Owner, sign in to the dashboard and open the **Governance** tab in organization settings.
### Choose protected actions
Toggle on the actions that should pause for review, then choose **Save Governance Rules**.
### Optional: add webhook visibility
If your compliance, audit, or ticketing system needs a copy of governance events, add a
`WEBHOOK` notification channel and subscribe it to `governance.approval.requested` and
`governance.approval.decided`. See
[governance approval webhooks](https://conto.finance/docs/integrations/notification-channels#governance-approval-webhooks).
### Test one protected change
Try a protected action such as updating a policy or rotating an API key. The dashboard shows
**Waiting for approval**, the request appears in the approval inbox, and the change is not applied.
### Review a request
Owners and Admins review pending requests in the **Governance** settings tab or on the
**Governance** tab of **Alerts & Approvals**, and approve or reject them there. Each request shows:
- The protected action, such as `POLICY_UPDATE` or `API_KEY_ROTATE`
- The target resource and resource ID
- The requester and request timestamp
- The serialized request payload
- Expiration time
- Decision history and optional reviewer comment
A rejected or expired request is not applied. Conto records each request and decision in
[audit logs](https://conto.finance/docs/guides/audit-logs).
### Apply an approved change
After approval, the requester opens the **Governance** tab of **Alerts & Approvals** and chooses
**Apply change** under **Ready to apply** within 15 minutes. Conto keeps the change with the
request, so the requester can apply it from any browser or device where they are signed in. Only
the requester can apply it. Other Owners and Admins see the request but no **Apply change** button.
A new or rotated API key is shown once, to the person who applies the change. If the window closes
or the change no longer matches, make the change again to request a new approval.
Through the API, a protected call returns HTTP 202 with `requiresGovernanceApproval: true` and the
pending request in `approvalRequest`, including its `id`. After approval, the same user or API key
resends the identical request with the `x-governance-approval-id` header set to that `id` within 15
minutes. The approval applies once. A changed payload, an expired window, or a request that is not
approved returns HTTP 409.
### Governance API
The dashboard uses these endpoints:
- `GET /api/governance/approval-settings`
- `PATCH /api/governance/approval-settings`
- `GET /api/governance/approval-requests`
- `POST /api/governance/approval-requests/{id}/decide`
An organization API key with `org:read` can read the approval settings and list approval requests.
Changing the settings needs a signed-in owner in the dashboard. Deciding a request needs a signed-in
owner or admin other than the requester.
## Managing your own sessions
Every signed-in user can review and revoke their own active browser sessions, whatever their
organization role.
- `GET /api/sessions` lists active sessions with a revocable session ID, current-session marker,
recognizable device label, IP address, sign-in time, and last-active time.
- `DELETE /api/sessions/{id}` signs out one session owned by the current user.
- `POST /api/sessions/terminate-all` signs out every other active session while keeping the current
browser signed in.
The session response does not include authentication tokens, user IDs, device fingerprints, or
audit metadata. If you do not recognize a device or IP address, revoke that session and rotate any
credentials that may have been exposed.
## API key scopes
Only Owners create organization API keys. For key types, presets, and scopes, see
[keys and scopes](https://conto.finance/docs/sdk/authentication#organization-api-key-scopes).
---
# Dashboard assistant
Source: https://conto.finance/docs/assistant/overview
The dashboard assistant (**Conto AI** in the panel header) answers questions about your Conto
account and prepares dashboard changes for you to confirm. It works in your active organization
with your dashboard role.
## What it can help with
Use it to:
- find agents, wallets, balances, policies, payment requests, transactions, alerts, approvals,
counterparties, and Conto Pay contacts
- summarize balances, recent activity, and policy results
- prepare changes for you to review and confirm, such as agent and wallet settings, spending
policies and policy templates, bulk pauses or limit changes across agents, approval workflows,
counterparties and recipient controls, approval decisions, and Conto Pay payments
- explain errors and suggest a safe next step
- check your plan, which agents have a test card, and what an agent still needs before its
integration can make a card purchase
Read requests do not change account state. For time-sensitive information such as balances and
payment status, ask the assistant to refresh the result before acting on it.
Available actions depend on your role, organization, and the part of the product you are using. The
assistant cannot create agents, create wallets, or generate SDK keys. Use the dashboard for those.
It also cannot make or simulate a card purchase, and no dashboard page does. Your agent's
integration makes purchases with the agent's SDK key. See
[test cards in the SDK guide](https://conto.finance/docs/sdk/connected-cards#test-cards).
## How to use it
1. Sign in to Conto and select the intended organization.
2. Click the sparkle button at the bottom right of any dashboard page, or press Ctrl/⌘J.
3. Ask for one clear outcome and include the relevant agent, wallet, payment, or policy name.
4. Review the confirmation card and confirm only when the details are correct.
Example prompts:
- "Show my agents and their status."
- "Summarize this week's payment activity."
- "Which approvals need attention?"
- "Explain why the last payment from the Research agent was denied."
- "Set a $100 daily spend limit for the Research agent."
Avoid combining unrelated changes in one request. Smaller requests are easier to verify and safer
to retry.
## Permissions and confirmation
The assistant does not bypass Conto permissions or spending controls. Each action requires the same
role as the matching dashboard page, and the role is checked again when you confirm. Some changes
require owner or administrator access. Payment policies can still deny a request or require a
separate approval. The assistant cannot reveal credentials or bypass authentication.
Every change, except Conto Pay draft edits and contact renames or favorites, appears as a
confirmation card and applies only after you confirm it. The card shows a one-line summary and key
details, such as the current and new limit, the policy scope, or the recipient's full wallet
address. If the underlying details change before you confirm, the change is not applied and the
assistant offers to prepare it again. The assistant prepares one change at a time and reports the
actual result after it runs.
Before confirming an action, check:
- the organization and environment
- the affected agent, wallet, policy, or payment
- the recipient, amount, asset, network, and purpose for any financial action
## Financial actions
Payment-related actions use the same policy checks, approval requirements, and execution safeguards
as the corresponding dashboard or API flow.
These confirmations can move funds:
- **Final approval on a managed-wallet payment.** Conto re-checks your policies, then sends the
payment. The confirmation card says so. An approval that is not the last required one only
records your decision.
- **Final approval on an external-wallet payment.** The payment is approved, but Conto does not send
it. The agent sends the transfer and confirms it.
- **Conto Pay send.** Conto Pay checks the payment against wallet policies and sends it when they
pass. It can also end up waiting for approval.
If the result is uncertain because of a timeout or connection problem, ask for the current payment
status before attempting the action again. Starting a second payment can create a duplicate.
## Conto Pay
The assistant finds Conto Pay profiles and saved payees, prepares payments and payment requests,
creates Pay and Request links, and reports a payment's status and next action. Preparing a payment
or request does not send funds.
| Goal | Example prompt |
| --------------------- | ------------------------------------------------------------------------------- |
| Prepare a payment | `Prepare a $25 test payment to Vendor AP for invoice 1042.` |
| Request payment | `Prepare a request for $250 from @buyer-org for the June invoice.` |
| Create a payment link | `Create a Pay link for $75 with the note "Order 2026-0718".` |
| Check a payment | `Show the current status and next action for my most recent Conto Pay payment.` |
| Find an approval | `Which Conto Pay approvals need attention?` |
Give the amount, currency, and purpose, plus an exact handle, payee name, invoice reference, or
payment reference when you have one. This reduces ambiguity and makes the result easier to verify.
Before you confirm a Conto Pay payment:
- Ask which organization, payment mode (**Test payments** or **Live payments**), wallet, network,
and available balance are in use. Use test funds for setup and workflow testing.
- Confirm the payee's display name, exact handle, wallet destination, and network. Use an alias
only after confirming which profile it represents.
- If a saved profile has changed, review the current profile before continuing.
- A handle that is not a saved payee yet must be saved as a contact before the payment can be
authorized.
- Ask for the policy result and next action. Ask to send only after the payment is ready.
Preparing a payment request loads its details for the payer to review. It does not make the
requester an approved payee. Before authorizing payment, check the handle and wallet address, then
select **Save as trusted contact** in the Conto Pay dashboard.
The assistant can create a Pay or Request link with an amount, note, or invoice reference. It
cannot set a link's expiry or cancel a link. Links expire after 30 days. To cancel one, open
**Manage Conto Pay > Links**.
For an approval, use its dashboard link to review the amount, recipient, purpose, and decision
history before acting. For reconciliation, ask for recent completed payments, their receipts, and
the associated request or invoice references. Use **Manage Conto Pay > Insights** for a broader
activity summary or a CSV export, and **Manage Conto Pay** for large comparisons or detailed
configuration.
See [Conto Pay](https://conto.finance/docs/conto-pay/overview) for profiles, links, requests, and moving to live payments.
## Context and memory
The assistant knows which dashboard page you have open, so on an agent's page you can ask about
"this agent" directly. It keeps each conversation, including what it looked up, for 7 days so
follow-up questions have context. Clear starts a new conversation.
## Handling sensitive information
Do not paste private keys, seed phrases, passwords, API keys, or authentication cookies into the
assistant. Refer to resources by their Conto name.
## If a request cannot be completed
The assistant may ask for a missing detail, explain that your role lacks access, or direct you to a
dashboard page.
## When to use another surface
- Use the dashboard directly when you want to compare many records or edit a detailed form.
- Use the [TypeScript SDK](https://conto.finance/docs/sdk/installation) for application integrations.
- Use the [MCP server](https://conto.finance/docs/mcp/install) to connect an MCP-compatible agent with an agent SDK key.
- Use [Conto Pay](https://conto.finance/docs/conto-pay/overview) for the hosted payment workspace.
### Roles and permissions
Link: https://conto.finance/docs/guides/roles-permissions
Understand access levels for dashboard users
### Approvals
Link: https://conto.finance/docs/guides/approval-workflows
Understand payment review and approval states
---
# Go live checklist
Source: https://conto.finance/docs/guides/production-readiness
Your application code can stay the same between sandbox and production. Complete the checks below
before you fund a live wallet.
Conto is a hosted service. If you need a dedicated environment, a particular region, or private
connectivity, contact Conto before implementation.
## 1. Ownership and custody
- [ ] Record the production owner for the Conto organization, each agent, and each funded wallet.
- [ ] Select and document managed, external, or smart-contract custody for every payment path.
- [ ] Confirm who can fund, freeze, and unfreeze each wallet.
- [ ] Start with a deliberately small live balance and transaction limit.
- [ ] Move off the anonymous sandbox to a full account. Sandbox organizations are simulated and expire.
- [ ] Check your plan's agent, monthly transaction, and payment-value limits in **Settings > Plan & Usage**.
## 2. Credentials
- [ ] Create separate sandbox and production keys. Never promote a sandbox secret.
- [ ] Grant only the scopes the runtime needs.
- [ ] Store secrets in a secret manager, not in source files or CI output.
- [ ] Test key expiry, rotation, revocation, and emergency access removal.
- [ ] Log Conto request IDs without logging credentials or full sensitive payloads.
## 3. Policy and approvals
- [ ] Configure per-transaction, daily, and monthly spend limits.
- [ ] Configure counterparty, category, geography, and time-window rules where applicable.
- [ ] Route exceptions to named approvers and test unavailable-approver behavior.
## 4. Verify the integration
[Test allow, review, and deny](https://conto.finance/docs/guides/policy-testing) builds the first four cases.
- [ ] From the buyer runtime, run an allowed payment, an over-budget refusal, a prohibited
recipient, and a request that waits for owner approval.
- [ ] Confirm refused and unresolved requests never sign, submit credentials, or reach payment
execution.
- [ ] Change the transaction after approval and confirm that the executor refuses the old
authorization.
## 5. Safe payment execution
- [ ] Supply and persist a stable idempotency key for authorization and record writes that support one.
- [ ] Retry a mutating request only when its API supports idempotency and you reuse the same key.
- [ ] Never automatically retry payment execution or confirmation. Reconcile the known payment status first.
- [ ] Treat timeouts and network disconnects as ambiguous until payment status is reconciled.
- [ ] Exercise insufficient-funds, rate-limit, policy-denial, and provider-outage paths.
- [ ] Reconcile Conto payment IDs, provider references, and onchain transaction hashes.
## 6. Webhooks and downstream systems
- [ ] Verify signatures against the unmodified request body.
- [ ] Reject stale `X-Conto-Timestamp` values and deduplicate on `X-Conto-Delivery-Id`.
- [ ] Return success only after durable receipt or queueing.
- [ ] Test retries, out-of-order events, and duplicate delivery. Conto stops after three failed attempts, so reconcile missed events through the REST API.
- [ ] Document whether ERP, accounting, and CRM connections use your webhook consumer or the REST API.
Do not assume a native connector.
## 7. Monitoring and incident response
- [ ] Monitor payment failure rate, approval backlog, wallet balance, and webhook lag.
- [ ] Alert on repeated policy denials, credential failures, and reconciliation gaps.
- [ ] Test freezing and unfreezing an agent, and record who may do it.
- [ ] Record primary and backup incident contacts for your team and for Conto support.
- [ ] Rehearse disabling keys, stopping payment execution, and restoring service safely.
## 8. Go-live evidence
- [ ] Capture the approved policy configuration and test results.
- [ ] Capture credential scopes and rotation date without storing secret values.
- [ ] Capture webhook retry and reconciliation test results.
- [ ] Record the initial funding cap and expansion criteria.
- [ ] Obtain explicit authorization from the wallet and business owners to enable live funds.
## Moving to production
Fund the live wallet only after every applicable item has an owner and evidence. Then:
1. Create a production wallet on a [supported network](https://conto.finance/docs/guides/choose-your-integration#networks): Tempo Mainnet
(`USDC.e`), Base (`USDC`), or Solana (`USDC`).
2. Fund it with real stablecoins, starting with the small balance you recorded.
3. Link it to your agent with production limits.
4. Set the agent's environment to `PROD`. Open the agent in **Agents**, go to the **Identity** tab,
choose **Edit Identity**, and set **Environment** to **Production**. From code, send
`PATCH /api/agents/{id}` with `{"environment": "PROD"}` using an organization API key with
`agents:write`. `DEV` and `STAGING` agents can pay only from testnet wallets.
5. Adjust policies for production thresholds. Your testnet wallet and test policies can stay active
alongside production.
6. Expect a production agent's first payment to each new payee to return `REQUIRES_APPROVAL` for
counterparty review.
Expand limits in stages after reviewing real payment, approval, webhook, and reconciliation
behavior.
---
# Conto Pay
Source: https://conto.finance/docs/conto-pay/overview
Conto Pay is a hosted payment workspace. The agent owner or budget holder sets spending policy and
approvers. Conto manages the payment account and checks those rules before sending.
Info:
New workspaces start in test mode. When test flows are complete, an owner or admin requests
production access from the dashboard, and Conto reviews the workspace before enabling live
payments.
### Run your first test payment
Link: https://conto.finance/docs/conto-pay/overview#payment-flow
Open the hosted dashboard and complete a small payment in test mode.
## Hosted profile
Each workspace has a public profile with a stable Conto Pay handle. The profile shows a
counterparty who they are paying and offers Pay and Request actions. You can share the profile URL
or its handle without sharing dashboard credentials.
The public Profile API provides the identity and payment information needed to present the profile
and start a payment safely. Treat that response as public information. Organization settings,
credentials, policy configuration, and operator activity stay in the authenticated dashboard.
Before paying a saved profile, confirm that its current name, handle, and destination match the
details you expect. If Conto reports that a saved profile changed, review the latest profile before
continuing.
## Manage Conto Pay
**Manage Conto Pay**, below the payment form, holds the operational tools. It is collapsed by
default and has these tabs:
- **Activity** shows recent payments and requests with their next action.
- **People** shows known contacts and searches the Conto Pay directory.
- **Links** creates and manages shareable pay or request links.
- **Profile** updates the public account name, description, aliases, and directory visibility.
- **Controls** summarizes the hosted wallet and links to shared wallet, policy, transaction, and
alert pages.
- **Insights** shows recent usage, conversion, and trust reports.
## Connected cards
On the Builder and Enterprise plans, the Conto Pay page lists the organization's
[connected cards](https://conto.finance/docs/guides/connected-cards) below the wallet. Each card shows its brand, last four
digits, and status, the agents it is assigned to with their limits and spend today, and how many
authorizations are active or waiting for verification. The panel also shows how many card purchases
are waiting for review and the most recent card purchases.
The panel is read-only. Connect a card, assign it to an agent, and verify or revoke authorizations
from the agent's **Connected Cards** tab. Review card purchases from **Alerts**. Card purchases use
the card's own limits and approvals and are listed in this panel, not in **Activity**.
`GET /api/conto-pay/cards` returns the same summary for the signed-in organization.
## Payments to any wallet
Conto Pay can prepare a payment to an approved wallet address. When the recipient also uses Conto
Pay, you can resolve the workspace by its handle or profile and save it as a recipient. Both paths
use the payer’s policy and approval controls. A recipient request supplies payment details. It
cannot grant spending authority or override the payer’s budget.
## Payment flow
The flow runs in the hosted dashboard. It needs no SDK, API key, or payment agent. You need:
- access to a Conto organization
- the Owner, Admin, or Manager role (Viewers and Members are view-only)
- a saved recipient, exact Conto Pay handle, or supported wallet address
### Open Conto Pay
Sign in to the dashboard, select the intended organization, and open **Conto Pay** from the
sidebar. If you see **Set up Conto Pay**, an owner or admin selects it once to create the hosted
payment account.
### Check the mode and wallet
Look for the **Test payments** badge beside the payment form. Confirm that the hosted wallet is
available and has the funds the payment needs. Use test funds while in test mode. Do not use
production funds for a test.
### Enter a recipient
In **Recipient**, enter a saved recipient name, an exact handle such as
`@vendor-org/accounts-payable`, or an approved wallet address. The example handle is a
placeholder. Use your own verified test recipient.
Prefer an exact handle or wallet address for a first test. It avoids ambiguity when several
profiles have similar names.
### Enter the amount
Enter the amount, such as `1.00` for a small test, and an optional recognizable note such as
`Conto Pay quickstart test`. Verify the destination before continuing.
### Continue to review
Select **Continue to review**. Conto resolves the recipient, checks the available balance, and
applies the organization's payment controls.
This step may authorize a payment request, but it does not send funds.
### Handle the result
Check the recipient, amount, network, and policy result, then follow the action shown on the
payment:
- **Send payment** means the payment is authorized and ready.
- **Review approval** means a person must approve it before sending. Open it and complete the
decision.
- A funding message means the hosted wallet needs more funds.
- A recipient error means Conto could not safely resolve a unique destination.
### Send and confirm
When **Send payment** is available, select it. Review the recipient and amount in the
confirmation dialog, then select **Confirm send payment** to authorize the transfer.
### Verify the receipt
Wait for the payment to complete, then open **View receipt** or the payment status page. Confirm
that the recipient, amount, note, and final status match what you intended, and keep the receipt
for reconciliation. A **Simulated test** receipt means no funds moved and no transaction hash
exists. A **Testnet** receipt shows the testnet transaction hash.
A test payment is complete when:
- the intended recipient was resolved
- the payment passed or followed the expected approval path
- the final status is complete
- a status page or receipt is available
### Common problems
### The recipient could not be found
Retry with the exact Conto Pay handle or full wallet address. A general name can match multiple
profiles or no saved recipient.
### The payment needs approval
Select **Review approval**, complete the approval in **Alerts & Approvals**, then return to Conto
Pay and refresh the payment status.
### The test wallet needs funds
Open **Wallets** and add the required test funds, or ask an organization administrator for help.
Do not substitute production funds for a test run.
### The payment was denied
Open **Payment details** and review the policy result. Change the recipient or amount only if that
matches the intended test. Otherwise, ask a policy administrator to review the active controls.
### The final status is unclear
Refresh the existing payment before trying again. Creating a second payment can produce a
duplicate if the first payment is still processing.
## Payment and request links
A payment link can prefill a profile, amount, memo, or invoice reference. A request link gives the
payer the same details in a reviewable form. Opening either link does not authorize or execute a
payment. The payer still reviews the request and completes the required controls.
Links expire after 30 days. When sharing a link:
- send it only to the intended counterparty
- ask the recipient to verify the profile and amount
- cancel the link on the **Links** tab if its details are no longer valid
## Requests and receipts
Payment requests have a hosted review page where the payer can inspect the details and decide what
to do. Completed payments have a status page and receipt. The **Activity** tab keeps these records
together for reconciliation.
## Activity analytics
Owners, admins, managers, and viewers signed in to the dashboard can use
`GET /api/conto-pay/analytics` to review Conto Pay activity totals, conversion rates, and stage
counts. Set `period` to `24h`, `7d`, `30d`, or `90d`. Add `format=csv` to download it as CSV.
## Moving from test to live payments
`GET /api/conto-pay/readiness` (dashboard session, not an API key) returns the current workspace's
setup, readiness, payment mode, profile, wallet network, controls, and test-flow counts. Use the
stable `canRequestProductionAccess`, `canPresentLivePayments`, blockers, and next-action fields to
decide whether to keep testing, finish setup, or request production access.
When `canRequestProductionAccess` is `true`, an organization owner or admin can call
`POST /api/conto-pay/production-access`. The response includes the request reference, status,
timestamps, and next action. Continue using test payments until payment availability explicitly
reports **Live payments**.
## Trust report review
Operators with alert access can review trust reports concerning their organization's Conto Pay
profiles. `GET /api/conto-pay/reports` returns 8 reports by default (`limit` up to 25) with status,
severity, reason, profile, checkout reference, timestamps, outcome, and next-step fields. Operators
with alert-management permission can update a report through
`PATCH /api/conto-pay/reports/{reportId}` using a status of `reviewing`, `resolved`, or `closed`.
The optional `hide_profile_from_directory` action removes the reported profile from broad directory
search while leaving an auditable outcome on the report.
### Testing payments safely
Link: https://conto.finance/docs/guides/policy-testing
Test integrations, policy outcomes, and payment edge cases
### Approval workflows
Link: https://conto.finance/docs/guides/approval-workflows
Configure and complete payment approvals
### Conto Pay with the assistant
Link: https://conto.finance/docs/assistant/overview#conto-pay
Prepare, review, and follow Conto Pay tasks with the dashboard assistant
### Conto Pay, assistant, and MCP
Link: https://conto.finance/docs/mcp/install#mcp-vs-dashboard-assistant-vs-conto-pay
Compare who acts, how each surface authenticates, and how it executes payments
---
# REST API
Source: https://conto.finance/docs/api/reference
The REST reference is generated from Conto's OpenAPI spec. Browse it in Swagger UI or download the
JSON for code generation.
| Resource | URL | Best for |
| -------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------- |
| Interactive API docs | [https://conto.finance/api-docs](https://conto.finance/api-docs) | Browsing endpoints and trying requests in Swagger UI |
| OpenAPI JSON | [https://conto.finance/api/openapi](https://conto.finance/api/openapi) | Code generation and schema validation |
`POST /api/sdk/payments/request` accepts `idempotencyKey` for safe client retries. See
[API conventions](https://conto.finance/docs/reference/api-conventions) for idempotency and response envelopes,
[rate limits](https://conto.finance/docs/guides/rate-limits) for SDK and dashboard limits, `429`, and `Retry-After`, and
[webhooks](https://conto.finance/docs/guides/webhooks) for HTTPS endpoint rules and signature checks.
## Authentication
Send the key as `Authorization: Bearer `. Agent-facing `/api/sdk/*` endpoints take an agent SDK
key. Organization endpoints such as `/api/agents`, `/api/wallets`, and `/api/policies` take an
organization API key. See [keys and scopes](https://conto.finance/docs/sdk/authentication) for key types, scopes, and
expiry.
## Base URL
Production API base URL: `https://conto.finance`.
## Generate an HTTP client
Download the OpenAPI document for your client generator or API testing tool:
```bash
curl -fsS https://conto.finance/api/openapi -o conto-openapi.json
```
Check each generated method's authentication and request schema before calling it. Agent SDK keys
and organization API keys have different permissions.
---
# API conventions
Source: https://conto.finance/docs/reference/api-conventions
## Amounts and currencies
| Operation | Amount encoding | Example |
| --------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
| Standard stablecoin payment request | Positive JSON number, at most two decimal places | `10.50` means 10.50 units of the wallet's stablecoin, not 1,050 cents |
| x402 or MPP pre-authorization/recording | Positive, finite JSON number. Sub-cent values supported. Maximum 1,000,000,000 | `0.005` means 0.005 units of the payment currency |
Managed payment requests choose currency from the selected wallet's chain. Use `walletId` to bind
that wallet. The request does not accept a `chainId` or top-level `currency` override.
External approval requires the sender and chain. Never infer the network from an EVM address alone.
Convert protocol base-unit amounts using the token's decimals before passing a human-unit amount
to Conto. When recording protocol payments, use the actual settlement currency and total. Batch
item amounts must match the aggregate settlement. Do not send floating-point calculation artifacts
such as `0.30000000000000004` to an endpoint requiring cent precision.
Managed authorization returns `APPROVED`, `DENIED`, `REQUIRES_APPROVAL`, `PROCESSING`, or
`COMPLETED`. Replaying a completed request with the same idempotency key returns `COMPLETED`.
Use the original `requestId` to check status and reconcile its existing transaction; do not
create a new intent to resume that payment.
## Identifiers and addresses
Treat returned IDs as opaque strings. Retain distinct identifiers for:
| Identifier | Purpose |
| ------------------------------------------ | -------------------------------------------------------------------- |
| Your business action ID | Connect one purchase or job to payment attempts |
| `idempotencyKey` | Deduplicate retries of one managed authorization intent |
| `requestId` | Execute or read the payment request |
| `receipt.transactionId` / `transaction.id` | Reconcile the Conto transaction |
| `txHash` | Inspect the network transaction. Not interchangeable with a Conto ID |
EVM addresses have `0x` followed by 40 hexadecimal characters. Solana uses a base58 public key.
Use the exact address and chain returned by setup. Abbreviated addresses are display values, not
valid request inputs.
## Dates and time windows
API timestamps use ISO 8601 strings. Parse them as instants, including the returned timezone.
Policy time windows use the configured IANA timezone, which can differ from your browser timezone.
Protocol budget summaries use UTC calendar periods. See [time windows](https://conto.finance/docs/policies/time-windows).
## Authentication
Send `Authorization: Bearer ` over HTTPS. Agent calls use `CONTO_API_KEY`. Organization
provisioning uses `CONTO_ORG_API_KEY`. Keys are not interchangeable. See
[authentication](https://conto.finance/docs/sdk/authentication) and the [scope reference](https://conto.finance/docs/sdk/authentication#scopes).
## Response envelopes and pagination
There is no universal `items` envelope. Read the schema of the endpoint you call.
For example, SDK transactions return `transactions` and counterparties return `counterparties`.
Both include a nested `pagination` object:
```json
{
"total": 75,
"limit": 50,
"offset": 0,
"hasMore": true
}
```
Default `limit` is 50 and maximum is 100 on these endpoints. Advance `offset` by the number of
records received and stop when `hasMore` is false. MCP may wrap an API array in
`{ "items": [...] }` for structured output. This does not change the underlying HTTP endpoint's
response schema.
## Idempotency and retries
Managed authorization accepts `idempotencyKey`. Reusing a key with the same payload returns the
same intent. A different payload gives `409 IDEMPOTENCY_CONFLICT`. Persist the key across worker
restarts. The SDK generates a key when omitted, but a new method invocation is a new intent unless
you explicitly reuse your saved key.
Execution is not automatically retried. SDK `payments.approve()` and `payments.confirm()` also
send each external-wallet write only once. Confirmation records a transfer your signer already
sent. It does not send funds or prove settlement. Retain the approval/request ID and transfer hash
for status reconciliation. External confirmation can return a duplicate conflict.
x402 uses `paymentId` and MPP uses `credentialId` for settlement-record identity. These are separate
contracts. Do not assume every write accepts the same idempotency field.
x402 and MPP pre-authorization reserve spend when approved. Retrying with the same `paymentId`
(x402) or `challengeId`/`credentialId` (MPP) returns the same live grant. A retry without one of
those fields issues a second grant and reserves the amount again until the first grant expires.
`purchases.create()` takes an `idempotencyKey` and returns the same purchase when the key and
input repeat. `purchases.respond()` returns the same acknowledgment for the same answer to an
action. The SDK retries both because the server replays them. A purchase never retries a payment
on its own: an uncertain outcome stays in `reconciling` until the merchant's confirmation or a
reviewer settles it.
See [payment recovery](https://conto.finance/docs/guides/payment-lifecycle) before repeating a write and
[SDK error handling](https://conto.finance/docs/sdk/error-handling) for HTTP 402, timeouts, and backoff.
## Diagnostic reads and errors
SDK `setup()` reads the agent, linked wallets, effective policies, and key scopes without creating
a payment. Use `payments.status(requestId)` to inspect an existing request. Recognized validation
failures expose `fieldErrors` with local guidance. Payment errors from `payments.pay()` and
`payments.execute()` expose `reasons`. A declined or held x402 or MPP
pre-authorization returns fixed `reasons` and `reasonCodes`. Arbitrary provider diagnostics and
server instructions are not forwarded.
## Version compatibility
Record the SDK version used by your application and keep its dependency lockfile. Check the
[changelog](https://conto.finance/docs/changelog/overview) and rerun integration tests when upgrading. Use the
[OpenAPI document](https://conto.finance/api/openapi) for HTTP contracts and the installed SDK's
types for client methods.
## Settlement evidence
Payment execute receipts always identify the Conto transaction and settlement mode. `txHash` and `explorerUrl` are absent for a simulation (`settlementMode: "test"`). Status and transaction reads report `unknown` when execution evidence is missing. `live` means onchain, including testnet. Neither a completed simulation nor a legacy hash alone proves settlement. See [payment lifecycle](https://conto.finance/docs/guides/payment-lifecycle).
## Connected-card writes
Connected cards are available on Builder and Enterprise. A draw's `idempotencyKey` identifies one
card credential. Replaying an issued draw returns its status without the one-time secret, and the
SDK does not retry draws automatically. After you receive the credential,
`connectedCards.acknowledgeDelivery(requestId)` is safe to retry and returns
`{ acknowledged: true, settlementVerified: false }`. After checkout,
`connectedCards.reportOutcome(requestId, { outcome })` is safe to retry with the same report. A
different second report returns 409, and a report never releases held spend by itself.
`credentialExpiresAt` comes from the card provider and is separate from the approval window. Merchant fields you send must match the
[mandate](https://conto.finance/docs/guides/connected-cards), the verified purchase authorization. Omitted fields default to
the stored merchant.
## Stripe Link card references
`checkout.getLinkCard()` keys one purchase on its `idempotencyKey`. Retrying with the same input
resumes the same checkpoint and the same Link request, so the person is not asked twice. Keep the
Link request ID (`lsrq_...`) as the reference for that purchase, and pass it to confirm and outcome
as `paymentReference`. A checkpoint keeps the first payment reference it receives and returns 409
for a different one. See [Stripe Link cards for agents](https://conto.finance/docs/guides/stripe-link-cards).
---
# Defaults
Source: https://conto.finance/docs/reference/defaults
## Chains and currencies
Wallet creation and the CLI default to Tempo Testnet. Payment requests use the selected wallet’s network and have no testnet default of their own. Mainnet setup is an explicit choice in the CLI.
| Setting | Default | Notes |
| ----------------------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Default chain ID (wallet creation) | `42431` (Tempo Testnet) | Applied when `chainId` is omitted from `POST /api/wallets`. For payments, select the intended wallet with `walletId`. |
| Default chain ID (CLI) | `42431` (Tempo Testnet) | Used by `create-conto-agent`. Override with `--chain`. Mainnet also requires confirmation. |
| Default chain type | `EVM` | Applied to new wallets when omitted. |
| Default Tempo Mainnet currency | `USDC.e` | Returned in payment responses for chain `4217`. |
| Default Tempo Testnet currency | `pathUSD` (TIP-20) | Returned in payment responses for chain `42431`. |
| Default Base/Ethereum/Arbitrum/Polygon currency | `USDC` | The chain's primary stablecoin. |
| Default Solana currency | `USDC` (SPL) | Resolved by chain ID `solana-mainnet`. |
For a headless mainnet setup, specify both the chain and acknowledgement, for example `--chain 4217 --confirm-mainnet`. Interactive mainnet setup displays a real-funds confirmation before creating any resources.
### Explorer URLs
Each Tempo network has its own canonical explorer.
| Chain ID | Name | Explorer |
| -------- | ------------- | ----------------------------------- |
| `4217` | Tempo Mainnet | `https://explore.tempo.xyz` |
| `42431` | Tempo Testnet | `https://explore.testnet.tempo.xyz` |
EVM chains use the standard block explorers (`basescan.org`, `etherscan.io`, etc.) and Solana uses `solscan.io` / `explorer.solana.com`.
## Wallets
`POST /api/wallets` uses these defaults when a field is omitted:
| Field | Default |
| -------------- | ----------------------------------------------------------------------------- |
| `walletType` | `EOA` |
| `chainType` | `EVM` |
| `chainId` | `42431` (Tempo Testnet) |
| `custodyMode` | `MANAGED` |
| `controlModel` | Organization default. `CONTO_MANAGED` until an owner public key is configured |
### Custody priority
The payment evaluator selects wallets in this order when an agent has multiple linked wallets:
`MANAGED > SMART_CONTRACT > EXTERNAL`
## Agent-wallet links
`POST /api/agents/{id}/wallets` applies these defaults. Linking a mainnet wallet to a `DEV` or
`STAGING` agent succeeds but returns a `warnings` entry, because only `PROD` agents can pay from
mainnet wallets.
| Field | Default | Notes |
| ------------------- | ---------- | -------------------------------------------------------------- |
| `delegationType` | `LIMITED` | Other values: `FULL`, `VIEW_ONLY`, `PREAPPROVED`, `ALLOWLIST`. |
| `spendLimitPerTx` | `100` | USD. `null` means unlimited. |
| `spendLimitDaily` | `1000` | `null` means unlimited. |
| `spendLimitWeekly` | `null` | Unlimited unless set. |
| `spendLimitMonthly` | `null` | Unlimited unless set. |
| `allowedHoursStart` | `0` | Hour of day, 0-23. |
| `allowedHoursEnd` | `24` | Hour of day, 1-24. |
| `allowedDays` | All 7 days | Pass a shorter list, e.g. `['Mon','Tue','Wed','Thu','Fri']`. |
| `timezone` | `UTC` | IANA names accepted (`America/New_York`, etc.). |
For wallet-level spend limits, `null` means no cap and `0` blocks all payments through the link.
To remove a cap, set the field to `null` with `PATCH /api/agents/{agentId}/wallets/{walletId}`.
## External-wallet auto-create
When `POST /api/sdk/payments/approve` is called with a `senderAddress` that does not exist in the organization, Conto creates an EXTERNAL wallet automatically with:
| Field | Default |
| ------------------- | ----------------- |
| `spendLimitPerTx` | `100` USD |
| `spendLimitDaily` | `1000` USD |
| `spendLimitWeekly` | `5000` USD |
| `spendLimitMonthly` | `20000` USD |
| `delegationType` | `FULL` |
| `custodyType` | `EXTERNAL` |
| `enforcementMode` | `MONITORING_ONLY` |
`delegationType` is a label on the agent-wallet link. It does not loosen the spend limits or
policies. `enforcementMode: MONITORING_ONLY` means Conto does not hold this wallet's keys. It checks
payments sent through `approve` and cannot block a transfer the wallet signs outside Conto. See
[wallet custody](https://conto.finance/docs/guides/custody-modes).
Tighten these via `PATCH /api/agents/{agentId}/wallets/{walletId}` before relying on them in production.
## Approval tokens
| Endpoint | Token TTL |
| --------------------------------------------------- | ---------- |
| `POST /api/sdk/payments/request` | 5 minutes |
| `POST /api/sdk/payments/approve` (external wallets) | 10 minutes |
Tokens are single-use. After expiry, request approval again.
## SDK keys
| Setting | Default | Notes |
| --------------------- | ----------------- | ------------------------------------------------------------------ |
| `expiresInDays` | `365` | Optional. Defaults to 365 when omitted. |
| Max `expiresInDays` | `730` | 2-year hard cap. |
| Key format (standard) | `conto_agent_...` | Per-agent. Used for payment operations. |
| Key format (admin) | `conto_agent_...` | Per-agent. Adds write scopes. See [Credential scopes](https://conto.finance/docs/sdk/authentication#scopes). |
| Org API key format | `conto_...` | Org-level. Used with `ContoAdmin`. |
Full keys are returned only once at creation. Store them immediately.
## Payment requests
| Setting | Default |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `currency` (on `POST /api/transactions`) | `USDC` |
| `urgency` | `NORMAL` |
| `autoExecute` | `false` |
| Idempotency window | Indefinite. Same `idempotencyKey` + same payload returns the cached result. Different payload returns `409 IDEMPOTENCY_CONFLICT`. |
## Policy evaluation
| Setting | Default | Notes |
| ------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------- |
| Policy priority direction | Higher first | Policies are evaluated in descending priority. |
| Default policy priority | `50` | Use `0-100`. Reserve 90+ for security/compliance rules you want evaluated first. |
| API scope when omitted | Inferred | `assigned` with `agentIds`, otherwise `organization`. |
| Rule combination | `AND` | All rules in a policy must pass. |
| Policy combination | `AND` | Organization baselines and all effective assignments must pass. First `DENY` stops evaluation. |
| Trust score scale | Decimal `0.0-1.0` | `TRUST_SCORE` compares this against the network score. An address with no network history scores `0.5`. |
## Counterparties
| Field | Default |
| ------------ | --------- |
| `trustScore` | `0.5` |
| `trustLevel` | `UNKNOWN` |
| `riskLevel` | `MEDIUM` |
| `verified` | `false` |
| `type` | `VENDOR` |
A new counterparty record starts at `0.5` with level `UNKNOWN`. Conto sets the level from the ranges below each time it rescores the counterparty, for example after a confirmed payment. `TRUSTED` also requires a verified counterparty, otherwise the level stays `VERIFIED`. Below 0.2 the level is `BLOCKED` only when there are adverse signals such as failed or flagged transactions, otherwise `UNKNOWN`.
| Range | Level |
| ------------ | ---------- |
| `0.75 - 1.0` | `TRUSTED` |
| `0.5 - 0.75` | `VERIFIED` |
| `0.2 - 0.5` | `UNKNOWN` |
| `0.0 - 0.2` | `BLOCKED` |
## Rate limits
See [Rate limits](https://conto.finance/docs/guides/rate-limits).
## Pagination
| Field | Default |
| ----------- | ------- |
| `limit` | `50` |
| Max `limit` | `100` |
| `offset` | `0` |
Response envelopes vary by endpoint. See [API conventions](https://conto.finance/docs/reference/api-conventions#response-envelopes-and-pagination).
## Webhook delivery
| Setting | Default |
| ---------------- | -------------------------------------------------- |
| Max attempts | `3` (network errors, `408`, `429`, and `5xx` only) |
| Backoff | Immediate first attempt, retries after 1s, then 2s |
| Timeout | 10 seconds |
| Signature header | `X-Conto-Signature` (HMAC-SHA256) |
| Required scheme | HTTPS (HTTP and private IPs blocked) |
## Auto-freeze thresholds
Automatic protection is off for new agents (`enabled: false`). These are the default thresholds:
| Setting | Default |
| -------------------------------- | ---------------------------------- |
| `maxConsecutivePolicyViolations` | `5` |
| `maxConsecutivePaymentFailures` | `5` |
| `spendVelocityMultiplier` | `3` (3x the agent's normal rate) |
| `largePaymentMultiplier` | `10` (10x the agent's normal size) |
| `minimumTrustScore` | `0.2` |
| `trustScoreDropThreshold` | `0.3` (a 30% drop) |
| `maxNewCounterpartiesPerHour` | `10` |
Tunable via `PATCH /api/agents/{id}/freeze-config`. See [Automatic agent protection](https://conto.finance/docs/guides/auto-freeze).
## Org roles
`OrgRole` enum: `OWNER`, `ADMIN`, `MANAGER`, `MEMBER`, `VIEWER`. New members default to `MEMBER`.
See [Roles and permissions](https://conto.finance/docs/guides/roles-permissions) for the permission matrix.
---
# Error handling
Source: https://conto.finance/docs/sdk/error-handling
## ContoError
SDK failures throw `ContoError`. `ContoAdmin` throws `ContoAdminError`, which has the same `code`, `status`, `requestId`, `retryAfter`, and `retryGuidance` fields:
```typescript
class ContoError extends Error {
readonly code: string;
readonly status: number;
readonly requestId?: string;
readonly retryAfter?: number;
readonly retryGuidance?: string;
readonly actionUrl?: string;
readonly fieldErrors?: Array<{ field: string; message: string }>;
readonly reasons?: string[];
}
```
`message` is generic text for the HTTP status, not the server's error text. Branch on `code`, show `reasons` to the user, and log `requestId`. For validation failures, `fieldErrors` names the fields to check. When a response carries no code, `code` falls back to a status value: `INVALID_REQUEST`, `AUTH_FAILED`, `FORBIDDEN`, `NOT_FOUND`, `CONFLICT`, `RATE_LIMITED` or `SERVER_ERROR`.
## Error codes
### Authentication errors
| Code | Status | Description | Solution |
| -------------------- | ------ | ------------------------------------------------------- | ------------------------------- |
| `AUTH_FAILED` | 401 | Missing, invalid, inactive, revoked, or expired API key | Check key is correct and active |
| `INSUFFICIENT_SCOPE` | 403 | Key lacks required permission | Use key with required scope |
### Payment errors
| Code | Status | Description | Solution |
| ---------------------------- | ---------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------- |
| `PAYMENT_DENIED` | 403 | `pay()` saw a denied request, `execute()` hit a declined payment, or `request()` exceeded an approved budget | Report `reasons`. Do not retry |
| `POLICY_DENIED_AT_EXECUTION` | 403 | Payment controls declined the payment when it was executed | Report `reasons`. Do not retry. No funds were moved |
| `POLICY_REAPPROVAL_REQUIRED` | 403 | Payment controls now require approval, so this request is declined | Request a new payment and wait for approval |
| `REQUIRES_APPROVAL` | 202 | `pay()` saw an approval-required request | Approval in Conto executes it. Read the receipt afterwards |
| `APPROVAL_PENDING` | 409 | `execute()` was called while the payment waits for approval | Wait. Approval in Conto executes the payment |
| `PAYMENT_PROCESSING` | 409 (202 from `pay()`) | The payment is already executing | Check `payments.status()`. Do not execute again |
| `IDEMPOTENCY_CONFLICT` | 409 | The `idempotencyKey` was already used with different fields | Use a new key for a different payment |
| `INSUFFICIENT_BALANCE` | 400 | Wallet has insufficient funds | Fund the wallet |
| `EXPIRED` | 400 | Payment request expired | Request new approval |
| `NOT_FOUND` | 404 | Request ID not found | Check the request ID |
| `INVALID_STATUS` | 400 | Cannot execute in current status | Check payment status |
| `NO_WALLET` | 400 | No wallet assigned | Link a wallet to the agent |
| `LIMIT_EXCEEDED` | 400 | A spend limit re-check failed at execution time | Re-request after reducing amount or freeing budget |
| `BUDGET_EXCEEDED` | 400 | The approved budget cannot cover the payment at execution. `BUDGET_EXPIRED`, `BUDGET_INACTIVE`, `BUDGET_CURRENCY_MISMATCH` and `BUDGET_INVALID_AMOUNT` mean the same | Request a new budget. No funds were moved |
| `MAX_RETRIES_EXCEEDED` | 400 | Execution reached its retry limit | Check status. Request a new payment if still needed |
| `MANUAL_EXECUTION_REQUIRED` | 400 | External or smart contract wallets cannot be auto-executed | Use the approve and confirm flow instead |
| `WALLET_NETWORK_MISMATCH` | 409 | The wallet network does not match this environment | Use a wallet on the right network. No funds were moved |
| `NEEDS_RECONCILIATION` | 409 | The transfer outcome is unknown and is under review | Do not execute again. Check status later |
| `ALREADY_EXECUTED` | 409 | This payment request was already executed | Check transaction status instead |
| `EXECUTION_UNAVAILABLE` | 503 | Execution or the payment controls check is temporarily unavailable | Retry later. The payment is still approved |
| `LIVE_PAYMENTS_PAUSED` | 503 | Mainnet payments are disabled by the live-payment controls | Ask the owner to check payment availability. Do not switch to another rail to bypass the refusal |
| `LIVE_PAYMENT_CAP_MISCONFIGURED` | 503 | The configured mainnet payment cap cannot be applied | Ask the owner to correct the cap. Retry after it is fixed |
| `LIVE_PAYMENT_CAP_EXCEEDED` | 403 | This mainnet payment exceeds the configured cap | Ask the owner to review the cap or request a smaller payment |
| `LIVE_PAYMENT_CAP_UNSUPPORTED_ASSET` | 403 | The asset cannot be compared with the USD cap | Use a supported asset or ask the owner to review the control |
| `RECEIPT_UNAVAILABLE` | 503 | Execution finished but the receipt could not be returned | Check `payments.status()`. Do not execute again |
| `EXECUTION_FAILED` | 500 | Execution failed for a server or provider reason | Check `payments.status()` before trying again |
A repeated `execute()` never sends a payment twice. If an approver executed the payment by
approving it in Conto, or an earlier call succeeded, `execute()` returns the existing receipt with
`alreadyExecuted: true`. While another execution is in flight it returns `409 PAYMENT_PROCESSING`.
After a timeout, still read the original request before deciding on another write.
### External wallet errors
`payments.approve()` and `payments.confirm()` add these codes.
| Code | Status | Description | Solution |
| --------------------------------- | -------- | ------------------------------------------------------------ | ------------------------------------------------ |
| `INVALID_TOKEN` | 400, 403 | The approval token is missing (400) or does not match (403) | Send the `approvalToken` from the approval |
| `PAYMENT_NOT_CONFIRMABLE` | 400 | The payment is not in an approved state | Read `status` in the response, then check status |
| `ALREADY_CONFIRMED` | 409 | A transaction hash was already confirmed for this payment | Use the returned `transactionId` and `txHash` |
| `UNAUTHORIZED_WALLET` | 403 | The sender wallet belongs to another organization | Use a wallet registered in your organization |
| `WALLET_LIMIT_REACHED` | 400 | Registering the sender wallet would exceed the plan limit | Remove an unused wallet or change plan |
| `POLICY_REVALIDATION_UNAVAILABLE` | 503 | Payment controls could not be checked. No approval was issued | Retry the approval |
### Payment reasons
`reasons` on a payment, an approval, or a payment error uses this fixed set. External-wallet
approvals (`/api/sdk/payments/approve`) also return the matching `reasonCodes`.
| Reason | `reasonCodes` value |
| -------------------------------------------------------------------------------------- | ----------------------- |
| The available balance is insufficient for this payment. | `INSUFFICIENT_BALANCE` |
| This payment exceeds a configured spending limit. | `SPENDING_LIMIT` |
| This recipient is not allowed by the current payment controls. | `RECIPIENT_NOT_ALLOWED` |
| This payment is outside the permitted schedule. | `OUTSIDE_SCHEDULE` |
| The available payment budget is insufficient. | `BUDGET_INSUFFICIENT` |
| No usable wallet is linked to this agent. | `NO_WALLET` |
| This agent's environment is DEV. Set it to PROD to pay from a mainnet wallet. | `NO_WALLET` |
| This payee needs review before payments to it can proceed automatically. | `PAYEE_REVIEW_REQUIRED` |
| This payment requires approval before it can proceed. | `APPROVAL_REQUIRED` |
| A reviewer declined this payment. | Status and execute only |
| This payment does not meet the current payment controls. | `PAYMENT_CONTROLS` |
| This payment is missing a category, which a payment control requires. | `PAYMENT_CONTROLS` |
| This payment is missing the recipient country, which a payment control requires. | `PAYMENT_CONTROLS` |
| This payment is missing the target contract address, which a payment control requires. | `PAYMENT_CONTROLS` |
| This payment is missing the service URL, which a payment control requires. | `PAYMENT_CONTROLS` |
| This payment is missing a session ID, which a payment control requires. | `PAYMENT_CONTROLS` |
| This payment is missing the x402 facilitator, which a payment control requires. | `PAYMENT_CONTROLS` |
| This payment is missing the MPP intent, which a payment control requires. | `PAYMENT_CONTROLS` |
| This payment is missing the MPP payment method, which a payment control requires. | `PAYMENT_CONTROLS` |
Reasons never include rule values, policy names, or text the agent supplied. Your organization's
dashboard shows the full evaluation.
### Validation errors
| Code | Status | Description | Solution |
| ------------------ | --------------------------------- | -------------------------------------- | ------------------------------------------------------------------------- |
| `VALIDATION_ERROR` | 400 | Invalid or malformed request body | Check `fieldErrors` against the request schema |
| `INVALID_AMOUNT` | 0 from `request()`, 400 from `execute()` | Amount must be positive | `request()` throws this before sending. Use a positive number |
| `INVALID_JSON` | 400 | Malformed body on `payments.approve()` | Send valid JSON. `payments.request()` returns `VALIDATION_ERROR` instead |
### System errors
| Code | Status | Description | Solution |
| --------------------------------- | ------ | ---------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `RATE_LIMITED` | 429 | Too many requests | Wait `retryAfter` seconds and retry |
| `SERVICE_UNAVAILABLE` | 503 | The rate limiter could not be checked, so the payment call was refused | Retry after a short wait |
| `PAYMENTS_PAUSED` | 503 | Conto has paused new payment actions. No new spend is reserved or signed | Stop checkout and ask the owner to check payment availability. Read the existing payment or request before retrying with the same idempotency key |
| `TIMEOUT` | 0 | The client timed out before a response arrived | Retry `request()` with the same key. After `execute()`, check status first |
| `NETWORK_ERROR` | 0 | The request did not complete | Same as `TIMEOUT` |
| `COUNTERPARTY_REVIEW_UNAVAILABLE` | 503 | Counterparty review could not run for a production agent | Retry `request()` with the same key |
| `INTERNAL_ERROR` | 500 | Server error | Retry or contact support |
Status `0` means the SDK raised the error before or without an HTTP response.
## Handle errors
```typescript
import { Conto, ContoError } from '@conto_finance/sdk';
const conto = new Conto({ apiKey: process.env.CONTO_API_KEY! });
try {
await conto.payments.pay({
amount: 100,
recipientAddress: '0x0000000000000000000000000000000000000001',
purpose: 'API credits',
});
} catch (error) {
if (!(error instanceof ContoError)) throw error;
switch (error.code) {
case 'PAYMENT_DENIED':
case 'POLICY_DENIED_AT_EXECUTION':
// Final for this request. Show error.reasons and do not retry.
console.log('Declined:', error.reasons);
break;
case 'REQUIRES_APPROVAL':
// Approval in Conto executes the payment. Keep error.requestId and
// read payments.status() later for the receipt.
console.log('Awaiting approval:', error.requestId);
break;
case 'INSUFFICIENT_BALANCE':
console.log('Wallet needs funding');
break;
case 'LIMIT_EXCEEDED':
// A limit changed before execution. Request a smaller payment.
break;
case 'RATE_LIMITED':
// The SDK already retried the request step. It never retries execute().
console.log('Retry after seconds:', error.retryAfter);
break;
default:
// Log the stable fields only. Never log request bodies or credentials.
console.error('Payment failed', {
code: error.code,
status: error.status,
requestId: error.requestId,
});
}
}
```
## Decision and error boundaries
`payments.request()` returns policy outcomes as a `status` (`APPROVED`, `DENIED`,
`REQUIRES_APPROVAL` or `PROCESSING`), not as errors. It throws `ContoError` only for transport,
authentication, validation, rate limit and idempotency failures, `403 PAYMENT_DENIED` when an
approved [budget](https://conto.finance/docs/sdk/budget-requests) cannot cover the payment, and
`503 COUNTERPARTY_REVIEW_UNAVAILABLE`. `pay()` turns `DENIED` and `REQUIRES_APPROVAL` into errors.
`execute()` throws on every execution failure. Use separate `request()` and `execute()` calls to
keep the `requestId` and handle each status. See
[Track and recover a payment](https://conto.finance/docs/guides/payment-lifecycle).
## Handle timeouts
A `TIMEOUT` from `request()` with a stable `idempotencyKey` is safe to retry. A `TIMEOUT` from
`execute()` means the payment may have been sent. Read its status before any other write:
```typescript
const conto = new Conto({
apiKey: process.env.CONTO_API_KEY!,
timeout: 60000, // 60 seconds
});
// A stable key protects request creation across retries and process restarts.
const request = await conto.payments.request({
amount: 100,
recipientAddress: '0x0000000000000000000000000000000000000001',
idempotencyKey: 'job-123-payment-1',
});
if (request.status === 'APPROVED') {
try {
await conto.payments.execute(request.requestId);
} catch (error) {
if (!(error instanceof ContoError) || error.code !== 'TIMEOUT') throw error;
// Do not request a new payment. Check what happened to this one.
const status = await conto.payments.status(request.requestId);
if (status.transaction) {
console.log('Payment was submitted:', status.transaction.txHash);
}
}
}
```
Never wrap `pay()` in a retry loop. Each `pay()` call without an `idempotencyKey` creates a new
payment request, so a retry after a lost response can pay twice. Retry only `request()`, with a
stable key you store with your job.
## Retry strategy
The SDK retries transient failures only when repeating the request cannot create an additional
side effect.
- Retries read-only requests up to **3 times** on network failures, `429`, and `5xx`
- Retries payment authorization requests with the same stable `idempotencyKey`
- Retries x402 and MPP pre-authorization. An approved pre-authorization issues a grant and
reserves spend, so pass a stable `paymentId` (x402) or `challengeId` or `credentialId` (MPP). A
retry with the same value returns the same live grant instead of reserving again
- Respects `Retry-After` headers from the server, up to 30 seconds
- Exponential backoff: 1s, 2s, 4s (capped at 10s)
- **Never automatically retries** payment execution, protocol record calls, or
`ContoAdmin` mutations
- Does not retry client errors (`4xx` except `429`) or auth failures
If a call is still rate limited after these retries, `ContoError.retryAfter` may carry a
server-supplied delay in seconds. Otherwise, back off on your own schedule.
---
# Rate limits
Source: https://conto.finance/docs/guides/rate-limits
API endpoints are rate-limited with fixed windows.
## Limits by endpoint
| Endpoint Category | Limit | Window | Key |
| ----------------------------------------------------------------------- | ------------ | --------- | ----------- |
| SDK payment endpoints (`/request`, `/execute`, `/approve`, `/confirm`) | 60 requests | 1 minute | Per agent |
| SDK read endpoints (`/wallets`, `/transactions`, `/policies`, `/setup`) | 120 requests | 1 minute | Per agent |
| Auth endpoints (login, register) | 10 requests | 5 minutes | Per IP |
| Auth endpoints, per account (applies with the per-IP limit) | 5 requests | 5 minutes | Per account |
| Dashboard and organization API endpoints | 100 requests | 1 minute | Per IP |
## Response headers
Rate-limited responses, and SDK responses from endpoints that report usage, include these headers:
| Header | Description |
| ----------------------- | ------------------------------------------------ |
| `X-RateLimit-Remaining` | Requests remaining in the current window |
| `X-RateLimit-Reset` | ISO 8601 timestamp when the window resets |
| `Retry-After` | Seconds to wait (only present when rate limited) |
## Rate limited response
A rate-limited request returns HTTP `429`:
```json
{
"error": "Rate limit exceeded. Please slow down your requests.",
"code": "RATE_LIMITED",
"retryAfter": 12
}
```
## Limiter unavailable
If the rate-limit store is unavailable, payment and write requests fail closed with HTTP `503` and
code `SERVICE_UNAVAILABLE`. Some read endpoints fall back to a smaller in-memory limit instead. A
`503` here does not mean you exceeded a quota. Retry after a short delay.
## Retry strategy
The SDK retries `429` and `5xx` responses only for read-only calls and writes protected by stable
idempotency. It never retries payment execution, protocol records, or admin mutations. Reconcile
their status before trying them again. See [retry strategy](https://conto.finance/docs/sdk/error-handling#retry-strategy) for
the SDK's retry limits and backoff.
For manual retries, wrap only read-only or explicitly idempotent operations. Never use a generic
retry wrapper around payment execution or another non-idempotent write.
```typescript
async function retryOnRateLimit(fn: () => Promise, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fn();
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '5', 10);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
continue;
}
return response;
}
throw new Error('Rate limited after max retries');
}
```
---
# Admin SDK
Source: https://conto.finance/docs/sdk/admin
Use `ContoAdmin` for organization-level automation: provisioning agents, attaching wallets, assigning
policies, and creating agent SDK keys. Use the `Conto` client for agent runtime payment calls.
`ContoAdmin` takes an organization API key (`conto_...`), never an agent SDK key. See
[authentication](https://conto.finance/docs/sdk/authentication) for the key types.
## Initialize
```typescript
import { ContoAdmin } from '@conto_finance/sdk';
const admin = new ContoAdmin({
orgApiKey: process.env.CONTO_ORG_API_KEY!,
});
```
Create and rotate organization API keys in **Settings > API Keys**. Store them in a secret manager
and use the smallest scope preset that can do the job.
## Provision an agent
```typescript
import { ContoAdmin } from '@conto_finance/sdk';
const admin = new ContoAdmin({
orgApiKey: process.env.CONTO_ORG_API_KEY!,
});
const { members } = await admin.team.listMembers();
const owner = members.find((member) => member.user?.email === 'ops@example.com');
const wallet = await admin.wallets.create({
name: 'ops-wallet',
chainType: 'EVM',
custodyMode: 'MANAGED',
controlModel: 'CONTO_MANAGED',
});
await admin.wallets.provision(wallet.id);
const agent = await admin.agents.create({
name: 'ops-agent',
agentType: 'CUSTOM',
externalId: 'erp-agent-42',
ownerMembershipId: owner?.id,
allowedContexts: ['supplier-payments'],
callbackUrl: 'https://agent.example.com/webhooks/conto',
});
await admin.agents.linkWallet(agent.id, {
walletId: wallet.id,
delegationType: 'LIMITED',
spendLimitPerTx: 100,
spendLimitDaily: 500,
});
// scope 'assigned' with agentIds limits the policy to this agent.
// Without them, the policy applies to every agent in the organization.
const policy = await admin.policies.create({
name: 'Daily $500 limit',
policyType: 'SPEND_LIMIT',
scope: 'assigned',
agentIds: [agent.id],
rules: [{ ruleType: 'DAILY_LIMIT', operator: 'LTE', value: '500' }],
});
const { key } = await admin.agents.createSdkKey(agent.id, {
name: 'Production',
keyType: 'standard',
expiresInDays: 90,
});
```
The returned SDK key is shown once. Pass it to the agent runtime as `CONTO_API_KEY`.
The example uses `CONTO_MANAGED`. `ORGANIZATION_CONTROLLED` wallets are EVM only and need the
owner's public authorization key registered in **Settings > Wallet Control** first. Use
`CONTO_MANAGED` when the owner does not need to export the key. If `keyAccess` is
`EXPORT_RECONCILIATION_REQUIRED`, wait before attempting another export.
`agents.list({ search })` also matches `externalId`. Callback query and fragment values are
redacted in responses, so send `callbackUrl` again when rotating a callback token. Each agent's
`setup` field reports `READY` or `ACTION_REQUIRED` and names the field to update.
## Method map
| Area | Methods |
| ------------------ | ------------------------------------------------------------------------------------------------------ |
| Agents | `agents.list`, `agents.create`, `agents.get`, `agents.update`, `agents.delete` |
| Agent state | `agents.freeze`, `agents.unfreeze` |
| Agent links | `agents.linkWallet`, `agents.unlinkWallet`, `agents.listWallets`, `agents.assignPolicy`, `agents.unassignPolicy`, `agents.listPolicies` |
| SDK keys | `agents.createSdkKey`, `agents.listSdkKeys`, `agents.revokeSdkKey` |
| Team | `team.listMembers` |
| Wallets | `wallets.list`, `wallets.create`, `wallets.get`, `wallets.update`, `wallets.delete` |
| Wallet chain state | `wallets.provision`, `wallets.refreshBalance` |
| Policies | `policies.list`, `policies.create`, `policies.get`, `policies.update`, `policies.delete` |
| Policy rules | `policies.listRules`, `policies.addRule`, `policies.addRules`, `policies.updateRule`, `policies.deleteRule`, `policies.deleteAllRules` |
| Policy versions | `policies.listVersions`, `policies.getVersion`, `policies.compareVersions`, `policies.rollbackVersion` |
## Policy version history
Every change to a policy is versioned. Review the history, diff two versions, and roll back.
```typescript
const { versions } = await admin.policies.listVersions(policy.id);
const latest = versions[0].version;
// See what changed between two versions.
const { diff } = await admin.policies.compareVersions(policy.id, latest - 1, latest);
// Restore an earlier version. If your organization requires governance approval
// for policy changes, the response indicates the rollback is pending approval
// instead of applying it.
const result = await admin.policies.rollbackVersion(policy.id, latest - 1, {
note: 'revert the vendor cap change',
});
```
## Scopes
Read methods need the matching `*:read` scope. Create, update, delete, link, assign, provision,
freeze, and revoke methods need the matching `*:write` or `admin` scope.
## Security notes
- Organization keys can manage all agents and wallets in the organization.
- Prefer scoped keys for CI and provisioning jobs.
- Rotate keys regularly and revoke unused keys.
- Only owners change billing plans, in the dashboard. Organization API keys cannot change them.
---
# MCP tools
Source: https://conto.finance/docs/mcp/tools
Info:
Conto API requests time out after **30 seconds**. The optional guarded-browser runtime times out
after **60 seconds**. Your MCP client receives each tool's full parameter schema from the server.
## Payments (6)
| Tool | Description |
| -------------------------- | ------------------------------------------------------------ |
| `pay` | Request and execute a payment in one step |
| `request_payment` | Request payment authorization (check policies first) |
| `execute_payment` | Execute a previously approved payment |
| `check_payment_status` | Check status of a payment request |
| `approve_external_payment` | Request approval for an external (agent-held) wallet payment |
| `confirm_external_payment` | Confirm an externally executed payment with its tx hash |
Managed wallets use request, then execute. External wallets use approve, your own transfer, then
confirm. See [wallet custody](https://conto.finance/docs/guides/custody-modes).
### pay
Request and execute a payment in one step. Combines `request_payment` and `execute_payment`. Fails
if the payment is denied or requires manual approval. Replaying a completed payment returns
`status: completed` and `alreadyExecuted: true` without calling execution again. Use
`check_payment_status` with the returned `requestId` to retrieve its existing transaction.
| Parameter | Type | Required | Description |
| ------------------ | ------ | -------- | ------------------------------------------------------------------ |
| `amount` | number | Yes | Amount in USDC |
| `recipientAddress` | string | Yes | Recipient wallet address |
| `recipientName` | string | No | Human-readable recipient name |
| `purpose` | string | No | Why this payment is needed |
| `category` | string | No | Spend category for analytics |
| `idempotencyKey` | string | Yes | Stable unique key. Reuse the same value when retrying this payment |
### request_payment
Authorization returns `APPROVED`, `DENIED`, `REQUIRES_APPROVAL`, `PROCESSING`, or `COMPLETED`.
Execute only an approved request. Use `check_payment_status` for a request that is awaiting review,
processing, or already completed.
**Parameters:** Same as `pay`.
**Returns:** `requestId`, `status`, `amount`, `currency`, `recipient`, `reasons`, and, when
approved, `expiresAt` and `actionUrl`.
### execute_payment
Execute a previously approved payment request.
Execution is not automatically retried. After a timeout or ambiguous failure, call
`check_payment_status` with the same `requestId` before deciding whether another execution attempt
is safe.
If an approver already executed the payment by approving it in Conto, `execute_payment` returns the
existing receipt with `alreadyExecuted: true` and sends nothing. Before approval it fails with
`APPROVAL_PENDING`. A payment declined at execution fails with `POLICY_DENIED_AT_EXECUTION` and must
not be retried. `POLICY_REAPPROVAL_REQUIRED` means the payment needs a new request and approval.
`check_payment_status` returns the `reasons`.
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------------------ |
| `requestId` | string | Yes | The requestId from `request_payment` |
### approve_external_payment
Request approval for a payment from an external (agent-held) wallet.
| Parameter | Type | Required | Description |
| ------------------ | ------ | -------- | ------------------------------------------------------- |
| `amount` | number | Yes | Amount in USDC |
| `recipientAddress` | string | Yes | Recipient wallet address |
| `senderAddress` | string | Yes | Sender wallet address (agent-held) |
| `chainId` | string | Yes | Chain ID as a string (e.g. `"42431"` for Tempo Testnet) |
| `recipientName` | string | No | Recipient name |
| `purpose` | string | No | Payment purpose |
| `category` | string | No | Spend category |
| `context` | object | No | Additional context metadata |
### confirm_external_payment
Confirm an externally executed payment was completed onchain.
| Parameter | Type | Required | Description |
| --------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------- |
| `requestId` | string | Yes | The `approvalId` from `approve_external_payment` |
| `txHash` | string | Yes | Onchain transaction hash |
| `approvalToken` | string | No | Approval token from `approve_external_payment`. Omit only after a human approved the request in Conto |
`approvalId` is the payment request ID, so pass it as `requestId`. `approvalRequestId`, returned
when the payment needs review, is a different ID for the human review. Do not pass it here.
Repeated confirmation does not duplicate spend but can return `409 ALREADY_CONFIRMED`. Compare the
returned hash with your saved transfer and reconcile through the status URL before taking further
action.
---
## Checkout checkpoints (5)
For a checkout the agent completes itself. Call `checkout_checkpoint_begin` right before opening
Link or another payment method, and `checkout_checkpoint_confirm` right before submitting the
payment. See [checkout checkpoints](https://conto.finance/docs/guides/checkout-checkpoints).
| Tool | Description |
| ------------------------------------ | ------------------------------------------------------------------------------ |
| `checkout_checkpoint_begin` | Check the purchase before the payment method opens |
| `checkout_checkpoint_confirm` | Check the final total right before the payment is submitted |
| `checkout_checkpoint_report_outcome` | Report `PAID`, `NOT_PAID`, or `UNKNOWN` after the payment |
| `checkout_checkpoint_cancel` | Cancel a checkpoint that was not confirmed, or stop waiting for the owner |
| `checkout_checkpoint_status` | Read a checkpoint's stage and next action, and poll it while the owner decides |
### checkout_checkpoint_begin
Open the payment method only if the result's `stage` is `APPROVED`. If the call fails, stop the
checkout.
If the `stage` is `AWAITING_APPROVAL`, the agent's rules need the owner to approve the purchase. The
result's `message` tells the agent not to open the payment method. Call `checkout_checkpoint_status`
every few seconds until the stage is `APPROVED`, then continue. Stop if it becomes `DENIED`,
`CANCELLED`, or `EXPIRED`. The spend stays held until then or until the agent cancels.
| Parameter | Type | Required | Description |
| ---------------- | ------- | -------- | ----------------------------------------------------------- |
| `idempotencyKey` | string | Yes | Stable unique key for this purchase. Reuse it when retrying |
| `merchantId` | string | Yes | Merchant label for the owner. Rules use the checkout domain |
| `merchantName` | string | Yes | Merchant name shown to the owner |
| `checkoutUrl` | string | Yes | HTTPS URL of the checkout page, on the store's domain |
| `amountMinor` | integer | Yes | Current checkout total in USD cents |
| `purpose` | string | Yes | Why the agent is buying this |
| `lineItems` | array | Yes | `name`, `quantity`, and `unitAmountMinor` for each item |
| `totals` | array | No | Total lines such as tax and shipping. Defaults to one total |
| `checkoutMethod` | string | No | `LINK` (default), `CARD`, `SHOP_PAY`, or `PAYPAL` |
| `category` | string | No | Spend category, such as `OFFICE_SUPPLIES` |
### checkout_checkpoint_confirm
Submit the payment only if the result's `stage` is `CONFIRMED`. Takes `checkpointId`, the final
`amountMinor`, and optional `totals`, `pageUrl`, and `linkRequestId`. If the final total differs
from the approved one, the tool replaces the approval with one for the final total and returns it.
Use its `id` from then on. With a Stripe Link card, pass its `linkRequestId`. A different total then
returns `LINK_CARD_TOTAL_CHANGED` instead, because the card covers only the approved total.
The new checkpoint can need the owner's approval. If the result's `stage` is `AWAITING_APPROVAL`,
do not pay. Poll `checkout_checkpoint_status` until the stage is `APPROVED`, then call
`checkout_checkpoint_confirm` again.
### checkout_checkpoint_report_outcome
Takes `checkpointId`, `outcome` (`PAID`, `NOT_PAID`, or `UNKNOWN`), an optional order or receipt
`reference`, and, with a Stripe Link card, its `linkRequestId`. `PAID` keeps the amount counted toward the agent's limits, `NOT_PAID` releases
it, and `UNKNOWN` keeps it counted until you report again. Report an outcome only after
`checkout_checkpoint_confirm` returns `CONFIRMED`.
---
## Stripe Link cards
The MCP server does not issue Stripe Link cards. A tool result goes into the model's context and
often into the MCP host's logs, so a card number and CVC must never be in one. The earlier
`checkout_get_link_card` tool is not listed, and a direct call returns `LINK_CARD_TOOL_DISABLED`
without contacting Conto or Link.
Get Link cards with `conto.checkout.getLinkCard()` in code the model does not see. Pass the agent
only the `linkRequestId` and masked details such as `last4`. The checkpoint tools take that
`linkRequestId`: `checkout_checkpoint_confirm` and `checkout_checkpoint_report_outcome` send it to
Conto as the payment reference, and `checkout_checkpoint_cancel` cancels the Link request too when
`CONTO_LINK_WALLET` is `link-cli` and `link-cli` is on the PATH. For an approved or waiting
checkpoint it cancels the Link request first. If Link cannot cancel it and does not report it
closed, the tool returns `LINK_CARD_NOT_CANCELLED` with `holdReleased: false` and keeps the spend
held. If a `linkRequestId` is supplied but the Link wallet is unavailable, the tool also keeps
the hold and returns this error. Restore Link access and call it again with the same IDs to retry.
See [Stripe Link cards for agents](https://conto.finance/docs/guides/stripe-link-cards).
---
## Guarded browser checkout (optional)
These four tools appear only when you configure a trusted browser runtime (see the
[configuration reference](https://conto.finance/docs/mcp/install#configuration-reference)).
| Tool | Description | Listed when |
| -------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------- |
| `guarded_browser_checkout` | Perform one approved payment action in a runtime-owned page | `CONTO_BROWSER_RUNTIME_URL` and `CONTO_BROWSER_RUNTIME_TOKEN` are set |
| `observe_browser_session` | Read the visible links and buttons on a runtime-owned page | `CONTO_BROWSER_OBSERVATION_URL` is also set |
| `create_browser_session` | Create one runtime-owned browser session | `CONTO_BROWSER_SESSIONS_URL` is also set |
| `release_browser_session` | Revoke and release a runtime-owned browser session | `CONTO_BROWSER_SESSIONS_URL` is also set |
### guarded_browser_checkout
The tool performs one payment-sensitive browser action through a trusted runtime that owns the
page. The agent remains responsible for merchant discovery, shopping, cart construction, checkout
preparation, and the purchase proposal. The runtime binds the exact target the agent supplies, asks
Conto for policy authorization, claims it once, re-verifies the target, and performs the click. The
MCP result never contains a raw locator, payment credential, or reusable authorization.
Required inputs are a stable `idempotencyKey`, `browserSessionId`, a runtime-supported `locator`,
`actionKind`, `checkoutMethod`, the expected amount in USD cents (`amountMinor`), `purpose`,
`counterparty`, `cart`, and `providerPresentation`. Supported checkout methods are `CARD`,
`SHOP_PAY`, and `PAYPAL`. For a Link checkout, use [checkout checkpoints](#checkout-checkpoints-5).
The returned `requiredControlBoundary` identifies checkout preparation as `AGENT`, the payment
action as `CONTO_RUNTIME`, and the requirement for an exclusive payment path. It states the
integration contract. It does not attest that the agent host removed bypass tools. The browser
outcome reports dispatch evidence with settlement set to `UNVERIFIED`. Reconcile settlement through
merchant or provider evidence. For enforceable policy controls, the agent host must not also expose
an unrestricted payment click or submit tool.
### observe_browser_session
Read-only. Takes one `browserSessionId` and returns only the page's HTTPS origin, a bounded title,
and up to 50 visible link and button descriptors. It withholds page text, DOM, form values,
screenshots, cookies, storage, and URL paths and queries. Returned positional locators are inert
target references for a later `guarded_browser_checkout`. The tool cannot navigate, click, type,
submit, or run JavaScript.
### create_browser_session and release_browser_session
`create_browser_session` takes no parameters and returns only an opaque session ID, status, and
expiry. Creation is not idempotent. Do not retry it automatically after a timeout or network error,
because the earlier request may have succeeded.
`release_browser_session` takes the exact `browserSessionId` and revokes Conto ownership before the
runtime closes the session. Call it after checkout or abandonment. Reconcile an ambiguous release
instead of repeating it.
An agent browser host that already owns and registers its session with the runtime does not need
these two tools.
---
## Wallets and limits (3)
| Tool | Description |
| --------------------- | -------------------------------------------------- |
| `get_wallets` | List all wallets linked to this agent |
| `get_wallet` | Get details of a specific wallet |
| `get_spending_limits` | Get current spending limits and remaining balances |
### get_wallet
| Parameter | Type | Required | Description |
| ---------- | ------ | -------- | ----------- |
| `walletId` | string | Yes | Wallet ID |
---
## Budget requests (2)
| Tool | Description |
| -------------------- | ------------------------------------------------- |
| `request_budget` | Request a spending budget for human approval |
| `get_budget_request` | Check budget request status and remaining balance |
### request_budget
Request a spending budget for payments. Submits a request for human approval. Only one active
budget request per agent at a time. Applies to all payment types (standard, x402, MPP).
| Parameter | Type | Required | Description |
| ---------- | ------ | -------- | --------------------------------------------------- |
| `amount` | number | Yes | Requested budget in USDC |
| `purpose` | string | Yes | What this budget is for (shown to reviewer) |
| `category` | string | No | Spend category (e.g., API_PROVIDER, INFRASTRUCTURE) |
### get_budget_request
Check status and remaining balance of your budget requests. Use it to poll for approval after
submitting a request, and to monitor remaining budget during spending.
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | -------------------------------------------------------------------- |
| `requestId` | string | No | Specific budget request ID |
| `status` | string | No | Filter: `review_required`, `approved`, `declined`, `expired`, `used` |
---
## Transactions (3)
| Tool | Description |
| ------------------- | --------------------------------------- |
| `list_transactions` | List transactions with optional filters |
| `get_transaction` | Get details of a specific transaction |
| `retry_transaction` | Retry a failed transaction |
`list_transactions` returns paginated transaction records with related resource IDs, lifecycle
timestamps, authorization decisions, and network receipt data. `get_transaction` adds source-wallet
and recipient identities plus linked alerts, so an agent can explain and reconcile a payment.
`retry_transaction` requires `transactionId` and both `transactions:write` and `payments:execute`
scopes. It creates or resumes one retry payment after a definitive failure, preserving the original
receipt and rechecking execution controls. The response includes the retry request ID, transaction
ID when available, current status, and `statusUrl`. Inspect that URL after an ambiguous client
result. If the outcome is unknown or a reconciliation alert is open, a person must resolve it in the
dashboard before a retry runs.
### list_transactions
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------------------------ |
| `status` | string | No | Filter: `PENDING`, `CONFIRMING`, `CONFIRMED`, `FAILED` |
| `from` | string | No | Start date (ISO string) |
| `to` | string | No | End date (ISO string) |
| `limit` | number | No | Results per page (default 50, max 100) |
| `offset` | number | No | Pagination offset |
---
## Connected cards (1)
| Tool | Description |
| --------------------- | --------------------------------------------------------------- |
| `connected_card_list` | List connected cards and active mandates this agent can draw on |
Connected cards (Builder and Enterprise plans) are cards you enroll in Conto and delegate to this
agent. `connected_card_list` is read-only (`transactions:read`), takes no parameters, and returns
masked cards only. Each card exposes last4, brand, funding type, spending limits, and its active
mandates. No card number, security value, or provider token is ever returned.
No MCP tool draws a connected-card credential, because tool results land in the model transcript
and host logs. Draw credentials with the SDK (`conto.connectedCards.draw`).
---
## Conto Commerce (7)
| Tool | Description |
| -------------------------------- | ------------------------------------------------------------- |
| `purchase_start` | Start a purchase for your buyer from a product URL and a task |
| `purchase_status` | Read status, phase, version, the required action and evidence |
| `purchase_respond` | Answer an `agent_or_buyer` action, such as delivery details |
| `purchase_cancel` | Cancel before payment. Never refunds a submitted payment |
| `recurring_purchase_list` | List the repeat orders your buyer set up |
| `recurring_purchase_start_cycle` | Start the next order of a repeat order, named by a cycle key |
| `recurring_purchase_skip_cycle` | Skip one order of a repeat order |
`purchase_start` needs `buyerId`, `url`, `maxCostMinor` (US cents) and an `idempotencyKey` you
reuse when retrying. Pass `paymentMethodId` and `delegationId` together to choose the card.
Without them, the buyer's one authorization for that kind of store is used. For groceries at an
Instacart store, pass `groceryList` instead of `items`. It returns at once and
the purchase continues in the background. Pass `after` to `purchase_status` to also get the events
since that version.
The buyer reviews every purchase and verifies their card on a Conto page, so `purchase_respond`
refuses review, approval and card verification. Share `requiredAction.url` with the buyer instead.
No purchase tool returns card details or a store browser. See the [Conto Commerce API](https://conto.finance/docs/sdk/purchases).
A repeat order is set up by the buyer while approving a purchase. Start each order with
`recurring_purchase_start_cycle` and a `cycleKey` you reuse when retrying, then follow the returned
purchase with `purchase_status`. The buyer gets a heads-up before each order and can skip it.
---
## x402 protocol (5)
| Tool | Description |
| -------------------- | ------------------------------------------------------------- |
| `x402_get_budget` | Check remaining x402 budget and recent usage |
| `x402_pre_authorize` | Check an x402 API payment against policies and get a grant |
| `x402_pay` | Check an x402 payment and have Conto sign it (managed wallet) |
| `x402_record` | Record a completed x402 payment (single or batch) |
| `x402_list_services` | List x402 services this agent has paid, with spend |
Request and response fields are in the [x402 API reference](https://conto.finance/docs/guides/x402-api-payments#api-reference). The notes below cover
behavior an agent needs to handle.
**Pre-authorize.** `x402_pre_authorize` requires `amount`, `recipientAddress`, and `resourceUrl`.
`serviceDomain` is derived from `resourceUrl`. An approved decision returns `grantId`,
`grantSignature`, and `grantExpiresAt`, and reserves the amount against the agent's spend limits
for 10 minutes. Calling again with the same `paymentId` returns the same live grant. A declined or
review-required decision returns fixed `reasons` and `reasonCodes`, such as `SPENDING_LIMIT` or
`RECIPIENT_NOT_ALLOWED`, without rule values.
**Pay from a managed wallet.** `x402_pay` requires `payments:execute` and takes the pre-authorize fields plus
`paymentRequirements`, the accepted entry from the 402 challenge. `recipientAddress` must equal
`paymentRequirements.payTo`, and the challenge `scheme` must be `exact`. Conto evaluates the payment
exactly like `x402_pre_authorize`. Only an approved payment is signed: Conto signs an EIP-3009
authorization for that payee, amount, and USDC contract with the managed EVM wallet, so a declined
payment cannot be paid from it. The result adds `paymentHeaderName` and `xPaymentHeader`. Retry the
resource request with that header, then call `x402_record` with the grant and the settlement
`txHash`. Each grant is signed once, and a second call for the same payment returns
`GRANT_ALREADY_SIGNED`. Wallets Conto cannot sign for return `MANAGED_SIGNING_UNAVAILABLE`. For
those, call `x402_pre_authorize` and sign with your own wallet.
**Record.** `x402_record` requires `amount`, `recipientAddress`, `resourceUrl`, `txHash`, and
`paymentId`. Reuse the same `paymentId` when retrying. Send `batchItems` to record several
micropayments at once. Provide `payerAddress` and `network` together on both the pre-authorize and
record calls. They are required when the agent has more than one active wallet.
Pass `grantId` and `grantSignature` from the approved `x402_pre_authorize` or `x402_pay` result on
every record. A record without a valid, unexpired grant is accepted as a policy bypass: the result
includes `policyBypass: true`, the payment is flagged, and the account owner receives a critical
alert. Organizations can also have the agent frozen automatically, and `agentFrozen` reports whether
it was.
**Budget and services.** Pass an optional `sessionId` to `x402_get_budget` to include spend for that
session alongside the daily, weekly, and monthly totals. `x402_list_services` takes `period`
(`24h`, `7d`, `30d`, or `90d`, default `7d`) and `limit` (default 50, max 100).
---
## MPP protocol (4)
| Tool | Description |
| ------------------- | ------------------------------------------------------------ |
| `mpp_get_budget` | Check remaining MPP budget and recent usage |
| `mpp_pre_authorize` | Check an MPP charge or session against policies, get a grant |
| `mpp_record` | Record a completed MPP payment (single or batch) |
| `mpp_list_services` | List MPP services this agent has paid, with spend |
Request and response fields are in the [MPP API reference](https://conto.finance/docs/guides/mpp-session-payments#api-reference).
**Pre-authorize.** `mpp_pre_authorize` requires `amount`, `recipientAddress`, and `resourceUrl`.
Set `intent` to `charge` or `session`. A `session` intent also requires `sessionId`, and
`depositAmount` sets the session deposit. An approved decision returns `grantId`, `grantSignature`,
and `grantExpiresAt`, and reserves the amount until `grantExpiresAt`. A `charge` grant covers one
payment of that exact amount and expires after 10 minutes. A `session` grant is a ceiling that
several `mpp_record` calls may draw down. It lasts for the session's maximum duration: the shortest
`MPP_MAX_SESSION_DURATION` that applies, or 24 hours by default.
Calling again with the same `challengeId` or `credentialId` returns the same live grant. A declined
or review-required decision returns fixed `reasons` and `reasonCodes`, as `x402_pre_authorize`
does.
**Record.** `mpp_record` requires `amount`, `recipientAddress`, `resourceUrl`, and `credentialId`.
Reuse the same `credentialId` when retrying. Send `closeSession: true` on a session's final record (mcp-server 0.5.3 or later). If the session charged nothing, send `amount: 0` with `closeSession: true` and the session grant. The result has `sessionClosed: true`. `txHash` and `paymentReceipt` are optional. Recording
rejects an unsuccessful receipt, a receipt bound to a different challenge, or a settled amount that
does not match `amount`. Receipt parsing is only a consistency check because the caller supplies
the payload, so a receipt-only record stays pending. An onchain transaction is marked confirmed only
after its sender, recipient, amount, and currency match the record. Without a valid, unexpired
grant, the record is accepted as a policy bypass, as with `x402_record`.
**Budget and services.** `mpp_get_budget` takes an optional `sessionId`, and `mpp_list_services`
takes `period` and `limit`, as the x402 tools do.
---
## Agent-to-agent (6)
| Tool | Description |
| --------------------- | -------------------------------------------- |
| `a2a_send_request` | Send a payment request to another agent |
| `a2a_list_requests` | List incoming and outgoing A2A requests |
| `a2a_respond` | Approve or reject an A2A request |
| `a2a_execute` | Execute an approved A2A payment |
| `a2a_resolve_address` | Check if an address belongs to a Conto agent |
| `a2a_get_stats` | Get A2A payment statistics |
### a2a_send_request
| Parameter | Type | Required | Description |
| --------------------- | ------ | -------- | -------------------------------------------- |
| `amount` | number | Yes | Amount in USDC |
| `targetAgentId` | string | No | Target agent ID (or use targetWalletAddress) |
| `targetWalletAddress` | string | No | Target wallet address |
| `purpose` | string | No | Why this payment is requested |
| `invoiceId` | string | No | Invoice reference ID |
| `dueDate` | string | No | Due date (ISO string) |
### a2a_list_requests
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| `direction` | string | No | `incoming`, `outgoing`, or `all` (default) |
| `status` | string | No | `review_required`, `approved`, `declined`, `processing`, `completed`, `failed`, or `expired` |
| `limit` | number | No | Results per page (default 50, max 100) |
| `offset` | number | No | Pagination offset |
### a2a_respond
Approval succeeds only when the paying agent's policies allow the payment. If policy requires
human approval, the request stays pending with `code: "HUMAN_APPROVAL_REQUIRED"` until a person in
the paying organization approves it while signed in to Conto.
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------- |
| `requestId` | string | Yes | A2A request ID |
| `action` | string | Yes | `'approve'` or `'reject'` |
| `reason` | string | No | Optional rejection reason |
---
## Trust and counterparties (4)
| Tool | Description |
| --------------------- | -------------------------------------------------- |
| `check_address_trust` | Get trust score and risk info for a wallet address |
| `list_counterparties` | List known counterparties with trust levels |
| `get_counterparty` | Get counterparty details and transaction history |
| `create_counterparty` | Create or update a counterparty |
### list_counterparties
| Parameter | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------------------------------ |
| `trustLevel` | string | No | Filter: `TRUSTED`, `VERIFIED`, `UNKNOWN`, or `BLOCKED` |
| `limit` | number | No | Results per page (default 50, max 100) |
| `offset` | number | No | Pagination offset |
### create_counterparty
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | ------------------------ |
| `name` | string | Yes | Counterparty name |
| `address` | string | Yes | Wallet address |
| `type` | string | No | Type (default: 'VENDOR') |
| `category` | string | No | Category |
| `description` | string | No | Description |
---
## Alerts and approvals (4)
| Tool | Description |
| ----------------------- | ------------------------------------ |
| `list_alerts` | List active alerts and notifications |
| `get_alert` | Get details of a specific alert |
| `respond_to_alert` | Acknowledge or resolve an alert |
| `get_approval_requests` | List pending approval requests |
### list_alerts
| Parameter | Type | Required | Description |
| ---------- | ------ | -------- | ---------------------------------------------------------- |
| `status` | string | No | Filter: `open`, `in_review`, `resolved`, `dismissed` |
| `severity` | string | No | Filter: `low`, `medium`, `high` (`high` includes critical) |
| `limit` | number | No | Results per page (default 50, max 100) |
| `offset` | number | No | Pagination offset |
### get_alert
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------- |
| `alertId` | string | Yes | Alert ID |
### respond_to_alert
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | -------------------------- |
| `alertId` | string | Yes | Alert ID |
| `action` | string | Yes | 'acknowledge' or 'resolve' |
Note:
Safety and reconciliation alerts (automatic agent freezes, protection-check failures, and payments
flagged for manual reconciliation) are read-only to an agent. `respond_to_alert` returns a `403`
for these, and they must be acknowledged or resolved by a human in the dashboard. `list_alerts`
and `get_alert` still return them.
### get_approval_requests
Lists payment requests waiting for human approval and A2A requests. The agent cannot decide its own
payment requests. A person approves or rejects them from the email link or the dashboard. Answer
incoming A2A requests with `a2a_respond`.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ----------------------------------------------------- |
| `type` | string | No | Filter: `payment`, `a2a_incoming`, or `a2a_outgoing` |
| `status` | string | No | Filter: `PENDING`, `APPROVED`, `DENIED`, or `EXPIRED` |
| `limit` | number | No | Results per page (default 50, max 100) |
| `offset` | number | No | Pagination offset |
---
## Analytics and info (7)
| Tool | Description |
| -------------------------- | ---------------------------------------------------------- |
| `get_analytics` | Get spending analytics and trends |
| `get_policies` | List spending policies assigned to this agent |
| `request_policy_exception` | Request a policy change for review |
| `list_policy_exceptions` | List and reconcile policy change requests |
| `get_agent_info` | Get information about this agent |
| `get_all` | Load aggregate agent data for compatibility with old flows |
| `get_setup` | Get agent setup and configuration |
`get_analytics` takes an optional `period`: `day`, `week`, `month` (default), or `year`.
### get_setup
Returns the agent, linked wallets, effective policies, scopes, and the versioned setup
`diagnostics` described in [setup diagnostics](https://conto.finance/docs/sdk/installation#setup-diagnostics). The tool's
output schema describes them. Call it first to learn the environment, whether the key can request
and execute payments, and which remaining issues belong to the agent or to an owner.
For a key Conto recognizes but will not serve, such as a paused or frozen agent, an expired or
revoked key, or a key without `agents:read`, `get_setup` returns an error result that still carries
the diagnostics. Relay the owner actions it lists instead of retrying.
### request_policy_exception
Use this when a payment is blocked and the agent needs a counterparty, spending-limit,
time-window, category, or custom policy change reviewed. The API key must include the
`policies:exceptions` scope.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | --------------------------------------------------------------------------------------------- |
| `type` | string | Yes | `ADD_TO_WHITELIST`, `INCREASE_SPEND_LIMIT`, `EXTEND_TIME_WINDOW`, `ADD_CATEGORY`, or `CUSTOM` |
| `reason` | string | Yes | Why the policy change is needed |
| `urgency` | string | No | `LOW`, `NORMAL`, `HIGH`, or `CRITICAL` |
| `details` | object | No | Structured counterparty, policy, limit, category, time-window, or payment details |
The response includes the request reference, normalized type and priority, current review status,
timestamps, and a dashboard review URL.
### list_policy_exceptions
List policy change requests with their review statuses and pagination. The API key must
include the `policies:exceptions` scope.
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | -------------------------------------------------- |
| `status` | string | No | `pending`, `in_review`, `resolved`, or `dismissed` |
| `limit` | number | No | Results per page (default 50, max 100) |
| `offset` | number | No | Pagination offset |
### get_all
`get_all` is retained for compatibility with existing MCP integrations. New integrations should
use `get_setup` and the dedicated list tools.
Use the optional `include` parameter to request only the sections you need. A section is returned
only when the API key has its corresponding read scope. `omittedSections` identifies sections that
could not be returned and the scope each one requires.
| Parameter | Type | Required | Description |
| ----------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `include` | string | No | Comma-separated `agent`, `wallets`, `policies`, `counterparties`, `transactions`, `alerts`, `analytics`, `capabilities`, or `endpoints` |
| `analyticsPeriod` | string | No | `day`, `week`, `month`, or `year` |
---
## Audit and rate limits (2)
| Tool | Description |
| ----------------- | --------------------------------------------------- |
| `get_audit_logs` | Get audit logs of agent actions |
| `get_rate_limits` | Get published SDK request ceilings and usage counts |
### get_audit_logs
| Parameter | Type | Required | Description |
| ---------- | ------ | -------- | -------------------------------------- |
| `action` | string | No | Filter by action type |
| `resource` | string | No | Filter by resource type |
| `from` | string | No | Start date (ISO string) |
| `to` | string | No | End date (ISO string) |
| `limit` | number | No | Results per page (default 50, max 100) |
| `offset` | number | No | Pagination offset |
## Simulated payment receipts
Sandbox payments are simulated. Every receipt has `transactionId` and `settlementMode`, and `txHash`
is present only for an onchain transfer. `check_payment_status` reports `settlementMode` as `live`,
`test`, or `unknown`. Never report a `test` receipt as an onchain transfer.
---
# How policies are evaluated
Source: https://conto.finance/docs/policies/evaluation
Every active organization baseline plus every policy assigned to the selected agent, wallet, or
card must pass. The first DENY stops evaluation immediately. Counterparty or relationship review is
considered only after hard wallet and policy controls pass, so an approval route cannot turn a
denied payment into an approvable request.
An unset (`null`) wallet or relationship limit means no cap. A limit of `0` blocks every payment. A
positive value is the cap.
## Policy scope
Every policy has one explicit scope:
- **Organization:** a mandatory baseline applied automatically to every payment in the organization.
- **Assigned:** applies only through an explicit agent, wallet, or card assignment. An unassigned
policy is a draft control and is not enforced.
Use organization scope for universal compliance and safety limits. Use assigned scope for rules that
vary by agent, wallet, or card. Organization baselines cannot be bypassed by removing an assignment.
The SDK policy response lists each policy's `scope` and whether it reaches the payment through the
organization, agent, wallet, or card.
Daily, weekly, and monthly limits at organization or agent scope use the agent's combined wallet
and live-card spend. See [how spend limits count](https://conto.finance/docs/policies/spend-limits#how-it-works) for
concurrent reservations, wallet-only and card-only limits, and test cards.
## Evaluation order
1. **Pre-checks**: the agent must be ACTIVE with linked wallets
- `DEV` and `STAGING` agents can use only known testnet wallets. Mainnet and unclassified chains
are denied.
2. **Geographic and sanctions**: address sanctions screening on every payment, plus an OFAC country check when the request includes `context.recipientCountry` (no policy needed)
3. **Counterparty trust**: pre-fetch trust level and network trust score for use in policy rules
4. **Wallet policies**: spend limits, wallet-level time windows (timezone-aware), and other configured policy rules
- Identity allow rules evaluate environment, risk tier, owner role, tags, and attestation mode.
Missing identity context denies the payment.
5. **Counterparty rules**: block list, trust requirements, network intelligence
6. **Relationship controls**: per-agent payee limits, category allowlists, approval requirements,
and temporary access expiry
7. **Final decision**: any denial denies. Otherwise a rule or workflow that requires approval holds
the payment. Otherwise it is approved. See [outcomes](https://conto.finance/docs/policies/overview#what-is-a-policy).
Protocol-specific budget rules only count transactions recorded through the matching protocol
flow, so ordinary wallet transfers do not reduce protocol allowances. Endpoint velocity and service
call counts include every item in a recorded batch.
Budget caps and velocity rules count recorded transactions plus payments that are executing or
approved but unconfirmed. Missing history denies the payment. See
[Budget allocations](https://conto.finance/docs/policies/advanced#budget-allocations) and
[Limit transaction velocity](https://conto.finance/docs/policies/advanced#limit-transaction-velocity).
Conto checks the full policy set again right before a managed payment executes and before it issues
an external-wallet approval token. A new denial at that point stops the payment. An external-wallet
approval counts toward budgets, velocity, and per-counterparty limits until it is confirmed or
expires. See
[wallet custody](https://conto.finance/docs/guides/custody-modes) for what policy can restrict on each wallet type.
Info:
Address sanctions screening is active by default and uses maintained compliance data. A sanctions
match can deny a payment even when every configured policy would otherwise allow it.
## Counterparty lifecycle and agent controls
Counterparty lifecycle state and per-agent relationship controls are evaluated independently:
- A counterparty can move through `DISCOVERED`, `PENDING_REVIEW`, `APPROVED`, `TRUSTED`,
`MONITORED`, `QUARANTINED`, and `BLOCKED`.
- `PENDING_REVIEW`, `MONITORED`, and `QUARANTINED` route payments to approval.
- `DISCOVERED` routes a production agent's payments to approval. Development and staging agents,
which can only use testnet wallets, are not held for payee discovery.
- `BLOCKED` denies the payment.
- Each agent-to-counterparty relationship can add stricter per-payment, daily, and monthly limits,
require approval, restrict categories, or expire at a specific time.
Approving a counterparty globally does not remove a stricter agent-specific approval requirement.
An expired relationship returns the payment to review.
## Evaluation semantics
Policy rules use AND logic. Each active policy that applies is evaluated once, and every rule in it
must pass unless it is a `DENY` or `REQUIRE_APPROVAL` trigger that does not match.
| Action | Meaning |
| ------------------ | ---------------------------------------------------------------------------------------------------------- |
| `ALLOW` | The condition describes what is permitted. If the condition does not match, the payment is denied. |
| `DENY` | The condition describes what is blocked. If the condition matches, the payment is denied. |
| `REQUIRE_APPROVAL` | The condition describes what needs review. If it matches and no DENY fires, the payment requires approval. |
Warning:
`priority` is sort order, not override precedence. A higher-priority `ALLOW` cannot override a
lower-priority deny or a failed allowlist. Use one wider allowlist rule when you need
alternatives, not two competing policies.
## When the request leaves out a field
Many rules read a field from the payment request, such as `category`, `context.recipientCountry`,
`targetContractAddress`, or the x402 or MPP service.
- If the request does not include a field a rule checks, the payment is denied, whatever the
rule's action. The reason names the missing field.
- Exceptions: a `DENY` or `REQUIRE_APPROVAL` rule on `context.recipientCountry`, on the Fairscale
score, or on `targetContractAddress` does not apply when the field is missing. Plain transfers
never carry a contract, and address sanctions screening still runs on every payment. `ALLOW`
versions of these rules still deny.
- A card payment is checked only against rules that apply to cards. A rule written for wallet,
x402, or MPP payments, such as a trust rule or an x402 price ceiling, is skipped on a card payment
whatever its action.
- On wallet, x402, and MPP payments, a `DENY` or `REQUIRE_APPROVAL` rule written for another
payment type is skipped, and an `ALLOW` rule still denies them. For example, an `ALLOW` card store
rule denies the agent's wallet payments, because they carry no store domain. To limit one payment
type without blocking the others, use `NOT_IN_LIST` with `DENY`.
Send every field your rules read. To make a country or contract list hold when the agent leaves
the field out, write it as an `ALLOW` list.
## Checkout purchases
A [checkout checkpoint](https://conto.finance/docs/guides/checkout-checkpoints) is evaluated against the agent's policies at
begin and again at confirm. It needs no linked wallet.
- **The merchant is the checkout domain.** Counterparty rules see the registrable domain of the
checkout URL as the recipient. Case and a leading `www.` do not matter, so
`https://WWW.Example.com/cart` is the merchant `example.com`.
- **Counterparty lists use that domain.** Allow and block lists, payee status, and relationship
controls match the checkout host and each parent domain up to the registrable domain. An entry
for `example.com` covers `shop.example.com`. Write entries as domains.
- **Velocity rules count checkout purchases.** `VELOCITY_LIMIT` counts a purchase with the agent's
other payments while its spend is held and after it is paid. Cancelled, rejected, expired, and
not-paid purchases stop counting. With `RECIPIENT` scope, the rule counts purchases at the same
merchant.
- **Period limits include checkout spend.** Daily, weekly, and monthly limits at organization or
agent scope count checkout spend together with wallet and card spend.
- **Budgets and relationship limits count checkout purchases.** `BUDGET_CAP` rules and relationship
daily and monthly limits count a purchase while its spend is held and after it is paid, as
velocity rules do. A relationship with `example.com` also counts purchases on `shop.example.com`.
[Connected-card store rules](https://conto.finance/docs/policies/advanced#connected-card-store-rules) also apply to
checkouts. They match the host of the checkout URL, so an entry for `example.com` covers a checkout
at `shop.example.com`.
## Wallet balance and custody
Evaluation checks the wallet balance only for wallets Conto custodies. For those, the recorded
balance is a ledger Conto maintains as it executes each payment, so a payment that would overdraw the
wallet is denied with `INSUFFICIENT_BALANCE`.
External wallets are not checked this way on the external-wallet approval flow
(`POST /api/sdk/payments/approve`). Conto does not hold the keys and never sees the transfers the agent
signs, so the balance it records is a point-in-time reading rather than a running total.
Enforcing it would block funded wallets while still clearing drained ones. Spend limits, policy
rules, counterparty controls, budgets, and approval workflows apply to external wallets exactly as
they do to custodied ones, and an underfunded transfer reverts onchain rather than settling.
For a hands-on walkthrough, [test allow, review, and deny](https://conto.finance/docs/guides/policy-testing).
---
# Policy rule reference
Source: https://conto.finance/docs/policies/advanced
## Rule shape
```json
{
"ruleType": "MAX_AMOUNT",
"operator": "LTE",
"value": "500",
"action": "ALLOW"
}
```
| Field | Purpose |
| ---------- | ------------------------------------------------------------------------ |
| `ruleType` | What the rule evaluates |
| `operator` | How the request value is compared |
| `value` | String value, or JSON encoded object/array |
| `action` | `ALLOW`, `DENY`, or `REQUIRE_APPROVAL`. Defaults to `ALLOW` when omitted |
## Operators
| Operator | Meaning |
| ----------------------------------------- | ----------------------- |
| `EQUALS`, `NOT_EQUALS` | Exact match or mismatch |
| `GREATER_THAN`, `GTE`, `LESS_THAN`, `LTE` | Numeric comparison |
| `IN`, `IN_LIST`, `NOT_IN`, `NOT_IN_LIST` | List membership |
| `BETWEEN`, `NOT_BETWEEN` | Range comparison |
## Supported rule types
| Category | Rule types |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Amount limits](https://conto.finance/docs/policies/spend-limits) | `MAX_AMOUNT`, `DAILY_LIMIT`, `WEEKLY_LIMIT`, `MONTHLY_LIMIT`, `BUDGET_CAP` |
| [Approval](#require-review-above-a-threshold) | `REQUIRE_APPROVAL_ABOVE` |
| [Time](https://conto.finance/docs/policies/time-windows) | `TIME_WINDOW`, `DAY_OF_WEEK`, `DATE_RANGE`, `BLACKOUT_PERIOD` (aliases `MAINTENANCE_WINDOW`, `BLOCKED_TIME_WINDOW`) |
| [Category](#restrict-by-category) | `ALLOWED_CATEGORIES`, `BLOCKED_CATEGORIES` |
| [Counterparty](https://conto.finance/docs/policies/counterparties) | `ALLOWED_COUNTERPARTIES`, `BLOCKED_COUNTERPARTIES`, `TRUST_SCORE`, `COUNTERPARTY_STATUS`, `COUNTERPARTY_APPROVAL_STATUS` |
| [Agent identity](#agent-identity-enforcement) | `AGENT_ENVIRONMENT`, `AGENT_RISK_TIER`, `AGENT_OWNER_ROLE`, `AGENT_TAG`, `ATTESTATION_LEVEL` |
| [Contract controls](#contract-allowlist) | `CONTRACT_ALLOWLIST` (aliases `ALLOWED_CONTRACTS`, `PROTOCOL_ALLOWLIST`) |
| [Velocity](#limit-transaction-velocity) | `VELOCITY_LIMIT` |
| [Geographic](#geographic-restrictions-ofac) | `GEOGRAPHIC_RESTRICTION` |
| [x402](#x402-protocol-rules) | `X402_MAX_PER_REQUEST` (alias `X402_PRICE_CEILING`), `X402_ALLOWED_SERVICES`, `X402_BLOCKED_SERVICES`, `X402_ALLOWED_FACILITATORS`, `X402_MAX_PER_SERVICE`, `X402_MAX_PER_ENDPOINT`, `X402_VELOCITY_PER_ENDPOINT`, `X402_SESSION_BUDGET` |
| [MPP](#mpp-protocol-rules) | `MPP_MAX_PER_REQUEST` (alias `MPP_PRICE_CEILING`), `MPP_MAX_SESSION_DEPOSIT`, `MPP_SESSION_BUDGET`, `MPP_MAX_CONCURRENT_SESSIONS`, `MPP_MAX_SESSION_DURATION`, `MPP_BLOCK_SESSION_INTENT`, `MPP_ALLOWED_SERVICES`, `MPP_BLOCKED_SERVICES`, `MPP_MAX_PER_SERVICE`, `MPP_MAX_PER_ENDPOINT`, `MPP_VELOCITY_PER_ENDPOINT` |
| [Connected cards](#connected-card-store-rules) | `CARD_ALLOWED_MERCHANT_DOMAINS`, `CARD_BLOCKED_MERCHANT_DOMAINS` |
The policy endpoints reject unsupported rule types with a 400. A stored rule whose type the engine
does not recognize denies every payment the policy covers.
## Rule patterns
### Agent identity enforcement
Identity rules limit which agents can pay by environment, risk tier, owner role, required identity
tag, and attestation mode. An identity `ALLOW` rule denies the payment when the agent has no value
for the field it checks. `DEV` and `STAGING` agents also cannot select mainnet or unclassified
wallets, even when one was accidentally assigned.
The value is one string or a JSON list. `AGENT_ENVIRONMENT` accepts `DEV`, `STAGING`, and `PROD`.
This rule lets only production agents pay:
```json
{
"ruleType": "AGENT_ENVIRONMENT",
"operator": "IN_LIST",
"value": "[\"PROD\"]",
"action": "ALLOW"
}
```
### Require review above a threshold
```json
{
"name": "Approval Over 500",
"policyType": "APPROVAL_THRESHOLD",
"rules": [
{
"ruleType": "REQUIRE_APPROVAL_ABOVE",
"operator": "GREATER_THAN",
"value": "500",
"action": "REQUIRE_APPROVAL"
}
]
}
```
Note:
A policy decision that requires approval always creates a request in **Alerts & Approvals**. If no
custom workflow matches, one review from an organization Owner, Admin, or Manager is required, and
the initiator cannot approve their own held payment. A final approval automatically submits the
payment for Conto-managed wallets and links the chain receipt from the approval review. External
and smart-contract wallets keep their manual execution requirements. An external wallet payment is
reserved against spend limits at the final approval and must be confirmed within 24 hours. A final
decision sends an `approval.approved` or `approval.denied` event to your organization webhook for
your own audit trail. See [Webhooks](https://conto.finance/docs/guides/webhooks).
### Request a scoped policy change
An agent with an admin SDK key can request a specific policy change without editing the policy
directly. Use `POST /api/sdk/policies/exceptions` with the `policies:exceptions` scope, then use
`GET /api/sdk/policies/exceptions` to track it.
```bash
curl -X POST https://conto.finance/api/sdk/policies/exceptions \
-H "Authorization: Bearer $CONTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "spend_limit",
"reason": "The annual vendor renewal is due this week.",
"priority": "high",
"details": {
"policyId": "policy_123",
"requestedLimit": 750,
"amount": 600
}
}'
```
The response:
```json
{
"id": "exception_123",
"exceptionId": "exception_123",
"type": "INCREASE_SPEND_LIMIT",
"normalizedType": "spend_limit",
"title": "Spending limit request",
"reason": "The annual vendor renewal is due this week.",
"priority": "high",
"severity": "HIGH",
"status": "PENDING",
"normalizedStatus": "pending",
"statusLabel": "Pending review",
"reviewUrl": "/alerts"
}
```
`normalizedStatus` is `pending`, `in_review`, `resolved`, or `dismissed`. Creating a request does
not change a policy. Review it in **Alerts & Approvals**.
### Limit transaction velocity
Set exactly one of `maxCount` or `maxAmount`. Supported periods are `HOUR`, `DAY`, `WEEK`, and
`MONTH`. `HOUR` and `DAY` use rolling one-hour and 24-hour history. Weekly and monthly counts use UTC
calendar periods. Weekly and monthly amount limits use the matching cumulative spend counters.
```json
{
"name": "Hourly Velocity Limit",
"policyType": "VELOCITY",
"rules": [
{
"ruleType": "VELOCITY_LIMIT",
"operator": "LTE",
"value": "{\"maxCount\": 10, \"period\": \"HOUR\"}",
"action": "ALLOW"
}
]
}
```
For an amount-based rate limit, use the same rule with `maxAmount`:
```json
{
"ruleType": "VELOCITY_LIMIT",
"operator": "LTE",
"value": "{\"maxAmount\": 300, \"period\": \"HOUR\"}",
"action": "ALLOW"
}
```
Count-based velocity accepts an optional `scope`. The default `WALLET` counts every transaction in
the window. `RECIPIENT` counts only transactions to the current recipient address, which limits
rapid repeated payments to one counterparty without counting unrelated activity. `RECIPIENT` works
with `maxCount` only. A `maxAmount` rule with `RECIPIENT` denies every payment.
```json
{
"ruleType": "VELOCITY_LIMIT",
"operator": "LTE",
"value": "{\"maxCount\": 5, \"period\": \"HOUR\", \"scope\": \"RECIPIENT\"}",
"action": "ALLOW"
}
```
Wallet velocity includes pending, confirming, and confirmed transactions, plus managed payments
being executed and external-wallet approvals that are not confirmed and not expired. A policy
assigned to the agent or the organization counts the agent's activity across all of its wallets. A
policy assigned only to a wallet counts the agent's activity through that wallet.
Card velocity includes recorded card transactions plus approved, executing, and confirming
reservations. An issued connected-card credential counts until its outcome is known. Once a person
confirms it was not used, it stops counting, and a smaller confirmed charge counts at that amount.
An agent's own report changes neither. A test card's credential that expires unused keeps counting.
A policy assigned to the agent or the organization counts the agent's activity across all of its
cards, and a policy linked to a card counts every agent's activity on that card.
Missing history, unsupported periods, and malformed velocity configuration deny the payment rather
than defaulting the prior count or spend to zero.
Velocity also counts [checkout checkpoint](https://conto.finance/docs/guides/checkout-checkpoints) purchases while their
spend is held and after they are paid. Cancelled, rejected, expired, and not-paid purchases stop
counting. A policy assigned to the agent or the organization counts the agent's checkout purchases.
With `RECIPIENT` scope, a checkout's recipient is the registrable domain of its checkout URL, so the
rule limits purchases at one store. See [checkout purchases](https://conto.finance/docs/policies/evaluation#checkout-purchases).
### Restrict by category
Category rules read the `category` the agent sends. Both `ALLOWED_CATEGORIES` and
`BLOCKED_CATEGORIES` deny a payment with no category, so always send `category` when you use them.
The value is a JSON list of categories. `{"categories": [...]}` is also accepted. The API rejects
any other shape and an empty list.
On a card payment the category is the merchant category code (MCC), such as `5411`. A category
rule can list the code or one of the categories the dashboard offers, such as `Retail` or
`Entertainment`. Conto files each MCC under one of those categories by the standard MCC ranges, and
a code outside them is `Other`. A card payment with no MCC is denied while a category rule applies
to it.
```json
{
"name": "Approved Categories",
"policyType": "CATEGORY",
"rules": [
{
"ruleType": "ALLOWED_CATEGORIES",
"operator": "IN_LIST",
"value": "[\"INFRASTRUCTURE\", \"API_PROVIDER\", \"MARKETING\"]",
"action": "ALLOW"
}
]
}
```
### Relationship-scoped payee controls
Use relationship controls when two agents need different limits for the same counterparty. Set
them on the counterparty detail page:
| Control | Behavior |
| -------------------- | ----------------------------------------------------------------- |
| Per-payment limit | Denies a payment above the relationship limit |
| Daily/monthly limits | Denies aggregate spend above the configured period limit |
| Require approval | Routes every matching payment to review |
| Allowed categories | Denies missing or non-allowed payment categories |
| Expiry | Returns the relationship to review after the configured timestamp |
These controls add to global counterparty lifecycle gating, organization baselines, and assigned
wallet or agent policies. Moving a counterparty to `APPROVED` or `TRUSTED` does not weaken a
relationship-level approval requirement.
For production agents, the first payment attempt to an unknown address creates a `DISCOVERED`
counterparty and relationship before evaluation. This applies to managed payments
(`/api/sdk/payments/request`) and self-custody approvals (`/api/sdk/payments/approve`). The payment
is routed to review until an operator advances the counterparty lifecycle. The agent sees the
reason `This payee needs review before payments to it can proceed automatically.`, and
`/api/sdk/payments/approve` returns the reason code `PAYEE_REVIEW_REQUIRED`. If Conto cannot persist
that review record, the request fails closed with `COUNTERPARTY_REVIEW_UNAVAILABLE`.
Development and staging agents are not held for payee discovery. A payee they pay still appears as
`DISCOVERED` in the dashboard for review, and moving it to `PENDING_REVIEW`, `MONITORED`,
`QUARANTINED`, or `BLOCKED` applies to every agent.
Review routing never overrides a hard denial. Spend limits, allowlists, sanctions rules, and other
deny controls must leave an eligible wallet before counterparty or relationship approval can apply.
### Geographic restrictions (OFAC)
The rule reads `context.recipientCountry` from the payment request. A `DENY` list is skipped when
the agent leaves it out. Conto already blocks OFAC-sanctioned countries, including CU, IR, KP, SY,
and RU, whenever the country is sent. On a card payment Conto checks the merchant's country the
same way. For a closed list, use `ALLOW` with the countries you permit,
which also denies payments that carry no country. Address sanctions screening applies to every
payment, with or without a geographic rule.
```json
{
"ruleType": "GEOGRAPHIC_RESTRICTION",
"operator": "IN_LIST",
"value": "[\"US\", \"CA\", \"GB\"]",
"action": "ALLOW"
}
```
### Contract allowlist
```json
{
"name": "Approved Protocols",
"policyType": "CONTRACT_ALLOWLIST",
"rules": [
{
"ruleType": "CONTRACT_ALLOWLIST",
"operator": "IN_LIST",
"value": "{\"protocols\": [\"uniswap\", \"aave\"], \"categories\": [\"DEX\", \"LENDING\"]}",
"action": "ALLOW"
}
]
}
```
Include `targetContractAddress` in the payment request. An `ALLOW` contract rule denies any payment
without it, including plain transfers and external-wallet approvals, so assign it only to agents
that call contracts. A `DENY` or `REQUIRE_APPROVAL` contract rule applies only to payments that
include `targetContractAddress`.
If a rule uses protocol names or categories, register the organization's known contract
classifications through the authenticated contract registry:
- `GET /api/contract-registry` lists entries and supports `category`, `limit`, and `offset`
- `POST /api/contract-registry` saves an address with optional `chainId`, `label`, `protocolName`,
and `protocolCategory`
- `GET`, `PATCH`, or `DELETE /api/contract-registry/{address}` reads, updates, or removes one entry
The address is the stable entry identifier. Registry responses return the customer-managed
classification fields for that address. Registry changes require policy write permission and are
recorded in the organization's audit history.
### x402 protocol rules
The dashboard exposes guided `X402_SPEND_LIMIT`, `X402_SERVICE_CONTROL`, and
`X402_ENDPOINT_CONTROL` policy types. The API also accepts those policy types directly. Use
`COMPOSITE` only when one policy deliberately mixes categories.
| Rule type | Value |
| ------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `X402_MAX_PER_REQUEST`, `X402_PRICE_CEILING`, `X402_SESSION_BUDGET` | An amount string, such as `"0.10"` |
| `X402_ALLOWED_SERVICES`, `X402_BLOCKED_SERVICES` | A JSON array of domains, such as `["api.example.com"]` |
| `X402_ALLOWED_FACILITATORS` | A JSON array of facilitators |
| `X402_MAX_PER_ENDPOINT`, `X402_MAX_PER_SERVICE` | `{"maxAmount": 5}`, `{"maxCalls": 100}`, or both, counted over a rolling 24 hours |
| `X402_VELOCITY_PER_ENDPOINT` | `{"maxCalls": 10, "period": "MINUTE"}`, with `MINUTE`, `HOUR`, or `DAY` |
The MPP rules use the same shapes. `MPP_MAX_SESSION_DEPOSIT`, `MPP_MAX_CONCURRENT_SESSIONS`, and
`MPP_MAX_SESSION_DURATION` (in hours) take a number. `MPP_MAX_CONCURRENT_SESSIONS` counts a session
from its approved pre-authorize until it is closed or its lifetime passes.
Warning:
Protocol rules apply to every wallet, x402, and MPP payment made by the agents a policy covers,
not only x402 or MPP payments. `X402_MAX_PER_REQUEST` caps each of those payment amounts, and
allowlist rules such as `X402_ALLOWED_SERVICES` deny payments that carry no x402 service context.
They do not apply to card payments. Assign protocol policies to a dedicated API-spend agent with
`"scope": "assigned"` and `agentIds`. A policy created without `scope` or `agentIds` applies to
the whole organization.
```json
{
"name": "x402 API Guardrails",
"policyType": "COMPOSITE",
"scope": "assigned",
"agentIds": ["cmm59z8k40001abcd1234efgh"],
"rules": [
{
"ruleType": "X402_MAX_PER_REQUEST",
"operator": "LTE",
"value": "0.10",
"action": "ALLOW"
},
{
"ruleType": "X402_ALLOWED_SERVICES",
"operator": "IN_LIST",
"value": "[\"api.example.com\", \"data.provider.io\"]",
"action": "ALLOW"
}
]
}
```
### MPP protocol rules
The dashboard exposes guided `MPP_SPEND_LIMIT`, `MPP_SERVICE_CONTROL`, `MPP_ENDPOINT_CONTROL`, and
`MPP_SESSION_LIFECYCLE` policy types. Session-lifecycle rules are evaluated only for session
intent, and expired sessions do not count toward concurrency.
```json
{
"name": "MPP Session Guardrails",
"policyType": "COMPOSITE",
"scope": "assigned",
"agentIds": ["cmm59z8k40001abcd1234efgh"],
"rules": [
{
"ruleType": "MPP_MAX_SESSION_DEPOSIT",
"operator": "LTE",
"value": "25",
"action": "ALLOW"
},
{
"ruleType": "MPP_MAX_CONCURRENT_SESSIONS",
"operator": "LTE",
"value": "3",
"action": "ALLOW"
}
]
}
```
### Connected-card store rules
Store rules name a store by the domain its checkout runs on. They apply when an agent requests a
connected-card credential and to [checkout purchases](https://conto.finance/docs/policies/evaluation#checkout-purchases). For
a card, Conto reads the domain from the checkout origin on the authorization the cardholder
verified, so the agent cannot change it. For a checkout, Conto reads it from the checkout URL, not
from the merchant id the agent sends.
```json
{
"name": "Approved stores",
"policyType": "MERCHANT",
"rules": [
{
"ruleType": "CARD_ALLOWED_MERCHANT_DOMAINS",
"operator": "IN_LIST",
"value": "[\"example.com\", \"shop.example.org\"]",
"action": "ALLOW"
}
]
}
```
| Goal | Rule type | Operator | Action |
| -------------------------------- | ------------------------------- | ------------- | ------------------ |
| Allow only the listed stores | `CARD_ALLOWED_MERCHANT_DOMAINS` | `IN_LIST` | `ALLOW` |
| Send every other store to review | `CARD_ALLOWED_MERCHANT_DOMAINS` | `NOT_IN_LIST` | `REQUIRE_APPROVAL` |
| Deny every other store | `CARD_ALLOWED_MERCHANT_DOMAINS` | `NOT_IN_LIST` | `DENY` |
| Block the listed stores | `CARD_BLOCKED_MERCHANT_DOMAINS` | `IN_LIST` | `DENY` |
| Send the listed stores to review | `CARD_BLOCKED_MERCHANT_DOMAINS` | `IN_LIST` | `REQUIRE_APPROVAL` |
- An entry covers its own domain and every subdomain. `example.com` covers `checkout.example.com`.
A leading `www.` is ignored.
- A list holds 1 to 100 domains. The policy endpoints reject any other operator and action pairing.
- A card payment or checkout with no checkout domain is denied while a store rule applies to it.
- An `ALLOW` store rule also denies that agent's wallet, x402, and MPP payments, because they carry
no store domain. For an agent that pays on more than one rail, use `NOT_IN_LIST` with `DENY`. That
form leaves other rails alone.
Store rules match a checkout by the host of its checkout URL, the same way they match a card
payment. Under the approved stores policy above, a checkout at `shop.example.com` is allowed, and a
checkout at a store the list does not name is denied. See
[checkout purchases](https://conto.finance/docs/policies/evaluation#checkout-purchases).
### Budget allocations
Use `BUDGET_CAP` when a wallet or agent needs a bounded allowance for a period, project, or
category. Supported periods are `DAILY`, `WEEKLY`, `MONTHLY`, `QUARTERLY`, and `YEARLY`. Period
boundaries are calculated in UTC.
```json
{
"ruleType": "BUDGET_CAP",
"operator": "LTE",
"value": "{\"amount\": 2500, \"period\": \"MONTHLY\", \"category\": \"INFRASTRUCTURE\"}",
"action": "ALLOW"
}
```
Add `department`, `project`, or `category` to the rule value to scope the cap. Conto compares only
transactions recorded with the same dimensions and includes pending, confirming, and confirmed
spend. The request must pass the matching scope through `context.department` and `context.project`.
`category` must match the payment's `category` exactly, including case. A payment with a different
or missing category is neither counted nor capped by that budget.
A budget without a department or project also counts managed payments being executed,
external-wallet approvals that are not confirmed and not expired, and
[checkout checkpoint](https://conto.finance/docs/guides/checkout-checkpoints) purchases while their spend is held and after
they are paid, matched by category. A budget on a
policy assigned to the agent or the organization counts the agent's spend across all of its wallets.
A policy assigned only to a wallet counts the agent's spend through that wallet. On cards, the same
budget counts the agent's spend across all of its cards, and a budget on a policy linked to a card
counts that card's spend.
On cards, a `QUARTERLY` or `YEARLY` budget counts the card purchases made in the period, including
issued credentials and purchases that are approved but not yet charged. These budget histories
remain separate for wallet and card payments. To set a daily, weekly, or
monthly cap across an agent's wallets and live cards together, use `DAILY_LIMIT`, `WEEKLY_LIMIT`,
or `MONTHLY_LIMIT` at agent or organization scope. Those rules use combined period counters. Scoped
`BUDGET_CAP` and velocity rules keep their own transaction histories. See
[Spend limits](https://conto.finance/docs/policies/spend-limits).
### Expiration policies
Use `DATE_RANGE` for temporary permissions and `BLACKOUT_PERIOD` for known no-spend windows. An
`ALLOW` date range denies every payment outside the range. It does not switch the policy off when
the range ends. `BLACKOUT_PERIOD` values list their windows under `windows`. The policy endpoints
return `400` for time, date, blackout, and geographic values in a shape the engine cannot read.
```json
{
"ruleType": "DATE_RANGE",
"operator": "BETWEEN",
"value": "{\"start\": \"2026-10-01T00:00:00Z\", \"end\": \"2026-12-31T23:59:59Z\"}",
"action": "ALLOW"
}
```
Time rules accept an IANA `timezone`. See [Time Windows](https://conto.finance/docs/policies/time-windows).
## Related
### Trust scoring
Link: https://conto.finance/docs/guides/trust-scoring
How payee trust scores are calculated, plus sanctions screening and wallet reputation
### Recipes
Link: https://conto.finance/docs/guides/recipes
Copy-paste policy setup snippets
---
# Changelog
Source: https://conto.finance/docs/changelog/overview
New Conto features, newest first.
Tip:
Follow [@contofinance on X](https://x.com/contofinance) or join the
[Discord community](https://discord.gg/h7rYrpkrRt) for release news.
### October 5, 2026
### Owner approval at checkout checkpoints
When an agent's rules require approval, a checkout checkpoint holds the spend and waits for the
owner. The owner approves or rejects the purchase in **Alerts & Approvals**. The SDK's
`checkout.waitForApproval()` and the MCP status tool wait for the decision. See
[checkout checkpoints](https://conto.finance/docs/guides/checkout-checkpoints#when-the-owner-must-approve).
### Mark a checkout paid or not paid
When a confirmed checkout has no outcome, an Owner, Admin, or Manager can mark it paid or not paid
on its checkout page. See [what the owner sees](https://conto.finance/docs/guides/checkout-checkpoints#what-the-owner-sees).
### Checkout checkpoints without a wallet
An agent can use checkout checkpoints with only its SDK key. Checkout spend counts toward the
agent's daily, weekly, and monthly limits together with its wallet and card spend. See
[checkout checkpoints](https://conto.finance/docs/guides/checkout-checkpoints).
### October 2, 2026
### Checkout checkpoints for agent-run checkouts
Checkout checkpoints check a proposed cart before opening a payment method and recheck its final
total before payment submission. The integration must honor both decisions and report the outcome.
These checks do not control a separate signer or payment credential. See
[checkout checkpoints](https://conto.finance/docs/guides/checkout-checkpoints).
### Shared wallet and card period limits
Daily, weekly, and monthly rules at agent or organization scope now count an agent's wallet and
live-card spend together. Test-card purchases remain separate. See [spend limits](https://conto.finance/docs/policies/spend-limits).
### Approval and governance recovery
Payment approval workflows can name escalation targets who may decide a request after its wait
period. A requester can also apply an approved governance change from another signed-in browser.
See [approval workflows](https://conto.finance/docs/guides/approval-workflows#escalation) and
[governance approvals](https://conto.finance/docs/guides/roles-permissions#apply-an-approved-change).
### Clear payment pause responses
When payments are paused, new payment actions return `503 PAYMENTS_PAUSED`. Agents should stop
checkout, read any existing request status, and retry only after service resumes. See
[error handling](https://conto.finance/docs/sdk/error-handling#system-errors).
### September 22, 2026
### Transaction-based plan limits
Plans now measure usage by transaction count:
- **Free:** 5 agents, 500 transactions a month, and up to $100 a month in real-money payments.
- **Builder ($500/month):** 25 agents and 1,000 transactions a month, with no payment volume cap.
- **Enterprise:** custom limits.
Connected cards are available on the Builder and Enterprise plans. See
[Connect an existing card](https://conto.finance/docs/guides/connected-cards).
### Usage alerts
Owners and admins get an email when a monthly plan allowance reaches 80% and again when it reaches
the limit. The same notice appears as a banner at the top of the dashboard, with a link to
**Plan & Usage**. Dismiss the banner and it stays hidden until the notice changes or the month
resets.
### Alert webhooks
Webhook notification channels can subscribe to the `alert.raised` event to receive `HIGH` and
`CRITICAL` organization alerts in your own systems. See
[alert webhooks](https://conto.finance/docs/integrations/notification-channels#alert-webhooks).
### September 21, 2026
### Connected cards
Your agents can now make purchases with a card you already have, within limits you set. Connect a
card from the agent's **Connected Cards** tab, set the agent's spending limits, and save it. You
don't need a merchant or purchase details yet, and saving the card doesn't authorize any spending.
When you know the merchant, authorize a purchase with a checkout URL, amount cap, and expiration,
then verify it. Conto checks every request against the authorization and your limits before it
releases a single-use credential for checkout. Revoke an authorization at any time to block new
requests. See [Connect an existing card](https://conto.finance/docs/guides/connected-cards) and the
[SDK checkout guide](https://conto.finance/docs/sdk/connected-cards).
### September 20, 2026
### A more capable AI assistant
The dashboard assistant can now handle more of your day-to-day work:
- **Explain a denial.** Ask why a payment was denied and get the rule that decided it.
- **Review agent controls.** See which limits and policies actually apply to an agent.
- **Apply a policy template.** Set up a template or preset in chat, and assign new policies by
agent name.
- **Work the approval queue.** Review pending approvals and act on them from the conversation.
- **Answer audit questions.** Ask what changed recently in your organization.
Replies link agents, counterparties, and payments to their dashboard pages. Every change still
appears as a confirmation card, and if the details change before you confirm, the change isn't
applied. See [Assistant actions](https://conto.finance/docs/assistant/overview).
### September 18, 2026
### x402 and MPP payments carry single-use grants
An approved x402 or MPP pre-authorization now returns a single-use grant (`grantId`,
`grantSignature`, `grantExpiresAt`) and reserves the amount against the agent's spend limits for
10 minutes. Send the grant back when you record the payment. Conto checks the settled transfer
against the grant and counts the spend once. Records without a valid grant are flagged and alert
the owner.
SDK `0.5.0` returns the grant from pre-authorization and forwards it on record. The agent skill's `conto-check.sh x402-record` command requires it. See
[x402 payments](https://conto.finance/docs/guides/x402-api-payments) and [MPP payments](https://conto.finance/docs/guides/mpp-session-payments).
### August 19, 2026
### Test a policy before you enable it
A new policy simulation API replays a candidate policy against your recent transactions and shows
what it would have decided, without saving anything. No policy is created, no approval runs, and no
money moves.
The response shows each transaction's outcome and a summary of how many payments the policy would
newly block or newly allow. See
[Test a policy before you enable it](https://conto.finance/docs/policies/overview#test-a-policy-before-you-enable-it).
### August 19, 2026
### Velocity limits per recipient
Count-based velocity limits now accept a `scope`. `WALLET` (the default) counts every payment from
the wallet. `RECIPIENT` counts only payments to the current recipient. See
[advanced policies](https://conto.finance/docs/policies/advanced).
### August 19, 2026
### Policy version history and rollback
Every change to a policy is now versioned. Each policy keeps a full history, and you can diff any two
versions or roll back to an earlier one from the API.
A version captures what the policy enforces: its metadata and its full rule set. New versions are
appended automatically when you create a policy, change its metadata, or add, edit, or remove a rule.
Rollback restores an earlier version and is recorded as its own version. When your organization
requires it, rollback runs through the same governance approval workflow as any other policy change.
See the [version history guide](https://conto.finance/docs/policies/versioning).
### August 19, 2026
### Governance and approval events stream to webhooks
Policy changes and approval decisions now emit webhook events for a SIEM, GRC platform, or your
own audit pipeline.
New events: `policy.created`, `policy.updated`, `policy.deleted`, `approval.approved`,
`approval.denied`, and `governance.approval.decided`. Policy events carry the policy name, type,
scope, and rule count. They never include the underlying rule values, so limits and thresholds
stay out of the event payload. Every event is signed with the same HMAC-SHA256 signature as
existing webhooks. See the [webhooks guide](https://conto.finance/docs/guides/webhooks) for the full payload reference.
### August 9, 2026
### Starter policies for new organizations
New organizations now start with editable starter policies: a `$25` per-transaction limit,
a `$100` daily limit, and approval for payments above `$10`. Active starter policies are linked to
new agents automatically, and approval requests have an owner email channel from the start.
Rename, edit, deactivate, or delete them from **Policies** as you move to your own setup.
### July 15, 2026
### Approved payments execute and link onchain receipts
Final approval on a managed-wallet payment now submits the transaction automatically.
Completed approvals link straight to the onchain transaction receipt, including on Tempo testnet.
### July 15, 2026
### Approve payments from your agent skill
An agent's human owner can approve or reject a pending payment through the agent skill, using the
single-use action token from their Conto approval notification.
The `conto` and `conto-hermes` skills add `pending-approvals`, `approve-request`, and
`deny-request` commands. Final approval returns the managed-wallet receipt, or hands back an
external-wallet execute and confirm step for agents that hold their own keys.
### July 13, 2026
### Governance approvals for sensitive changes
You can now require owner or admin review before sensitive organization changes apply, covering API
key creation, rotation, and revocation, policy and policy-rule edits, and agent freeze-configuration
updates.
Turn protected actions on in the Governance tab in settings, review each pending request with its
full payload and audit trail, and optionally subscribe a webhook channel to
`governance.approval.requested` and `governance.approval.decided`.
### July 5, 2026
### x402 budgets support session-aware tracking
Conto now tracks x402 budgets by `sessionId`, with burn rate and remaining budget for each API
payment session alongside aggregate protocol spend.
### June 22, 2026
### Anonymous agent sandbox
Conto now includes an anonymous agent sandbox flow that creates a test-mode organization, agent,
Tempo Testnet wallet, and SDK keys without email verification or manual setup.
The [agent sandbox quickstart](https://conto.finance/docs/quickstart/agent-sandbox) covers setup, a test payment, and the
claim flow.
### June 18, 2026
### Saved-contact trust checks
Conto Pay checks a saved contact's current payee details before preparing a payment. If a trust
warning is unresolved, you review the contact's current profile before the payment can continue.
### June 12, 2026
### CLI quickstart works end to end
The `npx @conto_finance/create-conto-agent` quickstart now produces a complete agent setup. The
wizard creates a wallet, installs the `conto` CLI into your project as a dev dependency, and writes a runnable `example.ts` with an opt-in `--execute` step.
New `conto doctor` command diagnoses auth, key scopes, wallet, custody mode, chain, balance, and base URL in one pass, backed by `GET /api/sdk/setup`.
Standard SDK keys cover the full payment lifecycle (`payments:request`, `payments:execute`, `payments:approve`, `payments:confirm`) plus read scopes, and the key creation response now lists the granted scopes.
### June 11, 2026
### Hosted Conto Pay profiles, requests, and receipts
Conto Pay now supports hosted profile URLs and handles, payee-initiated payment requests,
request review pages, direct-payment receipt pages, and a unified activity inbox for discovery,
receivables, approvals, and payment follow-up.
### June 11, 2026
### Network intelligence sharing controls and explainability
The Network workspace now exposes richer trust explainability, including detailed entity views,
contribution-aware trust lookups, Solana wallet reputation signals, and
organization-level sharing modes for full, partial, receive-only, or disabled participation.
### April 26, 2026
### Policy template library
The Policies workspace now includes use-case-driven templates for infrastructure, vendor payouts, marketing, research, compliance, and time-bound workflows, with recommended limits and similar-policy benchmarks.
### April 24, 2026
### Trust score explainability
Counterparty trust scores now show weighted score breakdowns, positive and negative signals, recommended actions, and the next milestone needed to improve trust.
### April 23, 2026
### Hosted Conto Pay assistant workflow
Conto Pay now supports a fuller end-to-end workflow in chat, including wallet readiness, recipient setup, approvals, retries, cancellations, and payment execution from one assistant thread.
### April 21, 2026
### Billing and usage workspace
Billing settings now show plan details, live usage meters, forecasts, alerts, and invoices in one workspace.
### April 16, 2026
### Machine spend analytics
A new Machine Spend Analytics view shows agent spend, counterparty activity, and payment protocol usage.
### April 12, 2026
### Global AI assistant
Conto now includes a global AI Assistant that can be opened from anywhere in the dashboard. Teams can use natural language to work across agents, wallets, policies, counterparties, and approvals from one interface.
---
## API Reference
### Authentication
User registration and authentication
- `POST /api/auth/register`: Register a new account
Create a new user account and organization, then send an email verification link. Privileged API credentials are not issued until the account is verified.
### Agent Onboarding
Agent-forward signup flows, including anonymous test-mode sandboxes and organization-token self-registration
- `GET /api/agents/sandbox`: Describe anonymous agent sandbox signup
Returns the supported inputs and defaults for creating a temporary Conto test sandbox. Returned credentials expire after 7 days.
- `POST /api/agents/sandbox`: Create an anonymous agent sandbox
Creates a temporary test workspace with an agent, test wallet, agent SDK key, sandbox API key, and sandbox setup key. No human sign-in is required. Use the setup key to configure policies and spend limits, and give the payment runtime only the SDK key. Payments are simulated. Returned keys are shown once and expire after 7 days.
- `POST /api/agents/sandbox/claim`: Claim an anonymous sandbox
Attaches an anonymous sandbox to the signed-in account. Use the sandbox API key returned by POST /api/agents/sandbox as the claim secret. The setup key cannot claim. Claiming revokes the setup key. The owner then manages controls in Conto.
- `GET /api/agents/register`: Describe organization-token agent registration
Returns public metadata for org-scoped agent self-registration. With a token query parameter, validates the registration token without exposing organization identity or capacity metadata.
- `POST /api/agents/register`: Register an agent into an existing organization
Lets an agent join an existing organization using a human-generated registration token. This path issues a least-privilege SDK key scoped to payments:request and agents:read, which reads its own setup. A retry with the same externalId returns the existing agent without the key and never changes its scopes. If that key lacks agents:read, the response includes a remediation for an organization owner.
- `POST /api/agents/sandbox/handoff`: Create a one-time human claim link
Authenticate with the sandbox organization API key. Returns a 15-minute claim token and browser URL. Do not log the token or URL. Share the URL only with the intended human owner.
- `GET /api/agents/sandbox/handoff`: Poll a sandbox handoff
Send the handoff token as Authorization: Bearer, not the sandbox API key. Poll no faster than the returned intervalSeconds. Status is pending or claimed until the token expires.
- `POST /api/agents/sandbox/handoff/complete`: Confirm sandbox ownership in the browser
Requires a signed-in verified human account, same-origin request, and unused handoff token. This does not enable production payments. Claiming revokes the sandbox setup key.
### Agents
AI agent management
- `GET /api/agents`: List all agents
Retrieve all agents for the current organization.
- `POST /api/agents`: Create a new agent
Create an AI agent that can be linked to wallets and policies for controlled payments. Requests made with org API keys should include `ownerMembershipId`; browser-session requests can default to the current member.
- `GET /api/agents/{id}`: Get agent by ID
Get agent details including linked wallets and transaction counts.
- `PATCH /api/agents/{id}`: Update agent
- `DELETE /api/agents/{id}`: Delete agent
- `GET /api/agents/{id}/wallets`: List wallets linked to agent
- `POST /api/agents/{id}/wallets`: Link wallet to agent
Link a wallet to an agent with delegation type, spend limits, and time window controls. Only PROD agents can pay from mainnet wallets. Linking a mainnet wallet to a DEV or STAGING agent still succeeds, and the response includes a `warnings` entry saying to set the agent's environment to PROD.
- `PATCH /api/agents/{id}/wallets/{walletId}`: Update agent-wallet link
Update spend limits, delegation type, or time window for an agent-wallet link.
- `DELETE /api/agents/{id}/wallets/{walletId}`: Unlink wallet from agent
- `GET /api/agents/{id}/policies`: List policies assigned to agent
- `POST /api/agents/{id}/policies`: Assign policy to agent
- `DELETE /api/agents/{id}/policies`: Unassign policy from agent
Uses query parameter: DELETE /api/agents/{id}/policies?policyId={policyId}
- `GET /api/agents/{id}/sdk-keys`: List SDK keys for agent
- `POST /api/agents/{id}/sdk-keys`: Generate SDK key for agent
Generate a new SDK API key for the agent. The full key is returned only once. Request body supports `name`, optional `expiresInDays`, and optional `keyType`. Standard keys cover the payment lifecycle (request, execute, approve, confirm) plus read scopes; admin keys add delegated organization-management scopes for agents, wallets, and policies.
- `GET /api/agents/{id}/freeze-config`: Get automatic protection settings
Returns whether automatic protection is enabled and the customer-configurable safety thresholds for an agent.
- `PATCH /api/agents/{id}/freeze-config`: Update automatic protection settings
Enables or disables automatic protection and updates selected safety thresholds. Omitted values keep their current setting.
- `POST /api/agents/{id}/freeze`: Freeze an agent
Stop an agent from making transactions. Associated wallets can also be frozen.
- `GET /api/agents/{id}/freeze`: Get freeze status
Returns whether the agent is currently frozen.
- `POST /api/agents/{id}/unfreeze`: Unfreeze an agent
Allow a frozen agent to make transactions again.
- `GET /api/agents/{id}/freeze-history`: Get agent freeze history
Paginated list of changes to this agent's freeze status.
### Wallets
Wallet management, provisioning, and funding
- `GET /api/wallets`: List all wallets
List organization wallets with balances and linked-agent counts.
- `POST /api/wallets`: Create a new wallet
Create a managed wallet with an explicit root-control model, or register an external address that you control. Organization-controlled managed wallets give the customer owner quorum root administration and export authority while Conto remains a policy-limited additional signer. This setting applies only to new wallets. Repeating a request for the same network and address returns the existing wallet.
- `GET /api/wallets/{id}`: Get wallet by ID
- `PATCH /api/wallets/{id}`: Update wallet
- `POST /api/wallets/{id}/provision`: Confirm managed wallet setup
Confirm that a managed wallet is reachable and synchronize its current balance. No request body is required.
- `POST /api/wallets/{id}/refresh-balance`: Refresh the configured wallet chain balance
Refresh the stored balance from the wallet’s configured chain. Unsettled local payments defer refresh so a chain snapshot cannot restore reserved funds. A concurrent wallet update also defers refresh; retry after settlement. No request body is required.
### Connected Cards
Connect customer-owned cards, delegate them to agents, set limits, and review card draws
- `GET /api/agents/{agentId}/external-cards`: List an agent's card assignments
List active and paused card assignments. Card credentials are never returned.
- `POST /api/agents/{agentId}/external-cards`: Assign a card alias to an agent
Create an integrated-path policy assignment for a masked customer-card alias.
- `PATCH /api/agents/{agentId}/external-cards/{cardId}`: Update an agent card assignment
Update controls, pause access, or resume access. Pause before editing when unused approvals are live.
- `DELETE /api/agents/{agentId}/external-cards/{cardId}`: Revoke an agent card assignment
Permanently revoke the assignment and cancel unused approvals. Claimed purchases remain available for reconciliation.
- `POST /api/connected-cards`: Connect a customer-owned card
Exchange the browser card token for a provider payment method and store a masked connected-card reference. The opaque token is never stored or returned.
- `GET /api/connected-cards`: List connected cards
List the organization connected-card references and their mandates, masked. Card credentials and provider tokens are never returned.
- `POST /api/connected-cards/{referenceId}/delegate`: Delegate a connected card to an agent
Bind an active tenant-scoped agent assignment to the connected payment method. When `assignment` is present, the agent limits and the binding are written in one transaction, and an agent limit above the card limit is rejected with 400 (with `raiseCardLimits` on a new card's first link, the card limits rise to fit instead). A card the agent already has under a different idempotency key, or a card removed from this agent, is rejected with 409 before any limit changes.
- `POST /api/connected-cards/{referenceId}/disconnect`: Disconnect a connected card from an agent
Take the card away from one agent: every live authorization on the binding is revoked, then the agent assignment and the provider binding are closed so no new credential can be minted. A purchase already in flight is counted in `reconciliationRequired` rather than released, and a credential already issued cannot be recalled. Idempotent. The card stays removed from this agent until a signed-in owner or admin adds it back with `/relink`.
- `POST /api/connected-cards/{referenceId}/mandates`: Create a connected-card mandate
Create a merchant-scoped mandate on the connected card. Both single-use and standing shapes are accepted; the mandate is created awaiting cardholder verification.
- `GET /api/connected-cards/mandates/{mandateId}`: Get a connected-card mandate
Return one masked mandate scoped to the organization.
- `POST /api/connected-cards/mandates/{mandateId}/verify`: Relay a mandate verify action
Relay one browser SDK verify action to the provider server-to-server. Only the cardholder can verify; the returned envelope carries no credential material.
- `POST /api/connected-cards/mandates/{mandateId}/revoke`: Revoke a connected-card mandate
Revoke a connected-card mandate. The mandate flips to REVOKED, its APPROVED-but-unexecuted draws are cancelled and released, and any EXECUTING or CONFIRMING draw is counted as reconciliationRequired without releasing it since a live single-use credential may already exist. Card credentials and provider tokens are never returned. Limited to a signed-in user.
- `PATCH /api/connected-cards/{referenceId}/limits`: Edit an agent's connected-card limits
Edit the agent's spend limits on one connected card (this agent's access to that card). Each agent limit must be at or below the parent card ceiling, and per-transaction must be at or below daily; a violation returns 400. Card-wide limits are not edited here. Card credentials and provider tokens are never returned. Limited to a signed-in user.
- `PATCH /api/connected-cards/{referenceId}/card-limits`: Edit a connected card's own limits
Edit the card-wide limits that cap every agent on this connected card. Per-transaction must be at or below daily, and weekly and monthly at or above daily. An agent limit never exceeds the card limit, so limits below any agent's limits on the card are rejected with 400, naming those agents. Agent limits are edited with `/limits`. Card credentials and provider tokens are never returned. Limited to a signed-in user.
- `POST /api/connected-cards/{referenceId}/relink`: Add a removed card back to an agent
Reconnect a card that was removed from this agent, with fresh agent limits checked against the card limits. Every earlier authorization stays revoked, so the cardholder must create and verify a new authorization before any purchase. A card removed because its agent was deleted cannot be added back. Limited to a signed-in user.
- `POST /api/connected-cards/reconcile`: Reconcile a stuck connected-card draw
Record the checkout outcome of a draw that needs a person. NOT_EXECUTED releases the draw's full hold from the card and agent limits, and EXECUTED with a smaller amount releases the difference. Neither verifies settlement or restores the provider's allowance capacity. Idempotent and tenant-scoped. No credential material is referenced or returned. Limited to a signed-in user.
- `GET /api/connected-cards/reconcile`: List connected-card draws needing reconciliation
List connected-card draws that need a person, masked, so an operator UI can resolve them: unresolved issuance, unacknowledged delivery, an agent report of a smaller charge or an unused card number, and a delivered card number with no reported outcome an hour after it expired. Card credentials, provider tokens, and the idempotency context are never returned. Limited to a signed-in user.
- `POST /api/connected-cards/mandates/{mandateId}/confirm-verification`: Confirm mandate verification
Read the server-authoritative allowance status and map it onto the mandate. An active allowance promotes the mandate to ACTIVE.
- `GET /api/connected-cards/draws`: List draws awaiting human review
- `POST /api/connected-cards/draws/{id}/decision`: Review a pending card draw
Owner, admin or manager review. After approval, the agent must retry the original draw with the same input and idempotency key. Current policy, mandate, expiry and limits are rechecked before issuance.
### Test Cards
Synthetic connected cards on every plan, with simulated checkout at Conto test merchants. No card is charged and no funds move.
- `POST /api/connected-cards/test`: Add a test card to an agent
Provisions a synthetic card for the agent, or returns the one it has. No card details are entered and no provider is involved. The signed-in owner or admin, or an organization API key with wallets:write acting for them, may add one. An agent key is refused.
- `GET /api/connected-cards/test`: List an agent's test cards
Every test authorization regardless of status, recent simulated receipts, and the test merchants' catalogs.
- `POST /api/connected-cards/test/{referenceId}/mandates`: Authorize a test purchase
Creates a test authorization for one of the Conto test merchants. Its verification is recorded as SIMULATED, which activates it. No cardholder ceremony runs. An agent key is refused.
- `PATCH /api/connected-cards/test/{referenceId}/limits`: Edit the agent's limits on a test card
Same rules as a live card: each limit at or below the test card's ceiling, per purchase at or below daily, spend counters untouched. An agent key is refused.
- `POST /api/connected-cards/test/{referenceId}/disconnect`: Remove a test card from an agent
Revokes its test authorizations and unlinks it. An agent key is refused.
- `POST /api/connected-cards/test/mandates/{mandateId}/revoke`: Revoke a test authorization
Cancels its pending and approved-but-unissued test purchases and releases their holds. An agent key is refused.
- `GET /api/connected-cards/test/orders/{orderId}`: Read a simulated receipt
- `GET /api/sdk/connected-cards/test`: List test cards for the agent
The calling agent's test cards and active test authorizations, masked, plus the Conto test merchants and what they sell.
- `POST /api/sdk/connected-cards/test/preview`: Preview a test-card purchase
Check a purchase against the agent's test-card limits and policies before asking for an authorization. Nothing is reserved and no credential is issued. The draw makes the final decision.
- `POST /api/sdk/connected-cards/test/draws`: Request a single-use test credential
Runs the connected-card draw pipeline in the test environment: the agent's limits, policies, human review, reservations, and idempotency. An approved draw returns testCredential once. A replay with the same idempotency key returns the decision without it. After a review is approved, resend the identical request with the same key.
- `POST /api/sdk/connected-cards/test/draws/{id}/acknowledge`: Acknowledge test credential receipt
Records receipt only. It is not a checkout and not a payment.
- `POST /api/sdk/connected-cards/test/checkout`: Complete a test checkout
The Conto test checkout, the only place a test credential works. Completes one simulated order at the test merchant the authorization named and returns the receipt. Retrying with the same draw and credential returns the same receipt. No card is charged and no funds move.
- `GET /api/sdk/connected-cards/test/orders/{orderId}`: Read one simulated receipt for this agent
### Policies
Spending policy configuration and rules
- `GET /api/policies`: List all policies
List all policies for the organization with their rules and assignments.
- `POST /api/policies`: Create a new policy
Create a complete policy with explicit scope, optional inline rules, and optional agent assignments. `organization` applies automatically to every payment in the organization. `assigned` applies only through agent, wallet, or card assignments. For backwards compatibility, omitted scope is inferred as `assigned` when `agentIds` are present and `organization` otherwise. Rules and assignments are created atomically.
- `POST /api/policies/simulate`: Simulate a policy (dry run)
Replay a candidate policy against the organization's real historical transactions and report what it would have decided, without saving anything. Read-only: no policy is created, no approval runs, no money moves. Rule types whose decision depends on state not present on a payment row (cumulative spend, velocity, live trust, geo, time-of-day) are not evaluated and are listed under summary.contextDependentRuleTypes.
- `GET /api/policies/{id}`: Get policy by ID
- `PATCH /api/policies/{id}`: Update policy
Update policy metadata or scope. Remove all target assignments before changing an assigned policy to organization scope.
- `DELETE /api/policies/{id}`: Delete policy
Permanently delete a policy and remove it from all agents. Consider deactivating (PATCH with isActive: false) instead if you want to preserve the policy configuration.
- `GET /api/policies/{id}/rules`: List rules for a policy
- `POST /api/policies/{id}/rules`: Add rules to a policy
Add one or more rules to an existing policy. For new policies, provide rules inline when creating the policy. Rules use AND logic, so every rule must pass for a payment to be approved.
- `GET /api/policies/{id}/versions`: List policy version history
Every change to a policy's definition or rules appends an immutable version. Returns the version history newest first. Assignments are not part of a version.
- `GET /api/policies/{id}/versions/compare`: Compare two policy versions
Diff two versions of a policy. Metadata fields are compared directly. Rules are compared by content, so an edited rule appears as one removed and one added.
- `GET /api/policies/{id}/versions/{version}`: Get a policy version snapshot
- `POST /api/policies/{id}/versions/{version}/rollback`: Roll back a policy to an earlier version
Restore a policy's definition and rules from an earlier version. This is itself a change, so it appends a new ROLLED_BACK version and, when the organization requires it, goes through the governance approval workflow. policyType is immutable and is not changed.
- `GET /api/contract-registry`: List known contract classifications
List organization-owned contract metadata used to resolve protocol names and categories during contract policy evaluation.
- `POST /api/contract-registry`: Save a contract classification
- `GET /api/contract-registry/{address}`: Get a contract classification
- `PATCH /api/contract-registry/{address}`: Update a contract classification
- `DELETE /api/contract-registry/{address}`: Delete a contract classification
### Approval Workflows
Organization approval routing, reviewer, timeout, and escalation controls
- `GET /api/approval-workflows`: List approval workflows
List the approval controls configured for the organization, including stable trigger, reviewer, timeout, escalation, and usage summaries.
- `POST /api/approval-workflows`: Create an approval workflow
Create a workflow that routes matching payments to role-based or named reviewers. Sequential review is supported for an ordered list of organization members.
- `GET /api/approval-workflows/{workflowId}`: Get an approval workflow
- `PATCH /api/approval-workflows/{workflowId}`: Update an approval workflow
Update supported workflow settings. The triggers object replaces the current trigger set; nested approval settings can be patched individually.
- `DELETE /api/approval-workflows/{workflowId}`: Delete an approval workflow
Delete a workflow after all pending approval requests have been resolved.
### Governance Approvals
Require a second person to approve sensitive organization changes, such as API key and policy edits
- `GET /api/governance/approval-settings`: Get governance approval settings
Return which organization changes need a second person to approve them, such as API key and policy changes. Requires `org:read`.
- `GET /api/governance/approval-requests`: List governance approval requests
When a change needs approval, the endpoint you called returns 202 with `requiresGovernanceApproval: true` and an `approvalRequest`. Poll this list for its status. An organization owner or admin other than the requester decides it in the Conto dashboard. After approval, send the identical request again with the `x-governance-approval-id` header. Requires `org:read`.
### Notification Channels
Send approval requests and payment events to email, Slack, Telegram, WhatsApp, or a webhook
- `GET /api/notification-channels`: List notification channels
List the channels that deliver approval requests and payment events for the organization. Requires `policies:read`.
- `POST /api/notification-channels`: Create a notification channel
Send approval requests and payment events to email, Slack, Telegram, WhatsApp, or a signed webhook. Webhook URLs must be publicly reachable. Requires `policies:write`.
- `GET /api/notification-channels/{id}`: Get a notification channel
Return one channel. Requires `policies:read`.
- `PATCH /api/notification-channels/{id}`: Update a notification channel
Rename a channel, change its destination or events, or pause it with `isActive: false`. Requires `policies:write`.
- `DELETE /api/notification-channels/{id}`: Delete a notification channel
Stop all deliveries to this channel and remove it. Requires `policies:write`.
- `POST /api/notification-channels/{id}/test`: Send a test notification
Deliver a sample approval request to the channel to confirm the destination works. No payment or approval is created. Requires `policies:write`.
### Organization Members
Look up members to assign as agent owners or approval reviewers
- `GET /api/organizations/me/members`: List organization members
List the members of the organization that owns the credential, oldest first. Requires `team:read` or `agents:write`.
### Transactions
Transaction history and management
- `GET /api/transactions`: List transactions
List payment transactions for the organization with merchant classification, customer-supplied reconciliation context, wallet and agent references, and network receipt details.
### Safety
Agent freeze, unfreeze, automatic protection, and freeze history
- `GET /api/freeze-events`: List organization-wide freeze history
Paginated list of agent freeze-status changes.
### SDK Payments
SDK endpoints for agent payment flow (request, execute, status)
- `GET /api/sdk/connected-cards`: List drawable connected cards
List the connected cards and active mandates the calling agent may draw against, masked. Scoped to the agent through its bindings. Card credentials and provider tokens are never returned.
- `POST /api/sdk/connected-cards/preview`: Preview a connected-card purchase
Check a purchase against the calling agent's card limits and policies before asking for an authorization. Nothing is reserved, no authorization is created, and no credential is issued. The result can change before the draw, which makes the final decision against the verified authorization.
- `POST /api/sdk/connected-cards/draws`: Draw a single-use card credential
Draw one single-use, merchant-locked, amount-capped card credential against a verified mandate. On a successful mint the response carries the card credential EXACTLY ONCE; it is never persisted server-side and cannot be re-fetched. An issued credential consumes allowance capacity even if unused. A reviewed request may resume with its original key. A replayed completed draw, or a DENIED, REQUIRES_APPROVAL, or reconciliation outcome, returns the decision without a credential.
- `POST /api/sdk/connected-cards/draws/{id}/acknowledge`: Acknowledge one-time credential receipt
Call after receiving a fresh credential. Idempotent and scoped to the calling agent. Receipt does not prove checkout or payment settlement. Available while new issuance is disabled.
- `POST /api/sdk/connected-cards/draws/{id}/outcome`: Report what happened with an issued card number
Call after checkout. USED at the full approved amount closes the draw. NOT_USED, or USED with a smaller amountMinor, is recorded and sent to a person, who confirms it before any held amount is released. A report never releases held spend and does not prove settlement. Repeating the same report returns the same result. Scoped to the calling agent. Live cards only. Available while new issuance is disabled.
- `POST /api/sdk/payments/request`: Request payment authorization
Request authorization for a payment. Returns APPROVED, DENIED, REQUIRES_APPROVAL, PROCESSING, or COMPLETED (an already completed payment).
- `GET /api/sdk/payments/{requestId}`: Get payment request status
Check the status of a payment request.
- `POST /api/sdk/payments/{requestId}/execute`: Execute approved payment
Execute a previously approved payment request from a managed wallet. The request must have status APPROVED and not be expired. Conto rechecks payment controls before sending. If an approver already executed the payment by approving it in Conto, returns the existing receipt with alreadyExecuted: true and sends nothing.
- `POST /api/sdk/payments/approve`: Check an external-wallet payment and get an approval token
Request policy approval for a payment the agent will execute via its own external wallet. Returns an approval token to use when confirming. Requires `payments:approve` scope.
- `POST /api/sdk/payments/{requestId}/confirm`: Confirm external wallet payment execution
Confirm that an externally approved payment was executed by providing the transaction hash and approval token. Requires `payments:confirm` scope.
### Checkout checkpoints
Agent checkout policy checks before payment-method entry and payment submission
- `POST /api/sdk/checkout-checkpoints`: Check a proposed checkout before opening its payment method
Requires payments:request. The caller must stop when the response does not approve the purchase. This check is enforced by the caller, not by the payment method. When the rules require approval, the spend is held, the owner is asked, and the checkpoint returns stage AWAITING_APPROVAL with the approval request id. Poll the checkpoint until the owner decides.
- `GET /api/sdk/checkout-checkpoints/{id}`: Read a checkout checkpoint
Requires transactions:read. Read before retrying after an uncertain response, and to poll while the stage is AWAITING_APPROVAL.
- `POST /api/sdk/checkout-checkpoints/{id}/confirm`: Recheck final checkout details before payment submission
Requires payments:execute. A changed total is refused; cancel and begin a new checkpoint for the final total.
- `POST /api/sdk/checkout-checkpoints/{id}/outcome`: Report the outcome of a confirmed checkout
Requires payments:confirm. PAID keeps the spend counted, NOT_PAID releases it, and UNKNOWN keeps it reserved for reconciliation. The report is not verified settlement evidence.
- `POST /api/sdk/checkout-checkpoints/{id}/cancel`: Cancel an unconfirmed checkout checkpoint
Requires payments:execute. Releases the reserved spend, and withdraws the approval request of a checkpoint waiting for the owner. After confirmation, report an outcome instead.
### Conto Commerce
Purchases from online stores for your users with an organization API key: buyers, hosted setup, payment methods, delegations and purchases. Test mode is open to every organization.
- `POST /api/purchasing/buyers`: Create a buyer
Requires agents:create. Maps one of your users to a buyer with its own agent, or returns the existing buyer for the same external id. Issue that agent an SDK key with the agent key API.
- `GET /api/purchasing/buyers`: List buyers
Requires agents:read.
- `GET /api/purchasing/buyers/{id}`: Get a buyer
Requires agents:read.
- `POST /api/purchasing/buyers/{id}/stop`: Stop a buyer
Requires agents:suspend. Stops the buyer’s agent: no new purchases or card credentials, and pending authorizations are revoked. A purchase whose payment may already be submitted stays visible until its outcome is known.
- `POST /api/purchasing/company-cards`: Add a company card
Requires wallets:create. One card your organization pays its buyers’ purchases with, under a per-order cap and a monthly cap for all buyers together. Test company cards only for now: they pay Conto test stores. Needs company cards enabled for your organization.
- `GET /api/purchasing/company-cards`: List company cards
Requires wallets:read. Masked cards only.
- `GET /api/purchasing/company-cards/{id}`: Get a company card
Requires wallets:read.
- `PATCH /api/purchasing/company-cards/{id}`: Change a company card’s caps
Requires wallets:create. Each buyer’s own limits stay as they are.
- `DELETE /api/purchasing/company-cards/{id}`: Remove a company card
Requires wallets:create. No buyer can start or pay a purchase with the card after this. A purchase whose payment may already be submitted stays visible until its outcome is known.
- `PUT /api/purchasing/buyers/{id}/company-card`: Let a buyer pay with a company card
Requires wallets:create. The buyer draws from the company card within the per-order, daily and monthly limits you set for them, so one buyer cannot use up the card. Orders at or below reviewAboveMinor go ahead without review. Orders above it wait for your purchase_review answer. Calling it again replaces the limits. The buyer must not have a card of their own.
- `DELETE /api/purchasing/buyers/{id}/company-card`: Stop a buyer paying with a company card
Requires wallets:create.
- `POST /api/purchasing/payment-methods/setup-sessions`: Create a hosted setup link
Requires wallets:create. A single-use link to Conto’s hosted setup page for one buyer, where the buyer sets limits, adds a card and authorizes the agent. A test setup offers a test card that pays Conto test merchants only.
- `GET /api/purchasing/payment-methods`: List payment methods
Requires wallets:read. Masked cards only.
- `DELETE /api/purchasing/payment-methods/{id}`: Remove a payment method
Requires wallets:create. Removes the card and revokes its delegations. Refused while a purchase uses it.
- `GET /api/purchasing/delegations`: List delegations
Requires wallets:read. Delegations are created by the buyer on the hosted setup page.
- `GET /api/purchasing/delegations/{id}`: Get a delegation
Requires wallets:read.
- `POST /api/purchasing/delegations/{id}/revoke`: Revoke a delegation
Requires wallets:create. New purchases under it are refused, and a purchase that has not requested its card credential is cancelled.
- `GET /api/purchasing/allowances`: List allowances
Requires wallets:read. An allowance lets the buyer’s agent buy at one store without the buyer’s review, inside a per-purchase cap and a period cap, until it expires. Only the buyer grants one, while approving a purchase at that store.
- `GET /api/purchasing/allowances/{id}`: Get an allowance
Requires wallets:read.
- `POST /api/purchasing/allowances/{id}/revoke`: Revoke an allowance
Requires wallets:create. Later purchases at the store go to the buyer’s review. A purchase under the allowance that has not requested its card credential stops before it does.
- `GET /api/purchasing/recurring-purchases`: List repeat orders
Requires transactions:read. Only the buyer sets up a repeat order, while approving a purchase on the hosted review page.
- `GET /api/purchasing/recurring-purchases/{id}`: Get a repeat order
Requires transactions:read.
- `POST /api/purchasing/recurring-purchases/{id}/cycles`: Start one order of a repeat order
Requires transactions:write. Starts one order of the buyer’s repeat order. The same cycle key returns the same order. Conto shops it. When it matches the order, the buyer gets a heads-up and it pays at its payment time unless they skip it. When the items changed or the total is over the price room, it waits for the buyer’s review. Refused while another order is open, before the schedule allows the next one, or while the buyer paused or ended the repeat.
- `POST /api/purchasing/recurring-purchases/{id}/cycles/{cycleKey}/skip`: Skip one order of a repeat order
Requires transactions:write. Skips the order with this cycle key, also before it starts. An order waiting for its payment time closes unpaid.
- `POST /api/purchasing/recurring-purchases/{id}/cancel`: Cancel a repeat order
Requires wallets:create. Its allowance and card authorization end, and an order waiting for its payment time closes unpaid.
- `POST /api/purchasing/purchases`: Start a purchase for a buyer
Requires transactions:write. Starts a purchase for one of the organization’s buyers with the organization key, as that buyer’s agent. The buyer’s delegation, review and card verification apply exactly as for an agent key, and the same idempotency key with the same body returns the same purchase. Returns at once.
- `GET /api/purchasing/purchases`: List the organization’s purchases
Requires transactions:read.
- `POST /api/purchasing/purchases/{id}/responses`: Answer a purchase action
Requires transactions:write. Answers an open buyer_details action with the organization key, or the purchase_review of a purchase paid with your company card: { decision: "approve", totalMinor } with the quote total you saw, or { decision: "decline" }. A review answer is applied at once. Other reviews, approvals and card verification cannot be answered here.
- `GET /api/purchasing/purchases/{id}/events/stream`: Stream a purchase’s events
Requires transactions:read. Same server-sent events and cursor rules as the agent route. Webhooks deliver the same events to a server without holding a connection open.
- `GET /api/purchasing/purchases/{id}`: Get a purchase
Requires transactions:read.
- `GET /api/purchasing/purchases/{id}/events`: List a purchase’s events
Requires transactions:read. Same cursor rules as the agent route.
- `POST /api/purchasing/purchases/{id}/cancel`: Cancel a purchase
Requires transactions:write. Same rules as the agent route. Never refunds.
- `POST /api/purchasing/purchases/{id}/action-sessions`: Create a hosted action link
Requires wallets:create. A single-use link that opens the purchase’s buyer actions, such as review and card verification, on Conto’s hosted page for the purchase’s buyer.
- `GET /api/purchasing/settings`: Get purchasing settings
Requires settings:read.
- `PUT /api/purchasing/settings`: Update purchasing settings
Requires settings:write. The https origins a hosted page may return buyers to.
- `GET /api/purchasing/capabilities`: Get purchasing capabilities
Requires agents:read. What this organization can run now: currencies, card networks and checkout paths (browser_checkout, store_order, test_store), with a reason for anything unavailable.
### Conto Commerce agent
The same purchases with an agent’s own SDK key, for agents that call Conto themselves
- `POST /api/sdk/purchases`: Start a purchase
Requires payments:request. Starts a purchase for the agent’s own buyer under an active delegation and returns at once. The purchase continues in the background until it succeeds, ends, or needs someone. The maximum cost can narrow the delegation, never widen it. USD only.
- `GET /api/sdk/purchases`: List purchases
Requires transactions:read. The agent’s own purchases, newest first.
- `GET /api/sdk/purchases/{id}`: Get a purchase
Requires transactions:read. The current snapshot: status, phase, version, the required action and payment evidence. Evidence that is missing is reported as unknown.
- `POST /api/sdk/purchases/{id}/responses`: Answer a purchase action
Requires payments:request. Answers an open buyer_details action with the fields it lists. The answer is acknowledged at once and applied in the background. The same answer again returns the same acknowledgment. A different answer conflicts. Review, approval and card verification cannot be answered here.
- `GET /api/sdk/purchases/{id}/events`: List purchase events
Requires transactions:read. Durable history after a version cursor, oldest first. A cursor ahead of the purchase returns CURSOR_INVALID: read the purchase and resume from its version.
- `GET /api/sdk/purchases/{id}/events/stream`: Stream purchase events
Requires transactions:read. Server-sent events after a version cursor, from the after query parameter or the Last-Event-ID header. Each event id is its version, so a duplicate after reconnecting is safe to drop. A cursor ahead of the purchase gets a resync event with the current purchase. The stream ends with an end event when the purchase finishes, or after about 50 seconds: reconnect with the last event id.
- `POST /api/sdk/purchases/{id}/cancel`: Cancel a purchase
Requires payments:request. Before payment the purchase stops. Once payment may have been submitted it stays in reconciliation until its outcome is known. Cancelling never refunds or reverses a payment.
- `GET /api/sdk/recurring-purchases`: List repeat orders
Requires transactions:read. Only the buyer sets up a repeat order, while approving a purchase on the hosted review page.
- `GET /api/sdk/recurring-purchases/{id}`: Get a repeat order
Requires transactions:read.
- `POST /api/sdk/recurring-purchases/{id}/cycles`: Start one order of a repeat order
Requires payments:request. Starts one order of the buyer’s repeat order. The same cycle key returns the same order. Conto shops it. When it matches the order, the buyer gets a heads-up and it pays at its payment time unless they skip it. When the items changed or the total is over the price room, it waits for the buyer’s review. Refused while another order is open, before the schedule allows the next one, or while the buyer paused or ended the repeat.
- `POST /api/sdk/recurring-purchases/{id}/cycles/{cycleKey}/skip`: Skip one order of a repeat order
Requires payments:request. Skips the order with this cycle key, also before it starts. An order waiting for its payment time closes unpaid.
### SDK Agent
SDK endpoints for agent self-service
- `GET /api/sdk/agents/me`: Get agent profile
Get the authenticated agent profile and summary statistics. Requires `agents:read`.
- `GET /api/sdk/all`: Get normalized agent data by section
Deprecated compatibility endpoint. Preserves the historical section names and customer-useful fields while normalizing provider custody values, transaction policy decisions, alert status/severity, and other customer states. Wallet, policy, counterparty, transaction, alert, and analytics sections require their corresponding read scopes; capability summaries require `wallets:read`. An unavailable requested section contains a legacy scope error object and is also listed in `omittedSections`. Unknown `include` values are ignored. Raw alert metadata is not returned. Use `/api/sdk/setup` and the dedicated list endpoints for new integrations.
- `GET /api/sdk/setup`: Get agent bootstrap configuration
Returns the authenticated agent profile, normalized wallets, all effective organization/agent/wallet policies, counterparties, granted scopes, and versioned setup diagnostics. Requires `agents:read`. The response covers only this agent and includes its wallet balances, limits, and policy rules without `wallets:read` or `policies:read`. `diagnostics` says whether this key can request and execute payments, how each wallet executes, whether test and live connected cards are ready, and which issues need the agent or an owner, each with documented next actions.
### SDK Wallets
SDK endpoints for agent wallet access
- `GET /api/sdk/wallets`: List agent wallets
List all wallets assigned to the authenticated agent.
- `GET /api/sdk/wallets/{id}`: Get wallet details
### SDK Transactions
SDK endpoints for agent transaction history
- `GET /api/sdk/transactions`: List agent transactions
- `GET /api/sdk/transactions/{id}`: Get transaction details
- `GET /api/sdk/transactions/reconciliation`: Reconcile payments (requested vs settled)
Per-payment reconciliation: requested amount, settled amount, settlement hash, status, and a reconciliation state. Pass format=csv for a full server-side export.
- `POST /api/sdk/transactions/{id}/retry`: Retry a failed transaction
Create or resume one retry payment after a definitive onchain failure. The original receipt is preserved, controls are rechecked, and uncertain outcomes cannot be retried. Requires `transactions:write` and `payments:execute` scopes.
### SDK Policies
SDK endpoints for agent policy inspection
- `POST /api/sdk/policies/exceptions`: Request a scoped policy change
Create a reviewable policy change request for the authenticated agent. Creating a request does not edit a policy. Responses preserve the legacy uppercase `type` and `status` fields and add normalized customer-facing aliases. Requires `policies:exceptions` scope.
- `GET /api/sdk/policies/exceptions`: List policy change requests
List the authenticated agent’s customer-visible policy change requests with compatibility fields and normalized review states. The same items are returned under `exceptions` and the additive `requests` alias. Requires `policies:exceptions` scope.
- `GET /api/sdk/policies`: List agent policies
List active policies that govern the authenticated agent. Organization policies always apply. Assigned policies apply through the agent and, when `walletId` is supplied, that actively linked wallet.
### Counterparties
Counterparty management and trust
- `GET /api/counterparties`: List counterparties
List counterparties (payment recipients) with their trust levels and transaction history. Trust levels: TRUSTED, VERIFIED, UNKNOWN, BLOCKED.
- `POST /api/counterparties`: Create a counterparty
### API Keys
Organization API key management
- `GET /api/api-keys`: List organization API keys
- `POST /api/api-keys`: Create organization API key
Create an org-level API key with scoped permissions. The full key is returned only once. Org keys always expire: `expiresInDays` defaults to 365 and is capped at 365. `null` is rejected with `400 VALIDATION_ERROR`.
- `DELETE /api/api-keys/{id}`: Revoke organization API key
Soft-revoke an organization API key. The key remains visible for audit history but can no longer authenticate.
- `POST /api/api-keys/{id}/rotate`: Rotate organization API key
Create a replacement key with the same scopes, revoke the old key, and link the lifecycle records. The replacement key is returned only once.
### Alerts
Alert management
- `GET /api/conto-pay/reports`: List Conto Pay trust reports
List trust reports concerning the current organization’s Conto Pay profiles. Requires alert read permission.
- `PATCH /api/conto-pay/reports/{reportId}`: Review a Conto Pay trust report
Update review status and optionally hide the reported profile from broad directory search. Requires alert management permission.
- `GET /api/alerts`: List alerts
List customer-visible alerts and notifications for the organization.
### Conto Pay
Conto Pay readiness and production access
- `GET /api/conto-pay/cards`: Get Conto Pay connected cards
Returns the organization's connected cards with masked card details, the agents each card is delegated to, their limits and spend today, authorization counts, card purchases waiting for review, and recent card purchases. Read-only. Returns availability `upgrade` when the plan does not include connected cards and `unavailable` when connected cards are not available to the organization.
- `GET /api/conto-pay/readiness`: Get Conto Pay launch readiness
Returns setup, payment-mode, controls, and test-flow context without private operational fields.
- `POST /api/conto-pay/production-access`: Request Conto Pay production access
Requests production access for live payments and returns stable status and next-action fields.
### Analytics
Conto Pay activity analytics
- `GET /api/conto-pay/analytics`: Get Conto Pay activity analytics
Returns organization-scoped activity totals, conversion rates, and stage counts for dashboards and reconciliation exports.
### x402
x402 protocol pre-authorization, recording, and analytics
- `POST /api/sdk/x402/pre-authorize`: Pre-authorize an x402 payment
Check policy approval before the agent signs an x402 payment with its own wallet. An approved decision returns a single-use grant for the exact wallet, recipient, and amount, and reserves the amount against spend limits for 10 minutes. Conto checks this decision but does not hold the key, so an agent that signs anyway is detected at record time, not stopped. For Privy-managed wallets use /api/sdk/x402/pay, where Conto signs and enforces.
- `POST /api/sdk/x402/pay`: Approve and sign an x402 payment (Privy-managed wallet)
Requires payments:execute. For agents whose wallet is a Privy-managed EVM wallet. Runs the same policy, budget, and grant issuance as pre-authorize. Only when approved, Conto signs an EIP-3009 TransferWithAuthorization for exactly the approved payee, amount, and USDC contract, and returns the payment header to retry the resource request with. The agent never holds the key, so Conto enforces this decision. Each grant is signed at most once. Pass the returned grant to /api/sdk/x402/record after settlement; an unrecorded grant is settled from the onchain nonce after the authorization expires. Returns 404 while managed x402 signing is not enabled for the deployment.
- `POST /api/sdk/x402/record`: Record x402 payment(s)
Record a single x402 payment or a batch of micropayments. Send the grant from an approved pre-authorize: Conto checks the settlement onchain against the grant recipient and amount, then consumes the grant. A record without a valid, unexpired grant is accepted because the payment already settled, but it is flagged as a policy bypass and a critical alert is raised. An organization can also configure Conto to freeze the agent automatically.
- `GET /api/sdk/x402/services`: List x402 services used by agent
- `GET /api/sdk/x402/budget`: Check x402 budget remaining
Get aggregate x402 spend, effective limits, and a customer-facing usage trend.
### MPP
Machine Payment Protocol pre-authorization, recording, and analytics
- `POST /api/sdk/mpp/pre-authorize`: Pre-authorize an MPP payment
Check policy approval before creating an MPP credential. The agent sends the WWW-Authenticate: Payment challenge contents and Conto evaluates policies without executing anything. An approved decision returns a grant and reserves the amount against spend limits. A charge grant lasts 10 minutes. A session grant lasts for the session: the shortest MPP_MAX_SESSION_DURATION rule that applies, up to 7 days, or 24 hours when no rule applies.
- `POST /api/sdk/mpp/record`: Record MPP payment(s)
Record a single MPP payment or a batch of micropayments. Send the grant from an approved pre-authorize: a charge grant must match the recipient and exact amount, and a session grant accepts amounts up to its remaining ceiling. A record without a valid, unexpired grant is accepted because the payment already settled, but it is flagged as a policy bypass and a critical alert is raised. An organization can also configure Conto to freeze the agent automatically. To close a session with nothing more to charge, send amount 0 with intent session, closeSession true, and the session grant: Conto closes the session and releases the unused grant without recording a transaction.
- `GET /api/sdk/mpp/services`: List MPP services used by agent
List all MPP services the agent has interacted with, including spend summaries, price trends, and known endpoints.
- `GET /api/sdk/mpp/budget`: Check MPP budget remaining
Get aggregate MPP spend, effective limits, and a customer-facing usage trend.
### SDK A2A Payments
Agent-to-agent payment requests and resolution
- `POST /api/sdk/a2a/request`: Create an A2A payment request
Request payment from another Conto agent. The requesting agent is asking the target agent to send them funds. Target can be specified by agent ID or wallet address.
- `GET /api/sdk/a2a/requests`: List A2A payment requests
List payment requests for the authenticated agent. Filter by direction (incoming/outgoing) and status.
- `GET /api/sdk/a2a/requests/{id}`: Get A2A payment request details
Get details of a specific A2A payment request, including direction and status.
- `POST /api/sdk/a2a/requests/{id}/approve`: Approve an A2A payment request
Approve an incoming request as the paying agent and receive the URL used to execute it. The agent can approve only when the payer's policies allow the payment outright. If a policy requires approval, the agent cannot clear it: the request stays pending, the response is 403 with `code: "HUMAN_APPROVAL_REQUIRED"`, and an authorized member of the paying organization must approve it while signed in to Conto (`POST /api/a2a/requests/{id}/approve`, session auth, `transactions:approve`). Policy denials cannot be overridden. Requires the `payments:approve` scope.
- `POST /api/sdk/a2a/requests/{id}/reject`: Reject a pending A2A payment request
Reject an incoming A2A payment request as the paying agent. An optional reason can be stored with the request.
- `POST /api/sdk/a2a/requests/{id}/execute`: Execute an approved A2A payment request
Execute a previously approved A2A payment request. The request must be in an approved state. Policy is evaluated again at execution. If it requires approval and no authorized human in the paying organization has approved the request, execution is refused with `code: "HUMAN_APPROVAL_REQUIRED"`. An approval recorded by the paying agent never satisfies a policy that requires human approval.
- `GET /api/sdk/a2a/resolve`: Resolve a wallet address to a Conto agent
Check if a wallet address belongs to a registered Conto agent. Useful for verifying recipients before sending A2A payment requests.
- `GET /api/sdk/a2a/stats`: Get A2A payment statistics
Get aggregate A2A payment statistics for the authenticated agent.
### SDK Counterparties
SDK endpoints for counterparty and network trust management
- `GET /api/sdk/counterparties`: List agent counterparties
List counterparties the agent has interacted with, including trust levels and transaction history. Requires `counterparties:read` scope.
- `POST /api/sdk/counterparties`: Create a counterparty for this agent
Register a new counterparty (payment recipient). Requires `counterparties:write` scope.
- `GET /api/sdk/counterparties/{id}`: Get counterparty details
Get detailed counterparty info including trust relationship and recent transactions. Requires `counterparties:read` scope.
- `PATCH /api/sdk/counterparties/{id}`: Update a counterparty
Update counterparty details. Requires `counterparties:write` scope.
- `GET /api/sdk/network/trust/{address}`: Get network trust for an address
Look up normalized trust information for a wallet address or domain. Returns entity, relationship, activity, and risk-signal summaries. Requires `network:read` scope.
### SDK Approvals
SDK endpoints for listing and deciding payment approval requests
- `GET /api/sdk/approval-requests`: List pending approval requests
List approval requests for the agent, including stable customer statuses, counterparty identifiers, and links to inspect or act on each request.
- `POST /api/sdk/approval-requests/{id}/decide`: Approve or deny a payment review from an agent skill
Submit the assigned human owner's decision for a payment approval request. A new decision requires the matching one-time human action token from an independently delivered approval notification; an idempotent retry of an already-recorded decision does not. The assigned owner must be active and permitted to approve transactions. Configured approvers, review order, expiry, and duplicate-decision rules apply. A final approval automatically executes managed wallets; external wallets receive an explicit execute-and-confirm handoff. The execution outcome uses the same fixed codes and reasons as the execute endpoint.
### SDK Alerts & Analytics
SDK endpoints for alerts, analytics, audit logs, spending limits, and rate limits
- `GET /api/sdk/alerts`: List agent alerts
List alerts for the authenticated agent. Requires `alerts:read` scope. Filter by status and severity.
- `GET /api/sdk/alerts/{id}`: Get alert details
Get details of a specific alert. Requires `alerts:read` scope.
- `POST /api/sdk/alerts/{id}`: Acknowledge or resolve an alert
Update an alert status by acknowledging or resolving it. Requires `alerts:write` scope.
- `POST /api/sdk/budget-request`: Request a spending budget
Submit a budget request for human approval. Only one active budget request per agent is allowed at a time.
- `GET /api/sdk/budget-request`: List budget requests
List budget requests for the authenticated agent and include the currently active approved budget when one exists.
- `GET /api/sdk/analytics`: Get spending analytics
Get spending analytics for the authenticated agent including volume trends, category breakdowns, and top merchants. Requires `analytics:read` scope.
- `GET /api/sdk/audit-logs`: List audit logs
Get audit logs of actions performed by or affecting this agent. Requires `audit:read` scope.
- `GET /api/sdk/service-spend/summary`: Get machine spend summary
Return a unified x402 and MPP summary for the authenticated agent, including total spend, call volume, and recent machine-spend alerts.
- `GET /api/sdk/service-spend/services`: List machine spend services and endpoints
Return the top machine-payable services and endpoints for the authenticated agent across x402 and MPP flows.
- `GET /api/sdk/spending-limits`: Get spending limits and usage
Get per-wallet spending limits and current usage for the authenticated agent. Requires `wallets:read` scope.
- `GET /api/sdk/rate-limits`: Get SDK request limits
Get the published SDK request ceilings and customer usage counts for the authenticated agent.
### Chains
Supported chains and stablecoins
- `GET /api/chains`: List supported chains
List the chains and stablecoins Conto supports, with the default chain ID. No authentication required.
## Support
- Dashboard: https://conto.finance
- Documentation: https://conto.finance/docs
- Email: support@conto.finance