MeiSIM Dealer API

REST API for authorized dealers to programmatically order eSIMs, top up SIMs, and manage their wallet.

Base URLhttps://meisimusa.com
Content typeapplication/json
API versionv1

Quick start

# 1. Get your API key (one-time)
curl -X POST https://meisimusa.com/dealer/send-otp \
  -H 'content-type: application/json' \
  -d '{"email":"you@yourcompany.com"}'

# Check email for the 6-digit code, then:
curl -X POST https://meisimusa.com/dealer/verify-otp \
  -H 'content-type: application/json' \
  -d '{"email":"you@yourcompany.com","code":"123456"}'
# → returns { "verified": true, "apiKey": "..." }

# 2. Use the API key on every request
curl https://meisimusa.com/api/v1/me \
  -H 'x-dealer-key: YOUR_API_KEY'

Authentication

Two equivalent auth methods. Use either on any endpoint.

Option A — API Key (recommended for backend integration)

Issued via the OTP flow below. Passed in the request header:

x-dealer-key: YOUR_API_KEY

The key does not expire. Treat it like a password.

Option B — JWT (for portal/UI integrations)

Issued via the password-login flow. Passed in the standard Bearer header:

Authorization: Bearer YOUR_JWT

JWT expires after 12 hours. Re-login to refresh.

Auth flow — OTP login

POST/dealer/send-otp

Send a 6-digit code to your email.

{ "email": "you@yourcompany.com" }

Response: { "sent": true }. Rate limit: 5 sends per 15 min per IP. Code expires in 5 minutes; max 5 verification attempts.

POST/dealer/verify-otp

Verify the code and receive your API key.

{ "email": "you@yourcompany.com", "code": "123456" }

Response:

{ "verified": true, "apiKey": "dk_..." }

Rate limit: 10 verifications per 15 min per IP.

Auth flow — password login (returns JWT)

POST/dealer/auth/set-password

One-time password setup. Requires x-dealer-key from the OTP flow.

{
  "email": "you@yourcompany.com",
  "password": "min-8-chars"
}

Response: { "ok": true }.

POST/dealer/auth/login

Email + password → JWT.

{ "email": "you@yourcompany.com", "password": "..." }

Response:

{
  "ok": true,
  "token": "eyJhbGc...",
  "expiresAt": "2026-05-06T12:00:00.000Z",
  "dealer": { "id": "...", "email": "...", "company": "..." }
}

Rate limit: 8 attempts per 15 min per IP.

eSIM endpoints

GET/api/v1/esim/countries

List supported countries. Returns the upstream country catalog.

POST/api/v1/esim/bundles

List bundles for a country/region.

{
  "countryISO":   "JP",            // optional, ISO 3166-1 alpha-2
  "countryName":  "Japan",         // optional, alternative to ISO
  "region":       "Asia",          // optional
  "pageNumber":   1,               // optional, default 1
  "pageSize":     20               // optional, default 20, max 100
}

Response: paginated bundle list with each bundle's name, data allowance, validity, and price.

POST/api/v1/esim/order

Order an eSIM for an end customer.

{
  "bundleName":        "esim_5gb_30days_jp",  // required, from /bundles
  "customerReference": "ORDER-12345",         // optional, your tracking id
  "emailAddress":      "end-customer@x.com",  // optional, where the QR is sent
  "qrToCustomer":      true                    // optional, default false
}

Response: { "Status": "Success", "Iccid": "...", "QrCode": "...", ... } (upstream-format). The order is logged in your dealer history.

POST/api/v1/esim/details

Get activation status and data usage.

{ "iccid": "894900..." }

Returns activation state, data used vs allowance, expiry.

POST/api/v1/esim/revoke

Deactivate an eSIM upstream and update your dealer order history.

{ "iccid": "894900..." }

Rate limit: 10 per minute per dealer.

Top-up endpoints

For recharging existing SIMs (not new eSIMs).

