Skip to content

Quickstart

Four calls: get a token, find a package, order it, read the eSIM. Everything below runs against sandbox, so nothing here spends real money.

In the partner portal, open API Integration and create a sandbox key with the scopes catalog:read, orders:write and esims:read.

Copy the client_secret now — it is shown once and stored only as a hash.

Terminal window
curl -X POST https://partner-api.esimfo.com/oauth/token \
-H 'Content-Type: application/json' \
-d '{
"grant_type": "client_credentials",
"client_id": "esf_test_ci_4f9c2a1b8e7d6c5a4b3f2e1d0c9b8a7f",
"client_secret": "esf_test_cs_xN2pQ7vR...redacted"
}'
{
"access_token": "esf_test_at_9K3mR7pQ2wX5nB8vC1zL4jH6tY0dF...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "catalog:read orders:write esims:read"
}

The token lasts an hour. Cache it — do not fetch one per request.

This example uses a flat package list. To build a storefront organized by catalog, use List catalogs with packages and take the order’s package_id from data[].packages[].id.

Terminal window
curl 'https://partner-api.esimfo.com/v1/catalog/packages?country=TR&limit=5' \
-H 'Authorization: Bearer esf_test_at_...'
{
"data": [
{
"id": "pkg_tr_3gb_30d",
"name": "Turkey 3 GB — 30 days",
"service_type": "DATA",
"data_policy": "FIXED",
"data_amount_mb": 3072,
"validity_days": 30,
"countries": ["TR"],
"currency": "EUR",
"price": "4.20",
"selling_price": "6.30"
}
],
"next_cursor": null,
"has_more": false
}

price is what you pay us. selling_price is what you charge. Take the id.

The Idempotency-Key is required. Generate a UUID and store it alongside your own order record before you make the call — that is what makes a retry safe.

Terminal window
curl -X POST https://partner-api.esimfo.com/v1/orders \
-H 'Authorization: Bearer esf_test_at_...' \
-H 'Idempotency-Key: 9f1c4e2a-7b3d-4a58-9c06-2e8f5d1b4a73' \
-H 'Content-Type: application/json' \
-d '{
"items": [{ "package_id": "pkg_tr_3gb_30d", "quantity": 1 }],
"client_reference": "cart-4821"
}'
{
"id": "ord_4b21c9e7",
"order_number": "ESF-2026-0004821",
"status": "COMPLETED",
"payment_mode": "BALANCE",
"subtotal": "4.20",
"commission_amount": "0.00",
"total": "4.20",
"currency": "EUR",
"client_reference": "cart-4821",
"created_at": "2026-08-22T09:14:03.512Z",
"items": [
{
"package_id": "pkg_tr_3gb_30d",
"quantity": 1,
"unit_price": "4.20",
"line_total": "4.20"
}
],
"esims": [
{
"id": "esm_9a3f1c",
"iccid": "89852350326100409575",
"lpa_code": "LPA:1$rsp.truphone.com$QR-G-5C-1LK-1V0FS",
"qr_code_url": "https://cdn.esimfo.com/qr/esm_9a3f1c.png",
"smdp_address": "rsp.truphone.com",
"matching_id": "QR-G-5C-1LK-1V0FS",
"status": "PROVISIONED",
"data_total_mb": 3072,
"data_used_mb": 0,
"data_remaining_mb": null,
"expires_at": null,
"failure_reason": null
}
]
}

Most orders come back COMPLETED with the eSIM already attached.

Everything the traveller needs is on the eSIM object:

  • qr_code_url — show or email this. Scanning it installs the profile.
  • lpa_code — the same thing as text, for manual installation. On iOS this is what “Enter Details Manually” expects.
  • smdp_address and matching_id — the two halves of the LPA string, for devices that ask for them separately.

That is a working integration. From here:

  • Read Idempotency properly before you handle real money.
  • Wire up Webhooks so you are not polling.
  • Work through Going live before your first real sale.