Developer reference

NXTL B2B REST API

Issue eSIMs, manage pooled balances, change network tiers, reconcile usage, and receive signed webhooks — one API for travel platforms, enterprises, and device programs.

Base URL: https://nxtlsim.com/api/v1

API status

Live check against the public health endpoint (no authentication).

Loading…

View raw JSON

Quick start

  1. After approval, create an API key (REST POST /api-keys with keys:write, or WP Admin → NXTL → Partners).
  2. Call GET /health (no auth) to verify reachability and plugin version.
  3. Call GET /partner with X-NXTL-Key to confirm the key resolves to your organization and scopes.
  4. Queue issuance with POST /esims + X-NXTL-Idempotency-Key; poll GET /esim-requests/{job_id} until the job completes.
  5. Subscribe webhooks with PUT /webhooks so you receive signed esim.* and balance.* events at your HTTPS endpoint.
curl -sS -H "X-NXTL-Key: nxtl_your_secret_here" \
  "https://nxtlsim.com/api/v1/partner"

What travel & mobility teams use the API for

  • Bind one eSIM per trip or traveller: issue with client_reference (PNR, employee id, device serial) and reconcile via GET /esims and webhooks.
  • Fund a shared pool: POST /topup credits customer balance from prepaid partner budget; GET /balance/history feeds finance.
  • Operational control: suspend stolen handsets, reactivate after travel, terminate eSIMs — PATCH /esims/{iccid}/status or POST suspend/resume aliases.
  • Support & finance: per-ICCID usage (GET …/usage), ledger events (GET …/events), partner-wide summary (GET /usage/summary), and internal invoice rows (GET /invoices) for API-driven top-ups.
  • Automation safety: idempotent POST /esims and POST /topup, rate-limit headers, and X-Request-Id on responses for ticket correlation.

Authentication & scopes

All routes except GET /health require the X-NXTL-Key header with your API secret. Secrets are stored hashed at rest; you only see the plaintext once when the key is created.

curl -H "X-NXTL-Key: nxtl_your_secret_here" "https://nxtlsim.com/api/v1/esims"

Scopes: esims:read, esims:write, balance:read, balance:write, keys:read, keys:write, webhooks:read, webhooks:write, usage:read, thresholds:read, thresholds:write, audit:read. A key may use "*" for full access.

All monetary fields render in your selected display currency when you send ?currency=EUR or Accept-Currency: EUR. The canonical USD value is always available alongside (e.g. threshold_value_usd on threshold rules, total_usd on invoices) so you can store the source value for accounting.

Idempotency

For POST /esims and POST /topup, send X-NXTL-Idempotency-Key. Retries with the same key return the original outcome — safe for network retries and queue workers.

curl -X POST \
  -H "X-NXTL-Key: ..." \
  -H "X-NXTL-Idempotency-Key: partner-job-2026-04-22-001" \
  -H "Content-Type: application/json" \
  -d '{"nickname":"Vehicle gateway 12","client_reference":"PNR-8F2Q"}' \
  "https://nxtlsim.com/api/v1/esims"

B2B issuance fee & balance

Each successful API-issued eSIM is charged a small USD fee from the partner owner’s pooled balance (default 1.99 USD, option nxtl_v2_b2b_issue_fee_usd; set to 0 to disable). POST /esims returns 402 if the wallet total is too low, or if the unallocated pool cannot cover the fee and partner budget cannot top it up automatically.

GET /balance includes customer_total_usd and available_usd (same value): your full “wallet” for capacity planning, plus unallocated_usd (pool not yet on an eSIM) and b2b_issue_fee_usd for the current per-eSIM charge.

Rate limits & tracing

Successful responses include rate-limit headers and a request id for support correlation:

  • X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
  • X-Request-Id — quote this in support tickets

Tune defaults with the developer filter nxtl_v2_rest_rate_limit_per_key (your NXTL operator can adjust this server-side).

Dashboard vs API

The signed-in customer portal (balance top-up, eSIM list, suspend/resume, exchange/reissue where configured) and the REST API operate on the same ledger and inventory. Actions you take in the UI are reflected in API responses and vice versa.

Currencies

Retail storefronts can display and settle purchases in multiple supported currencies. Partner REST fields for budgets, top-ups, and ledger entries use a normalized accounting unit (USD) so pools, invoices, and automation stay consistent regardless of display currency.