GET/api/v1/topup/networks

List networks eligible for top-up.

POST/api/v1/topup/confirm

Validate a target SIM exists on a network. Use this before recharge to avoid charging a non-existent SIM.

{
  "networkName":     "Vodafone-UK",
  "contactNumber":   "+447700900000",   // either contactNumber
  "simSerialNumber": "894400..."         // OR simSerialNumber
}

POST/api/v1/topup/recharge

Execute a top-up recharge.

{
  "networkName":       "Vodafone-UK",       // required
  "contactNumber":     "+447700900000",     // OR simSerialNumber
  "simSerialNumber":   "894400...",
  "topUpValue":        20,                  // required, currency depends on network
  "bundleValue":       null,                // optional bundle add-on
  "bundleProductCode": null
}

Account & history

GET/api/v1/me

Whoami.

{ "id": "...", "company_name": "...", "email": "...", "status": "approved" }

GET/api/v1/orders

Recent orders. Returns up to the last 100, newest first.

[
  {
    "id": "...",
    "order_type": "esim" | "topup",
    "bundle_name": "esim_5gb_30days_jp",
    "customer_reference": "ORDER-12345",
    "email_address": "end-customer@x.com",
    "iccid": "894900...",
    "amount": 6.50,
    "status": "completed",
    "created_at": "2026-05-05T12:34:56Z"
  }
]

Wallet

GET/dealer/wallet

Wallet balance and recent transactions.

{
  "ok": true,
  "balance": 245.30,
  "markupPct": 15,
  "resendFee": 1.00,
  "company": "...",
  "email": "...",
  "transactions": [
    {
      "id": "...",
      "created_at": "2026-05-05T12:34:56Z",
      "type": "debit" | "topup",
      "amount_usd": -6.50,
      "balance_after_usd": 245.30,
      "description": "...",
      "stripe_session_id": null,
      "related_order_id": "..."
    }
  ]
}

GET/dealer/statement

CSV export. Default range: last 90 days.

GET /dealer/statement?from=2026-04-01&to=2026-05-01

Returns CSV with columns: Date, Type, Amount USD, Balance after USD, Description, Order ID, Stripe session.

POST/dealer/topup

Create Stripe checkout session for wallet top-up.

{ "amountUsd": 100 }

amountUsd must be between $50 and $10,000. Response: { ok, checkoutUrl, netAmt, grossAmt, fee }. Redirect user to checkoutUrl. Rate limit: 5/min.

POST/dealer/topup-intent

Create Stripe PaymentIntent for embedded checkout.

{ "amountUsd": 100 }

Response: { ok, clientSecret, publishableKey, netAmt, grossAmt, fee }. Use Stripe.js with clientSecret. Rate limit: 10/min.

GET/dealer/topup/preview?net=100

Preview Stripe fee. Response: { ok, net, gross, fee }.

Order from your wallet

POST/dealer/order

Alternative to /api/v1/esim/order — debits your wallet at your dealer price.

{
  "productId":     "p3:att-prepaid-30",      // required
  "customerEmail": "end@x.com",                // required
  "customerName":  "Jane Doe",                 // required
  "quantity":      1,                          // optional, 1–20
  "language":      "en",                       // optional
  "imei":          "359123456789012",          // required for US prepaid (15 digits)
  "eid":           "89001012...",              // required for US prepaid (hex)
  "address": {
    "first_name":     "Jane",
    "last_name":      "Doe",
    "address_line_1": "123 Main St",
    "city":           "Phoenix",
    "state":          "AZ",
    "zip_code":       "85001",
    "phone":          "+15555550100"
  }
}

Response:

{
  "ok": true,
  "orderId": "...",
  "shortId": "MM-AB12CD",
  "unitPriceUsd": 25.00,
  "totalUsd": 25.00,
  "newBalance": 220.30
}

Returns 402 Payment Required if balance is insufficient.

POST/dealer/resend-email/:orderId

