Sardis

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 chain

TypeScript

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 route

Routing Logic

Sardis evaluates available rails in priority order:

  1. Direct USDC transfer -- If the recipient accepts USDC on a supported chain, this is the cheapest option (gas only).
  2. Virtual card -- If the merchant is a traditional web merchant and a card is available, Sardis uses the Stripe Issuing card.
  3. Fiat ACH -- For bank-to-bank transfers when the recipient has linked bank details.
  4. 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

FieldTypeRequiredDescription
wallet_idstringYesSource wallet ID (wal_...)
tostringYesRecipient address, merchant domain, or wallet ID
amountstringYesAmount in human-readable units (e.g. "29.99")
tokenstringPhase 1Token symbol (e.g. "USDC"). Optional in auto-route mode.
chainstringPhase 1Chain name (e.g. "base"). Optional in auto-route mode.
currencystringPhase 2Currency code (e.g. "USD") for auto-routing.
merchantstringNoMerchant identifier for policy evaluation
memostringNoHuman-readable description
idempotency_keystringNoPrevents duplicate execution
metadataobjectNoArbitrary key-value pairs attached to the transaction

Pay Response

FieldTypeDescription
idstringTransaction ID (tx_...)
statusstring"completed", "pending_approval", "failed", "blocked"
tx_hashstringOn-chain transaction hash (if on-chain)
railstringWhich payment rail was used
execution_pathstring"mpc_v1", "erc4337_v2", "card", "fiat_ach"
user_op_hashstringERC-4337 UserOperation hash (if applicable)
ap2_chainobjectFull AP2 mandate chain (intent, cart, payment)
policy_resultobject12-check policy evaluation summary
feeobjectGas fee, platform fee, provider fee breakdown
created_atstringISO 8601 timestamp

Policy Evaluation

Every sardis.pay() call runs through the 12-check enforcement pipeline before execution:

  1. Kill switch check
  2. Policy lookup
  3. Amount validation (per-transaction limit)
  4. Daily budget check
  5. Weekly/monthly budget check
  6. Merchant allowlist/blocklist
  7. Category check (MCC codes)
  8. Time window check
  9. First-seen merchant threshold
  10. Anomaly scoring (6-signal)
  11. Approval threshold
  12. 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 approved

For 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

CodeDescription
POLICY_VIOLATIONTransaction blocked by spending policy
INSUFFICIENT_BALANCEWallet does not have enough funds
APPROVAL_REQUIREDHuman approval needed (not an error, expected flow)
MERCHANT_BLOCKEDMerchant or category is on the blocklist
TIME_WINDOW_CLOSEDOutside allowed spending hours
DAILY_LIMIT_EXCEEDEDWould exceed daily spending cap
KILL_SWITCH_ACTIVESpending is frozen at some scope
CHAIN_EXECUTION_FAILEDOn-chain transaction reverted
COMPLIANCE_HOLDBlocked 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 transaction

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