Endpoints

Paths are relative to the base URL above. {iccid} is 18–22 digits. The "Idempotent" column flags routes that honour X-NXTL-Idempotency-Key — safe to retry from a queue worker without double-charging your pool.

Method Path Description Scope Idempotent
GET /health Public health check. No auth.
GET /partner Current partner profile (sanitized). balance:read
GET /quotas Single-shot snapshot: balance, lifetime quota, rate limit, issuance fee, subscriptions, key count. balance:read
GET /usage/summary Aggregated usage for partner eSIMs. ?days=7 esims:read
GET /usage/esims Per-eSIM usage rollups. Legacy alias: /usage/lines. esims:read
GET /usage/cdrs Itemised usage events from the ledger (CDR-equivalent). ?iccid=...&page=1 esims:read
GET /usage/timeseries Daily usage + top-up totals. ?days=30 esims:read
GET /invoices Completed internal orders linked to API balance top-ups. balance:read
GET /esims List eSIMs for the partner. esims:read
GET /lines Legacy alias — same response as GET /esims. esims:read
POST /esims Queue new eSIM issuance (per-eSIM fee from pool; 402 if insufficient). Returns job_id. esims:write
POST /esims/estimate Pre-flight cost + balance-after for a planned issuance batch. esims:read
POST /esims/bulk-issue Async batch issuance (up to ~1000). Returns 202 Accepted + job_id. esims:write
GET /esims/{iccid} Fetch one eSIM by ICCID. esims:read
GET /esim-requests/{job_id} Poll legacy async job status. esims:read
GET /jobs List async jobs (issuance, bulk-tier) with status counts. esims:read
GET /jobs/{id} Fetch one async job: status, summary, per-row outcomes. esims:read
PATCH /esims/{iccid}/nickname Body: {"nickname":"..."} esims:write
PATCH /esims/{iccid}/tier Body: {"tier":"economy|comfort|full"} esims:write
PATCH /esims/{iccid}/status Body: {"action":"suspend|reactivate|terminate"} esims:write
POST /esims/{iccid}/suspend Alias: suspend. Safe to retry — repeated calls converge. esims:write
POST /esims/{iccid}/resume Alias: reactivate. Safe to retry — repeated calls converge. esims:write
GET /esims/{iccid}/wallet Live wallet snapshot for an eSIM. esims:read
GET /esims/{iccid}/usage Usage rows. ?page=1 esims:read
GET /esims/{iccid}/events Ledger events for an eSIM. esims:read
POST /esims/bulk-status Body: {"iccids":["..."]} — up to 100 ICCIDs. esims:read
POST /esims/bulk-tier Body: {"iccids":[...],"tier":"..."} esims:write
GET /balance Partner balances + per-eSIM wallet breakdown. balance:read
GET /balance/history Balance ledger. ?page=1 balance:read
POST /topup Body: {"amount_usd":10} — credit pool from prepaid budget. balance:write
GET /api-keys List keys (public ids only). keys:read
POST /api-keys Body: {"label":"..."} — returns secret once. keys:write
DELETE /api-keys/{public_id} Revoke a key. keys:write
GET /webhooks Webhook URL + subscribed event names. webhooks:read
PUT /webhooks Body: {"webhook_url":"https://...","events":["esim.provisioned",...]} webhooks:write
GET /webhooks/events Catalog of every event emitted + your subscription state. webhooks:read
GET /webhooks/secret Reveal active HMAC signing secret + signed-payload spec. webhooks:read
GET /webhooks/deliveries Filterable delivery log (status, event, since, until). webhooks:read
POST /webhooks/test Queue test.ping delivery. webhooks:write
POST /webhooks/deliveries/{id}/replay Replay a stored delivery by id. webhooks:write
POST /webhooks/{id}/replay Legacy alias for delivery replay. webhooks:write
GET /thresholds List alert rules. thresholds:read
POST /thresholds Create a balance/usage alert rule (optionally auto-suspend). thresholds:write
PATCH /thresholds/{id} Update an existing alert rule. thresholds:write
DELETE /thresholds/{id} Remove a rule and its dedupe state. thresholds:write
GET /audit Partner-scoped audit log. ?action=esim.&since=YYYY-MM-DD audit:read

Recipes

