Skip to main content

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 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 and MPP payments. 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. Platforms use admin.purchasing.allowances to inspect and revoke existing buyer-approved store allowances. These methods do not grant spending authority. See store allowances for permissions and revocation boundaries.

payments.request()

Request authorization for a payment. This evaluates policies without executing.

Parameters

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

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

Example

payments.execute()

Execute an approved payment request.
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: Denials and approval holds include reasons, for example This payment exceeds a configured spending limit. See payment reasons.

Parameters

Response

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

payments.pay()

Convenience method that requests and executes in one call.

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

autoExecute flag

autoExecute: true requests authorization and executes an approved payment in one API call.
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

payments.status()

Check the status of a payment request.

Response

Example: polling for confirmation

Use bounded status 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 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. 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. 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.
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 to request a single-use credential, acknowledge delivery, report the outcome, and handle approvals or uncertain outcomes. Start with card setup.

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 and Conto with 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.

Next steps

Error handling

Handle payment errors

Examples

See complete integration examples

Controlled-payment test evidence

GET /api/sdk/test-report reads selected allowed, denied, and optional review payment request IDs. reviewOutcome is approved (default), denied, or expired. Both transactions:read and audit:read are required. Requests must belong to the authenticated agent and organization. Control verification additionally requires the same recorded wallet and currency. Explicitly select the same walletId when creating each exercise; unvalidated wallet IDs are not stored on denial. Older denied requests without wallet attribution remain incomplete evidence. The report includes safe numeric spending controls, reviewer decisions, settlement receipts, and audit references. It distinguishes payment completion, control verification, audit evidence, and file persistence. An omitted review is not_tested; simulated receipts do not prove onchain settlement. The API never executes a payment or marks a report saved. Use conto test-report for an owner-only JSON file, or the read-only MCP tool get_test_report to inspect the snapshot. Deploy the endpoint before publishing CLI/MCP releases that call it. New sandbox SDK keys include the audit read scope; existing keys do not gain it retroactively.