Skip to main content

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

payments.request()

Request authorization for a payment. This evaluates policies without executing.
const request = await conto.payments.request({
  amount: 100,
  recipientAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f...',
  recipientName: 'OpenAI',
  purpose: 'GPT-4 API credits',
  category: 'AI_SERVICES'
});

Parameters

ParameterTypeRequiredDescription
amountnumberYesPayment amount
recipientAddressstringYesEthereum address (0x…)
recipientNamestringNoHuman-readable name
purposestringNoWhy this payment is needed
categorystringNoSpending category
contextobjectNoAdditional metadata
walletIdstringNoSpecific wallet to use
urgencystringNoLOW, NORMAL, HIGH, CRITICAL
autoExecutebooleanNoRequest + execute in one call

Response

interface PaymentRequestResult {
  requestId: string;  // Use this to execute
  status: 'APPROVED' | 'DENIED' | 'REQUIRES_APPROVAL' | 'EXECUTED';

  // 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", "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;
  }[];
  context?: {
    wallets?: Array<{ id: string; address: string; chainId: string; custodyType: string; balance: number }>;
    nextSteps?: string[];
  };

  // If autoExecute failed
  autoExecuteError?: string;
}
All payment responses now include currency (e.g., “USDC” or “pathUSD”) and chain (with chainId, chainName, chainType, and explorerUrl) so agents know exactly which network and token the payment uses.

Example

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.
const result = await conto.payments.execute(requestId);

Parameters

ParameterTypeRequiredDescription
requestIdstringYesThe requestId from payment request

Response

interface PaymentExecuteResult {
  transactionId: string;
  txHash: string;  // Blockchain transaction hash
  status: 'PENDING' | 'CONFIRMING' | 'CONFIRMED' | 'FAILED';
  amount: number;
  currency: string;  // Stablecoin type
  recipient: string;
  recipientName?: string;
  wallet: {
    address: string;
  };
  explorerUrl: string;  // Block explorer link
}

Example

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

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.
Requires both payments:request and payments:execute scopes. If the key only has payments:request, the flag is silently ignored and the response is a normal APPROVED status.

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

// 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);
  const execResult = await fetch(result.executeUrl, { method: 'POST', ... });
}

payments.status()

Check the status of a payment request.
const status = await conto.payments.status(requestId);

Response

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

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

Categories

Use standard categories for better analytics:
CategoryDescription
INFRASTRUCTURECloud, hosting, compute
AI_SERVICESAI APIs, model training
MARKETINGAdvertising, promotions
OPERATIONSGeneral operations
VENDORVendor payments
EMPLOYEEEmployee reimbursements
TESTINGTest transactions

Urgency Levels

LevelDescription
LOWCan wait, batch if possible
NORMALStandard priority (default)
HIGHProcess quickly
CRITICALImmediate processing

Best Practices

Including purpose improves audit trails and analytics:
await conto.payments.pay({
  amount: 100,
  recipientAddress: '0x...',
  purpose: 'AWS EC2 instance for training job #1234',  // Specific
  category: 'INFRASTRUCTURE'
});
Approvals expire after 5 minutes. Check expiration before executing:
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
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 context field for debugging info:
await conto.payments.pay({
  amount: 100,
  recipientAddress: '0x...',
  purpose: 'API subscription',
  context: {
    jobId: '1234',
    userId: 'user_abc',
    environment: 'production'
  }
});

Next Steps