Subchapter 2.11
plugins/bitrefill.mdMarkdown23 KBView on GitHub
[!IMPORTANT] Complete Base MCP onboarding first (
SKILL.md). Path 1 (connect→JWT) is the default for agent-commerce. Fall back to Path 2 (pay-per-call x402) for multi-wallet or stateless agents. Use Path 3 (CLI) or Path 4 (Bitrefill MCP) only when the user has an existing Bitrefill account. Keep of Bitrefill MCP .
buy-productsautoApproveTurn USDC on Base into everyday spending — inside the conversation.
Bitrefill (opens in a new tab) is the world’s largest crypto-native storefront for digital goods: gift cards (Amazon, Apple, Steam, Uber, and 1,500+ more), mobile refills in 180+ countries, and data eSIMs for travel. Pay with USDC on Base from your Base Account; codes and install links land in chat — often in seconds, sometimes within a few minutes (see Business domain).
Why agents love it: one session can go from “I need a $25 Amazon US card” to a delivered PIN — search, pick a denomination, confirm, pay, poll, redeem — without leaving the thread. No Bitrefill signup on the default path: sign in once with your wallet, browse fee-free, pay only at checkout.
Already on Bitrefill? Link an existing account via CLI or the Bitrefill MCP and shop your usual catalog the same way.
Path selection, API routes, and Base MCP wiring: Detection · Submission
Path 1 — Connect → JWT (default): one wallet sign-in → session token (~2 h); browse/create/status without micro-fees; pay at checkout only.
Path 2 — Pay-per-call x402: no session; HTTP 402 + micro-fee on each gated call.
Path 3 — CLI: existing Bitrefill account via @bitrefill/cli (shell).
Path 4 — Bitrefill MCP: existing account via https://api.bitrefill.com/mcp (OAuth, shell-less).
After Base MCP is available (SKILL.md), pick a path:
Agent-commerce (USDC on Base, no Bitrefill account):
sign + web_request + x402 payment tools available, agent can persist a JWT → Path 1.Existing Bitrefill account:
npx @bitrefill/cli@latest --help → Path 3.search-products, buy-products, …) → Path 4.Never scrape https://www.bitrefill.com (403 from datacenters). Paths 1–2: Base MCP web_request to allowlisted api.bitrefill.com (custom-plugins.md). Paths 3–4: Bitrefill CLI or Bitrefill MCP.
https://api.bitrefill.com/mcp — OAuth at connector setup.
Cursor: .cursor/mcp.json or ~/.cursor/mcp.json:
{
"mcpServers": {
"bitrefill": {
"url": "https://api.bitrefill.com/mcp",
"autoApprove": [
"search-products", "get-product-details",
"list-invoices", "get-invoice-by-id",
"submit-prepayment-step", "update-order"
]
}
}
}buy-products out of autoApprove.
Claude Code: claude mcp add bitrefill --url https://api.bitrefill.com/mcp
Reconnect/restart after install. MCP tools (verified): search-products, get-product-details, buy-products, submit-prepayment-step, list-invoices, get-invoice-by-id, update-order.
Path 1 connect uses SIWX → JWT. Path 2 needs no auth. Post-purchase redemption codes on invoice/status require SIWX from the paying wallet (Path 1 JWT covers browse/status polling; if codes are missing, run SIWX for codes below). Path 3/4 use CLI/MCP OAuth.
The address in the signed message and SIWX payload must be EIP-55 checksummed (mixed case). Lowercase addresses from get_wallets cause the API to reject the signature and return another 402. Always run toChecksumAddress before building the message.
Base Account signatures are ERC-1271/6492 wrapped (~224 bytes). Pass the full signature unchanged; header type stays "eip191". Wallet reads and write approvals follow approval-mode.md.
Challenge nonce expires in 5 minutes and is single-use. Fetch challenge → build message → sign → send header within that window. If connect returns 402 again, fetch a fresh challenge and repeat — do not reuse a stale nonce or signature.
web_request POST https://api.bitrefill.com/x402/connect with body {} and header Content-Type: application/json → 402 whose JSON body includes extensions["sign-in-with-x"].info (domain, uri, version, nonce, issuedAt, expirationTime, statement, resources) and supportedChains.get_wallets → wallet address.{ chainId: "eip155:8453", type: "eip191" } from supportedChains.toChecksumAddress → buildSiweMessage). The uri in the challenge for connect is https://api.bitrefill.com/x402/connect.sign with type: "personal_sign", data: { message: <exact string> } → follow approval-mode.md → signature.{ message, signature }), base64-encode → sign-in-with-x request header (see payload shape below).web_request POST https://api.bitrefill.com/x402/connect with body {}, Content-Type: application/json, and header sign-in-with-x: <base64> → 200 with { token, token_header: "X-Access-Token", expires_in } (default ~7200 s).token in memory only. Attach X-Access-Token: <token> (raw JWT, no Bearer) on every subsequent gated web_request. Re-connect when expired.When invoice/status returns delivery complete but no redemption_info, or Path 2 without JWT:
web_request GET https://api.bitrefill.com/x402/invoice/status?invoice_id=<uuid> → 402 with SIWX challenge (save the full JSON body).info — uri must match the challenged route (includes ?invoice_id=).sign → follow approval-mode.md within 5 minutes.web_request same URL with sign-in-with-x header → 200 with redemption_info.orders[].redemption_info (code, pin, extra_fields).my/orders and my/esims use the same SIWX flow (Path 1 JWT also works on these routes).
Base64 of this JSON (send as sign-in-with-x header):
{
"domain": "api.bitrefill.com",
"address": "0x<EIP-55 checksummed>",
"statement": "<info.statement>",
"uri": "<info.uri — must match the route being authenticated>",
"version": "1",
"chainId": "eip155:8453",
"type": "eip191",
"nonce": "<info.nonce>",
"issuedAt": "<info.issuedAt>",
"expirationTime": "<info.expirationTime>",
"resources": ["<info.resources[]>"],
"signature": "0x<full signature from get_request_status>"
}Chain ID is numeric (8453) inside the signed message but CAIP-2 (eip155:8453) in the payload.
<domain> wants you to sign in with your Ethereum account:
<checksummed_address>
<statement>
URI: <uri>
Version: 1
Chain ID: 8453
Nonce: <nonce>
Issued At: <issuedAt>
Expiration Time: <expirationTime>
Resources:
- <resource_url>Verified against siwe@2.3.2 and @x402/extensions@2.3.0. Run in Node 18+ or any JS shell.
JavaScript:
const RC = [
0x0000000000000001n,0x0000000000008082n,0x800000000000808an,0x8000000080008000n,
0x000000000000808bn,0x0000000080000001n,0x8000000080008081n,0x8000000000008009n,
0x000000000000008an,0x0000000000000088n,0x0000000080008009n,0x000000008000000an,
0x000000008000808b
Run the JavaScript block in Node (node -e '…') or any harness with BigInt. Do not hand-type checksummed addresses. If no JavaScript runtime is available, use Path 2 (no connect SIWX) or obtain checksum/message from a one-line Node invocation before calling sign.
Bitrefill API names do not match everyday ecommerce wording — map them when talking to users:
| API term | Means | User-facing |
|---|---|---|
| invoice | Price-locked checkout (cart + total), not a billing document | “your order” / “checkout” |
| order | One cart line item (one product/denomination) | “item” |
Flow: invoice/create → invoice/pay → backend async fulfillment of each order. Gift-card codes are often near-instant; some products take minutes. Poll invoice/status until delivery_status is all_delivered — do not treat payment confirmation as delivery.
If an item is still not fulfilled after 3 hours, or the user hits any service issue, point them to help.bitrefill.com (opens in a new tab).
Base URL: https://api.bitrefill.com (no /api prefix on x402 routes). Every gated response embeds next_step: { url, body } chaining search → detail → create → pay → status.
402 envelope: payment-required header is base64 JSON, mirrored into the JSON body.
| Method | Path | Cost | Auth (no JWT) |
|---|---|---|---|
GET | /x402/gift-cards/search?q=&country= | $0.002 | x402 |
GET | /x402/esims/search?q= | $0.002 | x402 |
GET | /x402/topups/search?q= | $0.002 | x402 |
GET | /x402/checkout/info | $0.001 | x402 |
GET | /x402/products/detail?slug= | $0.001 | x402 |
POST | /x402/invoice/create | $0.002 | x402 |
POST | /x402/invoice/pay | invoice amount | x402 (never JWT-waived) |
GET | /x402/invoice/status?invoice_id= | $0.001 or SIWX | x402 or SIWX |
POST | /x402/connect | free | SIWX → JWT |
GET | /x402/my/orders | free | SIWX or JWT |
GET | /x402/my/esims | free | SIWX or JWT |
With valid X-Access-Token, the gate bypasses micro-fees and SIWX on all gated routes except /x402/connect (cannot mint with a token) and invoice/pay (invoice amount never waived).
Repeat reads: paying a route once grants that wallet fee-free access to the same path for 30 days. SIWX routes still need a signature per request when not using JWT.
| Field | Value |
|---|---|
| Network (CAIP-2) | eip155:8453 |
| USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| payTo | 0x480CD46E6faDe651a0437DeaddA53D5c8e7D846A |
All checkout and x402 pay flows use USDC on Base only.
Use the exact string from products/detail packages[].package_value — do not transform.
| Type | Format | Example |
|---|---|---|
| Gift cards | Bare integer string | "10", "50" |
| Top-ups | Bare integer string | "5", "20" |
| eSIMs | Descriptive label | "1GB, 7 Days", "Unlimited, 15 Days" |
Wrong package_value → HTTP 500 on invoice/create (no charge). Confirm no charge before retry (see Notes).
invoice/create — gift card:
{ "items": [{ "product_id": "amazon_it-italy", "package_value": "10" }] }Top-up with phone (recipient_required: true on detail):
{ "items": [{ "product_id": "iliad-italy", "package_value": "10", "refill_input": "+39XXXXXXXXXX" }] }Response: { "invoice_id", "price_usdc", "price_usd", "expires_in_minutes", "next_step" }.
invoice/pay — { "invoice_id": "<uuid>" }. Success: { "success": true, "status": "payment_confirmed", "transaction", "next_step" }.
invoice/status — without SIWX/JWT: status fields only. With SIWX from payer or Path 1 JWT: adds redemption_info.orders[].redemption_info (code, pin, extra_fields).
| Capability | Path 1 | Path 2 | Path 3 | Path 4 |
|---|---|---|---|---|
| Connect / JWT | sign + web_request /connect | — | — | — |
| Search / browse | web_request + X-Access-Token | x402 pay per route | CLI | search-products |
| Product detail | web_request + token | x402 | CLI | get-product-details |
| Invoice create | web_request + token | x402 | CLI / MCP | buy-products |
| Pay invoice | Base MCP x402 tools | Same | x402 or send | Same |
| Poll status | web_request + token | x402 or token-less poll + SIWX for codes | CLI | get-invoice-by-id |
| Redemption codes | JWT or SIWX on invoice/status | SIWX from payer | CLI | get-invoice-by-id → orders[].redemption_info |
| Order history | web_request /my/orders + token | SIWX per request | CLI | list-invoices |
On claude.ai, render search/detail/invoice/status via the built-in show_widget tool — inline in chat, not Artifacts, not HTML in prose. Narrative stays in the message; only the visual goes in widget_code (title: snake_case).
Fragment order: <style> → HTML → <script>. No html/head/body. Host CSS vars only (var(--color-*)); no font-family; no shadows/gradients/blur/transition: all. Body ≥16px; interactive min-height: 44px.
One primary + one secondary CTA. Primary follows next_step (“See details for …” → “Buy … on …” → “Confirm and pay … USDC” → “Check delivery status”); wire it with sendPrompt(...). Secondary is lateral escape only. Selection: transparent 2px border + outline at rest; selected var(--color-border-info) + var(--color-background-info). Pre-select defaults so primary is actionable on first paint.
expires_in. Do this once per session.web_request GET /x402/gift-cards/search?q=<term>&country=<ISO> with X-Access-Token. Take slug from products[]. (Or esims/search, topups/search.)web_request GET /x402/products/detail?slug=<slug> + token. Confirm in_stock, currency, packages[], recipient_required.web_request POST /x402/invoice/create + token with { "items": [{ "product_id": "<slug>", "package_value": "<exact>" }] } (+ refill_input if top-up). Read invoice_id, price_usd.POST https://api.bitrefill.com/x402/invoice/pay with { invoice_id }, maxPayment just above price_usd → approval-mode.md → complete x402 request.web_request GET /x402/invoice/status?invoice_id=<id> + token until delivery_status is all_delivered.redemption_info missing, run SIWX for codes with the paying wallet. Deliver code / pin securely (Risks & Warnings).web_request → 402 → x402 pay that URL/method/body, maxPayment: "0.01" → approval-mode.md.next_step through detail → create → pay.maxPayment just above price_usd.npx @bitrefill/cli@latest --help
npx @bitrefill/cli@latest --json whoami
npx @bitrefill/cli@latest search-products --query "Steam" --country US
npx @bitrefill/cli@latest buy-products \
--cart_items '[{"product_id":"steam-usa","package_id":5}]' \
--payment_method usdc_base --return_payment_link trueT3 (login/verify) for list-orders. Create with --payment_method usdc_base.
Pay the Base invoice — once buy-products returns invoice_id, agent picks one (follow agent_instructions):
x402_payment_url via Base MCP x402 tools → approval-mode.md.send to payment_info address/amount on base → approval-mode.md → poll CLI/get-invoice-by-id until paid.Confirm amount and destination with the user. Poll until fulfilled (see Business domain).
Install + OAuth → search-products → get-product-details → user approves → buy-products with payment_method: "usdc_base" → pay → poll → redeem.
Pay the Base invoice — same as Path 3: x402 on x402_payment_url or send to payment_info. Poll get-invoice-by-id until invoice_status is payment_confirmed, then until invoice_status: complete / orders_delivery_status: all_delivered. Redemption: orders[].redemption_info.
Bitrefill uses Base MCP for reads, SIWX, x402, and direct USDC — not send_calls. Read tool names and schemas from the MCP catalog (SKILL.md).
| Bitrefill step | Base MCP | Bitrefill-specific |
|---|---|---|
| Paths 1–2 API calls | web_request | Host api.bitrefill.com (allowlisted). Headers: X-Access-Token (raw JWT, not Authorization), sign-in-with-x, Content-Type. HTTP routing: custom-plugins.md |
| SIWX connect / codes | get_wallets, sign | Decomposed SIWX payload in sign-in-with-x header — see Auth. EIP-55 checksum required |
x402 micro-fees, invoice/pay, Path 3/4 x402_payment_url | x402 tools from MCP catalog | maxPayment: "0.01" for micro-fees; slightly above invoice price_usd for pay |
| Path 3/4 direct USDC | send | { chain: "base", asset: "USDC", recipient, amount } from payment_info |
All write paths: approval-mode.md. On 402 responses, pay the Base USDC (eip155:8453) entry from accepts[].
$25 Amazon US — Path 1: Connect → JWT → search + token → detail → create → confirm → x402 pay → poll → deliver code.
Browse, no session — Path 2: x402 micro-fee on each search/detail/create; SIWX for codes after pay.
Existing account — Path 3/4: CLI or MCP catalog → Base USDC invoice → x402 or send → poll → redeem.
pii — Redemption codes, eSIM QR URLs, PINs are bearer credentials. Never log, commit, or paste in public channels. Return codes only when the user explicitly asks.irreversible — Digital goods are non-refundable once fulfilled. Confirm product, denomination, price, and payment before pay.X-Access-Token is a bearer secret (~2 h). Never echo in chat. Re-connect when expired.~/.config/bitrefill-cli/<host>.v1.json is sensitive.US, IT).X-Access-Token header only — Base MCP strips Authorization.package_value; retry without paying until onchain history confirms no charge.INVOICE_NOT_PAYABLE on pay — already settled; poll status instead.