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 "x402[evm]" httpximport asyncio, json, os
from eth_account import Account
from x402 import x402Client
from x402.http import (
PAYMENT_REQUIRED_HEADER, X_PAYMENT_HEADER,
decode_payment_required_header, encode_payment_signature_header,
)
from x402.mechanisms.evm.exact import register_exact_evm_client
from x402.mechanisms.evm.signers import EthAccountSigner
import httpx
ENDPOINT = "https://mcp.soundside.ai/mcp"
NETWORK = "eip155:8453" # Base mainnet
async def main():
account = Account.from_key(os.environ["WALLET_PRIVATE_KEY"])
signer = EthAccountSigner(account)
payment_client = x402Client()
register_exact_evm_client(payment_client, signer, networks=NETWORK)
# Initialize MCP session
with httpx.Client(timeout=30) as c:
r = c.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"})
session_id = r.headers.get("mcp-session-id")
headers = {"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
"mcp-session-id": session_id}
async def call_tool(tool, args, timeout=60):
payload = {"jsonrpc": "2.0", "id": "2", "method": "tools/call",
"params": {"name": tool, "arguments": args}}
with httpx.Client(timeout=timeout) as c:
r = c.post(ENDPOINT, json=payload, headers=headers)
if r.status_code == 402:
pr = decode_payment_required_header(
r.headers.get(PAYMENT_REQUIRED_HEADER))
sig = encode_payment_signature_header(
await payment_client.create_payment_payload(pr))
with httpx.Client(timeout=timeout) as c:
r = c.post(ENDPOINT, json=payload,
headers={**headers, X_PAYMENT_HEADER: sig})
return r.json() if r.status_code == 200 else r.text
# Generate text — ~$0.01
print(await call_tool("create_text",
{"prompt": "Write a haiku about Base blockchain."}))
# Generate image — ~$0.04
print(await call_tool("create_image",
{"provider": "minimax", "prompt": "A futuristic city at sunset"}))
asyncio.run(main())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.39", "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 | ceiling; charged as quoted |
create_image | minimax / luma / grok / vertex / runway | $0.01–$0.04 | varies by provider |
create_video | luma / minimax / vertex / runway / grok | $0.28–$3.20 | async; includes Vertex Veo 3 |
edit_video | soundside.ai | $0.01 | 21 editing actions |
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 — use create_text or create_image |
x402_async_not_enabled | 400 | Tool is async; x402 is sync-only in beta — use /mcp with auth for async |
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 |