Payments API
The payments API allows agents to request authorization and execute stablecoin payments.
Overview
The payment flow has two steps:
Request - Request authorization and policy evaluation
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
Parameter Type Required Description amountnumber Yes Payment amount recipientAddressstring Yes Ethereum address (0x…) recipientNamestring No Human-readable name purposestring No Why this payment is needed categorystring No Spending category contextobject No Additional metadata walletIdstring No Specific wallet to use urgencystring No LOW, NORMAL, HIGH, CRITICAL autoExecuteboolean No Request + 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
Parameter Type Required Description requestIdstring Yes The 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:
Category Description INFRASTRUCTURECloud, hosting, compute AI_SERVICESAI APIs, model training MARKETINGAdvertising, promotions OPERATIONSGeneral operations VENDORVendor payments EMPLOYEEEmployee reimbursements TESTINGTest transactions
Urgency Levels
Level Description 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 Two-Step for Complex Flows
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 );
}
Add Context for Debugging
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