Order an eSIM
An order takes three steps: a quote, the order itself, and reading the result. The Idempotency-Key makes the second step safe to repeat.
The whole flow
This script does all three steps. Replace PACKAGE_ID with the id of a package from the catalogue.
# 1. Ask for a quote. The package_id comes from the catalogue.
curl https://api.travixorglobal.com/v1/quotes \
-H "Authorization: Bearer $TRAVIXOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"package_id": "PACKAGE_ID", "quantity": 1}'
# 2. Place the order with the quote id from step 1.
# Make up the Idempotency-Key yourself and keep it with your booking.
curl https://api.travixorglobal.com/v1/orders \
-H "Authorization: Bearer $TRAVIXOR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6b0f2c1e-8d4a-4c7e-9b53-1f2a7d90e4c8" \
-d '{"quote_id": "QUOTE_ID", "client_reference": "booking-4821"}'
# 3. Read the order until its status is completed or failed.
curl https://api.travixorglobal.com/v1/orders/ORDER_ID \
-H "Authorization: Bearer $TRAVIXOR_API_KEY"import { randomUUID } from 'node:crypto'
const API = 'https://api.travixorglobal.com'
async function api(path: string, init: { method?: string; body?: unknown; headers?: Record<string, string> } = {}) {
const res = await fetch(API + path, {
method: init.method ?? 'GET',
headers: {
authorization: `Bearer ${process.env.TRAVIXOR_API_KEY}`,
...(init.body === undefined ? {} : { 'content-type': 'application/json' }),
...init.headers,
},
body: init.body === undefined ? undefined : JSON.stringify(init.body),
})
const json = (await res.json()) as any
if (!res.ok) throw new Error(`${res.status} ${json.error.code}: ${json.error.message}`)
return json
}
// 1. A quote fixes the price for 5 minutes.
const { quote } = await api('/v1/quotes', { method: 'POST', body: { package_id: 'PACKAGE_ID', quantity: 1 } })
// 2. Save this key with your booking before you send the order.
// If the request times out, send the same order again with the same key.
const idempotencyKey = randomUUID()
let { order } = await api('/v1/orders', {
method: 'POST',
headers: { 'idempotency-key': idempotencyKey },
body: { quote_id: quote.id, client_reference: 'booking-4821' },
})
// 3. Read the order until it is no longer processing.
while (order.status === 'processing') {
await new Promise((resolve) => setTimeout(resolve, 3000))
;({ order } = await api(`/v1/orders/${order.id}`))
}
console.log(order.status, order.esims)import os
import time
import uuid
import requests
API = "https://api.travixorglobal.com"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['TRAVIXOR_API_KEY']}"
def api(method, path, **kwargs):
res = session.request(method, API + path, timeout=30, **kwargs)
body = res.json()
if not res.ok:
raise RuntimeError(f"{res.status_code} {body['error']['code']}: {body['error']['message']}")
return body
# 1. A quote fixes the price for 5 minutes.
quote = api("POST", "/v1/quotes", json={"package_id": "PACKAGE_ID", "quantity": 1})["quote"]
# 2. Save this key with your booking before you send the order.
# If the request times out, send the same order again with the same key.
idempotency_key = str(uuid.uuid4())
order = api(
"POST",
"/v1/orders",
headers={"Idempotency-Key": idempotency_key},
json={"quote_id": quote["id"], "client_reference": "booking-4821"},
)["order"]
# 3. Read the order until it is no longer processing.
while order["status"] == "processing":
time.sleep(3)
order = api("GET", f"/v1/orders/{order['id']}")["order"]
print(order["status"], order["esims"])1. Get a quote
POST /v1/quotes needs the orders:write scope. Send package_id and quantity (1 to 10). The price is worked out on our side and cannot be sent. A package sold per day also needs days.
{
"quote": {
"id": "5d1c7e92-3b48-4f06-8a2d-c7e1b09f4a63",
"package_id": "0b7e2a54-6f1c-4d58-9a3e-2c91f0d7b6a4",
"package_name": "Turkey 1GB 7 Days",
"days": null,
"quantity": 2,
"unit_price_cents": 1023,
"total_price_cents": 2046,
"currency": "USD",
"created_at": "2026-11-02T09:30:00.000Z",
"expires_at": "2026-11-02T09:35:00.000Z"
}
}A quote holds its price for 5 minutes (expires_at) and can be used for one order. It does not set any money aside.
2. Place the order
POST /v1/orders takes the quote_id and, if you like, a client_reference such as your own booking number. It returns the order with its client_reference so you can match them up later.
Every order needs an Idempotency-Key header: a new UUID for each order. Create it before you send the request and store it with your booking.
- If the request times out or the connection drops, you do not know whether the order was placed. Send the same request again with the same key. You get the first order back, with status 200 and an
Idempotent-Replayed: trueheader, and nothing is bought twice. - A new order answers 201.
- The same key with a different quote answers 422 with
idempotency_key_reused. Use a new key for each order. - If the wallet holds too little, you get 409 with
insufficient_fundsand nothing is created. After a top-up you can use the same key again.
{
"order": {
"id": "e3a94d17-72c0-4b85-9f1e-6d08a2c5b7f1",
"status": "processing",
"package_id": "0b7e2a54-6f1c-4d58-9a3e-2c91f0d7b6a4",
"package_name": "Turkey 1GB 7 Days",
"days": null,
"quantity": 1,
"unit_price_cents": 1023,
"total_price_cents": 1023,
"currency": "USD",
"client_reference": "booking-4821",
"failure": null,
"created_at": "2026-11-02T09:30:02.000Z",
"completed_at": null,
"esims": []
}
}The price is taken from your wallet when the order is placed and shown as held until the order is finished. A failed order releases it in full.
3. Read the result
An order is in one of three states.
| Status | Meaning |
|---|---|
processing | The order is being supplied. Read it again in a few seconds. |
completed | The eSIMs are ready and listed in esims. The money has been spent. |
failed | Nothing was charged. failure holds a code and a message. |
Poll GET /v1/orders/{id} every few seconds, or leave the order and read it later: it does not expire. Orders are never split, so you either get all the eSIMs of an order or none. See Deliver the eSIM for what to do with them.
{
"order": {
"id": "e3a94d17-72c0-4b85-9f1e-6d08a2c5b7f1",
"status": "completed",
"package_id": "0b7e2a54-6f1c-4d58-9a3e-2c91f0d7b6a4",
"package_name": "Turkey 1GB 7 Days",
"days": null,
"quantity": 1,
"unit_price_cents": 1023,
"total_price_cents": 1023,
"currency": "USD",
"client_reference": "booking-4821",
"failure": null,
"created_at": "2026-11-02T09:30:02.000Z",
"completed_at": "2026-11-02T09:30:07.000Z",
"esims": [
{
"id": "9c26b0e4-1a7d-4e38-b5f2-d4803c7a19e6",
"order_id": "e3a94d17-72c0-4b85-9f1e-6d08a2c5b7f1",
"iccid": "8901234567890123456",
"status": "ready",
"activation_code": "LPA:1$smdp.example.invalid$AAAA-BBBB-CCCC-DDDD",
"ios_install_link": "https://example.invalid/install/ios/AAAA-BBBB",
"android_install_link": "https://example.invalid/install/android/AAAA-BBBB",
"data_total_mb": null,
"data_used_mb": null,
"data_left_mb": null,
"activated_at": null,
"expires_at": null,
"updated_at": "2026-11-02T09:30:07.000Z"
}
]
}
}Ordering can be paused for a short time. Quotes and orders then answer 503 with ordering_paused. Nothing is charged; try again later.