OpenCard/Docs

OpenCard API Documentation

x402-Powered Virtual Card Issuance for AI Agents

OpenCard lets AI agents autonomously purchase and manage virtual debit cards by paying with USDC on Base via the x402 payment protocol.

No accounts. No API keys. No signups. Payment is authentication.

Base URL

plaintext
https://api.opencard.dev

Overview

OpenCard exposes a REST API with three classes of endpoints:

TypeAuth MethodCostUse Case
PublicNoneFreeHealth checks, pricing info, tier discovery
Paid (x402)USDC payment on BaseVaries by tierCreating and funding cards
Wallet-signedEIP-712 signatureFreeViewing cards, freeze/unfreeze

SDK

The official client SDK wraps the raw x402 flow (402 → parse challenge → pay USDC → retry with proof) into one-liner methods. No need to handle the payment handshake yourself.

Install

bash
npm install @opencardsdk/sdk viem

Quick Start

typescript
import { OpenCardClient } from '@opencardsdk/sdk';

const client = new OpenCardClient({
  privateKey: '0x...',       // or pass a viem WalletClient
  baseUrl: 'https://api.opencard.dev',
});

// One line — SDK handles payment automatically
const card = await client.createCard({
  amount: 10,               // $10 tier
  nameOnCard: 'AI AGENT',
  email: '[email protected]',
});

console.log(card.cardDetails); // { cardNumber, cvv, expiry, ... }

Configuration

new OpenCardClient(config)

ParameterTypeDescription
privateKey0x${string}Hex private key — SDK creates wallet internally. Provide either this or walletClient.
walletClientWalletClientYour own viem WalletClient (takes precedence over privateKey).
baseUrlstringAPI URL (default: https://api.opencard.dev)
rpcUrlstringBase RPC URL (default: public RPC)
timeoutnumberRequest timeout in ms (default: 60000)

Methods

client.createCard(params): Promise<CardResult>

Create a virtual card. Pays USDC automatically.

typescript
const result = await client.createCard({
  amount: 50,    // 10 | 25 | 50 | 100 | 200 | 500
  nameOnCard: 'AI AGENT',
  email: '[email protected]',
});

client.fundCard(params): Promise<FundResult>

Fund an existing card.

typescript
const result = await client.fundCard({
  amount: 25,
  cardId: 'card-uuid',
});

client.getTiers(): Promise<Tier[]>

Get pricing tiers and fee breakdown (no payment required).

client.health(): Promise<{ status: string }>

Check if the OpenCard API is reachable.

client.address: 0x${'string'}

The wallet address being used for payments.


Error Handling

typescript
import {
  OpenCardClient,
  InsufficientBalanceError,
  PaymentError,
  ApiError,
  TimeoutError,
} from '@opencardsdk/sdk';

try {
  const card = await client.createCard({ ... });
} catch (error) {
  if (error instanceof InsufficientBalanceError) {
    console.log(`Need ${error.required}, have ${error.available}`);
  } else if (error instanceof PaymentError) {
    console.log(`Payment failed: ${error.message}, tx: ${error.txHash}`);
  } else if (error instanceof ApiError) {
    console.log(`Server error ${error.status}:`, error.body);
  } else if (error instanceof TimeoutError) {
    console.log('Request timed out');
  }
}

Low-Level x402 Utilities

For custom integrations, the x402 protocol helpers are exported directly:

typescript
import {
  parseChallenge,
  checkBalance,
  executePayment,
  buildPaymentProof,
  handleX402Payment,
} from '@opencardsdk/sdk';

How It Works

plaintext
Your Code          SDK              OpenCard API        Base Chain
   |                |                    |                   |
   |-- createCard ->|                    |                   |
   |                |--- POST /create/tier -->|              |
   |                |<-- 402 + challenge -----|              |
   |                |                    |                   |
   |                |--- USDC.transfer() ---|-------- tx --->|
   |                |<-- txHash ------------|--- receipt ----|
   |                |                    |                   |
   |                |--- POST + X-Payment -->|               |
   |                |<-- 201 + card details -|               |
   |<- CardResult --|                    |                   |

Authentication

OpenCard uses two authentication modes depending on the endpoint.

x402 Payment Flow

Paid endpoints use the x402 protocol — your agent pays USDC on Base, and the payment itself serves as authentication. No pre-registration required.

Step 1 — Request without payment

bash
curl -X POST https://api.opencard.dev/cards/create/tier/10 \
  -H "Content-Type: application/json" \
  -d '{"nameOnCard": "AGENT ALPHA", "email": "[email protected]"}'

Step 2 — Receive 402 with payment instructions

json
{
  "x402Version": 1,
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "maxAmountRequired": "17200000",
      "payTo": "0x3Be892b244B7CE6731A297A7eBca16F16f27c288",
      "maxTimeoutSeconds": 300,
      "resource": "/cards/create/tier/10",
      "description": "Create card with $10 load"
    }
  ]
}
ParameterTypeDescription
networkstringeip155:8453 — Base Mainnet
assetstringUSDC contract on Base
maxAmountRequiredstringAmount in USDC with 6 decimals (17200000 = $17.20)
payTostringTreasury wallet address
maxTimeoutSecondsnumberPayment window (5 minutes)

