sardis.pay() API
The primary developer entry point for executing payments through Sardis. Covers explicit payments, auto-routing, error handling, and code examples in Python and TypeScript.
sardis.pay() is the primary entry point for executing payments through Sardis. It handles policy evaluation, MPC signing, on-chain execution, and receipt generation in a single call.
Phase 1: Explicit Payments
In explicit mode, you specify the exact recipient, amount, and token. Sardis evaluates the transaction against your wallet's policy before executing.
Python
from sardis import Sardis
client = Sardis(api_key="sk_live_...")
# Simple payment
result = await client.pay(
wallet_id="wal_abc123",
to="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68",
amount="29.99",
token="USDC",
chain="base",
merchant="openai.com",
memo="API credits for March"
)
print(result.status) # "completed"
print(result.tx_hash) # "0xabc123..."
print(result.ap2_chain) # Full AP2 mandate chainTypeScript
import { Sardis } from '@sardis/sdk';
const client = new Sardis({ apiKey: 'sk_live_...' });
const result = await client.pay({
walletId: 'wal_abc123',
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD68',
amount: '29.99',
token: 'USDC',
chain: 'base',
merchant: 'openai.com',
memo: 'API credits for March',
});
console.log(result.status); // "completed"
console.log(result.txHash); // "0xabc123..."MCP (Claude / Cursor)
When using the MCP server, Claude or Cursor calls sardis_pay directly:
Tool: sardis_pay
Arguments:
wallet_id: "wal_abc123"
to: "0x742d35Cc..."
amount: "29.99"
token: "USDC"
merchant: "openai.com"
memo: "API credits"Phase 2: Auto-Routing
In auto-routing mode, Sardis selects the optimal payment rail based on the merchant, amount, and wallet configuration. You do not need to specify chain or token -- Sardis picks the cheapest available path.
# Auto-routed payment -- Sardis picks the best rail
result = await client.pay(
wallet_id="wal_abc123",
to="openai.com",
amount="29.99",
currency="USD",
memo="API credits",
# No chain/token specified -- Sardis auto-routes
)
# Result includes which rail was selected
print(result.rail) # "card" | "onchain" | "fiat_ach"
print(result.execution_path) # Details of the selected routeRouting Logic
Sardis evaluates available rails in priority order:
- Direct USDC transfer -- If the recipient accepts USDC on a supported chain, this is the cheapest option (gas only).
- Virtual card -- If the merchant is a traditional web merchant and a card is available, Sardis uses the Stripe Issuing card.
- Fiat ACH -- For bank-to-bank transfers when the recipient has linked bank details.
- Cross-chain via CCTP -- If funds are on a different chain, Sardis bridges via Circle CCTP v2 before executing.
The routing decision is logged in the transaction receipt for full auditability.
Request and Response
Pay Request
| Field | Type | Required | Description |
|---|---|---|---|
wallet_id | string | Yes | Source wallet ID (wal_...) |
to | string | Yes | Recipient address, merchant domain, or wallet ID |
amount | string | Yes | Amount in human-readable units (e.g. "29.99") |
token | string | Phase 1 | Token symbol (e.g. "USDC"). Optional in auto-route mode. |
chain | string | Phase 1 | Chain name (e.g. "base"). Optional in auto-route mode. |
currency | string | Phase 2 | Currency code (e.g. "USD") for auto-routing. |
merchant | string | No | Merchant identifier for policy evaluation |
memo | string | No | Human-readable description |
idempotency_key | string | No | Prevents duplicate execution |
metadata | object | No | Arbitrary key-value pairs attached to the transaction |
Pay Response
| Field | Type | Description |
|---|---|---|
id | string | Transaction ID (tx_...) |
status | string | "completed", "pending_approval", "failed", "blocked" |
tx_hash | string | On-chain transaction hash (if on-chain) |
rail | string | Which payment rail was used |
execution_path | string | "mpc_v1", "erc4337_v2", "card", "fiat_ach" |
user_op_hash | string | ERC-4337 UserOperation hash (if applicable) |
ap2_chain | object | Full AP2 mandate chain (intent, cart, payment) |
policy_result | object | 12-check policy evaluation summary |
fee | object | Gas fee, platform fee, provider fee breakdown |
created_at | string | ISO 8601 timestamp |
Policy Evaluation
Every sardis.pay() call runs through the 12-check enforcement pipeline before execution:
- Kill switch check
- Policy lookup
- Amount validation (per-transaction limit)
- Daily budget check
- Weekly/monthly budget check
- Merchant allowlist/blocklist
- Category check (MCC codes)
- Time window check
- First-seen merchant threshold
- Anomaly scoring (6-signal)
- Approval threshold
- Compliance check (sanctions, KYC/KYA)
If any check fails, the transaction is blocked and the response includes which check failed and why.
Approval Workflows
When a payment exceeds the approval threshold, sardis.pay() returns status: "pending_approval" instead of executing immediately:
result = await client.pay(
wallet_id="wal_abc123",
to="0x...",
amount="500.00", # Above $200 approval threshold
token="USDC",
)
if result.status == "pending_approval":
print(f"Approval required: {result.approval_request.id}")
# Notification sent to configured webhook/Slack
# Transaction executes automatically once approvedFor high-value transactions, 4-eyes approval (multiple distinct reviewers) can be required via policy configuration.
Error Handling
from sardis.exceptions import (
PolicyViolationError,
InsufficientBalanceError,
ApprovalRequiredError,
ChainExecutionError,
)
try:
result = await client.pay(
wallet_id="wal_abc123",
to="0x...",
amount="999.99",
token="USDC",
)
except PolicyViolationError as e:
# Transaction blocked by policy
print(f"Policy check failed: {e.check_name}")
print(f"Reason: {e.reason}")
print(f"Policy snapshot: {e.policy_id}")
except InsufficientBalanceError as e:
print(f"Need {e.required}, have {e.available}")
except ApprovalRequiredError as e:
print(f"Approval needed: {e.approval_id}")
except ChainExecutionError as e:
# On-chain failure (revert, gas, etc.)
print(f"Chain error: {e.revert_reason}")
print(f"Tx hash: {e.tx_hash}")Error Codes
| Code | Description |
|---|---|
POLICY_VIOLATION | Transaction blocked by spending policy |
INSUFFICIENT_BALANCE | Wallet does not have enough funds |
APPROVAL_REQUIRED | Human approval needed (not an error, expected flow) |
MERCHANT_BLOCKED | Merchant or category is on the blocklist |
TIME_WINDOW_CLOSED | Outside allowed spending hours |
DAILY_LIMIT_EXCEEDED | Would exceed daily spending cap |
KILL_SWITCH_ACTIVE | Spending is frozen at some scope |
CHAIN_EXECUTION_FAILED | On-chain transaction reverted |
COMPLIANCE_HOLD | Blocked by sanctions/KYC check |
Idempotency
Pass an idempotency_key to prevent duplicate payments:
result = await client.pay(
wallet_id="wal_abc123",
to="0x...",
amount="29.99",
token="USDC",
idempotency_key="order-12345-payment-1",
)
# Calling again with the same key returns the same result
# without executing a second payment
result2 = await client.pay(
wallet_id="wal_abc123",
to="0x...",
amount="29.99",
token="USDC",
idempotency_key="order-12345-payment-1",
)
assert result.id == result2.id # Same transactionIdempotency keys are enforced at the database level with a unique constraint. They expire after 24 hours.
Batch Payments
For multiple payments in a single call:
results = await client.pay_batch(
wallet_id="wal_abc123",
payments=[
{"to": "0xaaa...", "amount": "10.00", "token": "USDC", "merchant": "service-a.com"},
{"to": "0xbbb...", "amount": "15.00", "token": "USDC", "merchant": "service-b.com"},
{"to": "0xccc...", "amount": "20.00", "token": "USDC", "merchant": "service-c.com"},
]
)
for r in results:
print(f"{r.merchant}: {r.status} ({r.tx_hash})")Each payment in the batch is independently policy-evaluated. A failure in one does not block the others.
Related
- Payments -- Payment concepts and lifecycle
- Policy Engine -- Spending policy configuration
- Spending Mandates -- Delegated authority
- Error Reference -- Complete error code list
- Webhooks -- Payment event notifications
Payments
Execute AI agent payments via bank transfer, virtual card, or stablecoins. Every payment passes policy checks and compliance screening before chain execution.
Payment Objects
UTXO-inspired one-time payment tokens with a 22-state lifecycle, funding cells, and cryptographic merchant verification.