API REFERENCE

API /v1/x402

Three endpoints. No authentication on plan discovery. Payment is the auth on order creation. Below: full request/response schema for each.

BASE URL
https://api.esimahora.com/api/v1/x402
Same backend as the consumer site eSIM Ahora — production uptime, real catalog, real eSIM Access wholesale relationship.
GET

/plans

List all available eSIM plans, optionally filtered by country. No authentication. Cached for 5 minutes server-side.

QUERY PARAMETERS
ParamTypeDescription
countrystring (ISO 2)Optional. Filter to plans for one country, e.g. JP, US, TR.
typeenumOptional. single | regional | global.
Example response
200 OK
{
  "plans": [
    {
      "id": "JP_5GB_30D",
      "country": "JP",
      "country_name": "Japan",
      "data_gb": 5,
      "validity_days": 30,
      "price_usd": 6.21,
      "carrier": "NTT Docomo / SoftBank",
      "type": "single",
      "topup_supported": true
    },
    ...
  ],
  "count": 18
}
POST

/order

Create a pending order. Server reserves the order, replies 402 Payment Required with payment details in the response headers. Order expires in 30 minutes if unpaid (no cost to you).

REQUEST BODY
application/json
{
  "plan_id": "JP_5GB_30D",
  "callback_url": "https://your-server.example.com/x402-webhook"  // optional
}
RESPONSE — 402 PAYMENT REQUIRED

Critical headers (the x402 protocol payload):

HeaderDescription
x-paytoRecipient address (0x... for Polygon, EQ... for TON)
x-amountAmount in USD as decimal string, e.g. "6.21"
x-assetUSDC or USDT (uppercase)
x-chainpolygon or ton (lowercase)
x-token-addressOn-chain token contract address (USDC/USDT on the chosen chain)
x-expiresISO 8601 — pay before this or the order expires
x-order-idSame as the body order_id, for clients that read headers only
Example response body
402 Payment Required
{
  "order_id": "ord_a8f3b2c1d5e4f6a9",
  "status": "awaiting_payment",
  "plan_id": "JP_5GB_30D",
  "payment": {
    "to": "0x742d35cc6634c053...",
    "amount_usd": "6.21",
    "asset": "USDC",
    "chain": "polygon",
    "token_address": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
    "expires_at": "2026-05-15T14:30:00Z"
  }
}
GET

/order/{order_id}

Poll an order's status. Returns 402 while awaiting payment, 200 once eSIM is delivered, 410 if the order expired, 404 if the id doesn't exist.

STATUS LIFECYCLE
awaiting_payment→ on-chain transfer detected
payment_received→ ordering eSIM from wholesale
deliveredterminal — eSIM in response body
failedterminal — refund issued automatically
expiredno payment received within window
Example response when status === 'delivered'
200 OK (delivered)
{
  "order_id": "ord_a8f3b2c1d5e4f6a9",
  "status": "delivered",
  "plan_id": "JP_5GB_30D",
  "esim": {
    "iccid": "8901260123456789012",
    "qr_code_data": "LPA:1$smdp.example.com$matching_id",
    "qr_image_url": "https://api.esimahora.com/esim/qr/8901260...png",
    "activation_link": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$..."
  },
  "payment": {
    "tx_hash": "0xabc123...",
    "confirmed_at": "2026-05-15T14:05:23Z",
    "asset": "USDC",
    "amount_usd": "6.21"
  }
}

Error catalog

StatusCodeCause
400invalid_planplan_id not in catalog or disabled
400malformed_requestJSON parse error or missing required field
402awaiting_paymentNormal — keep polling. Not actually an error.
404order_not_foundorder_id never existed or was purged
410order_expiredPayment window passed. Create a new order.
422underpaidTx confirmed but amount < required. Refund issued.
422wrong_chainTx confirmed on different chain than order specified.
429rate_limitedSlow down. 60 req/min per IP on /order, 600/min on /plans.
500internal_errorServer bug. Logged. Retry with exponential backoff.
503esim_provider_unavailableWholesale provider (eSIM Access) is down. Order auto-retries; if still 503 after 60s, refund.

Refunds

Failed orders refund automatically to the address that paid (within ~5 minutes after the failure is detected). No support ticket needed.

User-requested refund (e.g. "I activated the wrong country"): not available — once the eSIM ICCID is issued by the wholesaler, we owe them. This is a hard rule of the protocol and is documented at order time.

Webhooks (optional)

If you pass callback_url when creating an order, we POST status updates to that URL instead of you having to poll. Signed with HMAC-SHA256 using a secret you generate at first call.

Example webhook payload
POST callback_url
{
  "order_id": "ord_a8f3b2c1d5e4f6a9",
  "event": "delivered",
  "timestamp": "2026-05-15T14:05:23Z",
  "esim": { ... }   // same shape as GET /order/{id}
}

# Headers:
#   x-x402-signature: sha256=abc123...   (verify with your secret)
#   x-x402-timestamp: 1715785523

Rate limits

  • · GET /plans: 600 requests / minute / IP
  • · POST /order: 60 requests / minute / IP
  • · GET /order/{id}: 600 requests / minute / IP

Higher limits for verified high-volume customers — see /contact.

Need help?

Email dev@esimx402.com with: order_id (if relevant), tx_hash (if you paid), expected vs actual behavior. Same-business-day response on most enquiries.

Contact details