MeiSIM Dealer API
REST API for authorized dealers to programmatically order eSIMs, top up SIMs, and manage their wallet.
| Base URL | https://meisimusa.com |
|---|---|
| Content type | application/json |
| API version | v1 |
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" }
| Status | Meaning |
|---|---|
| 400 | Bad request — missing or malformed fields |
| 401 | Unauthorized — missing or invalid x-dealer-key / JWT |
| 402 | Payment required — insufficient wallet balance |
| 403 | Forbidden — account not approved or suspended |
| 404 | Not found — order, product, or dealer doesn't exist |
| 429 | Rate limit exceeded |
| 500 | Server error — try again, contact support if persistent |
Rate limits (per dealer unless noted)
| Endpoint group | Limit |
|---|---|
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 send | 5 / 15 min per IP |
| OTP verify | 10 / 15 min per IP |
| Password login | 8 / 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:
| HTTP | Code | Meaning |
|---|---|---|
| 400 | No LPAs provided | Empty lpas array |
| 402 | INSUFFICIENT_FUNDS | Wallet balance below required total — response.balance and response.required show details |
| 403 | — | Dealer 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