Step 3 — Agent pays USDC on Base

Parse the accepts array, send the specified USDC amount to the payTo address on Base.

Step 4 — Retry with payment proof

Resend the original request with the X-Payment header containing a base64-encoded JSON payload:

json
{
  "scheme": "exact",
  "network": "eip155:8453",
  "payload": {
    "authorization": {
      "from": "0xYourAgentWallet",
      "to": "0xTreasuryAddress",
      "value": "17200000"
    },
    "txHash": "0xYourTransactionHash..."
  }
}
If you're using the official x402 client libraries (@x402/client or @coinbase/x402), steps 2–4 are handled automatically. Your agent just makes a single request and the library manages the payment handshake.

Wallet Signature (Free Endpoints)

For read operations and card management, authenticate by signing an EIP-712 typed message with your wallet.

Required Headers

ParameterTypeDescription
X-WALLET-ADDRESSstringYour 0x... wallet address
X-WALLET-SIGNATUREstringEIP-712 signature
X-WALLET-TIMESTAMPstringUnix timestamp (must be within 5 minutes of server time)

EIP-712 Domain & Types

json
// Domain
{ "name": "OpenCard", "version": "1", "chainId": 8453 }

// Types
{ "Auth": [
    { "name": "action", "type": "string" },
    { "name": "timestamp", "type": "uint256" }
] }

// Message
{ "action": "opencard-auth", "timestamp": 1707600000 }

Example using viem

typescript
import { createWalletClient, http } from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY');
const timestamp = Math.floor(Date.now() / 1000);

const signature = await account.signTypedData({
  domain: { name: 'OpenCard', version: '1', chainId: 8453 },
  types: {
    Auth: [
      { name: 'action', type: 'string' },
      { name: 'timestamp', type: 'uint256' },
    ],
  },
  primaryType: 'Auth',
  message: { action: 'opencard-auth', timestamp: BigInt(timestamp) },
});

const response = await fetch('https://api.opencard.dev/cards', {
  headers: {
    'X-WALLET-ADDRESS': account.address,
    'X-WALLET-SIGNATURE': signature,
    'X-WALLET-TIMESTAMP': String(timestamp),
  },
});
Timestamps older than 5 minutes are rejected. Always generate a fresh timestamp for each request.

Pricing

Card Creation

Creating a new virtual card includes the card load amount plus fees.

LoadIssuanceTop-UpServiceTotalEndpoint
$10$3.00$2.20$2.00$17.20/cards/create/tier/10
$25$3.00$2.50$2.00$32.50/cards/create/tier/25
$50$3.00$3.00$2.00$58.00/cards/create/tier/50
$100$3.00$4.00$3.00$110.00/cards/create/tier/100
$200$3.00$6.00$5.00$214.00/cards/create/tier/200
$500$3.00$12.00$7.00$522.00/cards/create/tier/500

Card Funding

Adding funds to an existing card — no issuance fee.

FundTop-UpServiceTotalEndpoint
$10$2.20$2.00$14.20/cards/fund/tier/10
$25$2.50$2.00$29.50/cards/fund/tier/25
$50$3.00$2.00$55.00/cards/fund/tier/50
$100$4.00$3.00$107.00/cards/fund/tier/100
$200$6.00$5.00$211.00/cards/fund/tier/200
$500$12.00$7.00$519.00/cards/fund/tier/500
All amounts are in USD. Payments are made in USDC on Base (6 decimal precision). The x402 402 response will contain the exact USDC amount required.

Endpoints

Public Endpoints

These require no authentication.

GET/health

Health check. Use this to verify the API is running.

json
{
  "status": "ok",
  "timestamp": "2026-02-11T14:00:00.000Z",
  "version": "1.0.0"
}
GET/pricing

Returns the full pricing breakdown for all creation and funding tiers.

json
{
  "creation": {
    "tiers": [{
      "loadAmount": 10,
      "totalCost": 17.2,
      "issuanceFee": 3.0,
      "topUpFee": 2.2,
      "ourFee": 2.0,
      "endpoint": "/cards/create/tier/10"
    }]
  },
  "funding": {
    "tiers": [{
      "fundAmount": 10,
      "totalCost": 14.2,
      "topUpFee": 2.2,
      "ourFee": 2.0,
      "endpoint": "/cards/fund/tier/10"
    }]
  }
}
GET/cards/tiers

Returns available tiers with endpoints and detailed fee breakdowns.

json
{
  "creation": [{
    "loadAmount": 10,
    "totalCost": 17.2,
    "endpoint": "/cards/create/tier/10",
    "breakdown": {
      "cardLoad": 10, "issuanceFee": 3, "topUpFee": 2.2, "ourFee": 2, "buffer": 0
    }
  }],
  "funding": [{
    "fundAmount": 10,
    "totalCost": 14.2,
    "endpoint": "/cards/fund/tier/10",
    "breakdown": { "fundAmount": 10, "topUpFee": 2.2, "ourFee": 2 }
  }]
}

