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

## 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** (`PRIVY`, `SPONGE`) | `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](/sdk/skills) (OpenClaw and Hermes) for the external-wallet flow.
</Note>

<Note>
  If you include `context.invoice`, Conto preserves that payload in the raw request context and also
  stores a typed invoice record on the `PaymentRequest`. 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](/guides/delta-setup) and [Delta Smoke Test](/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, such as the Delta pilot workflow.
</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`.
</Note>

### Response

```typescript theme={null}
interface PaymentRequestResult {
  requestId: string; // Use this to execute
  status: 'APPROVED' | 'DENIED' | 'REQUIRES_APPROVAL' | 'EXECUTED';
  error?: string; // Present on some denied or verification-required responses
  code?: string; // Machine-readable denial or verification code
  idempotent?: boolean; // Present when a retry returns an existing request
  approvalRequestId?: string; // Present when the request is routed into an approval workflow

  // If approved or executed
  wallet?: {
    id: string;
    address: string;
    chainId: string;
    custodyType: string;
    availableBalance: number;
  };
  walletSelectionReason?: string;
  expiresAt?: string; // ISO timestamp
  currency?: string; // e.g., "USDC", "USDT", "pathUSD"
  chain?: {
    chainId: string;
    chainName: string;
    chainType: string;
    explorerUrl?: string;
  };

  // If executed (autoExecute was true)
  execution?: {
    transactionId: string;
    txHash: string;
    explorerUrl: string;
    status: string;
  };

  // Always present
  reasons: string[];

  // If denied - includes enriched context
  violations?: {
    type: string;
    limit: number;
    current: number;
    message: string; // Prefixed with source, e.g. "[Wallet limit]" or "[Policy: Name]"
    source?: 'wallet_limit' | 'policy_rule';
    policyName?: string; // Present when source is "policy_rule"
  }[];
  context?: {
    wallets?: Array<{
      id: string;
      address: string;
      chainId: string;
      custodyType: string;
      balance: number;
    }>;
    nextSteps?: string[];
  };

  // If autoExecute failed
  autoExecuteError?: { error: string; code?: string };
}
```

Denied and verification-required responses include machine-readable `code` values when Conto can
classify the stop reason, such as `BUDGET_EXCEEDED` for budget denials or `VERIFICATION_REQUIRED`
for merchant identity step-up.

<Info>
  All payment responses now include `currency` (e.g., "USDC", "USDT", "USDC.e", or "pathUSD") and
  `chain` (with chainId, chainName, chainType, and explorerUrl) so agents know exactly which network
  and token the payment uses.
</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` with `idempotent: true`
* 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('Wallet:', request.wallet?.address);
    console.log('Expires:', request.expiresAt);
    break;

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

  case 'REQUIRES_APPROVAL':
    console.log('Needs manual approval');
    break;
}
```

## payments.execute()

Execute an approved payment request.

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

<Info>
  `payments.execute()` now delegates execution to Conto's shared payment execution service. The
  route still authenticates the SDK key, checks ownership, enforces rate limits, and requires
  `payments:execute`; custody dispatch, claim locking, spend-limit revalidation, retry accounting,
  provisional ledger effects, and confirmation-job dispatch all happen in the shared service used by
  Conto Pay and dashboard approval flows.
</Info>

### Parameters

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

### Response

```typescript theme={null}
interface PaymentExecuteResult {
  transactionId: string;
  txHash: string; // Blockchain transaction hash (placeholder when simulated)
  status: 'COMPLETED' | 'CONFIRMING' | 'CONFIRMED' | 'FAILED';
  simulated: boolean; // True when no onchain transfer happened
  amount: number;
  currency: string; // Stablecoin type
  recipient: string;
  recipientName?: string;
  wallet: {
    address: string;
  };
  explorerUrl: string; // Block explorer link
}
```

<Info>
  Sandbox organizations always execute in simulated mode: the policy check, receipt, spend
  tracking, and audit log are real, no onchain transfer happens, and the response is finalized
  immediately with `status: 'COMPLETED'` and `simulated: true`. Real organizations execute onchain
  and return `status: 'CONFIRMING'` with `simulated: false` while chain confirmation is tracked in
  the background.
</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('Transaction hash:', result.txHash);
  console.log('Explorer:', result.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`

