Submit Payment

POST /v1/payments — submit a signed payment intent to the Axon relayer.

Submit Payment

POST/v1/payments

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.

ParameterTypeRequiredDescription
botaddressRequiredThe bot address that signed this intent. Must be whitelisted on the vault.
toaddressRequiredRecipient address (EOA or contract).
tokenaddressRequiredERC-20 token contract address (e.g. USDC).
amountstringRequiredPayment amount in the token's base units as a decimal string (e.g. "10000000" for 10 USDC).
deadlinestringRequiredUnix timestamp as a decimal string. The intent is invalid after this time. Recommended: 5 minutes from now.
refstringRequiredbytes32 hex string (0x-prefixed, 66 characters). Links the on-chain transaction to off-chain records.
signaturestringRequiredEIP-712 signature over the PaymentIntent struct, signed by the bot's private key.

Routing Fields

ParameterTypeRequiredDescription
chainIdnumberRequiredThe EVM chain ID where the vault is deployed (e.g. 8453 for Base, 42161 for Arbitrum).
vaultAddressaddressRequiredThe AxonVault contract address to execute the payment from.

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.

ParameterTypeRequiredDescription
idempotencyKeystringRequiredUUID (v4 recommended). Ensures at-most-once execution. Re-submitting the same key returns the cached response.
memostringOptionalHuman-readable description of the payment purpose. Max 1000 characters. Analyzed by the Reasoning AI agent if verification is triggered.
resourceUrlstring (URL)OptionalThe URL or resource being unlocked by this payment. Used in HTTP 402 flows. Stored in audit trail.
invoiceIdstringOptionalExternal invoice reference for reconciliation. Max 255 characters.
orderIdstringOptionalExternal order or job ID. Max 255 characters.
metadataobjectOptionalArbitrary key-value pairs (string keys and string values). Up to 10 keys, max 500 characters per value.
simulatebooleanOptionalIf true, the relayer simulates the payment via eth_call but does not execute it on-chain. Returns a simulationResult field.

Response

{
  requestId: string;
  status: "approved" | "pending_review" | "rejected";
  txHash?: string;
  pollUrl?: string;
  estimatedResolutionMs?: number;
  reason?: string;
  simulationResult?: {
    success: boolean;
    gasEstimate?: string;
    error?: string;
  };
}
ParameterTypeRequiredDescription
requestIdstringRequiredUnique identifier for this payment request. Use with GET /v1/payments/:requestId to poll status.
statusstringRequiredCurrent status: "approved" (on-chain), "pending_review" (awaiting AI or human decision), or "rejected" (denied).
txHashstringOptionalOn-chain transaction hash. Present when status is "approved".
pollUrlstringOptionalRelative URL for polling. Present when the payment is pending async resolution.
estimatedResolutionMsnumberOptionalEstimated time to resolution in milliseconds. Typically ~30000 for AI scan path. Absent for human review.
reasonstringOptionalHuman-readable explanation for rejection. Present when status is "rejected".
simulationResultobjectOptionalPresent when simulate: true was set. Contains success boolean, gas estimate, and error details.

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

StatusCodeDescription
400INVALID_REQUESTMalformed request body or missing required field.
400INVALID_SIGNATUREEIP-712 signature verification failed.
400DEADLINE_EXPIREDThe intent's deadline has already passed.
403BOT_NOT_ACTIVEThe bot address is not whitelisted on the vault.
403EXCEEDS_PER_TX_LIMITAmount exceeds the bot's maxPerTxAmount.
409IDEMPOTENCY_CONFLICTSame idempotency key submitted with different intent data.
422INSUFFICIENT_BALANCEVault does not have enough token balance.
422SIMULATION_FAILEDPre-flight eth_call simulation reverted.
429RATE_LIMITEDToo 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_call before submitting on-chain. If simulation fails, no gas is spent and you receive a 422 SIMULATION_FAILED error.
  • The deadline field should be set to a short window (default 5 minutes). Long deadlines increase the risk of stale intents being executed at unfavorable times.
  • The ref field 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.