Three end-to-end flows our most common partner shapes use. Each is copy-pastable curl plus the webhook events you should listen for.

Recipe 1 — Issue an eSIM bound to a booking

Travel platform: one eSIM per PNR, you want the provisioning to happen on booking confirmation and the QR delivered to the traveller before they reach the airport.

# 1. Pre-flight: confirm we can pay for it
curl -X POST -H "X-NXTL-Key: $NXTL_KEY" -H "Content-Type: application/json" \
  -d '{"quantity":1,"tier":"international"}' \
  "https://nxtlsim.com/api/v1/esims/estimate"

# 2. Issue with a stable Idempotency-Key so a retry returns the same job
curl -X POST -H "X-NXTL-Key: $NXTL_KEY" \
  -H "X-NXTL-Idempotency-Key: pnr-8F2Q-issue" \
  -H "Content-Type: application/json" \
  -d '{
    "tier": "international",
    "client_reference": "PNR-8F2Q",
    "nickname": "Trip 2026-05-12 Lima"
  }' \
  "https://nxtlsim.com/api/v1/esims"

# 3. Subscribe to esim.provisioned + esim.provision_failed webhooks
#    so your booking record can attach the QR or surface an error.

Recipe 2 — Fleet refresh: rotate tiers for 200 devices

Mobility / IoT: at the start of each month you want every car in the EU service area on the comfort tier and everything elsewhere on economy.

# 1. Pull every active eSIM (paginate with ?page=)
curl -H "X-NXTL-Key: $NXTL_KEY" \
  "https://nxtlsim.com/api/v1/esims?page=1"

# 2. Bulk-tier the EU subset (server-side cap: 100 ICCIDs per call)
curl -X POST -H "X-NXTL-Key: $NXTL_KEY" \
  -H "X-NXTL-Idempotency-Key: tier-rotate-2026-05" \
  -H "Content-Type: application/json" \
  -d '{
    "iccids": ["89...","89..."],
    "tier":   "comfort"
  }' \
  "https://nxtlsim.com/api/v1/esims/bulk-tier"

# 3. Listen for esim.tier_changed; reconcile against /audit?action=esim.tier
#    if anything looks off in the 24h window after the rotation.

Recipe 3 — Catch a runaway eSIM before it eats the pool

Customer success: a lost or compromised device is racking up usage. Set a threshold rule that auto-suspends when daily usage exceeds the limit, and follow up with an exchange call once the user reports the loss.

# 1. Define an auto-suspend rule (one-time setup per fleet)
curl -X POST -H "X-NXTL-Key: $NXTL_KEY" -H "Content-Type: application/json" \
  -d '{
    "scope":           "esim",
    "metric":          "line_usage_usd_today",
    "operator":        ">=",
    "threshold_value": 25.00,
    "period":          "daily",
    "action":          "suspend",
    "enabled":         true
  }' \
  "https://nxtlsim.com/api/v1/thresholds"

# 2. When the rule fires you receive esim.usage_threshold + esim.suspended.
# 3. Replace the SIM and return wallet to the pool in a single call:
curl -X POST -H "X-NXTL-Key: $NXTL_KEY" -H "Content-Type: application/json" \
  -d '{"action":"terminate"}' \
  "https://nxtlsim.com/api/v1/esims/89.../status"

Webhooks

When a partner webhook URL is configured, NXTL POSTs a JSON envelope to it. Each delivery includes X-NXTL-Signature = HMAC-SHA256(raw_body, signing_secret). The signing secret is configured on the NXTL platform (server-side secret store; the first active secret is used for new signatures) and is shared with your integration team out-of-band; it is not returned by the REST API.

The NXTL POST uses Content-Type: application/json and a raw JSON body (the same bytes you must verify).

Envelope

{
    "event": "esim.provisioned",
    "timestamp": 1714410000,
    "data": {
        "iccid": "89359012345678901234",
        "esim_id": 9012,
        "request_job_id": 4401,
        "client_reference": "PNR-8F2Q"
    }
}

timestamp is Unix seconds (UTC). data is event-specific. NXTL does not send query parameters; everything is in the JSON body.

Verify signature (receiver pseudo-code)

