Buy eSIMs
const url = 'https://api.roamex.com/v1/esims/purchase?lang=nl';const options = { method: 'POST', headers: { 'Idempotency-Key': 'order-2026-10-22-0001', Authorization: 'Bearer <token>', 'Content-Type': 'application/json' }, body: '{"planId":"00000000-0000-4000-8000-0000000000b1","quantity":2,"costCentre":"NL-SALES-2026"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url 'https://api.roamex.com/v1/esims/purchase?lang=nl' \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: order-2026-10-22-0001' \ --data '{ "planId": "00000000-0000-4000-8000-0000000000b1", "quantity": 2, "costCentre": "NL-SALES-2026" }'Buys one or more eSIMs of a plan and pays from your balance. Send an Idempotency-Key header so that a retry returns the first order instead of buying again. If fewer eSIMs are delivered than requested, partial states how many and what was refunded.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Query Parameters
Section titled “Query Parameters”Language of plan and destination names in the response: en, nl, es, de, it, fr, pl, pt, tr, el, cs or ro. Any other value, or none, gives English. It never changes which items are returned or any figure.
Example
nlHeader Parameters
Section titled “Header Parameters”Makes retries safe. The key is combined with your account, the plan, quantity, days and costCentre: repeating all of them answers with the original order and idempotent_replay: true, and charges nothing. Changing the plan, the quantity, the cost centre or, for a day pass, the days makes it a new order, charged again. days is ignored for other plans, and quantity and days are compared after they are rounded and capped. Without this header, identical requests in the same clock minute count as one order, and an identical order that is still being provisioned is answered with 409 ORDER_IN_PROGRESS.
Example
order-2026-10-22-0001Request Bodyrequired
Section titled “Request Bodyrequired”object
A plan id from /plans.
Number of eSIMs. Rounded to a whole number and kept between 1 and 50. A missing or non-numeric value means 1.
Day passes only: the number of days. Values above 60 are lowered to 60. A missing value, or one below 1, uses the plan’s own duration. Ignored for other plans.
Your own accounting label for this order, returned on its eSIMs by /esims/list and /esims/details. Trimmed first; blank means none. At most 64 characters and no control characters, or 400 INVALID_COST_CENTRE.
Example
{ "planId": "00000000-0000-4000-8000-0000000000b1", "quantity": 2, "costCentre": "NL-SALES-2026"}Responses
Section titled “Responses”The order. On a replay of an earlier request, idempotent_replay is true, newBalance is absent, and cost is what that order was charged.
object
The eSIMs of this order.
object
Use as esimId on the other eSIM endpoints.
ready once the install data is available, otherwise pending.
object
ESIMs delivered.
ESIMs requested, after the 1 to 50 limits are applied.
object
The plan’s name. For a day pass it includes the days bought.
What your balance was charged for the order, before any refund. Absent on a replay whose order has no recorded charge.
object
Euro cents.
Present when fewer eSIMs were delivered than requested, or part of the order was refunded.
object
ESIMs that were provisioned and charged but are missing from esims. Roamex recovers them by hand. Present only when greater than 0, and never on a replay.
The amount returned to your balance.
object
Euro cents.
true when a refund is owed and has not been made yet.
cost minus refunded.
object
Euro cents.
Your balance after the order. Absent on a replay.
object
Euro cents.
Present only when this answers a repeat of an earlier request.
Example
{ "success": true, "esims": [ { "id": "00000000-0000-4000-8000-0000000000c1", "status": "ready" }, { "id": "00000000-0000-4000-8000-0000000000c2", "status": "ready" } ], "order": { "id": "00000000-0000-4000-8000-0000000000d1", "quantity": 2, "requestedQuantity": 2, "plan": { "id": "00000000-0000-4000-8000-0000000000b1", "name": "Japan · 1 GB · 7 days" } }, "cost": { "amount": 900, "currency": "EUR", "formatted": "€9.00" }, "newBalance": { "amount": 24100, "currency": "EUR", "formatted": "€241.00" }}Headers
Section titled “Headers”Calls allowed per minute for this endpoint group.
Calls left in the current minute.
When the current window ends, in Unix seconds.
INVALID_JSON: the body is missing or not a JSON object.MISSING_PLAN_ID:planIdis missing.INVALID_COST_CENTRE:costCentreis not a string, is longer than 64 characters, or contains a control character.PLAN_NOT_PRICED: the plan has no price.PLAN_NOT_AVAILABLE: the plan is not sold.INSUFFICIENT_BALANCE: your balance does not cover the order. Nothing was charged.ORDER_FAILED: no eSIM could be ordered, and the order is refunded. If that refund fails, Roamex is alerted to make it by hand.
object
object
Machine-readable. Branch on this.
A sentence for people. It can change.
Example
{ "success": false}AUTH_ERROR: the key is missing, malformed, unknown or revoked. The message says which, for example Missing Authorization header, Invalid Authorization header format. Use: Bearer <api_key>, Invalid API key format or Invalid API key.
object
object
Machine-readable. Branch on this.
A sentence for people. It can change.
Example
{ "success": false, "error": { "code": "AUTH_ERROR", "message": "Invalid API key" }}DESTINATION_NOT_ALLOWED: the plan’s destination is not on your account’s list of allowed destinations.SPEND_CAP_EXCEEDED: the order would take you over your account’s monthly spend limit.TRIAL_RESTRICTED: a trial account asked for more than its trial allows.AUTH_ERROR: the account is suspended or not approved.
object
object
Machine-readable. Branch on this.
A sentence for people. It can change.
Example
{ "success": false}PLAN_NOT_FOUND: no plan has this id, or the plan is no longer active.NOT_FOUND: the API is switched off on the platform.
object
object
Machine-readable. Branch on this.
A sentence for people. It can change.
Example
{ "success": false}Do not retry these with a new Idempotency-Key unless you mean to buy again.
ORDER_IN_PROGRESS: an identical order is still being provisioned. When it completes, itsorder.createdevent arrives by webhook and in/events/recent, and its eSIMs appear in/esims/list.ORDER_TERMINAL: the earlier identical request ended without an eSIM. The message says whether it was refunded.
object
object
Machine-readable. Branch on this.
A sentence for people. It can change.
Example
{ "success": false}PAYLOAD_TOO_LARGE: the Content-Length header is over 100 KB.
object
object
Machine-readable. Branch on this.
A sentence for people. It can change.
Example
{ "success": false}RATE_LIMITED: the account’s per-minute budget for this endpoint group is used up, or too many refused keys came from this IP address. Wait the Retry-After seconds.
object
object
Machine-readable. Branch on this.
A sentence for people. It can change.
Example
{ "success": false, "error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded. Please wait 42 seconds before retrying." }}Headers
Section titled “Headers”Calls allowed per minute for this endpoint group.
Calls left in the current minute.
When the current window ends, in Unix seconds.
Seconds to wait before retrying.
PURCHASE_FAILED: the order could not be created. Nothing was charged.INTERNAL_ERROR, orAUTH_ERRORwith status 500: see the shared errors.
object
object
Machine-readable. Branch on this.
A sentence for people. It can change.
Example
{ "success": false}Transient. This request charged nothing.
PURCHASES_PAUSED: purchases are paused. Retry after theRetry-Afterseconds.PRICING_UNAVAILABLE: your account pricing could not be confirmed.TRIAL_CHECK_UNAVAILABLE: a trial account’s allowance could not be checked.REPLAY_UNAVAILABLE: an earlier request created this order but its eSIMs could not be read. Retry the identical request with the sameIdempotency-Key.
object
object
Machine-readable. Branch on this.
A sentence for people. It can change.
Example
{ "success": false}Headers
Section titled “Headers”Seconds to wait. Sent with PURCHASES_PAUSED (300).

