x402 Pay-Per-Call
Use Soundside AI tools with USDC on Base — no account, no API key, no signup. Ideal for autonomous agents that need to call tools without a pre-funded balance.
Quick start
Recommended: use the x402 Python library
The x402 library handles the full 402 → sign → retry cycle automatically. Do not hand-roll the payment-signature header — the encoding is non-trivial and manual construction is the #1 source of errors.
pip install "git+https://github.com/coinbase/x402.git#subdirectory=python/x402&egg=x402[evm]" requests eth-accountimport json, os, requests
from eth_account import Account
from x402 import x402ClientSync
from x402.http import encode_payment_signature_header, X_PAYMENT_HEADER
from x402.mechanisms.evm.exact import register_exact_evm_client
from x402.mechanisms.evm.signers import EthAccountSigner
from x402.schemas import PaymentRequired
ENDPOINT = "https://mcp.soundside.ai/mcp"
# Set up wallet + payment client
account = Account.from_key(os.environ["WALLET_PRIVATE_KEY"]) # 0x-prefixed hex
payment_client = x402ClientSync()
register_exact_evm_client(payment_client, EthAccountSigner(account))
def call_tool(tool: str, args: dict, timeout: int = 120) -> dict:
"""Call a Soundside MCP tool with automatic x402 payment."""
session = requests.Session()
# 1. Initialize MCP session (required for each call)
init_r = session.post(ENDPOINT, json={
"jsonrpc": "2.0", "id": "1", "method": "initialize",
"params": {"protocolVersion": "2025-11-25", "capabilities": {},
"clientInfo": {"name": "x402-example", "version": "1.0"}},
}, headers={"Content-Type": "application/json",
"Accept": "application/json, text/event-stream"}, timeout=30)
session_id = init_r.headers.get("mcp-session-id", "")
headers = {"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
"mcp-session-id": session_id,
"x402-wallet": account.address} # required for resource attribution
payload = {"jsonrpc": "2.0", "id": "2", "method": "tools/call",
"params": {"name": tool, "arguments": args}}
# 2. First attempt — server returns 402 Payment Required
r = session.post(ENDPOINT, json=payload, headers=headers, timeout=timeout)
if r.status_code == 402:
# 3. Parse requirements from response BODY (not a header)
pr = PaymentRequired.model_validate(r.json())
# 4. Sign off-chain EIP-3009 authorization (no gas needed)
sig = encode_payment_signature_header(
payment_client.create_payment_payload(pr))
# 5. Retry with signed payment header
r = session.post(ENDPOINT, json=payload,
headers={**headers, X_PAYMENT_HEADER: sig}, timeout=timeout)
# 6. Parse SSE response — prefer structuredContent
for line in r.text.splitlines():
if line.startswith("data:"):
obj = json.loads(line[5:])
if "result" in obj:
sc = obj["result"].get("structuredContent", {})
if sc: return sc
for ct in obj["result"].get("content", []):
if ct.get("type") == "text":
try: return json.loads(ct["text"])
except: return {"text": ct["text"]}
return {}
# ── Examples ──
# Generate text (~$0.01 USDC)
result = call_tool("create_text", {
"prompt": "Write a haiku about Base blockchain.",
"provider": "vertex"})
print(result.get("message") or result.get("text"))
# Generate image (~$0.04 USDC)
result = call_tool("create_image", {
"prompt": "A futuristic city at sunset",
"provider": "minimax"})
print(f"Resource: {result.get('resource_id')}")
print(f"Wallet link: {result.get('wallet_link')}")
# Edit an image (~$0.01 USDC)
result = call_tool("edit_video", {
"resource_id": result["resource_id"],
"action": "add_text",
"text": "Hello x402",
"position": "bottom"})
print(f"Edited: {result.get('resource_id')}")
# Analyze media (~$0.01 USDC)
result = call_tool("analyze_media", {
"resource_id": result["resource_id"],
"analysis_type": "technical"})
print(f"Type: {result.get('metadata', {}).get('mime_type')}")Fund your wallet with USDC on Base: send real USDC from Coinbase (select the Base network, not Ethereum mainnet). A small amount of ETH on Base is also needed for gas (~0.002 ETH is more than enough).
Discovery
Machine-readable pricing and status
{
"enabled": true,
"endpoint": "https://mcp.soundside.ai/mcp",
"settlement_provider": "coinbase",
"pay_to_mode": "static_wallet",
"network": "eip155:8453",
"token": "USDC",
"facilitator_url": "https://api.cdp.coinbase.com/platform/v2/x402",
"pricing_model": "ceiling-quote",
"enabled_tools": [
{ "tool": "create_text", "provider": "vertex", "price_usdc": "0.01", "sync": true },
{ "tool": "create_image", "provider": "minimax", "price_usdc": "0.04", "sync": true },
{ "tool": "create_video", "provider": "luma", "price_usdc": "0.40", "sync": false },
...
],
"docs": "https://soundside.ai/docs/x402"
}Always fetch this to get the live network and tool surface before making a call. The per-call 402 response is always authoritative for the exact payment amount.
Protocol
How the 402 cycle works
The x402 library handles all of this for you. This section is for agents implementing the protocol from scratch.
- Send your tool call — POST to
/mcpwith a standard MCPtools/callJSON-RPC body. No auth header needed.Step 1 — initial requestPOST https://mcp.soundside.ai/mcp Content-Type: application/json { "jsonrpc": "2.0", "id": "1", "method": "tools/call", "params": { "name": "create_text", "arguments": { "prompt": "Write a haiku about Base." } } } - Receive 402 — The response body is a JSON
PaymentRequiredobject. Theaccepts[0]entry has the deposit address, amount, and network.Step 2 — 402 response body{ "x402Version": 2, "error": "Payment required", "accepts": [{ "scheme": "exact", "network": "eip155:8453", "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "amount": "10000", "payTo": "0x<stripe-issued deposit address>", "maxTimeoutSeconds": 300, "extra": { "name": "USDC", "version": "2" } }] } - Sign an EIP-3009 authorization — This is an off-chain signature (no on-chain transaction, no gas). You authorize the transfer of USDC from your wallet to the
payToaddress using EIP-712 typed data and EIP-3009transferWithAuthorization. - Retry with payment-signature header — Re-send the identical request body with the
payment-signatureheader set to a base64-encoded JSONPaymentPayload:Step 4 — payment-signature header structure// payment-signature = base64( JSON.stringify({ { "x402Version": 2, "payload": { "signature": "0x<EIP-712 sig>", "authorization": { "from": "0x<your wallet>", "to": "0x<payTo from step 2>", "value": "10000", "validAfter": "0", "validBefore": "<now + maxTimeoutSeconds as unix timestamp>", "nonce": "0x<32 random bytes, hex>" } }, "accepted": { <exact copy of the accepts[0] object from step 2> } } // )) // The "accepted" field must be an exact copy of accepts[0]. // Any mismatch causes: "Invalid payment signature header" - Receive 200 — Facilitator verifies the authorization and executes the on-chain transfer. The tool result is in the response body.Step 5 — success
HTTP/1.1 200 OK PAYMENT-RESPONSE: <settlement confirmation> { "jsonrpc": "2.0", "id": "1", "result": { "content": [{ "type": "text", "text": "Base in spring bloom..." }] } }
Pricing
Ceiling-quote model
The amount in the 402 response is the exact payment charged.
| Tool | Provider | Price (USDC) | Notes |
|---|---|---|---|
create_text | vertex / grok / minimax | $0.01 | Sync |
create_image | minimax / luma / grok / vertex / runway | $0.02–$0.08 | Sync (varies by provider) |
create_video | luma / minimax / vertex / runway / grok | $0.28–$1.60 | Async — poll with x402_session_token |
create_audio | minimax / vertex | $0.01–$0.04 | TTS sync, sound effects async |
create_music | minimax | $0.04 | Async — poll with x402_session_token |
create_artifact | soundside.ai | $0.01 | Sync (presentations, charts, docs) |
edit_video | soundside.ai | $0.01 | Sync (trim, concat, crossfade, speed, loop, etc.) |
edit_audio / compose_media / apply_effect / extract_media | soundside.ai | $0.01 | Sync (editing tools) |
analyze_media | soundside.ai | $0.01–$0.03 | Sync ($0.03 for vision_qa) |
lib_list | — | Free | Use REST endpoints instead (see below) |
lib_manage | — | $0.01 | Create/update/delete library entities |
lib_share | — | $0.01 | Share projects by email |
Full live pricing: GET /api/x402/status | |||
Behavior
Stateless and authenticated modes
- • No auth header (default) — fully anonymous. Results returned directly in the response. For
create_image, the image is returned as a signed URL. Nothing is saved to a library. - • With auth header — add
Authorization: Bearer <token>to identify as a Soundside user. Payment still goes via x402, but results can be saved to your library if you passproject_idorcollection_id.
Browser access
Wallet link in x402 responses
After a successful tool execution via x402, the response includes a wallet_link field in structuredContent — a short-lived signed URL (15-minute TTL) that grants browser access to the payer's library.
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"structuredContent": {
"success": true,
"resource_id": "abc-123",
"url": "https://storage.googleapis.com/...",
"wallet_link": "https://www.soundside.ai/auth/wallet-link?token=eyJ...",
"x402_session_token": "eyJ..."
}
}
}For agents: Surface the wallet_link URL to the human user. Visiting it creates a full browser session — no password, no wallet extension, no signup required. The user can then view and manage their generated assets at /library and /projects.
Use x402_session_token for async resource polling on /api/x402/resource/<resource_id> and /api/x402/resources. Send it as either Authorization: Bearer <token> or X-Session-Token: <token>. It is also returned in the X-Session-Token response header.
For operators: these are server-issued tokens, not client secrets. Configure WALLET_LINK_SECRET to enable wallet_link, and optionally set X402_SESSION_TOKEN_SECRET for a dedicated polling-token signing key. If the latter is unset, the server reuses WALLET_LINK_SECRET.
For browser users: The login page at /login also includes a "Continue with Wallet" button that uses SIWE (Sign-In with Ethereum) — connect MetaMask or Coinbase Wallet, sign a message, and get a session tied to the same wallet identity.
Both flows produce the same session. Assets generated via x402 payments appear in the user's library immediately after sign-in.
Error reference
x402 error codes
| Code | HTTP | Meaning / Fix |
|---|---|---|
unknown_tool | 422 | Tool doesn't exist — call tools/list first |
invalid_tool_arguments | 400 | Argument validation failed — check tools/list for the input schema |
invalid_provider | 422 | Provider not available — check tools/list for valid providers |
x402_validation_failed | 400 | Missing required params for this tool/mode — no payment charged, fix args and retry |
Invalid payment signature header | 402 | Use the x402 library — accepted block must be an exact copy of the 402 response accepts[0] |
x402_tool_not_enabled | 403 | Tool not on x402 allowlist — check /api/x402/status for enabled tools |
x402_provider_not_enabled | 403 | Provider not supported on x402 — check status endpoint for valid providers |
x402_arguments_not_allowed | 403 | project_id/collection_id blocked without bearer token |
x402_replay_detected | 402 | Nonce already used — generate a fresh nonce per call |
x402_settlement_failed | 402 | Facilitator rejected settlement — check network and asset |