// $raw = file_get_contents('php://input');  // exact bytes NXTL signed
// $sig = $_SERVER['HTTP_X_NXTL_SIGNATURE'] ?? '';
// $secret = getenv('NXTL_WEBHOOK_SECRET');   // issued by NXTL ops
// $expected = hash_hmac('sha256', $raw, $secret);
// if (!hash_equals($expected, $sig)) { http_response_code(401); exit; }

HTTP response & retries

Return 2xx within a few seconds so we mark the delivery successful. NXTL stores the HTTP status and up to 2000 characters of your response body for debugging — the body is not parsed as an API contract. Non-2xx or network errors trigger exponential backoff retries (up to 5 attempts), then the delivery is dead-lettered.

Event quick reference

PUT /webhooks persists an events array for your records and future filtering; today deliveries are not filtered by that list — any event below may be POSTed whenever a webhook URL is set.

Event When data fields
esim.provisioned Async issuance completed successfully. iccid, esim_id, request_job_id, client_reference
esim.provision_failed Issuance job failed after retries. request_job_id, client_reference, message
esim.tier_changed Network tier updated via API or dashboard. iccid, tier
esim.suspended eSIM suspended (API, dashboard, or automation). iccid, esim_id, user_id
esim.reactivated eSIM reactivated from suspended state. iccid, esim_id, user_id
esim.terminated eSIM terminated; wallet return may be included. iccid, line_id, user_id, wallet_returned
balance.topup Partner pool credited from prepaid budget via API. amount_usd
test.ping Queued by POST /webhooks/test. message, timestamp (inside data, in addition to envelope timestamp)

Full examples: request body & recommended response

Below, “Request body” is the exact JSON POSTed by NXTL (pretty-printed). “Recommended response” is what your server should return to acknowledge receipt; you may use 204 No Content or any small JSON body — NXTL only checks the HTTP status for success.

esim.provisioned

Async issuance completed successfully.

Request body (POST from NXTL)
{
    "event": "esim.provisioned",
    "timestamp": 1714410000,
    "data": {
        "iccid": "89359012345678901234",
        "esim_id": 9012,
        "request_job_id": 4401,
        "client_reference": "PNR-8F2Q"
    }
}
Recommended HTTP response
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

{"received":true}

esim.provision_failed

Issuance job failed after retries.

Request body (POST from NXTL)
{
    "event": "esim.provision_failed",
    "timestamp": 1714410090,
    "data": {
        "request_job_id": 4401,
        "client_reference": "PNR-8F2Q",
        "message": "Upstream carrier timeout"
    }
}
Recommended HTTP response
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

{"received":true}

esim.tier_changed

Network tier updated via API or dashboard.

Request body (POST from NXTL)
{
    "event": "esim.tier_changed",
    "timestamp": 1714410180,
    "data": {
        "iccid": "89359012345678901234",
        "tier": "comfort"
    }
}
Recommended HTTP response
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

{"received":true}

esim.suspended

eSIM suspended (API, dashboard, or automation).

Request body (POST from NXTL)
{
    "event": "esim.suspended",
    "timestamp": 1714410270,
    "data": {
        "iccid": "89359012345678901234",
        "esim_id": 9012,
        "user_id": 501
    }
}
Recommended HTTP response
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

{"received":true}

esim.reactivated

eSIM reactivated from suspended state.

Request body (POST from NXTL)
{
    "event": "esim.reactivated",
    "timestamp": 1714410360,
    "data": {
        "iccid": "89359012345678901234",
        "esim_id": 9012,
        "user_id": 501
    }
}
Recommended HTTP response
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

{"received":true}

esim.terminated

eSIM terminated; wallet_returned is the amount credited back to the partner pool (USD).

Request body (POST from NXTL)
{
    "event": "esim.terminated",
    "timestamp": 1714410450,
    "data": {
        "iccid": "89359012345678901234",
        "esim_id": 9012,
        "user_id": 501,
        "wallet_returned": 12.75
    }
}
Recommended HTTP response
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

{"received":true}

balance.topup

Partner pool credited from prepaid budget via POST /topup.

Request body (POST from NXTL)
{
    "event": "balance.topup",
    "timestamp": 1714410540,
    "data": {
        "amount_usd": 250
    }
}
Recommended HTTP response
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

{"received":true}

test.ping

Queued by POST /webhooks/test (smoke-test your endpoint).

Request body (POST from NXTL)
{
    "event": "test.ping",
    "timestamp": 1714410630,
    "data": {
        "message": "This is a test webhook delivery.",
        "timestamp": 1714410480
    }
}
Recommended HTTP response
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

