Ship in 10 minutes.
Four HTTP requests. No SDK to install. Bring any x402-compatible wallet — we test against Coinbase reference clients in Python, Node, and Go, but the wire format is open and minimal.
01List plans (free, no auth)
Discover what's available before locking yourself into a payment. Filter by country code (ISO 2-letter) to scope the list. Returns ~10-30 plans per country.
# 1. List available plans (no auth required — discovery is free)
curl https://api.esimahora.com/api/v1/x402/plans?country=JP
# Response (200 OK)
# {
# "plans": [
# { "id": "JP_5GB_30D", "country": "JP", "data_gb": 5,
# "validity_days": 30, "price_usd": 6.21,
# "carrier": "NTT Docomo / SoftBank" },
# { "id": "JP_10GB_30D", ... },
# ...
# ]
# }02Request the order — server returns 402
POST the plan_id you want. Server creates a pending order and returns 402 Payment Required with payment details in the response headers. The order is reserved for ~30 minutes — pay within that window or the order expires (no charge).
# 2. POST /order — server replies 402 Payment Required
curl -X POST https://api.esimahora.com/api/v1/x402/order \
-H "Content-Type: application/json" \
-d '{"plan_id": "JP_5GB_30D"}'
# Response: 402 Payment Required
# Headers:
# x-payto: 0x742d35cc6634c053... (recipient address)
# x-amount: 6.21 (USD)
# x-asset: USDC (or USDT)
# x-chain: polygon (or ton)
# x-expires: 2026-05-15T14:30:00Z (your wallet has ~30min)
# Body: { "order_id": "abc123", "status": "awaiting_payment" }03Pay on-chain
Send the exact USD-equivalent amount of USDC or USDT (whichever x-asset said) to the address in x-payto on the chain in x-chain. Any wallet/library that can send ERC-20 transfers works — there's no x402-specific protocol on the payment side, just a standard token transfer.
# 3. Pay on-chain (your wallet's job — any x402-compatible client works) # Coinbase x402-fetch, AgentKit, or roll-your-own ERC-20 transfer # Example with cast (Foundry): cast send 0xUSDC_POLYGON_CONTRACT_ADDRESS \ "transfer(address,uint256)" \ 0x742d35cc6634c053... 6210000 \ --rpc-url polygon --private-key $PK
On Polygon: ~5-10 sec confirmation, ~$0.001 gas.
On TON: ~3-5 sec confirmation, ~$0.005 gas.
04Poll until your eSIM is ready
Once we see your transaction confirmed on-chain, we order the eSIM from the wholesale provider (eSIM Access — same upstream as Airalo/Saily). Total time from confirmed payment to delivered QR: typically 3-5 seconds.
# 4. Poll until ready (~5-10s on Polygon, 3-5s on TON)
curl https://api.esimahora.com/api/v1/x402/order/abc123
# 200 OK once payment confirmed
# {
# "status": "delivered",
# "esim": {
# "iccid": "8901260...",
# "qr_code_data": "LPA:1$...",
# "qr_image_url": "https://...",
# "activation_link": "https://esimsetup.apple.com/..."
# }
# }Full examples
import time
import httpx
API = "https://api.esimahora.com/api/v1/x402"
# 1. Browse + 2. Request order
plans = httpx.get(f"{API}/plans", params={"country": "JP"}).json()
plan = next(p for p in plans["plans"] if p["data_gb"] == 5)
r = httpx.post(f"{API}/order", json={"plan_id": plan["id"]})
assert r.status_code == 402
order_id = r.json()["order_id"]
payto = r.headers["x-payto"]
amount_usd = r.headers["x-amount"]
chain = r.headers["x-chain"]
# 3. Pay (delegated to your wallet — any USDC-on-Polygon transfer works)
your_wallet.send_usdc(to=payto, amount=amount_usd, chain=chain)
# 4. Poll until eSIM is ready
while True:
r = httpx.get(f"{API}/order/{order_id}")
if r.json()["status"] == "delivered":
esim = r.json()["esim"]
break
if r.json()["status"] == "failed":
raise Exception("Order failed")
time.sleep(2)
print("eSIM ready:", esim["qr_image_url"])import { fetch402 } from '@coinbase/x402-fetch';
// fetch402 transparently handles the 402 → wallet pay → poll loop
// Configure with your wallet/provider once at startup:
// import { configureWallet } from '@coinbase/x402-fetch';
// configureWallet({ chain: 'polygon', privateKey: process.env.PK });
const r = await fetch402('https://api.esimahora.com/api/v1/x402/order', {
method: 'POST',
body: JSON.stringify({ plan_id: 'JP_5GB_30D' }),
});
const esim = await r.json();
console.log('Activation link:', esim.activation_link);
console.log('QR image:', esim.qr_image_url);Errors you'll see
# Common error responses # 400 — invalid plan_id # 402 — payment not yet received (poll again) # 404 — order_id not found (check spelling, or it expired) # 410 — order expired (payment window passed; create new order) # 503 — eSIM provider temporarily unavailable (retry in 30s)
Standard exponential backoff for 503. Don't retry 410 (order expired) — create a new order instead. The full error catalog is in the docs.
Next: full API reference
Schema definitions, error codes, every endpoint, every header. Hosted on the API itself so it's always in sync with what the server actually returns.
Open the docs →