Developers

Four endpoints, two webhooks, no surprises.

Everything the store does, your product can do. The whole surface fits on one page.

REST over HTTPS at api.orbislo.com, bearer authentication, JSON in and JSON out. List the catalog, place an order, poll the provisioning job, read usage. Two webhooks tell you when an eSIM activates and when it runs low. Sandbox credentials arrive the same working day. Live access is by application today, and we explain why below rather than pretending the form is a rate limit.

Why this API exists

Provisioning connectivity should happen at the moment a traveller books, inside your own product. It should not be something they discover at an airport.

A booking platform can attach data to an itinerary. A device maker can activate a plan during onboarding. A team tool can hand a new starter their data alongside their laptop.

All three of those are four calls and one webhook. There is no partner portal to log into and no CSV to upload.

1
Tap install
2
Confirm in your phone's own sheet
3
Turn on data roaming
No QR code, no camera, no second device. Median setup across all orders is 41 seconds.
What happens between your POST and a traveller having signal.

Authentication

Send your key as a bearer token on every request. Keys are prefixed sk_test for sandbox and sk_live for production.

A key pasted into the wrong environment fails with a 401 rather than quietly spending money. Keys are scoped, so a read only key cannot place an order and a key issued for one team account cannot see another.

Rotate from the console at any time. The old key keeps working for 24 hours, so you are never forced into a flag day.

curl https://api.orbislo.com/v1/catalog?country=jp \
  -H "Authorization: Bearer sk_live_9f2c..." \
  -H "Orbislo-Version: 2026-08-01"

About the version header

The version header is optional and pins the response shape to a dated release. Without it you get the version your key was created against, which never changes underneath you.

We add fields without warning. We never remove a field or change its type inside a version.

4

endpoints in the whole API, plus 2 webhook events

90s

median time from a successful order to an activated profile

24h

window in which a repeated idempotency key returns the original response

The endpoints

MethodPathWhat it doesRate limit
GET/v1/catalogEvery plan we sell, with price, data, validity and the countries it covers.600 a minute
POST/v1/ordersBuys a plan and returns an order with a provisioning job attached.60 a minute
GET/v1/provisioning/:idThe state of one provisioning job, from queued through to activated or failed.600 a minute
GET/v1/usage/:esim_idBytes used, bytes remaining, and the carrier one eSIM is attached to.300 a minute

Limits are per key in a sliding window. Every response carries the remaining count and the reset time, and a 429 carries a retry delay in seconds. If your use case genuinely needs a bigger bucket, ask, because raising one is a configuration change and not a negotiation.

Catalog

The catalog is the source of truth for what exists and what it costs. It covers the 145 destinations live today.

Filter by country, by region or by plan family. Prices are returned in minor units, so floating point never reaches your billing code.

GET /v1/catalog?country=jp

{
  "object": "list",
  "data": [
    {
      "id": "plan_jp_5gb",
      "name": "Japan 5 GB",
      "countries": ["jp"],
      "data_mb": 5120,
      "price": { "amount": 1150, "currency": "usd" },
      "expires": false,
      "throttle_mbps": null,
      "tethering": true,
      "carriers": ["NTT Docomo", "KDDI", "SoftBank"]
    }
  ],
  "has_more": false
}

Two fields worth reading twice

The expires field is false on every metered plan we sell, because our data does not expire. If you are building a price comparison, that field changes the arithmetic more than the price does.

The throttle_mbps field is null on metered plans and 1 on unlimited day passes, where full speed runs to 2 GB a day and then drops to 1 Mbps.

We put the throttle number in the API for the same reason we print it on the buy button. A number a traveller finds out later is a support ticket.

Orders

One POST buys a plan and starts provisioning. The Idempotency-Key header is required rather than advisory.

The worst outcome in this API is a timeout that leaves you unsure whether a traveller was charged. A required key removes that state entirely.

POST /v1/orders
Idempotency-Key: 4f1d0f6e-1c3a-4a2b-9d77-1b6a0e7c9f21
Content-Type: application/json

