Nubatel Developer API
One REST API over the same Nubatel backend the website uses: the same inventory, the same official selling prices, the same coupons and the same automatic fulfilment. Every order created here appears in the Nubatel admin dashboard and in the sales and profit reports, tagged with the sales channel of the key that created it.
Base URL: https://nubatel.com/api/public/v1
Authentication
Every request carries the API key issued to your integration. Keys are shown once when they are created; if one is lost, Nubatel issues a replacement and the old key stops working immediately. Keep the key on your server or in secure app storage — never in public source code.
Authorization: Bearer nb_XXXXXXXXXXXX.YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYEach key has its own permissions (scopes), its own per-minute request limit and its own sales channel. A key can only read the orders it created.
Customer accounts (mobile app)
The API key identifies the app; a customer token identifies the person. Customers sign up and sign in with email and password against Nubatel accounts, then the app sends the returned token as an extra header on every customer-scoped call:
Authorization: Bearer nb_XXXXXXXXXXXX.YYYY… // the app key
X-Customer-Token: eyJhbGciOi… // the signed-in customer/customers/signupcustomers:auth403 email_not_confirmed. The reply includes needs_confirmation.POST /api/public/v1/customers/signup
{ "email": "sara@example.com", "password": "•••••••••", "full_name": "Sara Ahmed", "language": "en" }/customers/signincustomers:authcustomer and session (access_token, refresh_token, expires_in). Store the tokens in secure device storage./customers/refreshcustomers:authrefresh_token for a fresh access token, keeping the customer signed in./customers/password-resetcustomers:auth{ "sent": true }, never revealing whether the account exists./customers/mecustomers:authPOST the same path to update name, phone or language.When a customer token is present, POST /orders links the order to that customer (and can fill the name, email, phone and language from their profile), and GET /orders, GET /orders/{id}, /esim, /status and /cancel are restricted to that customer's own orders across every channel they bought on. Without a token, buying still works exactly as before as a guest purchase. A missing or expired token on /customers/me returns 401 customer_unauthorized.
Catalogue, prices and availability
/catalog/countriescatalog:read/catalog/packages?country=Oman&in_stock=truecatalog:readin_stock: false./catalog/availability?country=Oman&package_code=oman_unlimited_30dcatalog:read{
"packages": [
{
"package_code": "oman_unlimited_30d",
"country": "Oman",
"data_label": "Unlimited",
"is_unlimited": true,
"validity_days": 30,
"price": { "amount_minor": 2512, "currency": "OMR" },
"available_count": 6,
"in_stock": true
}
]
}Amounts are always integer minor units of the stated currency (2512 OMR minor = 25.120 OMR). OMR, BHD, KWD and TND use three decimals; most other currencies use two.
Creating an order
/ordersorders:writePOST /api/public/v1/orders
{
"country": "Oman",
"package_code": "oman_unlimited_30d",
"customer_name": "Sara Ahmed",
"customer_email": "sara@example.com",
"customer_phone": "+96890000000",
"customer_language": "en",
"coupon_code": "AWB50"
}201 Created
{
"order": {
"id": "0f3c…",
"reference": "9a41…",
"status": "awaiting_payment",
"payment_status": "awaiting_card_payment",
"fulfillment_status": "pending",
"sales_channel": "mobile_app",
"amounts": {
"official_price_minor": 2512,
"price_minor": 2512,
"discount_minor": 1256,
"service_fee_minor": 38,
"total_due_minor": 1294,
"currency": "OMR"
},
"payment_url": "https://nubatel.com/pay/…",
"payment_expires_at": "2026-09-16T10:00:00.000Z"
}
}Open payment_url in a browser or in-app browser. Payment is confirmed by the payment provider directly with Nubatel; the eSIM is assigned and the customer email sent only after that confirmation. Never treat a success page as proof of payment — poll the order instead.
Order status, eSIM and history
/orders/{id}orders:read/orders?customer_email=sara@example.comorders:read/orders/{id}/esimesim:read/orders/{id}/statusesim:readMobile app cheat sheet
The full buying flow for the Nubatel app: account → destination → package → device check → checkout → payment → fulfilment → eSIM in the account. Customer-scoped calls send both the API key (Authorization: Bearer keyId.secret) and the customer session (X-Customer-Token).
/customers/signupcustomers:auth{ full_name, email, password, phone?, language? } → emails a 6-digit code and returns needs_verification: true. Then /customers/verify-email, /customers/resend-otp, /customers/signin, /customers/refresh, /customers/password-reset, /customers/logout./customers/socialcustomers:auth{ provider: "google" | "facebook", token } — same customer record, never a duplicate account. GET /customers/social/status reports which providers are switched on./catalog/countries?scope=local|regional|globalcatalog:readscope, coverage, stock count and the cheapest price. GET /catalog/packages?country=Japan&scope=regional&covers=JP lists the packages, with Local / Regional / Global labels and the same official selling prices as the website./devices/checkcatalog:read{ brand?, model } → SUPPORTED, NOT_SUPPORTED or UNKNOWN. Never present UNKNOWN as supported. GET /devices lists brands and models for a picker./ordersorders:writepayment_method (card_sheet, card_link or manual) and device: { brand, model, compatibility, confirmed } where confirmed is the customer ticking "my device supports eSIM and is carrier-unlocked". The response carries next_step. Coupons and ambassador codes are the same codes the website accepts./orders/{id}/payment-sheetorders:writeclient_secret, publishable_key, payment_intent_id, amount_minor, currency, test_mode, merchant_display_name and apple_pay_merchant_id for the Stripe PaymentSheet (card, Apple Pay, Google Pay). The app never touches card numbers. Payment counts only once the provider confirms it to Nubatel — poll /orders/{id} instead of trusting the success screen./orders/{id}/manual-paymentorders:write{ currency, payment_method, amount_minor, reference?, notes?, receipt?: { content_type, data_base64 } } — receipt is JPG, PNG or PDF, 10 MB maximum, password-protected PDFs rejected. The order moves to admin review; on approval it is fulfilled automatically, on rejection no eSIM is issued./customers/me/esims?include_status=trueesim:readTest customers (set by an admin) create clearly marked TEST orders that use simulated eSIMs, never consume real stock and never reach the revenue or profit reports.
Cancelling and refunding
/orders/{id}/cancelorders:write/orders/{id}/refundrefunds:request{"full": true}) or partial ({"amount_minor": 500}) refund through the original payment provider. The returned status is the provider's real status, never an assumed success, and a refund can never exceed the captured amount.Errors and limits
{ "error": { "code": "out_of_stock", "message": "That package is currently out of stock." } }401 unauthorized— missing or invalid key.403 insufficient_scope/client_disabled— the key lacks that permission or has been switched off.404 not_found— no such order for this key.409—out_of_stock,no_price,invalid_coupon,already_paid,not_fulfilled,not_refundable.429 rate_limited— above the key's per-minute limit; retry after 60 seconds.
Every request is logged permanently (key, endpoint, result, order and timing) and is visible to Nubatel in the admin dashboard.