Resend an eSIM activation email. Charges the resend fee (~$1).

{ "email": "alternate@x.com" }

Body is optional — omit email to resend to the original address.

POST/dealer/replace/:orderId

Issue a replacement eSIM at the original product's current price. Charged from your wallet. Returns 402 if insufficient balance.

Errors

All errors return JSON:

{ "error": "Invalid request", "message": "amountUsd must be between 50 and 10000" }
StatusMeaning
400Bad request — missing or malformed fields
401Unauthorized — missing or invalid x-dealer-key / JWT
402Payment required — insufficient wallet balance
403Forbidden — account not approved or suspended
404Not found — order, product, or dealer doesn't exist
429Rate limit exceeded
500Server error — try again, contact support if persistent

Rate limits (per dealer unless noted)

Endpoint groupLimit
Read operations (/api/v1/me, /api/v1/orders, /api/v1/esim/countries, /api/v1/esim/bundles, /api/v1/esim/details, /api/v1/topup/networks)60 / min
Order operations (/api/v1/esim/order, /api/v1/topup/confirm, /api/v1/topup/recharge, /dealer/order)30 / min
Revocations (/api/v1/esim/revoke)10 / min
Wallet top-ups (/dealer/topup, /dealer/topup-intent)5–10 / min
OTP send5 / 15 min per IP
OTP verify10 / 15 min per IP
Password login8 / 15 min per IP

Rate-limit window is sliding; on 429, wait the indicated period and retry.

eSIM Verification — Dealer API

Submit a batch of LPAs (eSIM activation codes) and we'll classify each as one of: available, used, invalid, error, unknown. The check is read-only — LPAs are never consumed by the verification. Batches up to 50,000 rows. Results typically arrive within minutes.

Pricing: $1 per LPA, debited from your dealer wallet at submission time. Rows that return error are refunded automatically — you only pay for successful verdicts. Authentication uses either your bearer JWT (from /dealer/login) or the legacy x-dealer-key header.

POST /dealer/esim-verify — Submit batch

POST https://meisimusa.com/dealer/esim-verify
Authorization: Bearer <dealer-jwt>     # OR: x-dealer-key: <your-key>
Content-Type: application/json

{
  "lpas": [
    "LPA:1$T-MOBILE.IDEMIA.IO$AYU36-O48VE-8PWDE-ZRXGS",
    "LPA:1$T-MOBILE.IDEMIA.IO$O0VQX-7MJVW-99Y97-KLSI4",
    "..."
  ],
  "notify_email": "results@yourcompany.com"   // optional
}

Response (200):

{
  "ok":                  true,
  "batch_id":            "<uuid>",
  "total_rows":          2,
  "charged_usd":         2.00,
  "wallet_balance_usd":  148.00
}

Errors:

HTTPCodeMeaning
400No LPAs providedEmpty lpas array
402INSUFFICIENT_FUNDSWallet balance below required total — response.balance and response.required show details
403Dealer account not active

GET /dealer/esim-verify/:batch_id — Poll batch

GET https://meisimusa.com/dealer/esim-verify/<batch-uuid>
Authorization: Bearer <dealer-jwt>

200 OK
{
  "ok": true,
  "batch": {
    "id":            "<uuid>",
    "label":         "Dealer Acme Inc · 100 LPAs",
    "total_rows":    100,
    "started_at":    "2026-05-13T18:00:00Z",
    "completed_at":  null,
    "paid_total_usd": 100.00
  },
  "progress": {
    "total":       100,
    "pending":     40,
    "in_progress": 5,
    "used":        35,
    "available":   18,
    "invalid":     1,
    "error_count": 1,
    "unknown":     0
  }
}

GET /dealer/esim-verify/:batch_id/results.csv — Download CSV

Returns a CSV with columns iccid, lpa, status, reason, checked_at. Safe to call while the batch is still running — pending rows show status=pending.

