QUICKSTART

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.

$ curl
# 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).

$ curl
# 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.

$ cast (Foundry)
# 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.

$ curl
# 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

Python (httpx + your wallet of choice)
python
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"])
Node.js (Coinbase x402-fetch handles the loop)
node
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

errors
# 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 →