{"received":true}

Partner lifecycle emails (application received, approved, rejected) are sent separately via email — they are not duplicated on this webhook channel unless your project adds them.

Roadmap & common requests

The table above reflects what the public partner API exposes today. Travel platforms often also ask for:

  • Per-destination catalog & dynamic pricing feeds — today surfaced through your retail integration and NXTL operations; ask us if you need a machine-readable catalog API.
  • Push usage alerts (threshold webhooks) — combine GET /usage/summary and GET /esims/{iccid}/usage on a schedule until native threshold events exist.
  • Subscriber carrier diagnostics — available to support through NXTL; not all fields are exposed on the public REST surface yet.
  • Bulk CSV export — use partner dashboard exports where offered, or page through REST list endpoints to build your own extracts.

Bulk eSIM operations (recommended for fleets)

Use the bulk endpoints whenever you need to issue or change more than ~5 eSIMs at once. Each one returns a job id you can poll, and emits the job.completed webhook with per-row outcomes.

POST /esims/bulk-issue

curl -X POST \
  -H "X-NXTL-Key: $NXTL_KEY" \
  -H "Idempotency-Key: fleet-q2-launch" \
  -H "Content-Type: application/json" \
  -d '{
    "quantity": 50,
    "tier": "international",
    "client_reference": "fleet-q2",
    "metadata": { "department": "logistics" }
  }' \
  "https://nxtlsim.com/api/v1/esims/bulk-issue"

Response: 202 Accepted with job_id + estimated cost. Pre-flight checks: insufficient pooled balance returns 402 with type=INSUFFICIENT_BALANCE.

POST /esims/estimate

curl -X POST \
  -H "X-NXTL-Key: $NXTL_KEY" -H "Content-Type: application/json" \
  -d '{ "quantity": 50, "tier": "international" }' \
  "https://nxtlsim.com/api/v1/esims/estimate"

Returns total_usd + balance_after_usd + affordable boolean — gate your UX with this before calling bulk-issue.

GET /jobs/{id}

curl -H "X-NXTL-Key: $NXTL_KEY" \
  "https://nxtlsim.com/api/v1/jobs/12345"

Status: pending|running|complete|failed. summary { total, succeeded, failed }; result.rows lists per-eSIM outcomes.

Usage analytics

EndpointReturns
GET /usage/summaryPartner-wide totals (today, MTD, last 7d).
GET /usage/esims?since=YYYY-MM-DD&iccid=…Per-eSIM aggregates over a window: usage_usd, usage_events, first/last event. Legacy alias: /usage/lines.
GET /usage/cdrs?iccid=…&page=1Itemised usage events from the ledger (CDR-equivalent), paginated.
GET /usage/timeseries?days=30Daily usage and top-up totals — drive your charts directly off this.

Webhook management & verification

EndpointUse
GET /webhooksRead current URL + subscribed events.
PUT /webhooksUpdate URL and event subscriptions. Unknown events return 400 with rejected[].
GET /webhooks/eventsCatalog of every event we emit + your subscription state.
GET /webhooks/secretReveal the active HMAC signing secret + signed-payload spec.
GET /webhooks/deliveriesFilterable delivery log: status, event, since, until.
POST /webhooks/deliveries/{id}/replayReplay a single delivery (e.g. after fixing your endpoint).
POST /webhooks/testSend a test.ping event so you can verify HMAC end-to-end.

Verifying signatures (recommended pattern)

NXTL ships two signature headers on every delivery. Always verify using v1 (timestamped, replay-resistant). Reject events older than 5 minutes.

// Node.js (Express)
const crypto = require('crypto');
function verify(req, secret) {
  const hdr = req.header('X-NXTL-Signature-V1') || '';
  const m   = hdr.match(/t=(\d+),v1=([0-9a-f]+)/);
  if (!m) return false;
  const t = parseInt(m[1], 10);
  if (Math.abs(Date.now()/1000 - t) > 300) return false; // replay window
  const expected = crypto.createHmac('sha256', secret)
    .update(t + '.' + req.rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(m[2], 'hex'));
}
# Python (Flask)
import hmac, hashlib, time, re
def verify(headers, raw_body, secret):
    h = headers.get("X-NXTL-Signature-V1", "")
    m = re.match(r"t=(\d+),v1=([0-9a-f]+)", h)
    if not m: return False
    t, sig = int(m.group(1)), m.group(2)
    if abs(time.time() - t) > 300: return False
    expected = hmac.new(secret.encode(), f"{t}.{raw_body.decode()}".encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)