### 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');
  }
}
```

## 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`: Executes immediately, returns `status: "EXECUTED"` with `execution` object containing `txHash`
* If **DENIED** or **REQUIRES\_APPROVAL**: `autoExecute` is ignored, normal response returned
* If execution **fails**: Returns `status: "APPROVED"` with `autoExecuteError` message and `executeUrl` 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 === 'EXECUTED') {
  console.log('Payment complete!');
  console.log('TX Hash:', result.execution.txHash);
  console.log('Explorer:', result.execution.explorerUrl);
} else if (result.status === 'APPROVED' && result.autoExecuteError) {
  // Auto-execute failed, retry manually
  console.log('Auto-execute failed:', result.autoExecuteError.error);
  const execResult = await fetch(result.executeUrl, { method: 'POST', ... });
}
```

## 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: 'PENDING' | 'APPROVED' | 'DENIED' | 'COMPLETED' | 'EXPIRED';
  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;

  // If executed
  transaction?: {
    id: string;
    txHash: string;
    status: 'PENDING' | 'CONFIRMING' | 'CONFIRMED' | 'FAILED';
    confirmedAt?: string;
    blockNumber?: number;
  };
}
```

### 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 === 'CONFIRMED') {
      console.log('Confirmed at block:', status.transaction.blockNumber);
      return status;
    }

    if (status.transaction?.status === 'FAILED') {
      throw new Error('Transaction failed');
    }

    await new Promise((r) => setTimeout(r, 2000)); // Wait 2 seconds
  }
}
```

## Status Reference

Payment requests and transactions use different status values:

| Payment Request Status | Description                                                          |
| ---------------------- | -------------------------------------------------------------------- |
| `APPROVED`             | Policies passed, ready to execute                                    |
| `DENIED`               | Blocked by a policy or wallet limit                                  |
| `REQUIRES_APPROVAL`    | Needs manual human approval                                          |
| `EXECUTED`             | Auto-executed successfully (when `autoExecute: true`)                |
| `PENDING`              | Awaiting human approval (maps to REQUIRES\_APPROVAL in SDK response) |
| `COMPLETED`            | Payment fully confirmed onchain                                      |
| `EXPIRED`              | Approval window elapsed without execution                            |

| Transaction Status | Description                                        |
| ------------------ | -------------------------------------------------- |
| `PENDING`          | Transaction submitted, awaiting confirmation       |
| `EXECUTING`        | Transaction is being processed                     |
| `CONFIRMING`       | Transaction broadcast, awaiting block confirmation |
| `CONFIRMED`        | Transaction confirmed onchain                      |
| `FAILED`           | Transaction failed                                 |
| `REJECTED`         | Transaction rejected                               |
| `CANCELLED`        | Transaction cancelled before execution             |

| Policy Result       | Description                                      |
| ------------------- | ------------------------------------------------ |
| `ALLOWED`           | All policies passed                              |
| `DENIED`            | At least one policy denied the payment           |
| `REQUIRES_APPROVAL` | Policies passed but approval threshold triggered |
| `FLAGGED`           | Payment flagged for review                       |
| `PENDING`           | Policy evaluation not yet complete               |

## 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="Add Context for Debugging">
    Use the context field for debugging info:

    ```typescript theme={null}
    await conto.payments.pay({
      amount: 100,
      recipientAddress: '0x...',
      purpose: 'API subscription',
      context: {
        jobId: '1234',
        userId: 'user_abc',
        environment: 'production'
      }
    });
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

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

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