# 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 Agents and budget owners connect through the SDK, REST API, MCP, CLI, or Conto Pay. Conto checks each request with its policy engine, network intelligence, approvals, and agent wallets. Approved payments settle in stablecoins or x402 pay-per-call, and each one is recorded with the agent, policy result, approval, counterparty, settlement, and receipt. Agents and budget owners connect through the SDK, REST API, MCP, CLI, or Conto Pay. Conto checks each request with its policy engine, network intelligence, approvals, and agent wallets. Approved payments settle in stablecoins or x402 pay-per-call, and each one is recorded with the agent, policy result, approval, counterparty, settlement, and receipt. 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 Managed wallet flow. The agent requests a payment, Conto checks the owner's policy, the owner's approver decides only when the payment needs review, the managed wallet sends the funds, and the payment is logged with full context. Managed wallet flow. The agent requests a payment, Conto checks the owner's policy, the owner's approver decides only when the payment needs review, the managed wallet sends the funds, and the payment is logged with full context. 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 Agent-controlled wallet flow. The agent asks Conto to authorize a payment, Conto checks the owner's policy, the owner's approver decides only when the payment needs review, the agent's own signer sends the transaction within 10 minutes, and the agent confirms the transaction ID so Conto can record it. Agent-controlled wallet flow. The agent asks Conto to authorize a payment, Conto checks the owner's policy, the owner's approver decides only when the payment needs review, the agent's own signer sends the transaction within 10 minutes, and the agent confirms the transaction ID so Conto can record it. 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