Machine View
Payments
Source: https://conto.finance/docs/sdk/payments
# Payments
> Request payment authorization, execute onchain transactions, and use autoExecute for single-call payments
- Human URL: https://conto.finance/docs/sdk/payments
- Raw Markdown: https://conto.finance/docs/sdk/payments.md
- Terminal view: https://conto.finance/ai/docs/sdk/payments
Documentation group: Build
# Payments API
The payments API allows agents to request authorization and execute stablecoin payments.
Note:
Payment SDK routes enforce explicit API-key scopes. Reading payment status requires
`transactions:read`; requesting, approving, and executing payments require their corresponding
`payments:request`, `payments:approve`, and `payments:execute` scopes.
Payment audit entries snapshot the credential ID, sorted scopes, authentication type, and key type
used for the decision. This makes later reviews independent of subsequent key rotation or scope
changes.
## Credential-gated checkout relay alpha
The default-off checkout relay is a separate hard-control path for supported merchant checkouts.
An admin SDK key with `merchant:purchase` can issue and inspect a five-minute grant at:
- `POST /api/sdk/checkout-relay/grants`
- `GET /api/sdk/checkout-relay/grants/{grantId}`
Each grant is signed and bound to the agent, card reference, purchase intent, connector, merchant,
exact HTTPS origin, USD ceiling, cart hash, policy version, and decision-evidence hash. SDK
responses never expose the provider credential reference. Only an authenticated isolated worker can
consume a grant once and record its terminal outcome.
The alpha is disabled unless the global switch, organization allowlist, merchant allowlist, signing
secret, and independent worker secret are all configured. It does not accept or store PAN/CVC.
## 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
| Wallet model | Use this flow | What Conto can stop |
| ------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------- |
| **Managed** (`MANAGED`) | `request -> execute` | Conto stays in the execution path and can block the spend |
| **External** (`EXTERNAL`) | `approve -> transfer -> confirm` | Conto governs the Conto-routed flow, but cannot block a direct self-signed transfer outside Conto |
`payments.execute()` is for managed wallets. If your agent holds the signing keys, use the
external-wallet `approve -> confirm` flow instead.
Note:
Managed wallets are provisioned and executed exclusively through Privy. The payment request route
selects only Privy-backed managed wallets; self-custodied wallets must use the external-wallet
flow. A managed wallet can be organization-controlled or Conto-managed. Both use the same
`request -> execute` API, while their root ownership and key-access boundaries differ. See [Custody
Modes](https://conto.finance/guides/custody-modes).
## payments.request()
Request authorization for a payment. This evaluates policies without executing.
```typescript
const request = await conto.payments.request({
amount: 100,
recipientAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f...',
recipientName: 'OpenAI',
purpose: 'GPT-4 API credits',
category: 'AI_SERVICES',
});
```
### Parameters
| Parameter | Type | Required | Description |
| ----------------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `amount` | number | Yes | Payment amount |
| `recipientAddress` | string | Yes | Wallet address. `0x` + 40 hex chars for EVM, or base58 (32-44 chars) for Solana. Validated via Zod schema. |
| `recipientName` | string | No | Human-readable name |
| `purpose` | string | No | Why this payment is needed |
| `category` | string | No | Spending category |
| `context` | object | No | Additional metadata |
| `walletId` | string | No | Specific wallet to use |
| `urgency` | string | No | LOW, NORMAL, HIGH, CRITICAL |
| `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. |
Use `context.department` and `context.project` when the payment should count against a scoped
budget. These dimensions are persisted on the resulting transaction and evaluated together with
the payment `category`.
Set `context.recipientCountry` to a two-letter ISO country code when geographic policy evaluation
needs the recipient jurisdiction. Conto trims the value, normalizes it to uppercase, and includes
it in the idempotency fingerprint for the request.
Note:
**External wallet users:** If your agent controls its own wallet keys and uses the
`/api/sdk/payments/approve` endpoint instead, `chainId` is a required parameter. See the [Agent
Skills guide](https://conto.finance/sdk/skills) (OpenClaw and Hermes) for the external-wallet flow.
Note:
If you include `context.invoice`, Conto links the invoice details to the payment for
reconciliation and approval review. Supported invoice fields are `vendorId`, `vendorAddress`,
`id`, `hash`, `sourceUrl`, `payload`, `expectedAmount`, `currency`, and `dueDate`. Delta
verification workflows and `payment.executed` webhook payloads prefer this typed invoice record
for new requests. For a full setup and live test example, see [Delta Verification
Setup](https://conto.finance/guides/delta-setup) and [Delta Smoke Test](https://conto.finance/guides/delta-smoke-test).
Note:
Delta access is currently enabled by Conto during onboarding. If you want to use this flow,
contact [sales@conto.finance](mailto:sales@conto.finance).
Note:
If an approval workflow matches any non-denied request, Conto opens the approval request
immediately and includes `approvalRequestId` in the API response. This includes workflows that
intentionally gate otherwise policy-approved payments. If the payment controls require approval
and no custom workflow matches, Conto still opens an actionable review and returns its
`approvalRequestId`.
For production agents, a payment request to an unknown recipient first creates a reviewable
`DISCOVERED` counterparty and agent relationship. The request returns `REQUIRES_APPROVAL` until an
operator advances the counterparty lifecycle. If the review record cannot be persisted, the API
fails closed with HTTP `503` and `code: "COUNTERPARTY_REVIEW_UNAVAILABLE"`.
Relationship controls are evaluated after counterparty lifecycle checks. Per-payment, daily, and
monthly limits and category restrictions can deny a request. Relationship approval requirements or
expired access route the request to review. These controls are specific to the requesting agent and
do not replace assigned policies.
### External Wallet Approve/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
This keeps the same policy checks and audit trail while leaving execution in the agent's control.
It does not cryptographically block a direct transfer signed outside Conto.
Note:
Confirmation atomically claims an approved external-wallet payment before creating the transaction
record and updating spend tracking. A repeated confirmation for the same request returns a
conflict instead of creating duplicate ledger effects.
A submitted transaction hash is accepted for asynchronous verification, not treated as final
confirmation. The response contains `accepted: true`, `confirmed: false`, and `status:
"processing"`. Conto marks the transaction confirmed only after verifying the expected chain,
sender, recipient, asset, amount, and successful receipt. Poll the returned `statusUrl` for the
final result.
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.
Note:
The external-wallet `approve` flow accepts the same optional `context.invoice` object as
`payments.request()` and stores the same typed invoice fields on the resulting `PaymentRequest`.
Note:
If an approval workflow matches, `/api/sdk/payments/approve` returns `requiresHumanApproval: true`
and includes `approvalRequestId` in the response instead of issuing an `approvalToken`. After that
workflow approves the payment, `POST /api/sdk/payments/{requestId}/confirm` can be called with
just the final `txHash`. Policy-required reviews use the same response shape even when no custom
workflow matches, so every review remains actionable.
### Agent-skill approval decisions
OpenClaw can list pending reviews with `GET /api/sdk/approval-requests?type=payment&status=PENDING`
and submit the assigned human owner's explicit decision after the owner provides the matching
one-time token from their independently delivered approval notification:
```bash
curl -X POST https://conto.finance/api/sdk/approval-requests/APPROVAL_REQUEST_ID/decide \
-H "Authorization: Bearer $CONTO_SDK_KEY" \
-H "Content-Type: application/json" \
-d '{"decision":"APPROVED","actionToken":"HUMAN_ACTION_TOKEN","comment":"Approved"}'
```
This route requires `payments:approve`, but the agent SDK key alone cannot create a decision. The
one-time token must match the approval request, assigned owner, and approve/reject action. The route
still enforces that person's transaction-approval permission plus every workflow eligibility and
sequencing rule. When the decision completes the workflow, Conto automatically sends managed-wallet
payments and returns `receiptUrl`. External wallets receive a `nextAction` payload containing the
exact transfer and tokenless confirmation handoff. A retry of an already-recorded matching decision
does not require a second token.
### Response
```typescript
interface PaymentRequestResult {
requestId: string;
status: 'APPROVED' | 'DENIED' | 'REQUIRES_APPROVAL' | 'EXECUTED';
idempotent?: boolean;
wallet?: {
id: string;
address: string;
chainId: string;
custodyType: string;
availableBalance: number;
};
expiresAt?: string;
reasons: string[];
violations?: object[];
currency?: string;
chain?: {
chainId: string;
chainName: string;
chainType: string;
explorerUrl: string;
};
executeUrl?: string;
sessionId?: string | null;
walletSelectionReason?: string;
execution?: {
transactionId: string;
txHash: string;
explorerUrl: string;
status: string;
};
autoExecuteError?: {
error: string;
code?: string;
};
hint?: string;
context?: {
wallets?: object[];
nextSteps?: string[];
};
}
```
The `0.1.0` request response uses `requestId` and `executeUrl` for the two-step flow. For
`autoExecute: true`, an approved request returns `status: "EXECUTED"` and the flat `execution`
object. `reasons`, `violations`, `hint`, and `context` explain policy decisions without exposing
provider diagnostics or stored policy records.
Info:
Payment responses include `currency` (for example, `USDC`, `USDT`, `USDC.e`, or `pathUSD`) so the
caller can reconcile the requested amount with the selected wallet network.
Info:
**Currency depends on chain:** The `currency` field reflects the stablecoin used on the wallet's
blockchain. On Base, Ethereum, Arbitrum, and Polygon, both `USDC` and `USDT` are supported. 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
Use `idempotencyKey` when your caller may retry the same payment request because of network timeouts or uncertain client state.
- Same `idempotencyKey` + same request payload: returns the original `requestId` and current next action
- 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: '0x742d35Cc6634C0532925a3b844Bc9e7595f...',
purpose: 'Top up API credits',
idempotencyKey: 'payment-req-2026-04-17-001',
});
```
### Example
```typescript
const request = await conto.payments.request({
amount: 50,
recipientAddress: '0x...',
purpose: 'API credits',
});
switch (request.status) {
case 'APPROVED':
console.log('Payment approved');
console.log('Execute:', request.executeUrl);
console.log('Expires:', request.expiresAt);
break;
case 'DENIED':
console.log('Payment denied:', request.reasons);
break;
case 'REQUIRES_APPROVAL':
console.log('Awaiting approval for request:', request.requestId);
break;
}
```
## payments.execute()
Execute an approved payment request.
Immediately before custody dispatch, Conto re-evaluates the full current policy set. A newly denied
payment is not sent and its reservation is released. If the policy service cannot complete the
evaluation, execution fails closed.
```typescript
const result = await conto.payments.execute(requestId);
```
Info:
`payments.execute()` rechecks ownership and active spending limits before submitting the payment.
Successful responses include the stable Conto transaction ID needed for reconciliation.
### Parameters
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | ---------------------------------- |
| `requestId` | string | Yes | The requestId from payment request |
### Response
```typescript
interface PaymentExecuteResult {
transactionId: string;
txHash: string;
status: 'CONFIRMING' | 'CONFIRMED' | 'FAILED';
amount: number;
currency: string;
recipient: string;
recipientName?: string;
wallet: {
address: string;
};
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: '0x...',
});
if (request.status === 'APPROVED') {
const result = await conto.payments.execute(request.requestId);
console.log('Conto transaction:', result.transactionId);
console.log('Transaction hash:', result.txHash);
console.log('Explorer:', result.explorerUrl);
}
```
## payments.pay()
Convenience method that requests and executes in one call.
```typescript
const result = await conto.payments.pay({
amount: 50,
recipientAddress: '0x...',
purpose: 'API credits',
});
```
### Behavior
- If **approved**: Executes immediately and returns result
- If **denied**: Throws `ContoError` with code `PAYMENT_DENIED`
- If **requires approval**: Throws `ContoError` with code `REQUIRES_APPROVAL`
- Other API failures expose the stable `code` and `status` fields from the SDK error contract.
### Example
```typescript
try {
const result = await conto.payments.pay({
amount: 50,
recipientAddress: '0x...',
purpose: 'API credits',
});
console.log('Paid! TX:', result.txHash);
} catch (error) {
if (error instanceof Error && 'code' in error) {
const code = (error as { code: string }).code;
if (code === 'PAYMENT_DENIED') {
console.log('Payment denied:', error.message);
} else if (code === 'REQUIRES_APPROVAL') {
console.log('Payment needs manual approval');
}
}
}
```
## autoExecute Flag
The `autoExecute` flag lets `payments.request()` request authorization and execute the payment in a
single 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`: Returns `status: "EXECUTED"` with an `execution` object
containing `transactionId`, `txHash`, `explorerUrl`, and `status`.
- If **DENIED** or **REQUIRES_APPROVAL**: Returns the normal authorization response.
### Example
```typescript
// Single-call payment: request + execute
const result = await fetch('/api/sdk/payments/request', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 50,
recipientAddress: '0x...',
recipientName: 'OpenAI',
purpose: 'API credits',
autoExecute: true,
}),
}).then((r) => r.json());
if (result.status === 'EXECUTED' && result.execution) {
console.log('Conto transaction:', result.execution.transactionId);
console.log('TX Hash:', result.execution.txHash);
console.log('Explorer:', result.execution.explorerUrl);
}
```
## payments.status()
Check the status of a payment request.
```typescript
const status = await conto.payments.status(requestId);
```
### Response
```typescript
interface PaymentStatusResult {
requestId: string;
status: string;
policyResult: string;
amount: number;
currency: string;
recipient: string;
recipientName?: string;
purpose?: string;
category?: string;
wallet?: { address: string };
requiresApproval: boolean;
approvedAt?: string;
deniedAt?: string;
denialReason?: string;
expiresAt?: string;
createdAt: string;
transaction?: {
id: string;
txHash: string;
status: string;
confirmedAt?: string;
blockNumber?: number;
};
}
```
### Example: Polling for Confirmation
```typescript
async function waitForConfirmation(requestId: string) {
while (true) {
const status = await conto.payments.status(requestId);
if (status.transaction?.status === 'CONFIRMED') {
console.log('Conto transaction:', status.transaction.id);
console.log('Confirmed:', status.transaction.txHash);
return status;
}
if (status.transaction?.status === 'FAILED') {
throw new Error('Transaction failed');
}
await new Promise((r) => setTimeout(r, 2000)); // Wait 2 seconds
}
}
```
## Status Reference
The status endpoint returns the API's current status string plus policy and transaction details.
When a transaction is present, use its `status`, `txHash`, and `confirmedAt` fields to reconcile the
known request. Do not create a second request while the original request is still unresolved.
## Categories
Use standard categories for better analytics:
| Category | Description |
| ---------------- | ----------------------- |
| `INFRASTRUCTURE` | Cloud, hosting, compute |
| `AI_SERVICES` | AI APIs, model training |
| `MARKETING` | Advertising, promotions |
| `OPERATIONS` | General operations |
| `VENDOR` | Vendor payments |
| `EMPLOYEE` | Employee reimbursements |
| `TESTING` | Test transactions |
## Urgency Levels
| Level | Description |
| ---------- | --------------------------- |
| `LOW` | Can wait, batch if possible |
| `NORMAL` | Standard priority (default) |
| `HIGH` | Process quickly |
| `CRITICAL` | Immediate processing |
## Best Practices
Including purpose improves audit trails and analytics:
```typescript
await conto.payments.pay({
amount: 100,
recipientAddress: '0x...',
purpose: 'AWS EC2 instance for training job #1234', // Specific
category: 'INFRASTRUCTURE'
});
```
Approvals from `/request` expire after **5 minutes**. Approvals from `/approve` (external wallets) expire after **10 minutes**. Check expiration before executing:
```typescript
const request = await conto.payments.request({ ... });
if (request.status === 'APPROVED') {
const expiresAt = new Date(request.expiresAt!);
if (expiresAt > new Date()) {
await conto.payments.execute(request.requestId);
} else {
// Request a new approval
const newRequest = await conto.payments.request({ ... });
}
}
```
Use separate request/execute when you need to:
- Validate before executing
- Show user confirmation
- Handle requires_approval status
```typescript
const request = await conto.payments.request({ ... });
if (request.status === 'REQUIRES_APPROVAL') {
// Store requestId, notify approvers
await notifyApprovers(request.requestId);
return { pending: true, requestId: request.requestId };
}
if (request.status === 'APPROVED') {
return conto.payments.execute(request.requestId);
}
```
Use the typed `context.invoice` object when a payment is linked to an invoice:
```typescript
await conto.payments.pay({
amount: 100,
recipientAddress: '0x...',
purpose: 'API subscription',
context: {
invoice: {
id: 'invoice_1234',
expectedAmount: 100,
currency: 'USDC',
},
},
});
```
## Reconciling payments
`GET /api/sdk/transactions/reconciliation` returns a per-payment reconciliation view: the
requested amount, the settled amount, the settlement hash, the status, and a reconciliation state.
It reads settled transaction data, so finance can tie requested amounts to what actually settled and
export the result.
| Reconciliation state | Meaning |
| -------------------- | ------------------------------------------------------------------ |
| `RECONCILED` | Confirmed, and the settled amount matches the requested amount |
| `DISCREPANCY` | Confirmed, but the settled amount differs from the requested amount |
| `PENDING` | Not yet settled |
| `UNSETTLED` | Failed, rejected, cancelled, or expired |
Filter by `status`, `from`, and `to`. Pass `format=csv` for a full server-side export instead of a
paged JSON response.
```bash
# JSON, with a summary of counts per state
curl "https://conto.finance/api/sdk/transactions/reconciliation?from=2026-08-01" \
-H "Authorization: Bearer $CONTO_API_KEY"
# Full CSV export for a date range
curl "https://conto.finance/api/sdk/transactions/reconciliation?from=2026-08-01&format=csv" \
-H "Authorization: Bearer $CONTO_API_KEY" -o reconciliation.csv
```
The endpoint uses the `transactions:read` scope.
## Next Steps
### Error Handling
Link: https://conto.finance/sdk/error-handling
Handle payment errors gracefully
### Examples
Link: https://conto.finance/sdk/examples
See complete integration examples