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

# Payments

> Request payment authorization, execute onchain transactions, and use autoExecute for single-call payments

# 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.
</Note>

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

<Warning>
  `payments.execute()` is for managed wallets. If your agent holds the signing keys, use the
  external-wallet `approve -> confirm` flow instead.
</Warning>

## payments.request()

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

```typescript theme={null}
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. |

<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](/docs/sdk/skills) (OpenClaw and Hermes) for the external-wallet flow.
</Note>

<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](/docs/guides/delta-setup) and [Delta Smoke Test](/docs/guides/delta-smoke-test).
</Note>

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

<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`.
</Note>

### 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.
</Note>

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

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

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

### 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 with:

```bash theme={null}
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","comment":"Approved in OpenClaw"}'
```

This route requires `payments:approve`. It resolves the agent's assigned owner and 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.

### Response

```typescript theme={null}
interface PaymentRequestResult {
  requestId: string;
  status: 'APPROVED' | 'DENIED' | 'REQUIRES_APPROVAL' | 'ACTION_REQUIRED' | 'PROCESSING';
  amount: number;
  currency: string;
  recipient: string;
  custodyMode?: 'MANAGED' | 'EXTERNAL' | 'SMART_CONTRACT';
  reasons: string[];
  approvalRequestId?: string; // Present when the request is routed into an approval workflow
  statusUrl: string;
  expiresAt?: string;
  actionUrl?: string;
  receipt?: {
    transactionId: string;
    txHash: string;
    settlementMode: 'live' | 'test';
    explorerUrl?: string;
  };
}
```

`approvalRequestId` is the stable review reference. Use `statusUrl` to reconcile the request and
follow `actionUrl` for the next available step. When present, `custodyMode` tells the caller whether
Conto can execute the selected wallet or the customer's signer must complete the transfer.
`reasons` contains customer-facing explanations; provider diagnostics and stored policy records are
not returned.

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

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

### 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 theme={null}
const request = await conto.payments.request({
  amount: 100,
  recipientAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f...',
  purpose: 'Top up API credits',
  idempotencyKey: 'payment-req-2026-04-17-001',
});
```

### Example

```typescript theme={null}
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.actionUrl);
    console.log('Expires:', request.expiresAt);
    break;

  case 'DENIED':
    console.log('Payment denied:', request.reasons);
    break;

  case 'REQUIRES_APPROVAL':
    console.log('Review:', request.approvalRequestId);
    console.log('Track:', request.statusUrl);
    break;
}
```

## payments.execute()

Execute an approved payment request.

```typescript theme={null}
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.
</Info>

### Parameters

| Parameter   | Type   | Required | Description                        |
| ----------- | ------ | -------- | ---------------------------------- |
| `requestId` | string | Yes      | The requestId from payment request |

### Response

```typescript theme={null}
interface PaymentExecuteResult {
  requestId: string;
  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.
</Info>

### Example

```typescript theme={null}
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.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 theme={null}
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`
* If **an additional action is required**: Throws `ContoError` with code `ACTION_REQUIRED` and a
  validated `actionUrl`

### Example

```typescript theme={null}
try {
  const result = await conto.payments.pay({
    amount: 50,
    recipientAddress: '0x...',
    purpose: 'API credits',
  });

  console.log('Paid! TX:', result.txHash);
} catch (error) {
  if (error.code === 'PAYMENT_DENIED') {
    console.log('Payment denied:', error.message);
  } else if (error.code === 'REQUIRES_APPROVAL') {
    console.log('Payment needs manual approval');
  } else if (error.code === 'ACTION_REQUIRED') {
    console.log('Open:', error.actionUrl);
  }
}
```

## autoExecute Flag

The `autoExecute` flag lets you request authorization and execute the payment in a single API call, without needing a separate `execute()` 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.
</Warning>

### How It Works

* If **APPROVED** + `autoExecute: true`: Starts execution and returns `status: "PROCESSING"`; a
  customer receipt is included once execution has produced a transaction ID and hash
* If **DENIED** or **REQUIRES\_APPROVAL**: `autoExecute` is ignored, normal response returned
* If automatic execution **does not start**: Returns `status: "APPROVED"` with an `actionUrl` for
  manual retry

### Example

```typescript theme={null}
// 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 === 'PROCESSING' && result.receipt) {
  console.log('Conto transaction:', result.receipt.transactionId);
  console.log('Settlement mode:', result.receipt.settlementMode);
  console.log('TX Hash:', result.receipt.txHash);
} else if (result.status === 'APPROVED' && result.actionUrl) {
  const execResult = await fetch(result.actionUrl, {
    method: 'POST',
    headers: { Authorization: `Bearer ${apiKey}` },
  });
}
```

## payments.status()

Check the status of a payment request.

```typescript theme={null}
const status = await conto.payments.status(requestId);
```

### Response

```typescript theme={null}
interface PaymentStatusResult {
  requestId: string;
  status:
    | 'approved'
    | 'declined'
    | 'review_required'
    | 'action_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;
  action: {
    type: 'complete_verification';
    url: string;
  } | null;
  transaction: {
    id: string;
    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

```typescript theme={null}
async function waitForConfirmation(requestId: string) {
  while (true) {
    const status = await conto.payments.status(requestId);

    if (status.transaction?.status === 'completed') {
      console.log('Conto transaction:', status.transaction.id);
      console.log('Confirmed:', status.transaction.txHash);
      if (status.transaction.explorerUrl) {
        console.log('Explorer:', status.transaction.explorerUrl);
      }
      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 this compact status set:

| Status            | Meaning                                     |
| ----------------- | ------------------------------------------- |
| `approved`        | Ready for the next payment step             |
| `declined`        | Stopped by the current payment controls     |
| `review_required` | Waiting for an authorized reviewer          |
| `action_required` | Follow the returned action URL              |
| `processing`      | Submitted and still in progress             |
| `completed`       | Completed successfully                      |
| `failed`          | Processing failed                           |
| `expired`         | The approval window ended before completion |

When a transaction is present, its status is one of `processing`, `completed`, or `failed`.

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

<AccordionGroup>
  <Accordion title="Always Include Purpose">
    Including purpose improves audit trails and analytics:

    ```typescript theme={null}
    await conto.payments.pay({
      amount: 100,
      recipientAddress: '0x...',
      purpose: 'AWS EC2 instance for training job #1234',  // Specific
      category: 'INFRASTRUCTURE'
    });
    ```
  </Accordion>

  <Accordion title="Handle Expiration">
    Approvals from `/request` expire after **5 minutes**. Approvals from `/approve` (external wallets) expire after **10 minutes**. Check expiration before executing:

    ```typescript theme={null}
    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({ ... });
      }
    }
    ```
  </Accordion>

  <Accordion title="Use Two-Step for Complex Flows">
    Use separate request/execute when you need to:

    * Validate before executing
    * Show user confirmation
    * Handle requires\_approval status

    ```typescript theme={null}
    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);
    }
    ```
  </Accordion>

  <Accordion title="Attach Invoice Details">
    Use the typed `context.invoice` object when a payment is linked to an invoice:

    ```typescript theme={null}
    await conto.payments.pay({
      amount: 100,
      recipientAddress: '0x...',
      purpose: 'API subscription',
      context: {
        invoice: {
          id: 'invoice_1234',
          expectedAmount: 100,
          currency: 'USDC',
        },
      },
    });
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Error Handling" icon="bug" href="/docs/sdk/error-handling">
    Handle payment errors gracefully
  </Card>

  <Card title="Examples" icon="code" href="/docs/sdk/examples">
    See complete integration examples
  </Card>
</CardGroup>