{
  "plan_id": "plan_jp_5gb",
  "traveller_ref": "user_88213",
  "imei": "356938035643809",
  "activate": "on_first_use"
}

201 Created

{
  "id": "ord_7Kd2mQ",
  "status": "provisioning",
  "esim_id": "esim_2xB9Ln",
  "provisioning_id": "prv_5Ttq81",
  "amount": { "amount": 1150, "currency": "usd" },
  "activation": {
    "type": "universal_link",
    "url": "https://orbislo.com/i/2xB9Ln",
    "lpa": "LPA:1$rsp.orbislo.com$K4-9TT-2XB9LN"
  }
}

Device checks and delayed activation

The imei field is optional but strongly recommended. Send it and we check eSIM eligibility and carrier lock before taking money.

A phone that cannot hold a profile gets a 422 with device_not_eligible instead of a sold plan and an angry traveller.

Setting activate to on_first_use means any validity window starts when the traveller lands, not when your server called us.

Provisioning status

Provisioning is asynchronous, because a carrier platform is on the other end of it. Poll this endpoint, or take the webhook and skip the polling.

Median time from queued to activated is under 90 seconds. Anything still queued after 10 minutes is a failure, and it refunds itself.

GET /v1/provisioning/prv_5Ttq81

{
  "id": "prv_5Ttq81",
  "status": "activated",
  "states": [
    { "state": "queued",       "at": "2026-08-24T09:14:02Z" },
    { "state": "provisioning", "at": "2026-08-24T09:14:04Z" },
    { "state": "installed",    "at": "2026-08-24T09:14:41Z" },
    { "state": "activated",    "at": "2026-08-24T09:15:07Z" }
  ],
  "carrier": "KDDI",
  "failure_reason": null
}

Webhooks

Two events, both of which change what a traveller should be told. Point them at any HTTPS endpoint, set separately per environment.

Webhook events

EventFires whenWhat to do with it
esim.activatedThe profile attaches to a network for the first time.Tell the traveller they are online. This is the moment the purchase became real to them.
esim.depletedThe plan crosses a usage threshold, at 80 percent and again at 100 percent.Offer a top up before they are stranded rather than after.
POST https://your-app.example/hooks/orbislo
Orbislo-Signature: t=1756032907,v1=6c1b...
Content-Type: application/json

{
  "id": "evt_9pQ4rz",
  "type": "esim.depleted",
  "created": "2026-08-24T11:41:33Z",
  "data": {
    "esim_id": "esim_2xB9Ln",
    "threshold": 80,
    "used_mb": 4096,
    "remaining_mb": 1024,
    "country": "jp"
  }
}

Verifying a delivery

Compute HMAC SHA-256 over the timestamp, a full stop, and the raw request body, using your endpoint secret. Compare in constant time.

Reject anything with a timestamp older than 5 minutes, which stops a replay. Deliveries are at least once and retried 8 times over 24 hours, so key your handler on the event id.

Error codes

HTTPCodeWhat it meansWhat to do
400invalid_requestA field is missing or has the wrong type. The body names the field.Fix the request. Retrying will not help.
401invalid_tokenThe bearer token is wrong, revoked, or from the other environment.Check you are not sending a sandbox key to the live host.
402insufficient_balanceYour account balance will not cover the order.Top up, then retry with the same idempotency key.
404not_foundThere is no object with that id in this environment.Sandbox ids and live ids are not interchangeable.
409idempotency_conflictThe same idempotency key was reused with a different body.Use a new key, or resend the original body byte for byte.
422device_not_eligibleThe device is not eSIM capable, or it is carrier locked.Run the device check before you take the money.
429rate_limitedYou went past the bucket for that endpoint.Back off using the retry delay in the header. Do not spin.
500internal_errorOurs. It is already in our alerting.Retry with the same idempotency key after 2 seconds.
503provider_unavailableAn upstream carrier platform is down.Retry for up to 10 minutes. We fail over automatically where a second carrier exists.