These endpoints require USDC payment via the x402 protocol on Base. See Authentication → x402 Payment Flow.

POST/cards/create/tier/:amount

Create a new virtual card loaded with the specified tier amount.

Available tiers: 10, 25, 50, 100, 200, 500

ParameterTypeDescription
nameOnCardstringCardholder name (uppercase recommended)
emailstringEmail for card notifications
json
// Response 201 Created (after successful payment)
{
  "success": true,
  "card": {
    "cardId": "550e8400-e29b-41d4-a716-446655440000",
    "nameOnCard": "AGENT ALPHA",
    "balance": 10,
    "status": "active",
    "createdAt": "2026-02-11T14:00:00.000Z"
  },
  "payment": {
    "amountCharged": 17.2,
    "txHash": "0x...",
    "network": "base"
  },
  "details": {
    "cardNumber": "4111111111111111",
    "expiryMonth": 12,
    "expiryYear": 2028,
    "cvv": "123",
    "billingAddress": {
      "street": "123 Main St",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94105",
      "country": "US"
    }
  }
}
Card details (number, CVV, expiry) are only returned once at creation. Store them securely. You can retrieve them again via GET /cards/:cardId/details but that endpoint is rate-limited.
POST/cards/fund/tier/:amount

Add funds to an existing card.

ParameterTypeDescription
cardIdstring (UUID)The card to fund
json
// Response 200 OK
{
  "success": true,
  "cardId": "550e8400-e29b-41d4-a716-446655440000",
  "fundedAmount": 25,
  "newBalance": 35.0,
  "payment": {
    "amountCharged": 29.4,
    "txHash": "0x...",
    "network": "base"
  }
}

Wallet-Signed Endpoints

These endpoints are free but require wallet signature authentication.

GET/cards

List all cards owned by the authenticated wallet.

json
{
  "cards": [{
    "cardId": "550e8400-e29b-41d4-a716-446655440000",
    "nameOnCard": "AGENT ALPHA",
    "lastFour": "1111",
    "balance": 10.0,
    "status": "active",
    "createdAt": "2026-02-11T14:00:00.000Z"
  }]
}
GET/cards/:cardId

Get detailed info for a specific card. Fetches live balance from the card provider.

json
{
  "card": {
    "cardId": "550e8400-e29b-41d4-a716-446655440000",
    "nameOnCard": "AGENT ALPHA",
    "email": "[email protected]",
    "balance": 8.5,
    "initialAmountUsd": 10,
    "status": "active",
    "createdAt": "2026-02-11T14:00:00.000Z",
    "updatedAt": "2026-02-11T15:30:00.000Z"
  }
}
GET/cards/:cardId/details

Retrieve sensitive card details — full card number, CVV, expiry, and billing address.

Rate limited to 3 requests per card per hour. Store card details securely after the first retrieval.
json
{
  "details": {
    "cardNumber": "4111111111111111",
    "expiryMonth": 12,
    "expiryYear": 2028,
    "cvv": "123",
    "billingAddress": {
      "street": "123 Main St",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94105",
      "country": "US"
    }
  }
}
POST/cards/:cardId/freeze

Freeze a card. Blocks all transactions until unfrozen.

json
{ "success": true, "cardId": "550e8400-...", "status": "frozen" }
POST/cards/:cardId/unfreeze

Unfreeze a previously frozen card.

json
{ "success": true, "cardId": "550e8400-...", "status": "active" }

Errors

All error responses follow a consistent format:

json
{ "error": "Human-readable error message" }
StatusMeaning
400Invalid request body or parameters
401Missing or invalid authentication
402Payment required — follow the x402 flow
404Card not found or doesn't belong to your wallet
429Rate limit exceeded — slow down
500Internal server error

Rate Limits

ScopeLimit
General API100 requests / 15 min per IP
Paid endpoints10 requests / 5 min per IP
Card details3 requests / hour per card

When you hit a rate limit, the API returns 429 Too Many Requests. Wait for the window to reset before retrying.


Architecture

plaintext
AI Agent (Base wallet + USDC)
        │
        ▼
   HTTP Request (no auth)
        │
        ▼
┌──────────────────────────────┐
│        OpenCard API          │
│     (Express + x402)         │
│                              │
│  x402 paid (tier-based):     │
│   POST /cards/create/tier/N  │
│   POST /cards/fund/tier/N    │
│                              │
│  Wallet-signed (free):       │
│   GET  /cards                │
│   GET  /cards/:id            │
│   GET  /cards/:id/details    │
│   POST /cards/:id/freeze     │
│   POST /cards/:id/unfreeze   │
│                              │
│  Public:                     │
│   GET  /health               │
│   GET  /pricing              │
│   GET  /cards/tiers          │
└──────────┬───────────────────┘
           │
     ┌─────┴─────┐
     ▼           ▼
 Coinbase     Card
 x402          Issuing
 Facilitator   Provider