Submit Payment
POST /v1/payments — submit a signed payment intent to the Axon relayer.
Submit Payment
Submit a signed EIP-712 payment intent for execution. The relayer validates the signature, runs policy checks, and either executes the payment on-chain or routes it through AI verification or human review.
Request Body
Signed Intent Fields
These fields are part of the EIP-712 signed struct and are verified on-chain by the vault contract.
Routing Fields
Off-Chain Metadata
These fields are stored in the relayer's database and surfaced in the owner's dashboard. They are not signed and not sent on-chain.
Response
{
requestId: string;
status: "approved" | "pending_review" | "rejected";
txHash?: string;
pollUrl?: string;
estimatedResolutionMs?: number;
reason?: string;
simulationResult?: {
success: boolean;
gasEstimate?: string;
error?: string;
};
}Response Paths
The relayer evaluates every payment against the vault's policy configuration and routes through one of three paths:
Fast Path
The payment is below all thresholds and passes all policy checks. The relayer submits the transaction on-chain and returns synchronously.
Conditions: amount below AI trigger threshold, no velocity triggers, destination allowed.
{
"requestId": "req_7f2a9b",
"status": "approved",
"txHash": "0x1234567890abcdef..."
}AI Scan Path
The payment exceeds the AI trigger threshold or velocity window. The relayer runs 3-agent verification (Safety, Behavioral, Reasoning). If 2/3 agents approve, the payment executes. If there is no consensus, it routes to human review.
Conditions: amount above aiTriggerThreshold, or rolling velocity exceeded, or requireAiVerification is true for this bot.
{
"requestId": "req_7f2a9b",
"status": "approved",
"txHash": "0x1234567890abcdef...",
"estimatedResolutionMs": 28000
}Or, if no consensus:
{
"requestId": "req_7f2a9b",
"status": "pending_review",
"pollUrl": "/v1/payments/req_7f2a9b"
}Human Review Path
The payment requires manual approval by the owner via the dashboard. This occurs when AI agents do not reach consensus, or when the owner has configured mandatory human review for the bot.
{
"requestId": "req_7f2a9b",
"status": "pending_review",
"pollUrl": "/v1/payments/req_7f2a9b"
}Poll GET /v1/payments/:requestId to check when the owner approves or rejects the payment.
Error Responses
| Status | Code | Description |
|---|---|---|
400 | INVALID_REQUEST | Malformed request body or missing required field. |
400 | INVALID_SIGNATURE | EIP-712 signature verification failed. |
400 | DEADLINE_EXPIRED | The intent's deadline has already passed. |
403 | BOT_NOT_ACTIVE | The bot address is not whitelisted on the vault. |
403 | EXCEEDS_PER_TX_LIMIT | Amount exceeds the bot's maxPerTxAmount. |
409 | IDEMPOTENCY_CONFLICT | Same idempotency key submitted with different intent data. |
422 | INSUFFICIENT_BALANCE | Vault does not have enough token balance. |
422 | SIMULATION_FAILED | Pre-flight eth_call simulation reverted. |
429 | RATE_LIMITED | Too many requests. Check the Retry-After header. |
See Error Codes for the complete reference.
Example
curl
curl -X POST https://relay.axonfi.xyz/v1/payments \
-H "Content-Type: application/json" \
-d '{
"bot": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"to": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "10000000",
"deadline": "1740200000",
"ref": "0x696e762d30303100000000000000000000000000000000000000000000000000",
"signature": "0xabcdef...",
"chainId": 8453,
"vaultAddress": "0xYourVaultAddress",
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
"memo": "Payment for 100 API calls",
"metadata": {
"service": "image-generation",
"requestCount": "100"
}
}'TypeScript (without SDK)
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';
const account = privateKeyToAccount('0xYourBotPrivateKey');
const walletClient = createWalletClient({
account,
chain: base,
transport: http(),
});
const intent = {
bot: account.address,
to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
amount: 10_000_000n,
deadline: BigInt(Math.floor(Date.now() / 1000) + 300),
ref: '0x696e762d30303100000000000000000000000000000000000000000000000000',
};
const signature = await walletClient.signTypedData({
domain: {
name: 'AxonVault',
version: '1',
chainId: 8453,
verifyingContract: '0xYourVaultAddress',
},
types: {
PaymentIntent: [
{ name: 'bot', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'token', type: 'address' },
{ name: 'amount', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
{ name: 'ref', type: 'bytes32' },
],
},
primaryType: 'PaymentIntent',
message: {
bot: intent.bot,
to: intent.to,
token: intent.token,
amount: intent.amount,
deadline: intent.deadline,
ref: intent.ref,
},
});
const response = await fetch('https://relay.axonfi.xyz/v1/payments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...Object.fromEntries(Object.entries(intent).map(([k, v]) => [k, String(v)])),
signature,
chainId: 8453,
vaultAddress: '0xYourVaultAddress',
idempotencyKey: crypto.randomUUID(),
memo: 'Payment for 100 API calls',
}),
});
const result = await response.json();
console.log(result);Notes
- The relayer always simulates the transaction via
eth_callbefore submitting on-chain. If simulation fails, no gas is spent and you receive a422 SIMULATION_FAILEDerror. - The
deadlinefield should be set to a short window (default 5 minutes). Long deadlines increase the risk of stale intents being executed at unfavorable times. - The
reffield is the only piece of off-chain context that is recorded on-chain. Use it as the anchor linking on-chain transactions to your off-chain records.