Authentication
Ordian Mail uses x402 micropayments instead of API keys. Pay per request with USDC on Base.
Overview
Unlike traditional APIs that require registration and API keys, Ordian Mail uses the x402 payment protocol. Every paid endpoint returns a 402 Payment Required response with payment instructions. Your agent signs an EIP-3009 USDC authorization, includes it in the request headers, and the request proceeds.
This means: no accounts, no API keys, no rate limit tiers. If you have a USDC wallet on Base, you can use the API.
Wallet setup
Before your agent can pay for API requests, it needs a wallet with USDC on Base. There are two approaches depending on your use case.
Option A: Private key (quick start)
The fastest way to get started. Generate or import a private key and use it directly with viem.
import { privateKeyToAccount } from "viem/accounts";
// Load from environment variable — never hardcode private keys
const signer = privateKeyToAccount(
process.env.EVM_PRIVATE_KEY as `0x${string}`
);
console.log("Wallet address:", signer.address);Option B: CDP Server Wallet (recommended)
Coinbase CDP Server Wallets provide managed key infrastructure — no raw key exposure, configurable spending policies, and built-in guardrails for autonomous agents.
npm install @coinbase/cdp-sdk dotenvimport { CdpClient } from "@coinbase/cdp-sdk";
import { toAccount } from "viem/accounts";
import dotenv from "dotenv";
dotenv.config();
// Requires CDP_API_KEY_ID, CDP_API_KEY_SECRET, CDP_WALLET_SECRET in .env
const cdp = new CdpClient();
const cdpAccount = await cdp.evm.createAccount();
const signer = toAccount(cdpAccount);
console.log("CDP wallet address:", signer.address);CDP wallets are ideal for production agents: keys are managed server-side, you can set spending limits, and there's no risk of leaking a raw private key.
Funding your wallet
Your wallet needs two things on Base mainnet:
- USDC — to pay for API requests. Contract:
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 - ETH — a tiny amount for gas fees (very low on Base)
Ways to fund your wallet:
- Coinbase — withdraw USDC directly to Base (free withdrawals, no bridge fees)
- Bridge from Ethereum — use the Base Bridge to move USDC from Ethereum L1
- DEX on Base — swap ETH for USDC on Uniswap, Aerodrome, or similar
Setting up the x402 client
The x402 client libraries handle the full payment flow automatically: detect 402 responses, sign payments, and retry requests with the payment header. Install the packages for your language:
Node.js
# Using fetch (recommended)
npm install @x402/fetch @x402/evm viem
# Or using axios
npm install @x402/axios @x402/evm viemWith @x402/fetch:
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
const signer = privateKeyToAccount(
process.env.EVM_PRIVATE_KEY as `0x${string}`
);
const client = new x402Client();
registerExactEvmScheme(client, { signer });
// Wrap native fetch — all 402 responses are handled automatically
const fetchWithPayment = wrapFetchWithPayment(fetch, client);With @x402/axios:
import { x402Client, withPaymentInterceptor } from "@x402/axios";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
import axios from "axios";
const signer = privateKeyToAccount(
process.env.EVM_PRIVATE_KEY as `0x${string}`
);
const client = new x402Client();
registerExactEvmScheme(client, { signer });
// Wrap axios instance — 402 interceptor handles payments
const api = withPaymentInterceptor(
axios.create({ baseURL: "https://mail-api.ordian.ai" }),
client
);Python
# For httpx (async) — recommended
pip install "x402[httpx,evm]" eth_account
# For requests (sync)
pip install "x402[requests,evm]" eth_accountimport os
from eth_account import Account
from x402 import x402Client
from x402.http.clients import x402HttpxClient
from x402.mechanisms.evm import EthAccountSigner
from x402.mechanisms.evm.exact.register import register_exact_evm_client
# Create signer from private key
account = Account.from_key(os.getenv("EVM_PRIVATE_KEY"))
signer = EthAccountSigner(account)
# Create x402 client and register EVM scheme
client = x402Client()
register_exact_evm_client(client, signer)
# Use the httpx client — 402 payments are handled automatically
async with x402HttpxClient(client) as http:
response = await http.post(
"https://mail-api.ordian.ai/v1/inboxes",
json={"display_name": "My Agent"}
)Payment flow
Under the hood, the x402 client handles a 4-step flow. You only need to understand this if you're implementing the protocol manually.
Step 1: Make a request without payment
curl -X POST https://mail-api.ordian.ai/v1/send \
-H "Content-Type: application/json" \
-d '{"inbox_id": "...", "to": "...", "subject": "...", "text": "..."}'Step 2: Receive 402 response
The server responds with payment instructions:
{
"x402Version": 1,
"accepts": [
{
"scheme": "exact",
"network": "base",
"maxAmountRequired": "10000",
"resource": "POST /v1/send",
"description": "Send an email from an agent inbox",
"payTo": "0x...",
"maxTimeoutSeconds": 300
}
],
"error": "X-PAYMENT header is required"
}Step 3: Sign payment
Your agent signs an EIP-3009 transferWithAuthorization for the USDC amount specified. This authorizes the transfer without actually moving funds until the server settles.
Step 4: Retry with payment header
curl -X POST https://mail-api.ordian.ai/v1/send \
-H "Content-Type: application/json" \
-H "X-PAYMENT: <base64-encoded-payment-payload>" \
-d '{"inbox_id": "...", "to": "...", "subject": "...", "text": "..."}'The server validates the payment, settles the USDC transfer, and processes your request.
@x402/fetch, @x402/axios, or the Python x402 package) to handle this flow automatically. See Code Examples for complete working examples.Network
Ordian Mail operates on Base mainnet (Coinbase L2). Payments are in USDC. Gas costs on Base are negligible — typically very low per transaction.
Free endpoints
Some endpoints don't require payment:
GET /health— service health checkGET /skill.md— Agent Skills file downloadDELETE /v1/inboxes/:id— delete an inboxGET /v1/webhooks— list webhooksPOST /v1/domains/:id/verify— verify domain DNS