Every error body carries a code, a human readable message, and a request id. Quote the request id to support and you skip the first four questions.

Live access is by application today, and that is a real limitation.You cannot sign up at 2am and be provisioning by 3am, which is genuinely worse than a self serve API. We are not going to describe it as a curated onboarding experience. A live key moves money and creates a carrier profile, and we would rather read a paragraph about what you are building than clean up after a leaked key. Sandbox credentials come the same working day, so nothing stops you writing the integration while we read.

Sandbox against live

Same host, same paths, same response shapes. A key prefixed sk_test never touches money and never creates a carrier profile.

Provisioning jobs move through the full state machine in about 4 seconds, so your tests do not sleep for 90 seconds each.

Order the plan id plan_test_fail to get a failed job with a reason. Order plan_test_slow to get one that sits in provisioning for 11 minutes, so you can exercise your timeout path.

Webhook deliveries fire in sandbox too, against a URL you set per environment.

If you would rather not build anything

The affiliate track pays on referred orders with no integration at all, which is the right answer for most content sites.

The MCP endpoint exposes our coverage dataset and price index to an AI assistant with one line of configuration and no code.

The plans you would be selling, the measured speed data behind the catalog and the full country list are all published openly on this site.

Questions developers ask

Can I get an API key today?
Not by self serve. Access is by application right now, and that is a deliberate choice rather than a queue we forgot to open. Every live key can move real money and provision a real profile on a real carrier platform, and a stolen key costs a traveller their connectivity. So we read the applications. Sandbox is different: ask and you get sandbox credentials the same working day, with no contract and no commitment.
How long does approval take?
Two working days for a first reply and usually under a week end to end. We want to know what you are building, roughly how many activations a month you expect, and which countries. If we do not think the API is the right tool for what you described, we will say so and point you at the affiliate track instead, which pays without any engineering.
What is the difference between sandbox and live?
Sandbox uses the same host with a key prefixed sk_test. It returns the real catalog, accepts orders, and moves a provisioning job through queued, provisioning and activated on a compressed timeline of about 4 seconds. No money moves and no carrier profile is created. You can force any failure state by ordering the plan id plan_test_fail. Live keys are prefixed sk_live and everything about them is real.
Do I need idempotency keys?
On order creation, yes, and the endpoint rejects the request without one. A network timeout on an order is the most expensive ambiguity in this API, because retrying blindly buys the plan twice. Send a UUID in the Idempotency-Key header. We store the key alongside the response for 24 hours, so a retry inside that window returns the original result rather than creating a second order.
What are the rate limits?
600 requests a minute on read endpoints, 300 a minute on usage, and 60 a minute on order creation, per key, in a sliding window. Every response carries the remaining count and the reset time. A 429 carries a retry delay in seconds. If your use case genuinely needs more, ask, because raising a bucket is a configuration change and not a negotiation.
How reliable are webhooks?
We retry a non 2xx response 8 times over 24 hours with exponential backoff, starting at 10 seconds. Every delivery is signed with HMAC SHA-256 over the raw body using your endpoint secret, with a timestamp you should check against a 5 minute window to stop replays. Deliveries are at least once, so make your handler idempotent on the event id.
Is there an SDK?
There is a TypeScript client and a Python client, both thin wrappers over the same four endpoints. Neither hides anything. The wire format is stable enough that curl is a reasonable production client. We would rather document the wire format well than maintain eleven SDKs badly.
What happens when provisioning fails?
The job moves to failed with a reason, we fire the activation webhook carrying that status, and the order refunds itself inside 60 seconds without anybody asking. You do not need to build a refund path for this case. You do need to handle the failed status, because your traveller still has no data and should be told at once.

Start with the sandbox, or skip the code entirely

Sandbox credentials arrive the same working day. If you would rather not write an integration, the affiliate track and the MCP endpoint both need nothing built.