Skip to main content

Connecting Agents via API

This guide shows how to connect different AI agent frameworks to Conto, enabling them to request, approve, execute, and confirm payments depending on the wallet model you choose.
Before integrating, make sure you’ve connected your agent, linked a wallet, and generated an SDK key. If you are an autonomous agent evaluating Conto without a human account, use the agent sandbox quickstart first.

SDK Authentication

All SDK endpoints require the agent’s SDK key in the Authorization header:
Authorization: Bearer conto_agent_xxxxx...
Set that key as CONTO_API_KEY. Do not use CONTO_ORG_API_KEY for these /api/sdk/* calls.

Choose Your Wallet Flow First

Before wiring up tool calls, decide whether your agent uses a managed wallet or an external wallet.
Wallet modelWho holds the keysEndpointsWhat Conto can stop
Managed (PRIVY, SPONGE)The custody providerrequest -> executeConto stays in the execution path and can block the spend
External (EXTERNAL)You, your agent, or your wallet stackapprove -> transfer -> confirmConto can gate the Conto-routed flow, but cannot block a direct self-signed transfer outside Conto
The framework examples below assume a managed wallet and use request -> execute. If your agent holds the signing keys, switch to approve -> transfer -> confirm instead. See Custody Modes for the full breakdown.

Register The Wallet The Right Way

If your integration already has a wallet, register that same wallet in Conto instead of creating a second one.
  • For an existing Privy wallet, create the Conto wallet with custodyType=PRIVY, externalWalletId, and address. Conto will attach or reuse the wallet record instead of minting a new Privy wallet.
  • For a self-custodied wallet or MPC stack outside Conto, register it as custodyType=EXTERNAL with its address.
  • Wallet registration is idempotent per externalWalletId + chainId or address + chainId, so your backend can safely retry the request.
If the signer lives in a different Privy app than the one Conto is configured to use, do not use the managed PRIVY attach path. Register that wallet as EXTERNAL and use the external approve -> transfer -> confirm flow instead.

The Shared Payment Helper

Every framework integration wraps the same two-call flow: request (policy check), then execute. Define it once and reuse it from each framework’s tool handler:
const CONTO_API_KEY = process.env.CONTO_API_KEY;

type PaymentInput = {
  amount: number;
  recipientAddress: string;
  recipientName?: string;
  purpose: string;
  category?: string;
};

async function executeContoPayment(input: PaymentInput) {
  // Step 1: Request payment authorization (policy check)
  const request = await fetch('https://conto.finance/api/sdk/payments/request', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${CONTO_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ category: 'OPERATIONS', ...input }),
  }).then((r) => r.json());

  if (request.status === 'DENIED') {
    return { success: false, error: request.reasons.join(', ') };
  }

  if (request.status === 'REQUIRES_APPROVAL') {
    return {
      success: false,
      pending: true,
      requestId: request.requestId,
      message: 'Payment requires manual approval',
    };
  }

  // Step 2: Execute the approved payment
  const result = await fetch(
    `https://conto.finance/api/sdk/payments/${request.requestId}/execute`,
    {
      method: 'POST',
      headers: { Authorization: `Bearer ${CONTO_API_KEY}` },
    }
  ).then((r) => r.json());

  return {
    success: true,
    transactionHash: result.txHash,
    amount: result.amount,
    explorerUrl: result.explorerUrl,
  };
}
The Python equivalent for Python-based frameworks:
import os
import requests

CONTO_API_KEY = os.environ.get('CONTO_API_KEY')
CONTO_API_URL = 'https://conto.finance/api'

def make_payment(amount: float, recipient_address: str, purpose: str, recipient_name: str = None):
    """Make a payment via Conto API"""

    headers = {
        'Authorization': f'Bearer {CONTO_API_KEY}',
        'Content-Type': 'application/json'
    }

    # Step 1: Request authorization
    response = requests.post(
        f'{CONTO_API_URL}/sdk/payments/request',
        headers=headers,
        json={
            'amount': amount,
            'recipientAddress': recipient_address,
            'purpose': purpose,
            'recipientName': recipient_name,
            'category': 'OPERATIONS'
        }
    )
    request_data = response.json()

    if request_data.get('status') == 'DENIED':
        return {'success': False, 'error': ', '.join(request_data.get('reasons', []))}

    if request_data.get('status') == 'REQUIRES_APPROVAL':
        return {'success': False, 'pending': True, 'requestId': request_data['requestId']}

    # Step 2: Execute the payment
    result = requests.post(
        f'{CONTO_API_URL}/sdk/payments/{request_data["requestId"]}/execute',
        headers=headers
    ).json()

    return {
        'success': True,
        'transactionHash': result.get('txHash'),
        'amount': result.get('amount'),
        'explorerUrl': result.get('explorerUrl')
    }

Framework Integrations

With the shared helper in place, each framework only needs its own tool wiring.

OpenAI Assistants

import OpenAI from 'openai';

const openai = new OpenAI();

// Define the payment tool for your assistant
const tools = [
  {
    type: 'function',
    function: {
      name: 'make_payment',
      description: 'Make a payment to a recipient',
      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'],
      },
    },
  },
];
In your OpenAI chat loop, check for tool_calls in the response. When the model calls make_payment, parse the arguments, pass them to executeContoPayment, and send the result back as a tool response message.

Anthropic Claude (Tool Use)

import Anthropic from '@anthropic-ai/sdk';

const anthropic = new Anthropic();

const tools = [
  {
    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-4-8',
    max_tokens: 1024,
    tools: 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...
    }
  }
}

LangChain

import { Tool } from '@langchain/core/tools';

class ContoPaymentTool extends Tool {
  name = 'conto_payment';
  description =
    'Make a payment using Conto. Input should be JSON with amount, recipientAddress, and purpose.';

  async _call(input: string): Promise<string> {
    const params = JSON.parse(input) as PaymentInput;
    return JSON.stringify(await executeContoPayment(params));
  }
}

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.

Payment Endpoints Reference

EndpointMethodDescription
/api/sdk/payments/requestPOSTRequest payment authorization
/api/sdk/payments/approvePOSTApprove payment for external wallet
/api/sdk/payments/{id}/executePOSTExecute approved payment
/api/sdk/payments/{id}/confirmPOSTConfirm external wallet payment
/api/sdk/payments/{id}GETGet payment status

Error Handling

StatusCodeDescription
400INVALID_INPUTInvalid request data
401AUTH_FAILEDInvalid, expired, or suspended-agent SDK key
400INSUFFICIENT_BALANCEWallet balance too low (at execution)
429RATE_LIMITEDToo many requests
Policy denials are not HTTP errors. When a payment violates a policy (spend limit, time window, counterparty rule), /api/sdk/payments/request returns 200 with status: "DENIED" and a violations array describing each violation (for example DAILY_LIMIT or TIME_WINDOW). For full error handling patterns, see Error Handling.

Next Steps

Make Your First Payment

Detailed payment flow walkthrough

SDK Reference

Full SDK documentation

Examples

Complete integration examples

Error Handling

Handle errors gracefully