curl -H "Authorization: Bearer $DEALER_JWT" \
  https://meisimusa.com/dealer/esim-verify/<batch-uuid>/results.csv \
  -o results.csv

eSIM Supply — Portal API

Authentication is JWT bearer (not the x-dealer-key header above). POST to /api/portal/v1/auth/login with the credentials we issued you to receive a token. All endpoints in this section share the prefix /api/portal/v1. Reseller portal access is by invitation only — contact support@meisimusa.com.

eSIM Supply sells pre-validated AT&T eSIM stock. Each purchase atomically claims rows from inventory using FOR UPDATE SKIP LOCKED, so two concurrent buyers never collide on the same iccid. Wallet is debited per unit at your tier's price.

Before any purchase, read the eSIM Supply Terms of Service — in particular the rule that removing an eSIM as a troubleshooting step permanently destroys it and disqualifies it from replacement.

POST /services/esim_supply_att/execute — Buy eSIMs

Atomically claims quantity rows from inventory and assigns them to the caller.

POST /api/portal/v1/services/esim_supply_att/execute
Authorization: Bearer <jwt>
Content-Type: application/json

{
  "sku_id":   "<sku-uuid-from-/services/esim_supply_att/skus>",
  "quantity": 10
}

Response (200):

{
  "usage_id":         12345,
  "vendor_order_ref": "INV-12345",
  "cost_usd":         40.00,
  "status":           "succeeded",
  "result": {
    "carrier":          "att",
    "plan_type":        "qr",
    "quantity_ordered": 10,
    "quantity_filled":  10,
    "partial":          false,
    "esims": [
      { "iccid": "8901410...", "lpa": "LPA:1$...", "inventory_id": "...", "assigned_at": "..." },
      ...
    ]
  }
}

If stock is short, quantity_filled < quantity_ordered, partial is true, and the wallet is automatically credited for the shortfall. If stock is zero, the call fails with VENDOR_ERROR and the full debit is refunded.

GET /esim-supply/stock — Stock count

Returns available counts so your UI can hide "Buy 1000" when only 12 are left.

GET /api/portal/v1/esim-supply/stock
Authorization: Bearer <jwt>

200 OK
{ "stats": { "att.qr.available": 12345, "att.qr.sold": 678, "att.qr.burned": 23 } }

GET /esim-supply/my — My eSIMs

Lists eSIMs assigned to the caller, newest first. Paged via limit and offset.

GET /api/portal/v1/esim-supply/my?limit=100&offset=0
Authorization: Bearer <jwt>

200 OK
{
  "esims": [
    {
      "id":                "...",
      "iccid":             "8901410...",
      "lpa":               "LPA:1$...",
      "carrier":           "att",
      "plan_type":         "qr",
      "status":            "sold",
      "assigned_at":       "2026-05-12T10:00:00Z",
      "sold_via_usage_id": 12345
    }
  ],
  "total":  42,
  "limit":  100,
  "offset": 0
}

POST /esim-supply/replace — Request replacement

Submit an iccid you believe is broken. We enqueue a validator check on the matching LPA, respond synchronously with a check_id, and you poll /esim-supply/replace/status/:check_id until decision is not pending.

POST /api/portal/v1/esim-supply/replace
Authorization: Bearer <jwt>
{ "iccid": "8901410..." }

200 OK
{ "ok": true, "decision": "submitted", "check_id": "<uuid>", "message": "..." }
GET /api/portal/v1/esim-supply/replace/status/:check_id
Authorization: Bearer <jwt>

200 OK  (one of)
{ "decision": "pending",          "validator_status": "in_progress" }
{ "decision": "install_again",    "validator_status": "available",  "message": "..." }
{ "decision": "replaced",         "validator_status": "used",       "replacement": { "iccid": "...", "lpa": "...", "inventory_id": "..." } }
{ "decision": "awaiting_restock", "validator_status": "used",       "message": "..." }
{ "decision": "admin_review",     "validator_status": "error",      "message": "..." }