// PHP
function nxtl_verify($header_v1, $raw_body, $secret) {
    if (!preg_match('/t=(\d+),v1=([0-9a-f]+)/', $header_v1, $m)) return false;
    if (abs(time() - (int) $m[1]) > 300) return false;
    $expected = hash_hmac('sha256', $m[1] . '.' . $raw_body, $secret);
    return hash_equals($expected, $m[2]);
}

Alert thresholds

Define rules that fire balance.low / esim.usage_threshold webhooks (and optionally auto-suspend an eSIM) when a metric crosses the configured threshold.

EndpointUse
GET /thresholdsList all rules.
POST /thresholdsCreate a rule (see schema below).
PATCH /thresholds/{id}Update an existing rule.
DELETE /thresholds/{id}Remove the rule and its dedupe state.

Rule schema

{
  "scope":           "balance" | "esim",
  "metric":          "balance_below" | "line_usage_pct" | "line_usage_usd_today",
  "operator":        "<=" | "<" | ">=" | ">" | "=",
  "threshold_value": 25.00,                      // value in `threshold_currency` (default: USD)
  "threshold_currency": "EUR",                   // optional ISO 4217 — server converts to USD on save
  "period":          "instant" | "daily" | "weekly" | "monthly",
  "action":          "notify" | "suspend",
  "enabled":         true,
  "notes": {
    "cap_per_line_usd":      50,                 // required for line_usage_pct
    "cap_per_line_currency": "EUR",              // optional — server converts cap to USD on save
    "min_renotify_seconds":  86400               // dedupe window per (rule, key)
  }
}

Storage is always USD because the ledger is USD-aligned with the carrier. Send threshold_currency to record the value you typed in another currency — the response (and webhook payload) always echoes the canonical threshold_value_usd alongside.

List response

{
  "rules": [
    {
      "id": 7,
      "metric": "balance_below",
      "operator": "<=",
      "threshold_value":         25.00,        // canonical USD (legacy field)
      "threshold_value_usd":     25.00,        // explicit USD (recommended)
      "threshold_value_in":      22.84,        // converted to your selected currency
      "threshold_value_display": "€22.84",  // pre-formatted, ready for UI
      "threshold_currency":      "EUR",        // selected via ?currency=EUR or Accept-Currency: EUR header
      "cap_per_line_usd":        50.00,
      "cap_per_line_in":         45.68,
      "cap_per_line_display":    "€45.68",
      "action": "notify",
      "enabled": 1
    }
  ]
}

Audit log & quotas

EndpointUse
GET /audit?action=esim&since=YYYY-MM-DDPartner-scoped audit log (every mutating call). Filter by action prefix, target_type, target_id, time window.
GET /quotasSingle-shot snapshot: pooled balance, lifetime quota, rate limit, issuance fee, subscribed events, active key count.
GET /jobs?per_page=50List your async jobs (issuance, bulk-tier) with status counts.

Errors

Every error is returned with a uniform JSON envelope so you can switch on type/code instead of HTTP status alone.

{
  "error":      true,
  "type":       "INSUFFICIENT_BALANCE",
  "code":       "insufficient_balance",
  "message":    "Pooled balance is below the estimated cost of this batch.",
  "doc_url":    "https://nxtlsim.com/business-api/errors/insufficient_balance",
  "request_id": "5f8b2e0d-…",
  "details":    { "total_usd": 100.0, "balance_usd": 42.30 }
}
HTTPtypeMeaning
400BAD_REQUESTInvalid body or parameters.
401UNAUTHORIZEDMissing or invalid API key.
402INSUFFICIENT_BALANCEPre-flight or live balance check failed.
403ACCESS_DENIEDKey is missing the required scope.
404NOT_FOUNDUnknown ICCID, job, delivery, key.
409QUOTA_EXCEEDEDLifetime issuance quota exhausted.
429RATE_LIMIT_EXCEEDEDBack off using RateLimit-* + Retry-After.
502UPSTREAM_ERRORUpstream carrier or provisioning service error.