Replacement policy: only validator-confirmed used verdicts auto-replace from current inventory. available means the eSIM is still valid — try installing again. Anything else routes to admin review within 24h.

eSIM Validator — Portal API

Submit a CSV of LPAs and we run each through a real eSIM-capable device on our worker pool. Each row returns one of: available, used, invalid, error, unknown. Median throughput is ~5s per LPA.

Pricing is per-row at the price shown in /services/esim_validate/skus. The wallet is debited up-front for cost_per_row × rows; rows that error out are refunded automatically — you only pay for successful verdicts.

POST /services/esim_validate/execute — Submit batch

POST /api/portal/v1/services/esim_validate/execute
Authorization: Bearer <jwt>
Content-Type: application/json

{
  "sku_id": "<sku-uuid>",
  "label":  "att-batch-may12",
  "csv":    "iccid,lpa\n8901410...,LPA:1$...\n..."
}

Response (200) — batch is queued; status is executing until workers finish.

{
  "usage_id":         54321,
  "vendor_order_ref": "<batch-uuid>",
  "cost_usd":         150.00,
  "status":           "executing",
  "result": {
    "batch_id":   "<batch-uuid>",
    "total_rows": 1000,
    "cost_usd":   150.00,
    "per_row_usd":0.15,
    "message":    "Batch queued. Poll /esim-check/customer/batches/:id for progress."
  }
}

GET /esim-check/customer/batches/:id — Poll batch

GET /api/portal/v1/esim-check/customer/batches/<batch-uuid>
Authorization: Bearer <jwt>

200 OK
{
  "batch": {
    "id":            "<uuid>",
    "label":         "att-batch-may12",
    "total_rows":    1000,
    "started_at":    "...",
    "completed_at":  null,
    "paid_total_usd": 150.00
  },
  "progress": {
    "total":     1000,
    "pending":   400,
    "in_progress": 8,
    "used":      350,
    "available": 230,
    "invalid":   8,
    "error_count": 2,
    "unknown":   2
  }
}

GET /esim-check/customer/batches/:id/results.csv — Download results

Returns a CSV with columns iccid, lpa, status, reason, checked_at. Safe to call while the batch is still running — pending rows show status=pending.

curl -H "Authorization: Bearer $TOKEN" \
  https://meisimusa.com/api/portal/v1/esim-check/customer/batches/<batch-uuid>/results.csv \
  -o results.csv

Code samples

JavaScript / Node.js

const API = 'https://meisimusa.com';
const KEY = process.env.MEISIM_DEALER_KEY;

async function orderEsim(bundleName, customerEmail) {
  const r = await fetch(`${API}/api/v1/esim/order`, {
    method: 'POST',
    headers: {
      'x-dealer-key': KEY,
      'content-type': 'application/json',
    },
    body: JSON.stringify({
      bundleName,
      emailAddress: customerEmail,
      qrToCustomer: true,
    }),
  });
  if (!r.ok) throw new Error(`HTTP ${r.status}: ${await r.text()}`);
  return r.json();
}

Python

import os, requests

API = 'https://meisimusa.com'
KEY = os.environ['MEISIM_DEALER_KEY']

def order_esim(bundle_name, customer_email):
    r = requests.post(
        f'{API}/api/v1/esim/order',
        headers={'x-dealer-key': KEY},
        json={
            'bundleName': bundle_name,
            'emailAddress': customer_email,
            'qrToCustomer': True,
        },
        timeout=30,
    )
    r.raise_for_status()
    return r.json()

curl

curl -X POST https://meisimusa.com/api/v1/esim/order \
  -H "x-dealer-key: $MEISIM_DEALER_KEY" \
  -H 'content-type: application/json' \
  -d '{"bundleName":"esim_5gb_30days_jp","emailAddress":"end@x.com","qrToCustomer":true}'

For dealer onboarding, account questions, or technical support, contact your account manager directly.
Document version: v1.0 — 2026-05-05