# Roamify eSIM API - full documentation > Roamify is a REST API for buying, activating and managing travel eSIMs, plus travel insurance, webhooks, affiliate links and exchange rates. Built for travel apps, OTAs, eSIM resellers and partner platforms. --- # Roamify API Documentation > Welcome to the Roamify API documentation. This guide helps you authenticate, choose the correct environment, and integrate Roamify services into your product. Canonical: https://docs.getroamify.com/ Welcome to the Roamify API documentation. This guide helps you authenticate, choose the correct environment, and integrate Roamify services into your product. Roamify API is a REST API that allows you to buy eSIMs, manage eSIM lifecycle events, issue travel insurance policies, check partner balance, and work with exchange rates. Currently, we support the following services: - [Authentication](https://docs.getroamify.com/authentication/) - [eSIMs](https://docs.getroamify.com/esims/overview/) - [Insurance](https://docs.getroamify.com/insurance/overview/) - [Exchange Rates](https://docs.getroamify.com/exchange-rates/overview/) - [Health Check](https://docs.getroamify.com/health/) If you need access or support, use the [Roamify Partner Portal](https://partner.getroamify.com/sign-up) or email [support@getroamify.com](mailto:support@getroamify.com). ## Getting Started If this is your first time using Roamify API, follow the steps below: 1. Sign up or sign in via [Roamify Partner Portal](https://partner.getroamify.com/sign-in). 2. Get your Access Token from `API Keys`. 3. Pick your API environment: - Development: `https://api-dev.getroamify.com` - Production: `https://api.getroamify.com` 4. Read [Authentication](https://docs.getroamify.com/authentication/), then start with [eSIMs](https://docs.getroamify.com/esims/overview/). ## Resources ### Postman We have created a Postman collection that you can import into your Postman app. [Check Postman Collection](https://www.postman.com/roamifyhq/roamify-developers/overview) ### API Dog We have also created an API Dog documentation for our API. [Check API Dog](https://roamify.apidog.io/) ### Environments We have two environments for our API: - **Development**: Use this environment to test your integration with Roamify API. Endpoint: `https://api-dev.getroamify.com` - **Production**: Use this environment when you are ready to go live with your application. Endpoint: `https://api.getroamify.com` --- # Authentication > Roamify API uses Bearer token authentication. Send your access token in the Authorization header for every authenticated request. Canonical: https://docs.getroamify.com/authentication/ Roamify API uses Bearer token authentication. Send your access token in the `Authorization` header for every authenticated request. ## Authentication Flow 1. **Get Access Token**: Retrieve your Access Token (called API Key in the portal) from [Roamify Partner Portal](https://partner.getroamify.com/sign-in). 2. **Choose environment**: - Development: `https://api-dev.getroamify.com` - Production: `https://api.getroamify.com` 3. **Use Access Token**: Include the token in the `Authorization` header as `Bearer `. ## Access Token Policy Your Access Token is long-lived and does not expire automatically. It remains valid until you rotate it in the Partner Portal. ## Token Rotation You can rotate your Access Token in the Roamify Partner Portal under `API Keys`. After rotation, the old token is immediately invalidated. ## Usage - how to use your Access Token Here is an example of how you can use your Access Token to make requests to Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/countries' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/countries', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ## Response and Error Handling Authentication itself is header-based, so response payloads depend on the endpoint you call. Common status codes: - `200`: Request accepted. - `401`: Missing token, malformed `Authorization` header, or invalid/rotated token. - `403`: Token is valid but does not have permission for the requested action. - `500`: Internal server error. Retry the request with backoff or contact support if the issue persists. For exact response schema, check each endpoint reference page. --- # About Roamify eSIM API > Roamify eSIM API is a RESTful API that allows you to integrate Roamify services into your application. With Roamify eSIM API, you can buy eSIMs, check your balance, and more. Canonical: https://docs.getroamify.com/about/ Roamify eSIM API is a RESTful API that allows you to integrate Roamify services into your application. With Roamify eSIM API, you can buy eSIMs, check your balance, and more. ## Features - **Buy eSIMs**: You can buy eSIMs using Roamify eSIM API. - **Check Balance**: You can check your balance using Roamify eSIM API. - **Top Up**: You can top up your balance using Roamify eSIM API. - **View Transactions**: You can view your transactions using Roamify eSIM API. - **Manage eSIMs**: You can manage your eSIMs using Roamify eSIM API. - **Get eSIM Details**: You can get details of your eSIMs using Roamify eSIM API. - **Get eSIM Plans**: You can get eSIM plans using Roamify eSIM API. - **Get eSIM Countries**: You can get eSIM countries using Roamify eSIM API. - **More**: We are constantly working on adding more features to Roamify eSIM API. --- # Health Check > The GET /api/health endpoint reports the status of the Roamify API in the development and production environments. Canonical: https://docs.getroamify.com/health/ We provide a health check endpoint that you can use to check the status of Roamify eSIM API. The health check endpoint is available at: - Development: [https://api-dev.getroamify.com/api/health](https://api-dev.getroamify.com/api/health) - Production: [https://api.getroamify.com/api/health](https://api.getroamify.com/api/health) You can send a `GET` request to the health check endpoint to check the status of Roamify eSIM API. The response will contain the status of the API. --- # How to sell eSIMs in your app > Step-by-step guide to selling travel eSIMs from your own app with the Roamify API: authenticate, find a package, place an order, generate the eSIM profile, share the install link and track usage. Canonical: https://docs.getroamify.com/guides/sell-esims-in-your-app/ This guide walks through the shortest path from an API key to an eSIM your customer can install. It uses the same endpoints as the reference pages; follow the links for every field and response. ## Before you start 1. Sign up at the [Roamify Partner Portal](https://partner.getroamify.com/sign-up) and copy your Access Token (called an API Key in the portal). 2. Build against the development environment, `https://api-dev.getroamify.com`, and switch to `https://api.getroamify.com` for production. 3. Send `Authorization: Bearer ` on every request. See [Authentication](https://docs.getroamify.com/authentication/). ## 1. Find the package Roamify eSIM covers 200+ countries. Call [`GET /api/esim/countries`](https://docs.getroamify.com/esims/countries/) first, then use the returned `name` as `country` or the returned `code` as `countryCode` when you query [`GET /api/esim/packages`](https://docs.getroamify.com/esims/packages/). Packages are paginated. Pass `page` and `pageSize` and iterate when you need the full catalog. You can also filter by `geography`, `days`, `gbs` and `isUnlimited`. Keep the `packageId` of the package your customer picks. To show prices in a customer's currency, use the [exchange rates API](https://docs.getroamify.com/exchange-rates/overview/). ## 2. Place the order There are two ways to order. Both take an `items` array of `{ packageId, quantity }` and an optional `referenceId` of your own. | | Order, then apply | Instant order | | --- | --- | --- | | Endpoint | `POST /api/esim/order`, then `POST /api/esim/apply` | `POST /api/esim/order/instant` | | Steps | Two calls | One call, no `/apply` needed | | Notes | Gives you the `esimId` of each eSIM to apply when you are ready | Set a timeout of at least 60 seconds, because fulfilment time varies with what you order | > **Note:** Send a `referenceId` so you can find the order again with `GET /api/esim/order`, which accepts `orderId` or `referenceId`. See [Orders](https://docs.getroamify.com/esims/orders/). Keep an eye on your account funds with [`GET /api/balance`](https://docs.getroamify.com/esims/balance/), or subscribe to [partner balance webhooks](https://docs.getroamify.com/webhooks/types/partner-balance/). ## 3. Generate the eSIM profile If you used `POST /api/esim/order`, call [`POST /api/esim/apply`](https://docs.getroamify.com/esims/esims/#generate-esim-profile) with the `esimId` from the order items. If the package requires a travel date, send `startDate` in ISO 8601 format. The response contains the eSIM profile. Skip this step if you used the instant order. ## 4. Give the customer the eSIM Generate a branded installation link with [`POST /api/esim/link`](https://docs.getroamify.com/esims/esims-link/) and send it to your customer. The response includes an access code and a ready-made WhatsApp share URL. To check which phones support eSIM, use [`GET /api/compatible-devices`](https://docs.getroamify.com/esims/compatible-devices/). ## 5. Track usage and top up Read data usage with `GET /api/esim/usage?iccid=...`, and a daily breakdown with `GET /api/esim/usage/details`. When the customer needs more data, follow the [top-up flow](https://docs.getroamify.com/esims/topup-flow/). Top-up works only for eSIMs that support it (`withTopUp = true`). ## 6. Listen for events Instead of polling, register a webhook URL in the Roamify dashboard. Roamify sends signed events for [eSIM status](https://docs.getroamify.com/webhooks/types/esim-status/), [eSIM usage](https://docs.getroamify.com/webhooks/types/esim-usage/), [partner balance](https://docs.getroamify.com/webhooks/types/partner-balance/), [affiliate orders](https://docs.getroamify.com/webhooks/types/affiliate-orders/) and [refunds](https://docs.getroamify.com/webhooks/types/refund/). Verify every request with the `X-Roamify-Signature` header (HMAC-SHA256) and answer `200 OK` within 3 seconds. See [Webhook verification](https://docs.getroamify.com/webhooks/verification/). ## If something goes wrong - Look the status and message up in [API Errors](https://docs.getroamify.com/errors/). - If a customer cannot use the eSIM, you can file a [refund request](https://docs.getroamify.com/esims/refunds/). Roamify support reviews it and the refund happens only after approval. - Questions? Email [support@getroamify.com](mailto:support@getroamify.com) or see the [FAQ](https://docs.getroamify.com/faq/). --- # Retries and idempotency > How to retry Roamify API requests safely: which errors are temporary, how referenceId protects orders and voucher batches from duplicates, and why insurance issuance must not be retried blindly. Canonical: https://docs.getroamify.com/guides/retries-and-idempotency/ Network calls fail. This guide explains what the Roamify docs say about retrying safely, so a timeout does not turn into a duplicate order. It only covers behavior stated in the reference pages. ## Retry only temporary errors `500` and `503` responses are temporary. Retry them with backoff, and contact support if they persist. `400`, `404` and `422` describe a problem with your request, so fix the request instead of repeating it. Read a `409` before you retry, because it means the resource is already in a state that conflicts with your request. The full list is in [API Errors](https://docs.getroamify.com/errors/). ## Use a referenceId Orders accept an optional `referenceId` of your own, and you can fetch an order by `orderId` or `referenceId`. If a request times out, look the order up by your `referenceId` before you submit it again. - **eSIM orders** (`POST /api/esim/order`, `POST /api/esim/order/instant`, `POST /api/esim/order/topup`): `referenceId` is optional. Fetch with `GET /api/esim/order?referenceId=...`. See [Orders](https://docs.getroamify.com/esims/orders/). The instant order can take a while to fulfil, so set a timeout of at least 60 seconds and check by `referenceId` before you retry after a timeout. - **Insurance orders**: `referenceId` must be unique across your orders. Reusing one returns `409`, which makes it a safe guard against double submission. See [Insurance orders](https://docs.getroamify.com/insurance/orders/). - **Vouchers**: creating vouchers is not idempotent on its own. A retried request with no `referenceId` mints a whole second batch. Always send a `referenceId`: a repeat of a batch that already landed is rejected with `409`, and you can fetch the vouchers that were created with `GET /api/esim/vouchers?referenceId=...`. See [Vouchers](https://docs.getroamify.com/esims/vouchers/). ## Insurance issuance is not idempotent `POST /api/insurance/apply` issues a policy. Applying to a policy that is already `ISSUED`, `ACTIVE` or `EXPIRED` returns `409`. Do not blindly retry it. Read the message: - If the policy is being issued, poll `GET /api/insurance/policy?policyId=...` until the policy document is ready. - If it has already been issued, it cannot be applied again. See [Insurance policies](https://docs.getroamify.com/insurance/policies/). ## Refund requests An eSIM can have one open refund request at a time. A second request while one is `PENDING` or `PROCESSING` returns `409` with the `refundId` of the open request, so a retried request cannot create a duplicate. In a bulk request, a repeated `iccid` or `esimId` comes back as `DUPLICATE` and the first entry is kept. See [Refunds](https://docs.getroamify.com/esims/refunds/). ## Webhooks can repeat Handle duplicate webhook deliveries. The refund event, for example, has a stable `event_id` that is the same every time the same event is delivered. See [Refund event](https://docs.getroamify.com/webhooks/types/refund/#handling-duplicates). Respond with `200 OK` within 3 seconds, or Roamify retries the webhook up to 3 times at increasing intervals ([verification](https://docs.getroamify.com/webhooks/verification/)). --- # eSIM API Overview > Overview of the Roamify eSIM API services and the recommended steps to buy eSIMs, check your balance and more. Canonical: https://docs.getroamify.com/esims/overview/ ## Roamify eSIM Overview Roamify eSIM API consists of the following services: - [Countries](https://docs.getroamify.com/esims/countries/) - [Packages](https://docs.getroamify.com/esims/packages/) - [Orders](https://docs.getroamify.com/esims/orders/) - [eSIMs](https://docs.getroamify.com/esims/esims/) - [eSIM Events](https://docs.getroamify.com/esims/events/) - [eSIMs Link](https://docs.getroamify.com/esims/esims-link/) - [Vouchers](https://docs.getroamify.com/esims/vouchers/) (selected partners only) - [Refunds](https://docs.getroamify.com/esims/refunds/) - [Compatible Devices](https://docs.getroamify.com/esims/compatible-devices/) - [Balance](https://docs.getroamify.com/esims/balance/) You can use these services to buy eSIMs, check your balance, and more. ## Recommended steps to use eSIM API Before implementing each step, review the endpoint-specific reference page for request and response schema details: - [Countries](https://docs.getroamify.com/esims/countries/) - [Packages](https://docs.getroamify.com/esims/packages/) - [Orders](https://docs.getroamify.com/esims/orders/) - [eSIMs](https://docs.getroamify.com/esims/esims/) - [Events](https://docs.getroamify.com/esims/events/) - [Balance](https://docs.getroamify.com/esims/balance/) ### Step 0: Flow Diagram ```mermaid graph TD; subgraph Auth [Authentication] A[Step 1: Get Access Token] end subgraph Purchase [eSIM Purchase Flow] B[Step 2: Get eSIM Packages] C[Step 3: Submit eSIM Order] D[Step 4: Generate eSIM] end subgraph Usage [eSIM Usage & Management] E[Step 5: Get eSIM Usage] F[Step 6: Get Compatible Top-up Packages] G[Step 7: Top-up eSIM] end subgraph Account [Account Management] H[Step 8: Get Balance] end A --> B; B --> C; C --> D; D --> E; E --> F; F --> G; G -.-> E; A --> H; ``` ### Step 1: Authentication You need to have your Access Token to make requests to Roamify eSIM API. You can get your Access Token from [Roamify Partner Portal](https://partner.getroamify.com/sign-in). If you don't have an account, you can sign up [here](https://partner.getroamify.com/sign-up) or contact us at [support@getroamify.com](mailto:support@getroamify.com). Our token is a Bearer token, which means you need to include it in the `Authorization` header of your requests. ### Step 2: Get eSIM Packages To get a list of eSIM packages, you need to send a `GET` request to the `/api/esim/packages` endpoint. This endpoint now returns paginated results. Use `page` and `pageSize` query parameters and iterate through pages when you need the full catalog. Before filtering by country, fetch `GET /api/esim/countries` and use only these values: - `country`: use the returned `name` - `countryCode`: use the returned `code` You can also filter packages by `geography`, `days`, `gbs`, and `isUnlimited`. #### Usage - how to get eSIM packages Here is an example of how you can get a list of eSIM packages using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/packages?page=1&pageSize=50' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/packages?page=1&pageSize=50', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` You can read more about this endpoint [here](https://docs.getroamify.com/esims/packages/). ### Step 3: Submit an eSIM Order To submit an eSIM order, you need to send a `POST` request to the `/api/esim/order` endpoint with the required body parameters. The request body should contain the following parameters: - `items`: An array of eSIMs you want to order. Each eSIM should contain the following details: - `packageId`: The ID of the eSIM package. This is a required field of type `string`. - `quantity`: The quantity of the eSIMs you want to order. This is a required field of type `number`. Alternatively, you can send a `POST` request to the `/api/esim/order` endpoint using a previously provided `referenceId` in the request body. #### Usage - how to create an eSIM order Here is an example of how you can create an eSIM order using Roamify eSIM API. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/esim/order' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "items": [ { "packageId": "Package ID", "quantity": 1 } ] }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/esim/order', { items: [ { packageId: 'Package ID', quantity: 1 } ] }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` You can read more about this endpoint [here](https://docs.getroamify.com/esims/orders/). ### Step 4: Generate eSIM As soon as you submit an eSIM order, in the response, you will get a dedicated `esimId` for each eSIM package you ordered. You can use this `esimId` to generate an eSIM, which will be ready to be used by your customers. Alternatively, you can also use the `referenceId` you provided in the request body to generate an eSIM if applicable. To generate an eSIM, you need to send a `POST` request to the `/api/esim/apply` endpoint with the required body parameters. > **Warning:** `startDate` is required for packages where `requirements.travelDate` is `true`. Use ISO 8601 format (for example, `2026-07-01`). > For full endpoint details, see [Generate eSIM Profile](https://roamify.apidog.io/generate-esim-profile-12947632e0). The request body should contain the following parameters: - `esimId`: The ID of the eSIM. This is a required field of type `string`. - `referenceId`: The reference ID you provided when creating the order. This is an optional field of type `string`. - `startDate`: Travel start date in ISO 8601 format. This is a conditional field required only when `requirements.travelDate` is `true`. #### Usage - how to generate an eSIM profile Here is an example of how you can apply an eSIM profile using Roamify eSIM API. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/esim/apply' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "esimId": "eSIM ID" }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/esim/apply', { esimId: 'eSIM ID' }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` You can read more about this endpoint [here](https://docs.getroamify.com/esims/esims/). ### Step 5: Get eSIM Usage To get the usage details of an eSIM, you need to send a `GET` request to the `/api/esim/usage` endpoint with the required parameters. The parameters required for this endpoint are: - `iccid`: The ICCID of the eSIM. This is a required field of type `string`. #### Usage - how to check eSIM usage Here is an example of how you can get the usage details of an eSIM using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/usage?iccid=ICCID' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/usage?iccid=ICCID', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` You can read more about this endpoint [here](https://docs.getroamify.com/esims/esims/). ### Step 6: Get compatible top-up packages for an eSIM Before you can top-up an eSIM, you need to know which packages are compatible with the eSIM you want to top-up. To get the compatible top-up packages for an eSIM, you need to send a `GET` request to the `/api/esim/topup/packages` endpoint with the required parameters. For an end-to-end walkthrough (with decision points and diagram), see [Top-up Flow](https://docs.getroamify.com/esims/topup-flow/). The parameters required for this endpoint are: - `iccid`: The ICCID of the eSIM. This is a required field of type `string`. - `days`: The number of days for which the eSIM package is valid. This is an optional field of type `number`. - `gbs`: The amount of data in GB for which the eSIM package is valid. This is an optional field of type `number`. - `isUnlimited`: The flag to indicate if the eSIM package is unlimited. This is an optional field of type `boolean`. - `packageId`: The ID of the eSIM package. This is an optional field of type `string`. #### Usage - fetch compatible top-ups Here is an example of how you can get a list of compatible top-ups for an eSIM using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/topup/packages?iccid=ICCID' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/topup/packages?iccid=ICCID', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Step 7: Top-up an eSIM You may need to top-up an eSIM if the balance is low or the data limit is reached. To top-up an eSIM, you need to send a `POST` request to the `/api/esim/order/topup` endpoint with the required body parameters. The request body should contain the following parameters: - `iccid`: The ICCID of the eSIM you want to top-up. This is a required field of type `string`. - `packageId`: The ID of the eSIM package you want to top-up. This is a required field of type `string`. #### Usage - how to create a top-up order Here is an example of how you can create a top-up order using Roamify eSIM API. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/esim/order/topup' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "iccid": "ICCID", "packageId": "Package ID" }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/esim/order/topup', { iccid: 'ICCID', packageId: 'Package ID' }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` You can read more about this endpoint [here](https://docs.getroamify.com/esims/orders/). ### Step 8: Get Balance To get the remaining balance of your funds, you need to send a `GET` request to the `/api/balance` endpoint. There are no required parameters for this endpoint. #### Usage - how to check your balance Here is an example of how you can get the remaining balance of your funds using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/balance' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/balance', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` You can read more about this endpoint [here](https://docs.getroamify.com/esims/balance/). ### The End That's it! You have successfully integrated Roamify eSIM API into your application. If you have any questions or need help, feel free to contact us at [support@getroamify.com](mailto:support@getroamify.com). --- # eSIM API - Supported Countries > To get a list of countries, you need to send a GET request to the /api/esim/countries endpoint. Canonical: https://docs.getroamify.com/esims/countries/ ## Countries To get a list of countries, you need to send a `GET` request to the `/api/esim/countries` endpoint. There are no required parameters for this endpoint. Optional query parameters: - `geography`: Filter countries by geography scope. Accepted values: - `local` - `regional` - `global` > **Warning:** Use the returned `name` and `code` from this endpoint when querying `/api/esim/packages`. > For package filters: > - `country` must use `name` > - `countryCode` must use `code` > Arbitrary country strings are not accepted. ## Usage - fetch a list of eSIM supported countries Here is an example of how you can get a list of countries using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/countries' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/countries', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Usage - fetch countries by geography (optional) **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/countries?geography=regional' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/countries?geography=regional', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ## Sample Response ```json { "status": "success", "data": { "countries": [ { "code": "AF", "name": "Afghanistan", "geography": "local" }, { "code": "AFR", "name": "Africa", "geography": "regional" }, { "code": "AX", "name": "Ă…land Islands", "geography": "local" }, { "code": "AL", "name": "Albania", "geography": "local" }, { "code": "DZ", "name": "Algeria", "geography": "local" }, { "code": "AD", "name": "Andorra", "geography": "local" } ] } } ``` --- # eSIM API - Package(s) > To get a list of eSIM packages, you need to send a GET request to the /api/esim/packages endpoint. Canonical: https://docs.getroamify.com/esims/packages/ ## Ways to get eSIM packages To get a list of eSIM packages, you need to send a `GET` request to the `/api/esim/packages` endpoint. > **Warning:** Breaking change: `/api/esim/packages` now returns paginated results. If you do not pass `page` and `pageSize`, you may only receive the first page. > **Warning:** `country` and `countryCode` now have different meanings. Both must use values returned from `GET /api/esim/countries`. > **Warning:** Watch out property "withUsageCheck", if it is set to false, usage check won't be supported, find instructions in the notes property > **Warning:** Watch out property "withThrottle", if it is set to true, throttling will in-effect, find more info in the throttle property The request query can include the following parameters: - `page`: Page number to retrieve. This is an optional field of type `string`. Default: `1`. - `pageSize`: Number of results per page. This is an optional field of type `string`. Default: `50`. - `country`: Full country name filter. This is an optional field of type `string`. Use the `name` value from `GET /api/esim/countries`. - `countryCode`: ISO country code filter. This is an optional field of type `string`. Use the `code` value from `GET /api/esim/countries`. - `geography`: The geography of the eSIM packages. This is an optional field of type `string`. It can be any of the following values: - `local`: Local packages - `regional`: Regional packages - `global`: Global packages - `days`: The number of days for which the eSIM package is valid. This is an optional field of type `number`. - `gbs`: The amount of data in GB for which the eSIM package is valid. This is an optional field of type `number`. - `isUnlimited`: The flag to indicate if the eSIM package is unlimited. This is an optional field of type `boolean`. - `packageId`: The ID of the eSIM package. This is an optional field of type `string`. > **Note:** To fetch all packages, iterate pages until the current page returns fewer items than your `pageSize`. ## Usage examples Here is an example of how you can get a list of eSIM packages using Roamify eSIM API. ### Example Request 1: Get first page of eSIM packages **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/packages?page=1&pageSize=50' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/packages?page=1&pageSize=50', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ```

### Example Request 2: Filter by `country` (full country name) **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/packages?country=United%20States&page=1&pageSize=50' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/packages?country=United%20States&page=1&pageSize=50', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ```
### Example Request 3: Filter by `countryCode` (ISO code) **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/packages?countryCode=US&page=1&pageSize=50' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/packages?countryCode=US&page=1&pageSize=50', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Example Request 4: Get eSIM packages for a specific geography **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/packages?geography=global&page=1&pageSize=50' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/packages?geography=global&page=1&pageSize=50', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Example Request 5: Get eSIM packages for specific days and GBs **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/packages?days=3&gbs=10&page=1&pageSize=50' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/packages?days=3&gbs=10&page=1&pageSize=50', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "packages": [ { "id": "afghanistan-esims", "countryName": "Afghanistan", "countryCode": "AF", "countrySlug": "afghanistan", "region": "Asia", "geography": "local", "minPrice": 5, "maxPrice": 5, "image": "https://imagedelivery.net/uUE6LNs_ci8SFBH4_n66dQ/countries/flags/afghanistan/public", "signals": [ { "code": "AF", "name": "Afghanistan", "networks": [ "3G", "4G" ], "carriers": [ "MTN" ] } ], "packages": [ { "packageId": "esim-canada-15days-un2gb-all", "package": "Unlimited Data - 15 Days (2 GB / 128 KBPS)", "plan": "data-only", "activation": "first-use", "day": 15, "price": 1.32, "isUnlimited": true, "isAPNAutomatic": true, "apn": "drei.at", "dataAmount": 9007199254740991, "dataUnit": "MB", "callType": "all", "callAmount": -1, "callUnit": "", "smsType": "all", "smsAmount": -1, "smsUnit": "", "withSMS": false, "withCall": false, "withHotspot": true, "withDataRoaming": true, "withDestinationInstall": false, "withUsageCheck": true, "withThrottle": true, "throttle": { "throttleSpeed": 128, "throttleSpeedUnit": "kbps", "throttleThreshold": 2, "throttleThresholdUnit": "gb" }, "notes": [] } ] } ] }, "pagination": { "page": 1, "pageSize": 50, "totalItems": 2, "totalPages": 1, "hasNextPage": false, "hasPreviousPage": false } } ``` --- # eSIM API - Orders > To create an eSIM order, you need to send a POST request to the /api/esim/order endpoint. Canonical: https://docs.getroamify.com/esims/orders/ ## Create eSIM Order To create an eSIM order, you need to send a `POST` request to the `/api/esim/order` endpoint. You are required to provide the following parameters in the request body: - `items`: An array of eSIM items you want to order. Each item should contain the following details: - `packageId`: The ID of the eSIM package you want to order. This is a required field of type `string`. - `quantity`: The quantity of the eSIM package you want to order. This is a required field of type `number`. - `referenceId`: The reference ID for the order. This is an optional field of type `string`. (Your custom reference ID for the order, if needed.) ### Usage - create an eSIM order Here is an example of how you can create an eSIM order using Roamify eSIM API. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/esim/order' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "items": [ { "packageId": "Package ID", "quantity": 1 } ], "referenceId": "Optional Reference ID" }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/esim/order', { items: [ { packageId: 'Package ID', quantity: 1 } ] }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "id": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "referenceId": "ref-123456", "orderType": "api", "status": "COMPLETED", "total": 5, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539, "items": [ { "esimId": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "packageId": "esim-cambodia-7days-1gb-all", "package": "1 GB - 7 Days", "type": "esim", "price": 5, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539 } ] } } ``` ## Create Top-up Order To create a top-up order, you need to send a `POST` request to the `/api/esim/order/topup` endpoint. You are required to provide the following parameters in the request body: - `iccid`: The ICCID of the eSIM you want to top-up. This is a required field of type `string`. - `packageId`: The ID of the eSIM package you want to top-up. This is a required field of type `string`. - `referenceId`: The reference ID for the top-up order. This is an optional field of type `string`. (Your custom reference ID for the order, if needed.) ### Usage - create a top-up order Here is an example of how you can create a top-up order using Roamify eSIM API. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/esim/order/topup' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "iccid": "ICCID", "packageId": "Package ID", "referenceId": "Optional Reference ID" }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/esim/order/topup', { iccid: 'ICCID', packageId: 'Package ID' }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ``` { "status": "success", "data": { "id": "0501aef3-7cf1-4b4c-a293-c03a6bd7a8ab", "referenceId": "ref-123456", "orderType": "api", "status": "COMPLETED", "total": 5, "currency": "USD", "createdAt": 1731838277, "updatedAt": 1731838280, "items": [ { "esimId": "22dcdb84-968b-415c-bb70-79e2f3ffc296", "packageId": "esim-cambodia-7days-1gb-all", "package": "1 GB - 7 Days", "type": "esim", "price": 5, "currency": "USD", "createdAt": 1731838277, "updatedAt": 1731838280 } ] } } ``` ## Create Instant eSIM Order To create an instant eSIM order, you need to send a `POST` request to the `/api/esim/order/instant` endpoint. > **Warning:** Please make sure you have timeout set to at least `60 seconds` to use this endpoint. > Depends on what you order, the fulfillment time can varies > **Warning:** There is no need to call /apply endpoint with this endpoint You are required to provide the following parameters in the request body: - `items`: An array of eSIM items you want to order. Each item should contain the following details: - `packageId`: The ID of the eSIM package you want to order. This is a required field of type `string`. - `quantity`: The quantity of the eSIM package you want to order. This is a required field of type `number`. - `referenceId`: The reference ID for the order. This is an optional field of type `string`. (Your custom reference ID for the order, if needed.) ### Usage - create an instant eSIM order Here is an example of how you can create an instant eSIM order using Roamify eSIM API. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/esim/order/instant' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "items": [ { "packageId": "Package ID", "quantity": 1 } ], "referenceId": "Optional Reference ID" }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/esim/order/instant', { items: [ { packageId: 'Package ID', quantity: 1 } ] }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "id": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "referenceId": "ref-123456", "orderType": "api", "status": "COMPLETED", "total": 5, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539, "esims": [ { "id": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "iccid": "894000000000029365", "status": "READY", "activation": "first-use", "withTopUp": true, "withHotspot": true, "withDataRoaming": true, "withDestinationInstall": false, "withUsageCheck": true, "esim": { "lpaCode": "LPA:1$lpa.roamify.com$TEST", "smdpAddress": "lpa.roamify.com", "activationCode": "TEST", "qrCodeUrl": "https://imagedelivery.net/uUE6LNs_ci8SFBH4_n66dQ/partners/b06036c5-eadc-4f88-aa31-8c207fec91ec/iccid/public", "pin": "", "puk": "", "number": "", "iosQuickInstall": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.roamify.com$TEST", "androidQuickInstall": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.roamify.com$TEST" }, "apn": { "apnAutomatic": true, "apnName": "", "apnUsername": "", "apnPassword": "" }, "data": { "isUnlimited": false }, "call": { "withCall": false, "callType": "all" }, "sms": { "withSMS": false, "smsType": "all" }, "notes": [], "createdAt": 1731837539, "updatedAt": 1731837987, "expiredAt": 1763373985 } ] } } ``` ## Get eSIM Order To get a specific eSIM order, you need to send a `GET` request to the `/api/esim/order` endpoint. You are required to provide either orderId or referenceId as a query parameter: - `orderId`: The ID of the eSIM order you want to retrieve. This is an optional field of type `string`. - `referenceId`: The reference ID of the eSIM order you want to retrieve. This is an optional field of type `string`. ### Usage - fetch an eSIM order Here is an example of how you can get a specific eSIM order using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/order?orderId=Order ID' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/order?orderId=Order ID', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "id": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "referenceId": "ref-123456", "orderType": "api", "status": "COMPLETED", "total": 5, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539, "items": [ { "esimId": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "packageId": "esim-cambodia-7days-1gb-all", "package": "1 GB - 7 Days", "type": "esim", "price": 5, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539 } ] } } ``` ## Get eSIM Orders To get a list of eSIM orders, you need to send a `GET` request to the `/api/esim/orders` endpoint. There are no required parameters for this endpoint, but you can provide the following optional parameters: - `status`: The status of the eSIM orders. This is an optional field of type `string`. It can be any of the following values: - `PENDING`: Pending orders - `COMPLETED`: Completed orders - `ARCHIVED`: Archived orders - `CANCELLED`: Cancelled orders - `REFUNDED`: Refunded orders - `REQUIRE_ACTION`: Orders that require action - `limit`: The number of eSIM orders to return. This is an optional field of type `number`. The default value is `50`. - `referenceId`: The reference ID of the eSIM orders you want to retrieve. This is an optional field of type `string`. ### Usage - fetch a list of eSIM orders Here is an example of how you can get a list of eSIM orders using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/orders?status=PENDING&limit=10' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/orders?status=PENDING&limit=10', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "orders": [ { "id": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "referenceId": "ref-123456", "orderType": "api", "status": "COMPLETED", "total": 5, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539, "items": [ { "esimId": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "packageId": "esim-cambodia-7days-1gb-all", "package": "1 GB - 7 Days", "type": "esim", "price": 5, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539 } ] } ] } } ``` --- # eSIM Top-up Flow > Recommended end-to-end flow for topping up an existing eSIM with a compatible top-up package. Works only for eSIMs that support top-up (withTopUp = true). Canonical: https://docs.getroamify.com/esims/topup-flow/ This page explains the recommended end-to-end flow for topping up an existing eSIM. ## When to Use This Flow Use this flow when your customer already has an eSIM and you need to add more data by purchasing a compatible top-up package. > **Warning:** Top-up works only for eSIMs that support top-up (`withTopUp = true`). > **Warning:** If the eSIM profile is not generated yet, complete `/api/esim/apply` first. > For packages with `requirements.travelDate = true`, include `startDate` (ISO 8601) in the apply request. ## End-to-End Diagram ```mermaid graph TD; A[Authenticate with Bearer token] --> B[Get eSIM by ICCID: GET /api/esim?iccid=...]; B --> C{withTopUp = true?}; C -->|No| X[Stop: top-up not supported for this eSIM]; C -->|Yes| D[Get compatible top-ups: GET /api/esim/topup/packages?iccid=...]; D --> E[Select packageId]; E --> F[Create top-up order: POST /api/esim/order/topup]; F --> G[Check order status: GET /api/esim/order?orderId=...]; G --> H{Order completed?}; H -->|No| I[Retry status check with backoff]; I --> G; H -->|Yes| J[Verify usage: GET /api/esim/usage?iccid=...]; ``` ## Step-by-Step Flow ### Step 1: Verify eSIM eligibility Call `GET /api/esim?iccid=...` and confirm: - eSIM exists and is active for your use case. - `withTopUp` is `true`. ### Step 2: Find compatible top-up packages Call `GET /api/esim/topup/packages?iccid=...` to get available top-up options. Optional filters: - `days` - `gbs` - `isUnlimited` - `packageId` ### Step 3: Create top-up order Call `POST /api/esim/order/topup` with: - `iccid` - `packageId` - `referenceId` (optional) ### Step 4: Confirm order status Use `GET /api/esim/order?orderId=...` to verify the top-up order status. ### Step 5: Confirm updated usage Call `GET /api/esim/usage?iccid=...` to verify the expected balance/usage update. ## Error Handling Common status codes in this flow: - `400`: Invalid request payload or missing required fields. - `401`: Missing or invalid token. - `403`: Token is valid but not authorized for this action. - `500`: Internal server error. Retry with exponential backoff and monitor order status before repeating purchase calls. ## Related Endpoints - [eSIM API - Manage eSIMs](https://docs.getroamify.com/esims/esims/) - [eSIM API - Orders](https://docs.getroamify.com/esims/orders/) - [eSIM API Overview](https://docs.getroamify.com/esims/overview/) --- # eSIM API - Manage eSIMs > Manage eSIMs after ordering: use the esimId of each eSIM to generate an eSIM profile ready for your customer. Canonical: https://docs.getroamify.com/esims/esims/ ## Generate eSIM Profile After you placed an order for eSIMs, you will get a dedicated ID called `esimId` for each eSIM.You can use this ID to generate a eSIM profile and ready to be used by your customers. Alternatively, if you placed an order with `referenceId`, you can use this ID to bulk generate eSIM profiles for all eSIMs in the order. To apply an eSIM profile, you need to send a `POST` request to the `/api/esim/apply` endpoint. > **Warning:** If you created an order with `referenceId`, you can use the `referenceId` to generate eSIM profiles for all eSIMs in the order. > You can use the `esimId` to generate a single eSIM profile. > **Warning:** Due to server load, the eSIM profile generation can take up to 90 seconds. Please make sure the timeout of your request is set to at least 90 seconds. > **Warning:** `startDate` is required for packages where `requirements.travelDate` is `true`. Use ISO 8601 format (for example, `2026-07-01`). > For full endpoint details, see [Generate eSIM Profile](https://roamify.apidog.io/generate-esim-profile-12947632e0). > **Error:** If your request times out or failed with a error message stating: "eSIM is still pending processing", > please send a follow up request to /esim?esimId=YOUR_ESIM_ID to retrieve the eSIM. The request body should contain the following parameters: - `esimId`: The ID of the eSIM. This is a required field of type `string`. - `referenceId`: The reference ID of the eSIM order. This is an optional field of type `string`. If you do not provide a reference ID, the API will generate a single eSIM profile for the provided `esimId`. - `startDate`: Travel start date in ISO 8601 format. This is a conditional field required only when `requirements.travelDate` is `true`. ### Usage - generate eSIM profile Here is an example of how you can apply an eSIM profile using Roamify eSIM API. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/esim/apply' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "esimId": "eSIM ID" }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/esim/apply', { esimId: 'eSIM ID' }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response with esimId ```json { "status": "success", "data": { "id": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "iccid": "894000000000029365", "status": "READY", "activation": "first-use", "withTopUp": true, "withHotspot": true, "withDataRoaming": true, "withDestinationInstall": false, "withUsageCheck": true, "esim": { "lpaCode": "LPA:1$lpa.roamify.com$TEST", "smdpAddress": "lpa.roamify.com", "activationCode": "TEST", "qrCodeUrl": "https://imagedelivery.net/uUE6LNs_ci8SFBH4_n66dQ/partners/b06036c5-eadc-4f88-aa31-8c207fec91ec/iccid/public", "pin": "", "puk": "", "number": "", "iosQuickInstall": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.roamify.com$TEST", "androidQuickInstall": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.roamify.com$TEST" }, "apn": { "apnAutomatic": true, "apnName": "", "apnUsername": "", "apnPassword": "" }, "data": { "isUnlimited": false }, "call": { "withCall": false, "callType": "all" }, "sms": { "withSMS": false, "smsType": "all" }, "notes": [], "createdAt": 1731837539, "updatedAt": 1731837987, "expiredAt": 1763373985 } } ``` ### Sample Response with referenceId (array of eSIMs) ```json { "status": "success", "data": [ { "id": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "iccid": "894000000000029365", "status": "READY", "activation": "first-use", "withTopUp": true, "withHotspot": true, "withDataRoaming": true, "withDestinationInstall": false, "withUsageCheck": true, "esim": { "lpaCode": "LPA:1$lpa.roamify.com$TEST", "smdpAddress": "lpa.roamify.com", "activationCode": "TEST", "qrCodeUrl": "https://imagedelivery.net/uUE6LNs_ci8SFBH4_n66dQ/partners/b06036c5-eadc-4f88-aa31-8c207fec91ec/iccid/public", "pin": "", "puk": "", "number": "", "iosQuickInstall": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.roamify.com$TEST", "androidQuickInstall": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.roamify.com$TEST" }, "apn": { "apnAutomatic": true, "apnName": "", "apnUsername": "", "apnPassword": "" }, "data": { "isUnlimited": false }, "call": { "withCall": false, "callType": "all" }, "sms": { "withSMS": false, "smsType": "all" }, "notes": [], "createdAt": 1731837539, "updatedAt": 1731837987, "expiredAt": 1763373985 } ] } ``` ## Get eSIM Usage To get the usage details of an eSIM, you need to send a `GET` request to the `/api/esim/usage` endpoint. > **Warning:** Watch out property "withUsageCheck", if it is set to false, usage check won't be supported, find instructions in the notes property The parameters required for this endpoint are: - `iccid`: The ICCID of the eSIM. This is a required field of type `string`. ### Usage - fetch eSIM usage Here is an example of how you can get the usage details of an eSIM using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/usage?iccid=ICCID' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/usage?iccid=ICCID', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "id": "xxxxxxx-xxxxxx-xxxxxx-xxxxxx", "iccid": "894000000000029365", "status": "INSTALLED", "startedAt": 1732442794, "expiredAt": 1732442794, "allowedData": 1024, "remainingData": 924, "allowedSms": 0, "remainingSms": 0, "allowedVoice": 0, "remainingVoice": 0 } } ``` ## Get eSIM Usage Details To get the usage details of an eSIM, you need to send a `GET` request to the `/api/esim/usage/details` endpoint. > **Warning:** Watch out property "withUsageCheck", if it is set to false, usage check won't be supported, find instructions in the notes property The parameters required for this endpoint are: - `iccid`: The ICCID of the eSIM. This is a required field of type `string`. ### Usage - fetch eSIM usage Here is an example of how you can get the usage details of an eSIM using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/usage/details?iccid=ICCID' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/usage/details?iccid=ICCID', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "id": "xxxxxxx-xxxxxx-xxxxxx-xxxxxx", "iccid": "894000000000029365", "status": "INSTALLED", "startedAt": 1732442794, "expiredAt": 1732442794, "allowedData": 1024, "remainingData": 924, "allowedSms": 0, "remainingSms": 0, "allowedVoice": 0, "remainingVoice": 0, "dataUsages": [ { "date": "20250102", "usage": 10000, "location": "United Kingdom" }, { "date": "20250103", "usage": 20000, "location": "United Kingdom" } ] } } ``` ## Get eSIM Compatible Top-ups To get a list of compatible top-ups for an eSIM, you need to send a `GET` request to the `/api/esim/topup/packages` endpoint. The parameters required for this endpoint are: - `iccid`: The ICCID of the eSIM. This is a required field of type `string`. - `days`: The number of days for which the eSIM package is valid. This is an optional field of type `number`. - `gbs`: The amount of data in GB for which the eSIM package is valid. This is an optional field of type `number`. - `isUnlimited`: The flag to indicate if the eSIM package is unlimited. This is an optional field of type `boolean`. - `packageId`: The ID of the eSIM package. This is an optional field of type `string`. ### Usage - fetch compatible top-ups Here is an example of how you can get a list of compatible top-ups for an eSIM using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/topup/packages?iccid=ICCID' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/topup/packages?iccid=ICCID', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": [ { "packageId": "esim-cambodia-7days-1gb-all", "package": "1 GB - 7 Days", "plan": "data-only", "activation": "first-use", "day": 7, "price": 5, "withHotspot": true, "withDataRoaming": true, "withDestinationInstall": false, "withUsageCheck": true, "apn": { "apnAutomatic": true, "apnName": "globaldata", "apnUsername": "", "apnPassword": "" }, "data": { "isUnlimited": false, "dataAmount": 1024, "dataUnit": "MB" }, "call": { "withCall": false, "callType": "all", "callAmount": -1, "callUnit": "" }, "sms": { "withSMS": false, "smsType": "all", "smsAmount": -1, "smsUnit": "" }, "notes": [] } ] } ``` ## Get eSIM Packages History To get a history of eSIM packages applied to a eSIM, you need to send a `GET` request to the `/api/esim/history` endpoint. The parameters required for this endpoint are: - `iccid`: The ICCID of the eSIM. This is a required field of type `string`. ### Usage - fetch eSIM packages history Here is an example of how you can get a history of eSIM packages applied to an eSIM using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/history?iccid=ICCID' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/history?iccid=ICCID', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": [ { "esimId": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "packageId": "esim-cambodia-7days-1gb-all", "package": "1 GB - 7 Days", "type": "esim", "price": 5, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539 } ] } ``` ## Get eSIM By ICCID To get an eSIM, you need to send a `GET` request to the `/api/esim` endpoint. > **Warning:** The `/api/esim` endpoint requires either `iccid` or `esimId`, but not both. The request body should contain the following parameters: - `iccid`: The ICCID of the eSIM. This is a required field of type `string`. ### Usage - fetch an eSIM Here is an example of how you can get an eSIM using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim?iccid=ICCID' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim?iccid=ICCID', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "id": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "iccid": "894000000000029365", "status": "READY", "activation": "first-use", "withTopUp": true, "withHotspot": true, "withDataRoaming": true, "withDestinationInstall": false, "withUsageCheck": true, "esim": { "lpaCode": "LPA:1$lpa.roamify.com$TEST", "smdpAddress": "lpa.roamify.com", "activationCode": "TEST", "qrCodeUrl": "https://imagedelivery.net/uUE6LNs_ci8SFBH4_n66dQ/partners/b06036c5-eadc-4f88-aa31-8c207fec91ec/iccid/public", "pin": "", "puk": "", "number": "", "iosQuickInstall": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.roamify.com$TEST", "androidQuickInstall": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.roamify.com$TEST" }, "apn": { "apnAutomatic": true, "apnName": "", "apnUsername": "", "apnPassword": "" }, "data": { "isUnlimited": false }, "call": { "withCall": false, "callType": "all" }, "sms": { "withSMS": false, "smsType": "all" }, "notes": [], "createdAt": 1731837539, "updatedAt": 1731838043, "expiredAt": 1732442794 } } ``` ## Get eSIM By eSIMID Sometimes the generate eSIM profile request may fail due to server load. In such cases, you can use the eSIM ID to retrieve the eSIM details immediately after the previous request. The request body should contain the following parameters: - `esimId`: The ID of the eSIM. This is a required field of type `string`. ### Usage - fetch an eSIM by eSIM ID Here is an example of how you can get an eSIM using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim?esimId=YOUR eSIMID' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim?esimId=YOUR eSIMID', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "id": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "iccid": "894000000000029365", "status": "READY", "activation": "first-use", "withTopUp": true, "withHotspot": true, "withDataRoaming": true, "withDestinationInstall": false, "withUsageCheck": true, "esim": { "lpaCode": "LPA:1$lpa.roamify.com$TEST", "smdpAddress": "lpa.roamify.com", "activationCode": "TEST", "qrCodeUrl": "https://imagedelivery.net/uUE6LNs_ci8SFBH4_n66dQ/partners/b06036c5-eadc-4f88-aa31-8c207fec91ec/iccid/public", "pin": "", "puk": "", "number": "", "iosQuickInstall": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.roamify.com$TEST", "androidQuickInstall": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.roamify.com$TEST" }, "apn": { "apnAutomatic": true, "apnName": "", "apnUsername": "", "apnPassword": "" }, "data": { "isUnlimited": false }, "call": { "withCall": false, "callType": "all" }, "sms": { "withSMS": false, "smsType": "all" }, "notes": [], "createdAt": 1731837539, "updatedAt": 1731838043, "expiredAt": 1732442794 } } ``` ## Send SMS To send an SMS message to an eSIM, you need to send a `POST` request to the `/api/esim/sms` endpoint. > **Warning:** Both `iccid` and `message` are required in the request body. The request body should contain the following parameters: - `iccid`: The ICCID of the eSIM. This is a required field of type `string`. - `message`: The message content to send. This is a required field of type `string`. ### Usage - send SMS Here is an example of how you can send an SMS to an eSIM using Roamify eSIM API. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/esim/sms' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "iccid": "871003213213123131", "message": "Hello Customer!" }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/esim/sms', { iccid: '871003213213123131', message: 'Hello Customer!' }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ## Get eSIMs List To get a list of eSIMs, you need to send a `GET` request to the `/api/esims` endpoint. There are no required parameters for this endpoint, but you can provide the following optional parameters: - `status`: The status of the eSIM orders. This is an optional field of type `string`. It can be any of the following values: - `NEW`: New eSIM, not yet processed - `READY`: eSIM is ready to be downloaded - `INSTALLED`: eSIM has been installed - `REVOKED`: eSIM has been revoked - `EXPIRED`: eSIM has expired - `UNKNOWN`: Unknown status - `limit`: The number of eSIM orders to return. This is an optional field of type `number`. The default value is `50`. - `referenceId`: The reference ID of the eSIM. This is an optional field of type `string`. It can be used to filter the eSIMs by reference ID. ### Usage - fetch a list of eSIMs Here is an example of how you can get a list of eSIMs using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esims' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esims', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": [ { "id": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "iccid": "894000000000029365", "status": "READY", "activation": "first-use", "withTopUp": true, "withHotspot": true, "withDataRoaming": true, "withDestinationInstall": false, "withUsageCheck": true, "esim": { "lpaCode": "LPA:1$lpa.roamify.com$TEST", "smdpAddress": "lpa.roamify.com", "activationCode": "TEST", "qrCodeUrl": "https://imagedelivery.net/uUE6LNs_ci8SFBH4_n66dQ/partners/b06036c5-eadc-4f88-aa31-8c207fec91ec/iccid/public", "pin": "", "puk": "", "number": "", "iosQuickInstall": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.roamify.com$TEST", "androidQuickInstall": "https://esimsetup.android.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.roamify.com$TEST" }, "apn": { "apnAutomatic": true, "apnName": "", "apnUsername": "", "apnPassword": "" }, "data": { "isUnlimited": false }, "call": { "withCall": false, "callType": "all" }, "sms": { "withSMS": false, "smsType": "all" }, "notes": [], "createdAt": 1731837539, "updatedAt": 1731838043, "expiredAt": 1732442794 } ] } ``` --- # eSIMs Link API - New way to distribute/share eSIM with your customers > After you have created an eSIM profile, you can generate a link to share with your customers. This link will allow them to install the eSIM profile on their devices. Canonical: https://docs.getroamify.com/esims/esims-link/ ## Generate eSIMs Link After you have created an eSIM profile, you can generate a link to share with your customers. This link will allow them to install the eSIM profile on their devices. To generate an eSIM link, you will need to send a `POST` request to the `/api/esim/link` endpoint. The request body should contain the either the following parameters: - `iccid`: The ICCID of the eSIM. This is a required field of type `string`. - `esimId`: The ID of the eSIM. This is a required field of type `string`, no need to provide if you included the `iccid`. ### Usage - Generate eSIM Link Here is an example of how you can generate an eSIM link using `curl` or `javascript`. - Sample eSIMs Link URL: [eSIMs Link URL](https://dev.esims.link/acme/4OYONUH79IW1) - Sample Access Code: `5897` - Sample eSIMs Link WhatsApp Message URL: [WhatsApp Message URL](https://api.whatsapp.com/send?text=Check%20out%20your%20eSIM%3A%20https%3A%2F%2Fdev.esims.link%2Facme%2F4OYONUH79IW1%20with%20access%20code%3A%205897) **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/esim/link' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "iccid": "iccid here" }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/esim/link', { iccid: 'iccid here' }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response with ICCID ```json { "status": "success", "data": { "iccid": "8935306745498905", "esimId": "f72d9b90-52b3-47d9-95f6-2cf61ab176aa", "link": "https://dev.esims.link/acme/4OYONUH79IW1", "whatsappLink": "https://api.whatsapp.com/send?text=Check%20out%20your%20eSIM%3A%20https%3A%2F%2Fdev.esims.link%2Facme%2F4OYONUH79IW1%20with%20access%20code%3A%205897", "key": "4OYONUH79IW1", "accessCode": "5897", "createdAt": 1767812581, "updatedAt": 1767812581 } } ``` --- # eSIM API - Vouchers > Mint batches of redeemable voucher codes that each hand one eSIM to whoever redeems them. Available to selected partners, enabled per API key. Canonical: https://docs.getroamify.com/esims/vouchers/ > **Warning:** **Selected partners only.** The voucher endpoints are not part of the standard partner surface. They are enabled per API key, together with an allow-list of the exact packages that key may mint vouchers for. If the feature is not enabled on your key, every endpoint on this page returns `403`. Contact [support@getroamify.com](mailto:support@getroamify.com) to have it enabled. ## How Vouchers Work A voucher is a redeemable code that hands one eSIM to whoever holds it. Unlike [Orders](https://docs.getroamify.com/esims/orders/), a voucher is not tied to a traveller at creation time — you mint a batch, distribute the codes however you like (gift cards, campaigns, hotel check-in, support goodwill), and the eSIM is claimed later by the person who redeems it. Every voucher is backed by a real eSIM bundle that is created up front, so a batch is capped and is not free to mint. The voucher carries a `code` and a `redeemUrl` of the form `https://www.getroamify.com/voucher/{code}`. Give your customer either one — the code alone if they will type it, the URL if you are sending a link. ```mermaid graph TD; A[Step 1: Create vouchers for an allowed packageId] --> B[Step 2: Distribute code or redeemUrl]; B --> C[Step 3: Customer redeems on the Roamify website]; C --> D[Voucher status becomes REDEEMED]; A --> E[Track the batch with GET /api/esim/vouchers]; E --> F[Revoke an unredeemed voucher if needed]; ``` ## Access Requirements Two separate checks run on every voucher request, both driven by your API key: | Check | Failure | | --- | --- | | The voucher feature is enabled on the API key | `403` - Voucher creation is not enabled for this API key | | The `packageId` is on the key's allow-list | `403` - This packageId is not allowed for voucher creation with this API key | The allow-list is an **allowlist**, not a filter: a key with no configured packages can mint nothing. The packages your key is allowed to use are agreed with Roamify when the feature is switched on — the API does not expose the list, so keep it on your side alongside your key. > **Note:** Only [Create Vouchers](#create-vouchers) checks the package allow-list. The read endpoints and [Revoke Voucher](#revoke-voucher) only require the voucher feature to be enabled. ## Create Vouchers To mint a batch of vouchers, you need to send a `POST` request to the `/api/esim/vouchers` endpoint. You are required to provide the following parameters in the request body: - `packageId`: The ID of the eSIM package to mint vouchers for. This is a required field of type `string`. It must be one of the packages your API key is allowed to mint. - `quantity`: How many vouchers to mint. This is an optional field of type `number`. It must be a whole number between `1` and `10`. The default value is `1`. - `referenceId`: Your own identifier for this batch. This is an optional field of type `string`. It must not have been used for a previous batch. - `notes`: A free-text note stored on every voucher in the batch. This is an optional field of type `string`, and may be an empty string. > **Warning:** **Always send a `referenceId`.** Creating vouchers is not idempotent on its own — a retried request with no `referenceId` mints a whole second batch. The `referenceId` is what makes a retry safe: a repeat of a batch that already landed is rejected with `409`, and the vouchers that were created can be fetched with [`GET /api/esim/vouchers?referenceId=...`](#get-vouchers). ### Usage - create a batch of vouchers Here is an example of how you can create vouchers using Roamify eSIM API. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/esim/vouchers' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "packageId": "esim-global-one-year-365days-0gb-all", "quantity": 2, "referenceId": "campaign-2026-03", "notes": "Spring campaign giveaway" }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/esim/vouchers', { packageId: 'esim-global-one-year-365days-0gb-all', quantity: 2, referenceId: 'campaign-2026-03', notes: 'Spring campaign giveaway' }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response A successful request returns `201 Created`. ```json { "status": "success", "data": { "referenceId": "campaign-2026-03", "requestedQuantity": 2, "quantity": 2, "vouchers": [ { "id": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "code": "R7K2M9QX4B1D", "redeemUrl": "https://www.getroamify.com/voucher/R7K2M9QX4B1D", "status": "ACTIVE", "referenceId": "campaign-2026-03", "packageId": "esim-global-one-year-365days-0gb-all", "package": "1 GB - 365 Days", "destination": "Global", "price": 9, "currency": "USD", "esimId": "f72d9b90-52b3-47d9-95f6-2cf61ab176aa", "orderId": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "notes": "Spring campaign giveaway", "redeemedAt": null, "createdAt": 1731837535, "updatedAt": 1731837535 }, { "id": "3f9c1d42-6a77-4a0e-9d19-9d4c1c2f77ab", "code": "T4W8ZP2N6C5H", "redeemUrl": "https://www.getroamify.com/voucher/T4W8ZP2N6C5H", "status": "ACTIVE", "referenceId": "campaign-2026-03", "packageId": "esim-global-one-year-365days-0gb-all", "package": "1 GB - 365 Days", "destination": "Global", "price": 9, "currency": "USD", "esimId": "0c1a7b55-9f34-4b62-9a4e-2b7e5d0c9a11", "orderId": "a1d2f3c4-5b6e-4f70-8a9b-0c1d2e3f4a5b", "notes": "Spring campaign giveaway", "redeemedAt": null, "createdAt": 1731837535, "updatedAt": 1731837535 } ] } } ``` ### Response fields - `referenceId`: The reference ID you sent, or an empty string if you did not send one. - `requestedQuantity`: How many vouchers you asked for. - `quantity`: How many vouchers were actually created. - `vouchers`: The vouchers that were created. See [The Voucher Object](#the-voucher-object). > **Warning:** A bulk mint spans several eSIM orders, so `quantity` can come back lower than `requestedQuantity` if the batch stopped part-way. Compare the two on every response. Do not retry the same call to make up the difference — the vouchers that did land are already yours, and are retrievable by `referenceId`. Mint the shortfall as a new batch with a new `referenceId`. ### Errors | HTTP status | Message | | --- | --- | | `400` | "packageId" is required | | `400` | "quantity" must be a number | | `400` | "quantity" must be greater than or equal to 1 | | `400` | "quantity" must be less than or equal to 10 | | `400` | Invalid quantity, please provide a whole number of at least 1 | | `401` | Unauthorized, please make sure your have provided the correct api key | | `403` | Voucher creation is not enabled for this API key, please contact support to have it enabled | | `403` | This packageId is not allowed for voucher creation with this API key, please use one of the packages allowed for your account | | `409` | Vouchers with the same referenceId already exist, please make sure you have provided a unique referenceId | | `500` | Failed to create vouchers, please try again later | | `503` | Vouchers cannot be issued at the moment, please retry shortly or contact support | ## Get Vouchers To get a list of your vouchers, you need to send a `GET` request to the `/api/esim/vouchers` endpoint. There are no required parameters for this endpoint, but you can provide the following optional parameters: - `referenceId`: Return only the vouchers minted under this reference ID. This is an optional field of type `string`. - `status`: Return only the vouchers in this status. This is an optional field of type `string`. It must be one of `ACTIVE`, `REDEEMED`, or `CANCELLED`, in uppercase. See [Voucher Statuses](#voucher-statuses). - `packageId`: Return only the vouchers for this package. This is an optional field of type `string`. - `limit`: The maximum number of vouchers to return. This is an optional field of type `number`, and must be a whole number of at least `1`. Vouchers are returned newest first, sorted by `createdAt` descending. `limit` is applied after sorting, so it returns the most recent N vouchers. Omitting `limit` returns every matching voucher. > **Note:** This endpoint is not paginated. If you mint large volumes, filter by `referenceId` per batch rather than listing everything. ### Usage - fetch a list of vouchers **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/vouchers?referenceId=campaign-2026-03&status=ACTIVE&limit=10' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/vouchers', { params: { referenceId: 'campaign-2026-03', status: 'ACTIVE', limit: 10 }, headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "referenceId": "campaign-2026-03", "vouchers": [ { "id": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "code": "R7K2M9QX4B1D", "redeemUrl": "https://www.getroamify.com/voucher/R7K2M9QX4B1D", "status": "ACTIVE", "referenceId": "campaign-2026-03", "packageId": "esim-global-one-year-365days-0gb-all", "package": "1 GB - 365 Days", "destination": "Global", "price": 9, "currency": "USD", "esimId": "f72d9b90-52b3-47d9-95f6-2cf61ab176aa", "orderId": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "notes": "Spring campaign giveaway", "redeemedAt": null, "createdAt": 1731837535, "updatedAt": 1731837535 } ] } } ``` `data.referenceId` echoes back the `referenceId` you filtered on, or an empty string if you did not filter by one. ### Errors | HTTP status | Message | | --- | --- | | `400` | "status" must be one of [ACTIVE, REDEEMED, CANCELLED] | | `400` | "limit" must be greater than or equal to 1 | | `401` | Unauthorized, please make sure your have provided the correct api key | | `403` | Voucher creation is not enabled for this API key, please contact support to have it enabled | | `500` | Failed to get vouchers, please try again later | ## Get Voucher To get a single voucher by its ID, you need to send a `GET` request to the `/api/esim/voucher/{voucherId}` endpoint. You are required to provide the following path parameter: - `voucherId`: The ID of the voucher, as returned in the voucher's `id` field. This is a required field of type `string`. ### Usage - fetch a voucher by ID **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/voucher/b06036c5-eadc-4f88-aa31-8c207fec91ec' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); const voucherId = 'b06036c5-eadc-4f88-aa31-8c207fec91ec'; axios.get(`https://api-dev.getroamify.com/api/esim/voucher/${voucherId}`, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "id": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "code": "R7K2M9QX4B1D", "redeemUrl": "https://www.getroamify.com/voucher/R7K2M9QX4B1D", "status": "REDEEMED", "referenceId": "campaign-2026-03", "packageId": "esim-global-one-year-365days-0gb-all", "package": "1 GB - 365 Days", "destination": "Global", "price": 9, "currency": "USD", "esimId": "f72d9b90-52b3-47d9-95f6-2cf61ab176aa", "orderId": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "notes": "Spring campaign giveaway", "redeemedAt": 1731999000, "createdAt": 1731837535, "updatedAt": 1731999000 } } ``` ### Errors | HTTP status | Message | | --- | --- | | `401` | Unauthorized, please make sure your have provided the correct api key | | `403` | Voucher creation is not enabled for this API key, please contact support to have it enabled | | `404` | Voucher not found, please make sure you have provided the correct voucher ID or code | | `500` | Failed to get vouchers, please try again later | > **Note:** A voucher belonging to another partner is reported as `404`, not `403`. ## Get Voucher by Code To look a voucher up by the code you handed out, you need to send a `GET` request to the `/api/esim/voucher/code/{code}` endpoint. This is the endpoint to call when a customer contacts you holding only a code. You are required to provide the following path parameter: - `code`: The voucher code. This is a required field of type `string`. The code is normalised before it is matched: it is trimmed, upper-cased, and every character that is not a letter or digit is stripped. So `r7k2-m9qx-4b1d` and `R7K2M9QX4B1D` both resolve to the same voucher. After normalisation the code must be exactly **12 alphanumeric characters** — anything else is reported as `404`. ### Usage - fetch a voucher by code **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/voucher/code/R7K2M9QX4B1D' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); const code = 'R7K2M9QX4B1D'; axios.get(`https://api-dev.getroamify.com/api/esim/voucher/code/${code}`, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "id": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "code": "R7K2M9QX4B1D", "redeemUrl": "https://www.getroamify.com/voucher/R7K2M9QX4B1D", "status": "ACTIVE", "referenceId": "campaign-2026-03", "packageId": "esim-global-one-year-365days-0gb-all", "package": "1 GB - 365 Days", "destination": "Global", "price": 9, "currency": "USD", "esimId": "f72d9b90-52b3-47d9-95f6-2cf61ab176aa", "orderId": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "notes": "Spring campaign giveaway", "redeemedAt": null, "createdAt": 1731837535, "updatedAt": 1731837535 } } ``` ### Errors | HTTP status | Message | | --- | --- | | `401` | Unauthorized, please make sure your have provided the correct api key | | `403` | Voucher creation is not enabled for this API key, please contact support to have it enabled | | `404` | Voucher not found, please make sure you have provided the correct voucher ID or code | | `500` | Failed to get vouchers, please try again later | ## Revoke Voucher To cancel a voucher and the eSIM behind it, you need to send a `PUT` request to the `/api/esim/voucher/{voucherId}/revoke` endpoint. Use it for a code that was distributed by mistake, leaked, or is no longer wanted. You are required to provide the following path parameter: - `voucherId`: The ID of the voucher to revoke. This is a required field of type `string`. No request body is required. > **Warning:** Revoking is permanent and cancels the underlying eSIM as well as the code. Only `ACTIVE` vouchers can be revoked — a voucher a customer has already redeemed cannot be taken back, and returns `409`. ### Usage - revoke a voucher **curl** ```bash curl --request PUT \ --url 'https://api-dev.getroamify.com/api/esim/voucher/b06036c5-eadc-4f88-aa31-8c207fec91ec/revoke' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); const voucherId = 'b06036c5-eadc-4f88-aa31-8c207fec91ec'; axios.put(`https://api-dev.getroamify.com/api/esim/voucher/${voucherId}/revoke`, {}, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "id": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "status": "CANCELLED" } } ``` ### Errors | HTTP status | Message | | --- | --- | | `401` | Unauthorized, please make sure your have provided the correct api key | | `403` | Voucher creation is not enabled for this API key, please contact support to have it enabled | | `404` | Voucher not found, please make sure you have provided the correct voucher ID or code | | `409` | Voucher has already been redeemed and can no longer be revoked | | `409` | Only active vouchers can be revoked, this voucher is no longer active | | `500` | Failed to revoke voucher, please try again later | ## The Voucher Object Every voucher endpoint returns the same voucher shape. | Field | Type | Description | | --- | --- | --- | | `id` | `string` | The ID of the voucher. Use it with [Get Voucher](#get-voucher) and [Revoke Voucher](#revoke-voucher). | | `code` | `string` | The 12-character redemption code. This is what you hand to your customer. | | `redeemUrl` | `string` | The redemption link, `https://www.getroamify.com/voucher/{code}`. | | `status` | `string` | The voucher's status. See [Voucher Statuses](#voucher-statuses). | | `referenceId` | `string` | The reference ID of the batch this voucher was minted in, or an empty string. | | `packageId` | `string` | The ID of the eSIM package the voucher is worth. | | `package` | `string` | The display name of the package, for example `1 GB - 365 Days`. | | `destination` | `string` | The country or region the package covers. | | `price` | `number` | The price of the package. | | `currency` | `string` | The currency of `price`. Always `USD`. | | `esimId` | `string` | The ID of the eSIM backing this voucher. | | `orderId` | `string` | The ID of the order the eSIM was created under. | | `notes` | `string` | The note you stored on the batch, or an empty string. | | `redeemedAt` | `number \| null` | The Unix timestamp of redemption, or `null` if the voucher has not been redeemed. | | `createdAt` | `number` | The Unix timestamp of when the voucher was created. | | `updatedAt` | `number` | The Unix timestamp of when the voucher was last updated. | ## Voucher Statuses A voucher's `status` can be any of the following values: | Status | Meaning | | --- | --- | | `ACTIVE` | Minted and not yet claimed. The code can be redeemed, and the voucher can be revoked. | | `REDEEMED` | A customer has claimed the voucher. `redeemedAt` is set, and the voucher can no longer be revoked. | | `CANCELLED` | The voucher was revoked. The code and the eSIM behind it are dead. | --- # eSIM API - Refunds > Create, bulk-create, get, list and cancel refund requests for eSIMs on your orders. A refund happens only after Roamify support approves the request. Canonical: https://docs.getroamify.com/esims/refunds/ ## How Refund Requests Work A refund request asks Roamify support to refund one eSIM of one of your orders. **You submit a request, Roamify support reviews it, and the refund happens only after support approves it.** Submitting a request never moves money by itself, and it does not revoke the eSIM. ```mermaid graph TD; A[Step 1: You submit a request by iccid or esimId] --> B[PENDING]; B --> C{Roamify support reviews}; C -->|Approved| D[PROCESSING]; D --> E[REFUNDED: the eSIM is deactivated and the refund is paid]; C -->|Rejected| F[REJECTED: with a reason]; B -->|You cancel| G[CANCELLED]; ``` - Every request is decided by Roamify support. A request for an eSIM that was already installed or has used data is accepted and reviewed, not refused up front. - The only request refused immediately is one for an eSIM that was **already refunded** (`422`). - An eSIM can have **one open request at a time**. A second request while one is `PENDING` or `PROCESSING` is rejected with `409` and the `refundId` of the open request. - The refund is paid back the way the order was paid. For an order paid with your balance, the amount is credited back to your balance. - In the partner portal, only team members with the **ADMIN** or **OPERATION** role can submit and cancel requests. Other roles can only track them. API requests are made with your API key. > **Note:** Track a request with [Get a Refund Request](#get-a-refund-request), or configure a webhook URL to receive a [Refund event](https://docs.getroamify.com/webhooks/types/refund/) when a request is refunded or rejected. ## Request a Refund in the Partner Portal You can also file and track requests in the Roamify partner portal. It uses the same rules as the API. - **Inventory** (unassigned bundles): choose **Request refund** on a bundle. A side sheet opens with the eSIM ID already filled in. Pick a reason, add notes if you like, and submit. - **eSIMs**: open **View eSIM** on an eSIM and choose **Request refund**. The sheet opens with the ICCID already filled in. Pick a reason and submit. - **Refund Requests** page: lists every request with its status, lets you search by ICCID, cancel a pending request and open its details. **New refund request** lets you submit up to 50 ICCIDs at once, or one eSIM ID for an eSIM that has no ICCID yet. The reason is required. When the reason is `OTHER`, the notes are required too (at least 10 characters). ## Create a Refund Request To request a refund for one eSIM, send a `POST` request to the `/api/refund` endpoint. You are required to provide the following parameters in the request body: - `productType`: What is refunded. This is a required field of type `string`. Only `esim` is supported for now. Any other value is answered with `422` `REFUND_PRODUCT_TYPE_NOT_SUPPORTED`. - `iccid` or `esimId`: Which eSIM. Provide **exactly one** of them, both of type `string`. Use `iccid` for an eSIM that was redeemed. Use `esimId` for an eSIM that has no ICCID yet, such as a bundle that was never assigned. - `reason`: Why you are asking for the refund. This is a required field of type `string`. See [Refund Reasons](#refund-reasons). - `notes`: Free text for the support team. This is an optional field of type `string` of up to 1000 characters. It is required, with at least 10 characters, when `reason` is `OTHER`. ### Usage - request a refund **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/refund' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "productType": "esim", "iccid": "8935303411619613", "reason": "UNABLE_TO_INSTALL", "notes": "The customer could not scan the QR code on an iPhone 12." }' ``` **javascript** ```javascript const response = await fetch('https://api-dev.getroamify.com/api/refund', { method: 'POST', headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json', }, body: JSON.stringify({ productType: 'esim', iccid: '8935303411619613', reason: 'UNABLE_TO_INSTALL', notes: 'The customer could not scan the QR code on an iPhone 12.', }), }); const refund = await response.json(); ``` ### Response A new request answers `201` with the [refund request object](#the-refund-request-object) and a `tracking` object that tells you where to poll it. ```json { "refundId": "9f3c1c0e-6a52-4b8e-9d0a-0d3f1e2a7c11", "status": "PENDING", "productType": "esim", "item": { "esimId": "74b73655-0fa7-49e4-bab6-eb80b5522104", "iccid": "8935303411619613", "orderId": "ee9357c2-0000-0000-0000-000000000000", "countryName": "Bahrain", "optionName": "5 GB - 3 Days" }, "reason": "UNABLE_TO_INSTALL", "notes": "The customer could not scan the QR code on an iPhone 12.", "amount": null, "currency": null, "balanceCreditUsd": null, "transaction": null, "rejectionReason": null, "rejectionNote": null, "createdAt": 1762570000, "updatedAt": 1762570000, "processedAt": null, "cancelledAt": null, "history": [ { "status": "PENDING", "at": 1762570000 } ], "tracking": { "method": "GET", "path": "/api/refund?refundId=9f3c1c0e-6a52-4b8e-9d0a-0d3f1e2a7c11" } } ``` ## Create Refund Requests in Bulk To request refunds for several eSIMs in one call, send a `POST` request to the `/api/refund/bulk` endpoint. - `productType`: As above. It applies to every entry. - `refunds`: An array of between `1` and `20` entries. Each entry has the same `iccid` or `esimId` (exactly one), `reason` and `notes` fields as a single request. > **Warning:** A bad entry fails the **whole call** with `400`, and nothing is created. Fix the entry and send the call again. Entries that are valid but refused individually (for example an eSIM that already has an open request) are reported in the results instead. ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/refund/bulk' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "productType": "esim", "refunds": [ { "iccid": "8935303411619613", "reason": "NO_CONNECTIVITY" }, { "esimId": "74b73655-0fa7-49e4-bab6-eb80b5522104", "reason": "OTHER", "notes": "Customer cancelled the trip." } ] }' ``` The call answers `200` with one result per entry, in the order you sent them: ```json { "summary": { "requested": 2, "created": 1, "failed": 1, "duplicates": 0 }, "results": [ { "index": 0, "iccid": "8935303411619613", "status": "CREATED", "refund": { "refundId": "9f3c1c0e-6a52-4b8e-9d0a-0d3f1e2a7c11", "status": "PENDING" } }, { "index": 1, "esimId": "74b73655-0fa7-49e4-bab6-eb80b5522104", "status": "FAILED", "error": { "code": "REFUND_REQUEST_ALREADY_EXISTS", "message": "A refund request is already open for this eSIM", "refundId": "2b1d7e40-0000-0000-0000-000000000000" } } ] } ``` Each result has a `status` of: - `CREATED`: the request was created. `refund` holds it. - `FAILED`: the request was refused. `error` holds the `code` and `message`, plus `reasons` (for `REFUND_NOT_ELIGIBLE`) or the open `refundId` (for `REFUND_REQUEST_ALREADY_EXISTS`). - `DUPLICATE`: the same `iccid` or `esimId` appeared earlier in the call. Only the first is created, and `duplicateOf` is the index of the entry that was kept. ## Get a Refund Request To get one request, send a `GET` request to the `/api/refund` endpoint with **exactly one** of these query parameters: - `refundId`: the request. - `esimId`: the newest request for that eSIM. - `iccid`: the newest request for that ICCID. ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/refund?refundId=9f3c1c0e-6a52-4b8e-9d0a-0d3f1e2a7c11' \ --header 'Authorization: Bearer {{token}}' ``` It answers `200` with the [refund request object](#the-refund-request-object), or `404` `REFUND_REQUEST_NOT_FOUND`. ## List Refund Requests To list your requests, newest first, send a `GET` request to the `/api/refunds` endpoint. All query parameters are optional: - `status`: Only requests with this [status](#refund-statuses). - `iccid` or `esimId`: Only requests for that eSIM. Send at most one of them. - `limit`: Page size, from `1` to `100`. - `cursor`: The `nextCursor` of the previous page. ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/refunds?status=PENDING&limit=20' \ --header 'Authorization: Bearer {{token}}' ``` ```json { "items": [ { "refundId": "9f3c1c0e-6a52-4b8e-9d0a-0d3f1e2a7c11", "status": "PENDING" } ], "nextCursor": null } ``` When `nextCursor` is not `null`, send it back as `cursor` to get the next page. ## Cancel a Refund Request To withdraw a request, send a `PUT` request to `/api/refund/{refundId}/cancel`. Only a request that is still `PENDING` can be cancelled. Once support has acted on it, the call answers `409` `REFUND_INVALID_STATUS`. ```bash curl --request PUT \ --url 'https://api-dev.getroamify.com/api/refund/9f3c1c0e-6a52-4b8e-9d0a-0d3f1e2a7c11/cancel' \ --header 'Authorization: Bearer {{token}}' ``` It answers `200` with the cancelled request, whose `status` is `CANCELLED`. You can submit a new request for the same eSIM afterwards. ## The Refund Request Object | Field | Type | Description | | --- | --- | --- | | `refundId` | string | The ID of the request. | | `status` | string | See [Refund Statuses](#refund-statuses). | | `productType` | string | Always `esim` for now. | | `item` | object | The eSIM: `esimId`, `orderId`, and when known `iccid` (only once the eSIM was redeemed), `referenceId`, `countryName` and `optionName`. | | `reason` | string | See [Refund Reasons](#refund-reasons). | | `notes` | string | Your notes. | | `amount` | number \| null | The amount refunded, in `currency`. `null` until the request is `REFUNDED`. | | `currency` | string \| null | The currency of `amount`. | | `balanceCreditUsd` | number \| null | The amount credited to your balance in USD, for orders paid with your balance. | | `transaction` | object \| null | The refund of the payment: `id`, `paymentMethod`, `paymentStatus` and `refundedAt`. | | `rejectionReason` | string \| null | Why support rejected it: `DATA_USED`, `ESIM_ACTIVATED`, `OUTSIDE_REFUND_WINDOW`, `ESIM_EXPIRED`, `NETWORK_CONDITIONS_ONLY`, `INSUFFICIENT_INFORMATION`, `DUPLICATE_REQUEST` or `OTHER`. | | `rejectionNote` | string \| null | Support's explanation, when given. | | `createdAt`, `updatedAt`, `processedAt`, `cancelledAt` | number \| null | Unix timestamps in seconds. | | `history` | array | Every status the request went through, as `{ status, at }`. | ## Refund Reasons | Reason | Use it when | | --- | --- | | `UNABLE_TO_INSTALL` | The eSIM could not be installed or activated on the device. | | `NO_CONNECTIVITY` | The eSIM is installed but there is no data connection at the destination. | | `WRONG_PACKAGE` | The wrong destination or data plan was purchased. | | `DUPLICATE_PURCHASE` | The eSIM was purchased more than once by mistake. | | `TRIP_CANCELLED` | The trip was cancelled or changed. | | `DEVICE_NOT_COMPATIBLE` | The customer's device does not support eSIM or is locked. | | `CUSTOMER_REQUEST` | The customer changed their mind. | | `OTHER` | Anything else. `notes` of at least 10 characters are required. | ## Refund Statuses | Status | Meaning | | --- | --- | | `PENDING` | Submitted. Roamify support has not acted on it yet. You can cancel it. | | `PROCESSING` | Approved. The refund is being processed. | | `REFUNDED` | The refund is complete and the eSIM was deactivated. | | `REJECTED` | Support rejected the request. See `rejectionReason` and `rejectionNote`. | | `CANCELLED` | You cancelled the request before review. | ## Errors Every error has the form `{ "message": "...", "status": "failed", "code": "..." }`. | HTTP | `code` | Meaning | | --- | --- | --- | | `400` | `INVALID_PARAMETER` | A parameter is missing or invalid. For example both or neither of `iccid` and `esimId`, more than 20 bulk entries, or an invalid `limit`. | | `400` | `REFUND_INVALID_REASON` | `reason` is not one of the [supported reasons](#refund-reasons). | | `400` | `REFUND_NOTES_REQUIRED` | `reason` is `OTHER` without `notes` of at least 10 characters. | | `404` | `ESIM_NOT_FOUND` | No eSIM of your account has that `iccid` or `esimId`. | | `404` | `REFUND_REQUEST_NOT_FOUND` | No request matches the `refundId`, `esimId` or `iccid`. | | `409` | `REFUND_REQUEST_ALREADY_EXISTS` | The eSIM already has an open request. The body carries its `refundId`. | | `409` | `REFUND_INVALID_STATUS` | Only a `PENDING` request can be cancelled. | | `422` | `REFUND_NOT_ELIGIBLE` | The eSIM cannot be refunded. `reasons` lists why, for example `ESIM_ALREADY_REFUNDED`. | | `422` | `REFUND_PRODUCT_TYPE_NOT_SUPPORTED` | Only `esim` is supported. | | `500` | `REFUND_CREATE_FAILED`, `REFUND_GET_FAILED`, `REFUND_CANCEL_FAILED`, `REFUND_SERVICE_NOT_CONFIGURED` | A temporary failure. Try again later. | Authentication errors (`401`, `403`, `404`, `429`) carry a `code` as well. See [Authentication](https://docs.getroamify.com/authentication/). --- # eSIM API - Events > Retrieve the end-to-end lifecycle event history of an eSIM so you and your support team can see what happened to it. Canonical: https://docs.getroamify.com/esims/events/ ## eSIM Events This is a super handy feature to get eSIM lifecycle events end-to-end when applicable. It will help you and your support team understand the history of the eSIM with a series of events. ## Get eSIM Events To get the list of eSIM events, you need to send a `GET` request to the `/api/esim/event` endpoint. The query parameters should contain one of the following parameters: - `iccid`: The ICCID of the eSIM. - `esimId`: The esimId provided by Roamify. - `referenceId`: The referenceId provided by you when creating the order. > **Warning:** At least one of `iccid`, `esimId`, or `referenceId` is required. ### Usage - get eSIM events with ICCID Here is an example of how you can get eSIM events using ICCID **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/event?iccid={your iccid}' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/event?iccid={your_iccid}', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "iccid": "89851000000006262546", "esimId": "f6d83dcb-e7b5-45f7-8c1d-b2a07ebd49f4", "referenceId": "", "events": [ { "iccid": "89851000000006262546", "eid": "89049032007008882600092301372109", "status": "INACTIVE", "timeStamp": 1756528206, "dateTime": "2025-08-30 04:30:06" }, { "iccid": "89851000000006262546", "eid": "89049032007008882600092301372109", "status": "ACTIVATED", "timeStamp": 1756528208, "dateTime": "2025-08-30 04:30:08" }, { "iccid": "89851000000006262546", "eid": "89049032007008882600092301372109", "status": "INACTIVE", "timeStamp": 1756528211, "dateTime": "2025-08-30 04:30:11" }, { "iccid": "89851000000006262546", "eid": "89049032007008882600092301372109", "status": "ACTIVATED", "timeStamp": 1756528213, "dateTime": "2025-08-30 04:30:13" } ], "locations": [] } ``` --- # eSIM API - Compatible Devices > To get a list of compatible devices, you need to send a GET request to the /api/compatible-devices endpoint. Canonical: https://docs.getroamify.com/esims/compatible-devices/ ## eSIM Compatible Devices To get a list of compatible devices, you need to send a `GET` request to the `/api/compatible-devices` endpoint. There are no required parameters for this endpoint. ## Usage - fetch a list of eSIM compatible devices Here is an example of how you can get a list of compatible devices using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/compatible-devices' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/compatible-devices', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ## Sample Response ```json { "status": "success", "data": [ { "model": "Samsung Galaxy Note 20 / Note 20 Plus / Note 20 Ultra", "deviceType": "smartphone", "os": "android", "brand": "Samsung", "name": "Samsung Galaxy Note 20 series" }, { "model": "Rakuten BIG / Rakuten BIG-S", "deviceType": "smartphone", "os": "android", "brand": "Rakuten", "name": "Rakuten BIG series" } ] } ``` --- # eSIM API - Account Balance > To get the remaining balance of your funds, you need to send a GET request to the /api/balance endpoint. Canonical: https://docs.getroamify.com/esims/balance/ ## Account Balance To get the remaining balance of your funds, you need to send a `GET` request to the `/api/balance` endpoint. There are no required parameters for this endpoint. ## Usage - fetch account balance Here is an example of how you can get the remaining balance of your funds using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/balance' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/balance', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ## Sample Response ```json { "status": "success", "data": { "balance": 3723.35, "currency": "USD" } } ``` --- # Insurance API Overview > Overview of the Roamify Insurance API: browse insurance products, reserve policies through an order and issue each policy against a named traveller. Saudi Arabia citizens and residents only. Canonical: https://docs.getroamify.com/insurance/overview/ > **Warning:** **Eligibility: Saudi Arabia citizens and residents only.** Insurance packages may only be sold to travellers who are citizens or residents of Saudi Arabia. Roamify's request validation is deliberately looser than the provider's, so an ineligible traveller can pass validation on `POST /api/insurance/apply` and still be rejected at issuance. ## Roamify Insurance Overview Roamify Insurance API consists of the following services: - [Countries](https://docs.getroamify.com/insurance/countries/) - [Packages](https://docs.getroamify.com/insurance/packages/) - [Orders](https://docs.getroamify.com/insurance/orders/) - [Policies](https://docs.getroamify.com/insurance/policies/) You use these services to browse insurance products, reserve policies through an order, and issue each policy against a named traveller. ## How insurance differs from eSIMs If you have already integrated the eSIM API, three differences matter: 1. **Ordering and issuance are separate.** `POST /api/insurance/order` reserves one policy per unit of quantity and returns a `policyId` for each. The policy is not issued until you call `POST /api/insurance/apply` with the traveller's details. 2. **Traveller details are required at issuance.** Each package declares a `requirements` object that tells you which traveller fields you must collect. See [Policies](https://docs.getroamify.com/insurance/policies/). 3. **The catalogs do not mix.** Every insurance `packageId` starts with `insurance-`. Insurance packages cannot be ordered through `POST /api/esim/order`, and eSIM packages cannot be ordered through `POST /api/insurance/order`. ## Recommended steps to use Insurance API Before implementing each step, review the endpoint-specific reference page for request and response schema details: - [Countries](https://docs.getroamify.com/insurance/countries/) - [Packages](https://docs.getroamify.com/insurance/packages/) - [Orders](https://docs.getroamify.com/insurance/orders/) - [Policies](https://docs.getroamify.com/insurance/policies/) ### Step 0: Flow Diagram ```mermaid graph TD; subgraph Auth [Authentication] A[Step 1: Get Access Token] end subgraph Browse [Catalog] B[Step 2: Get Supported Countries] C[Step 3: Get Insurance Packages] end subgraph Purchase [Purchase Flow] D[Step 4: Submit Insurance Order] E[Step 5: Apply Insurance] end subgraph Manage [Policy Management] F[Step 6: Get Policy] end A --> B; B --> C; C --> D; D --> E; E --> F; F -.-> E; ``` ### Step 1: Authentication You need your Access Token to make requests to Roamify Insurance API. You can get your Access Token from the [Roamify Partner Portal](https://partner.getroamify.com/sign-in). If you don't have an account, you can sign up [here](https://partner.getroamify.com/sign-up) or contact us at [support@getroamify.com](mailto:support@getroamify.com). Our token is a Bearer token, which means you need to include it in the `Authorization` header of your requests. ### Step 2: Get Supported Countries To get the countries the insurance catalog covers, send a `GET` request to the `/api/insurance/countries` endpoint. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/countries' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/countries', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` You can read more about this endpoint [here](https://docs.getroamify.com/insurance/countries/). ### Step 3: Get Insurance Packages To get a list of insurance packages, send a `GET` request to the `/api/insurance/packages` endpoint. Before filtering by country, fetch `GET /api/insurance/countries` and use only these values: - `country`: use the returned `name` - `countryCode`: use the returned `code` You can also filter by `geography`, `day`, and `packageId`. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/packages?countryCode=SA' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/packages?countryCode=SA', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` > **Note:** Read the `requirements` object on the package you intend to sell. It tells you which traveller fields you must collect before you can issue the policy in Step 5. You can read more about this endpoint [here](https://docs.getroamify.com/insurance/packages/). ### Step 4: Submit an Insurance Order To reserve policies, send a `POST` request to the `/api/insurance/order` endpoint. The request body should contain the following parameters: - `items`: An array of insurance packages you want to order. Each item should contain the following details: - `packageId`: The ID of the insurance package. This is a required field of type `string`. - `quantity`: The number of policies you want to reserve for this package. This is a required field of type `number`. - `referenceId`: Your own reference for the order. This is an optional field of type `string`. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/insurance/order' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "items": [ { "packageId": "Package ID", "quantity": 1 } ] }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/insurance/order', { items: [ { packageId: 'Package ID', quantity: 1 } ] }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` The response contains one item per reserved policy, each with its own `policyId`. A `quantity` of `2` returns two items with two distinct `policyId` values. You can read more about this endpoint [here](https://docs.getroamify.com/insurance/orders/). ### Step 5: Apply Insurance A reserved policy carries no traveller. To issue it, send a `POST` request to the `/api/insurance/apply` endpoint with the `policyId` from Step 4 and the traveller's details. The request body should contain the following parameters: - `policyId`: The ID of the reserved policy. This is a required field of type `string`. - `startDate`: Coverage start date in `DD/MM/YYYY` format. This is a required field of type `string`. - `endDate`: Coverage end date in `DD/MM/YYYY` format. This is a required field of type `string`. - `mobile`: The traveller's mobile number, 8 to 15 digits, in international format without the leading `+`. This is a required field of type `string`. - `email`: The traveller's email address. This is a required field of type `string`. - `insurer`: The primary insured traveller. This is a required field of type `object`. > **Warning:** Coverage cannot start today. `startDate` must be at least 1 day in the future, and `endDate` must be after `startDate`. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/insurance/apply' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "policyId": "Policy ID", "startDate": "01/08/2026", "endDate": "08/08/2026", "mobile": "966512345678", "email": "traveller@example.com", "insurer": { "firstName": "Fahad", "lastName": "Al Otaibi", "nationality": "Saudi Arabia", "dateOfBirth": "24/06/1990", "idNumber": "1012345678" } }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/insurance/apply', { policyId: 'Policy ID', startDate: '01/08/2026', endDate: '08/08/2026', mobile: '966512345678', email: 'traveller@example.com', insurer: { firstName: 'Fahad', lastName: 'Al Otaibi', nationality: 'Saudi Arabia', dateOfBirth: '24/06/1990', idNumber: '1012345678' } }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` On success, the response contains the issued `policy` object with the provider's `policyNo` and a `policyLink` you can pass to your customer. You can read more about this endpoint [here](https://docs.getroamify.com/insurance/policies/). ### Step 6: Get Policy To read the current state of a policy, send a `GET` request to the `/api/insurance/policy` endpoint with the `policyId`. > **Note:** If `POST /api/insurance/apply` returns `409` with the message about the policy currently being issued, do not retry the apply call. Poll `GET /api/insurance/policy?policyId=Policy ID` until `status` becomes `ISSUED`. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/policy?policyId=Policy ID' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/policy?policyId=Policy ID', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` You can read more about this endpoint [here](https://docs.getroamify.com/insurance/policies/). ### The End That's it! You have successfully integrated Roamify Insurance API into your application. If you have any questions or need help, feel free to contact us at [support@getroamify.com](mailto:support@getroamify.com). --- # Insurance API - Supported Countries > To get a list of countries covered by the insurance catalog, you need to send a GET request to the /api/insurance/countries endpoint. Canonical: https://docs.getroamify.com/insurance/countries/ > **Warning:** **Eligibility: Saudi Arabia citizens and residents only.** Insurance packages may only be sold to travellers who are citizens or residents of Saudi Arabia, regardless of which countries this endpoint returns. ## Get Insurance Countries To get a list of countries covered by the insurance catalog, you need to send a `GET` request to the `/api/insurance/countries` endpoint. There are no required parameters for this endpoint. Optional query parameters: - `geography`: Filter countries by geography scope. This is an optional field of type `string`. Matching is case-insensitive. Accepted values: - `local` - `regional` - `global` > **Warning:** Use the returned `name` and `code` from this endpoint when querying `/api/insurance/packages`. > For package filters: > - `country` must use `name` > - `countryCode` must use `code` > Arbitrary country strings are not accepted, and a value that does not match exactly returns an empty `packages` array rather than an error. > **Note:** Only countries that have at least one activated insurance product are returned. Results are deduplicated by `code` and sorted alphabetically by `name`. ## Usage - fetch a list of insurance supported countries Here is an example of how you can get a list of countries using Roamify Insurance API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/countries' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/countries', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Usage - fetch countries by geography (optional) **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/countries?geography=local' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/countries?geography=local', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ## Response fields Each entry in `countries` contains: - `code`: The ISO country code. Use this as `countryCode` on `/api/insurance/packages`. - `name`: The full country name. Use this as `country` on `/api/insurance/packages`. - `slug`: The URL-friendly form of the country name. - `geography`: The geography scope of the country. One of `local`, `regional`, or `global`. ## Sample Response ```json { "status": "success", "data": { "countries": [ { "code": "SA", "name": "Saudi Arabia", "slug": "saudi-arabia", "geography": "local" } ] } } ``` --- # Insurance API - Package(s) > To get a list of insurance packages, you need to send a GET request to the /api/insurance/packages endpoint. Canonical: https://docs.getroamify.com/insurance/packages/ > **Warning:** **Eligibility: Saudi Arabia citizens and residents only.** Insurance packages may only be sold to travellers who are citizens or residents of Saudi Arabia. Check each product's `eligibleNationality` and `eligibleNationCodes` before offering it. ## Get Insurance Packages To get a list of insurance packages, you need to send a `GET` request to the `/api/insurance/packages` endpoint. > **Warning:** Unlike `/api/esim/packages`, this endpoint is **not paginated**. The insurance catalog is small, so every matching product is returned in a single response. Do not send `page` or `pageSize`. > **Warning:** `country` and `countryCode` have different meanings. Both must use values returned from `GET /api/insurance/countries`. A value that does not match exactly yields an empty `packages` array, not an error. The request query can include the following parameters: - `geography`: The geography of the insurance packages. This is an optional field of type `string`. Matching is case-insensitive. It can be any of the following values: - `local`: Local packages - `regional`: Regional packages - `global`: Global packages - `country`: Full country name filter. This is an optional field of type `string`. Use the `name` value from `GET /api/insurance/countries`. - `countryCode`: ISO country code filter. This is an optional field of type `string`. Use the `code` value from `GET /api/insurance/countries`. - `packageId`: The ID of a specific insurance package. This is an optional field of type `string`. - `day`: The coverage duration in days. This is an optional field of type `number`, and must be an integer of at least `1`. Only packages whose `day` matches exactly are returned. > **Note:** Only activated products and activated packages are returned. A product whose packages are all filtered out is omitted from the response entirely. ## Usage examples Here is an example of how you can get a list of insurance packages using Roamify Insurance API. ### Example Request 1: Get all insurance packages **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/packages' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/packages', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ```
### Example Request 2: Filter by `countryCode` (ISO code) **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/packages?countryCode=SA' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/packages?countryCode=SA', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ```
### Example Request 3: Filter by `country` (full country name) **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/packages?country=Saudi%20Arabia' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/packages?country=Saudi Arabia', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Example Request 4: Filter by coverage duration **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/packages?countryCode=SA&day=7' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/packages?countryCode=SA&day=7', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ## Response fields ### Product Each entry in `packages` is an insurance product: - `id`: The ID of the insurance product. - `name`: The display name of the product. - `slug`: The URL-friendly form of the product name. - `countryName`: The full name of the country the product is associated with. - `countryCode`: The ISO code of the country the product is associated with. - `countrySlug`: The URL-friendly form of the country name. - `geography`: The geography scope. One of `local`, `regional`, or `global`. - `region`: The region the country belongs to. - `description`: A description of the product. - `image`: The image URL for the product. - `coverages`: The coverage areas included in the product. Array. - `insuredType`: The type of traveller the product insures. - `eligibleNationality`: The nationalities eligible for this product. Array. - `eligibleNationCodes`: The ISO codes of the eligible nationalities. Array. - `benefits`: The benefits included in the product. Array. - `minPrice`: The lowest package price for this product. - `maxPrice`: The highest package price for this product. - `packages`: The purchasable packages for this product. Array. ### Package Each entry in the product's `packages` array is a purchasable package: - `packageId`: The ID you pass to `POST /api/insurance/order`. Always starts with `insurance-`. - `package`: The display name of the package. - `provider`: The insurance provider that underwrites the policy. - `type`: The package type. - `charge`: The charge amount for the package, expressed in `currency`. - `day`: The number of days of coverage the package allows. - `price`: The price of the package, expressed in `currency`. - `currency`: The currency of `price` and `charge`. Always `USD`. - `requirements`: The traveller fields you must supply when issuing this policy. See below. - `notes`: Additional provider notes for the package. Array. > **Note:** `coverages`, `benefits`, and `notes` are provider-defined. Treat them as opaque display content rather than fields you branch on. ### Requirements The `requirements` object determines which traveller fields `POST /api/insurance/apply` will accept or reject for this package. It is snapshotted onto every policy created from the package, so what you read here is what will be enforced at issuance. - `phoneNumber`: When `true`, `mobile` must be supplied. - `dateOfBirth`: When `true`, `insurer.dateOfBirth` must be supplied. - `idNumber`: When `true`, `insurer.idNumber` must be supplied. - `passportNumber`: When `true`, `insurer.passportNumber` must be supplied. > **Warning:** `mobile` and `email` are required on every apply call, whatever the `requirements` flags say. The provider issues the policy against a reachable contact. Read more about how these flags are enforced on the [Policies](https://docs.getroamify.com/insurance/policies/) page. ### Sample Response ```json { "status": "success", "data": { "packages": [ { "id": "saudi-arabia-insurance", "name": "Saudi Arabia Travel Insurance", "slug": "saudi-arabia-travel-insurance", "countryName": "Saudi Arabia", "countryCode": "SA", "countrySlug": "saudi-arabia", "geography": "local", "region": "Middle East", "description": "Travel insurance for Saudi Arabia citizens and residents.", "image": "https://imagedelivery.net/uUE6LNs_ci8SFBH4_n66dQ/countries/flags/saudi-arabia/public", "coverages": [ "Medical expenses", "Trip cancellation", "Lost baggage" ], "insuredType": "individual", "eligibleNationality": [ "Saudi Arabia" ], "eligibleNationCodes": [ "SA" ], "benefits": [ "Emergency medical cover up to USD 50,000", "24/7 assistance hotline" ], "minPrice": 12, "maxPrice": 30, "packages": [ { "packageId": "insurance-saudi-arabia-individual-7days", "package": "Individual - 7 Days", "provider": "tree", "type": "individual", "charge": 12, "day": 7, "price": 12, "currency": "USD", "requirements": { "phoneNumber": true, "dateOfBirth": true, "idNumber": true, "passportNumber": false }, "notes": [] } ] } ] } } ``` ## Get a Single Insurance Package To get one insurance package by its ID, you need to send a `GET` request to the `/api/insurance/package` endpoint. You are required to provide the following query parameter: - `packageId`: The ID of the insurance package you want to retrieve. This is a required field of type `string`. The response is the full product the package belongs to, with its `packages` array narrowed to the single package you requested. ### Usage - fetch a single insurance package **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/package?packageId=insurance-saudi-arabia-individual-7days' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/package?packageId=insurance-saudi-arabia-individual-7days', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "id": "saudi-arabia-insurance", "name": "Saudi Arabia Travel Insurance", "slug": "saudi-arabia-travel-insurance", "countryName": "Saudi Arabia", "countryCode": "SA", "countrySlug": "saudi-arabia", "geography": "local", "region": "Middle East", "description": "Travel insurance for Saudi Arabia citizens and residents.", "image": "https://imagedelivery.net/uUE6LNs_ci8SFBH4_n66dQ/countries/flags/saudi-arabia/public", "coverages": [ "Medical expenses", "Trip cancellation", "Lost baggage" ], "insuredType": "individual", "eligibleNationality": [ "Saudi Arabia" ], "eligibleNationCodes": [ "SA" ], "benefits": [ "Emergency medical cover up to USD 50,000", "24/7 assistance hotline" ], "minPrice": 12, "maxPrice": 30, "packages": [ { "packageId": "insurance-saudi-arabia-individual-7days", "package": "Individual - 7 Days", "provider": "tree", "type": "individual", "charge": 12, "day": 7, "price": 12, "currency": "USD", "requirements": { "phoneNumber": true, "dateOfBirth": true, "idNumber": true, "passportNumber": false }, "notes": [] } ] } } ``` ### Errors | HTTP status | Message | | --- | --- | | `404` | Insurance package not found, please make sure you have provided a valid packageId | | `500` | Failed to get insurance packages, please try again later | --- # Insurance API - Orders > To create an insurance order, you need to send a POST request to the /api/insurance/order endpoint. Canonical: https://docs.getroamify.com/insurance/orders/ > **Warning:** **Eligibility: Saudi Arabia citizens and residents only.** Insurance packages may only be sold to travellers who are citizens or residents of Saudi Arabia. ## Create Insurance Order To create an insurance order, you need to send a `POST` request to the `/api/insurance/order` endpoint. An order **reserves** one policy per unit of quantity and debits your partner balance. It does not issue anything. Each reserved policy comes back with its own `policyId`, which you then issue against a named traveller with [`POST /api/insurance/apply`](https://docs.getroamify.com/insurance/policies/#apply-insurance). You are required to provide the following parameters in the request body: - `items`: An array of insurance items you want to order. At least one item is required. Each item should contain the following details: - `packageId`: The ID of the insurance package you want to order. This is a required field of type `string`. - `quantity`: The number of policies to reserve for this package. This is a required field of type `number`, and must be an integer of at least `1`. - `referenceId`: The reference ID for the order. This is an optional field of type `string`. (Your custom reference ID for the order, if needed.) > **Warning:** Every `packageId` must start with `insurance-`. eSIM packages cannot be ordered through this endpoint; use `POST /api/esim/order` for those. The reverse also holds: insurance packages are rejected by the eSIM order endpoints. > **Warning:** The total quantity across all items in a single order cannot exceed `5`. > **Note:** `referenceId` must be unique across your orders. Reusing one returns `409`, which makes it a safe idempotency guard against double submission. ### Usage - create an insurance order Here is an example of how you can create an insurance order using Roamify Insurance API. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/insurance/order' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "items": [ { "packageId": "insurance-saudi-arabia-individual-7days", "quantity": 1 } ], "referenceId": "Optional Reference ID" }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/insurance/order', { items: [ { packageId: 'insurance-saudi-arabia-individual-7days', quantity: 1 } ], referenceId: 'Optional Reference ID' }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Response fields - `id`: The ID of the insurance order. - `referenceId`: The reference ID you provided, or an empty string. - `orderType`: How the order was placed. Always `api` for orders created through this endpoint. - `status`: The status of the order. - `total`: The total charged for the order. - `currency`: The currency of `total`. - `createdAt`: The Unix timestamp of when the order was created. - `updatedAt`: The Unix timestamp of when the order was last updated. - `items`: One entry per reserved policy. Each entry contains: - `policyId`: The ID of the reserved policy. Pass this to `POST /api/insurance/apply`. - `packageId`: The ID of the insurance package that was ordered. - `package`: The display name of the package. - `type`: The service type. Always `insurance`. - `price`: The price of the package. - `currency`: The currency of `price`. - `createdAt`: The Unix timestamp of when the order was created. - `updatedAt`: The Unix timestamp of when the order was last updated. > **Note:** A `quantity` of `2` produces two entries in `items`, sharing the same `packageId` but each with a distinct `policyId`. Issue each one separately. ### Sample Response ```json { "status": "success", "data": { "id": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "referenceId": "ref-123456", "orderType": "api", "status": "COMPLETED", "total": 12, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539, "items": [ { "policyId": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "packageId": "insurance-saudi-arabia-individual-7days", "package": "Individual - 7 Days", "type": "insurance", "price": 12, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539 } ] } } ``` ### Errors | HTTP status | Message | | --- | --- | | `400` | Only insurance packages can be ordered through this endpoint. Every packageId must start with 'insurance-'; use POST /esim/order for eSIM packages | | `400` | Invalid quantity, please make sure your have provided the correct quantity | | `400` | Exceed maximum total quantity of 5, please make sure your have provided the correct quantity and the quantity does not exceed the maximum allowed quantity 5 | | `400` | Missing required fields, please provide all required fields according to the documentation | | `409` | Order with the same referenceId already exists, please make sure your have provided a unique referenceId | | `409` | Insufficient partner balance, please recharge it first before placing order | ## Get Insurance Order To get a specific insurance order, you need to send a `GET` request to the `/api/insurance/order` endpoint. > **Warning:** You must provide exactly one of `orderId` or `referenceId`. Providing both, or neither, is a validation error. - `orderId`: The ID of the insurance order you want to retrieve. This is a field of type `string`. - `referenceId`: The reference ID of the insurance order you want to retrieve. This is a field of type `string`. ### Usage - fetch an insurance order Here is an example of how you can get a specific insurance order using Roamify Insurance API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/order?orderId=Order ID' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/order?orderId=Order ID', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "id": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "referenceId": "ref-123456", "orderType": "api", "status": "COMPLETED", "total": 12, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539, "items": [ { "policyId": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "packageId": "insurance-saudi-arabia-individual-7days", "package": "Individual - 7 Days", "type": "insurance", "price": 12, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539 } ] } } ``` ### Errors | HTTP status | Message | | --- | --- | | `404` | Order not found, please make sure your have provided the correct orderId or referenceId | > **Note:** This endpoint only resolves insurance orders. An eSIM order ID returns `404` here, even though the order exists. ## Get Insurance Orders To get a list of insurance orders, you need to send a `GET` request to the `/api/insurance/orders` endpoint. There are no required parameters for this endpoint, but you can provide the following optional parameters: - `status`: The status of the insurance orders to return. This is an optional field of type `string`. Matching is exact and case-sensitive. - `limit`: The number of insurance orders to return. This is an optional field of type `number`. The default value is `50`. Orders are returned newest first, sorted by `createdAt` descending. Only orders you placed through the API are included. ### Usage - fetch a list of insurance orders Here is an example of how you can get a list of insurance orders using Roamify Insurance API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/orders?status=COMPLETED&limit=10' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/orders?status=COMPLETED&limit=10', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "orders": [ { "id": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "referenceId": "ref-123456", "orderType": "api", "status": "COMPLETED", "total": 12, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539, "items": [ { "policyId": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "packageId": "insurance-saudi-arabia-individual-7days", "package": "Individual - 7 Days", "type": "insurance", "price": 12, "currency": "USD", "createdAt": 1731837535, "updatedAt": 1731837539 } ] } ] } } ``` --- # Insurance API - Policies > Ordering an insurance package reserves a policy but does not issue it. To issue a reserved policy against a named traveller, you need to send a POST request to the /api/insurance/apply endpoint. Canonical: https://docs.getroamify.com/insurance/policies/ > **Warning:** **Eligibility: Saudi Arabia citizens and residents only.** Insurance packages may only be sold to travellers who are citizens or residents of Saudi Arabia. Roamify's validation is deliberately looser than any single provider's rule, so an ineligible traveller can pass validation here and still be rejected by the provider at issuance. ## Apply Insurance Ordering an insurance package reserves a policy but does not issue it. To issue a reserved policy against a named traveller, you need to send a `POST` request to the `/api/insurance/apply` endpoint. You are required to provide the following parameters in the request body: - `policyId`: The ID of the reserved policy, returned by [`POST /api/insurance/order`](https://docs.getroamify.com/insurance/orders/#create-insurance-order). This is a required field of type `string`. - `startDate`: The coverage start date in `DD/MM/YYYY` format. This is a required field of type `string`. - `endDate`: The coverage end date in `DD/MM/YYYY` format. This is a required field of type `string`. - `mobile`: The traveller's mobile number. This is a required field of type `string`. - `email`: The traveller's email address. This is a required field of type `string`. - `insurer`: The primary insured traveller. This is a required field of type `object`, containing: - `firstName`: The traveller's first name. This is a required field of type `string`. - `lastName`: The traveller's last name. This is a required field of type `string`. - `nationality`: The traveller's nationality. This is a required field of type `string`. - `dateOfBirth`: The traveller's date of birth in `DD/MM/YYYY` format. Conditional, see below. - `idNumber`: The traveller's identification number. Conditional, see below. - `passportNumber`: The traveller's passport number. Conditional, see below. ### Which traveller fields are required `dateOfBirth`, `idNumber`, and `passportNumber` are conditional. Whether each one is mandatory is decided by the `requirements` object of the package that was purchased, snapshotted onto the policy when the order was created. Read `requirements` from [`GET /api/insurance/packages`](https://docs.getroamify.com/insurance/packages/) before you build your collection form, or from `GET /api/insurance/policy` for a policy you already hold. | `requirements` flag | Field it governs | When `true` | | --- | --- | --- | | `dateOfBirth` | `insurer.dateOfBirth` | Must be supplied | | `idNumber` | `insurer.idNumber` | Must be supplied | | `passportNumber` | `insurer.passportNumber` | Must be supplied | | `phoneNumber` | `mobile` | Must be supplied | A field the package does not ask for is ignored. A field it does ask for must be present and well formed. > **Warning:** `mobile` and `email` are required on **every** apply call, whatever the `requirements` flags say. The provider issues the policy against a reachable contact. ### Validation rules | Field | Rule | | --- | --- | | `startDate` | `DD/MM/YYYY`. At least 1 day in the future — coverage cannot start today. | | `endDate` | `DD/MM/YYYY`. Must be strictly after `startDate`. Must fit within the purchased package's `day` allowance. | | `mobile` | 8 to 15 digits, international format, no leading `+`. For example `966512345678`. | | `email` | A valid email address. | | `insurer.dateOfBirth` | `DD/MM/YYYY`. Must be in the past, and no more than 120 years ago. | | `insurer.idNumber` | 5 to 20 alphanumeric characters. | | `insurer.passportNumber` | 5 to 20 alphanumeric characters. | > **Note:** These rules are intentionally broader than what any one provider accepts. The current Saudi Arabia provider, for example, only accepts a 10-digit Saudi ID and a `966`-prefixed mobile, and enforces that itself at issuance. Passing Roamify's validation does not guarantee the provider will issue the policy. ### Usage - issue an insurance policy Here is an example of how you can issue an insurance policy using Roamify Insurance API. **curl** ```bash curl --request POST \ --url 'https://api-dev.getroamify.com/api/insurance/apply' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' \ --data '{ "policyId": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "startDate": "01/08/2026", "endDate": "08/08/2026", "mobile": "966512345678", "email": "traveller@example.com", "insurer": { "firstName": "Fahad", "lastName": "Al Otaibi", "nationality": "Saudi Arabia", "dateOfBirth": "24/06/1990", "idNumber": "1012345678" } }' ``` **javascript** ```javascript const axios = require('axios'); axios.post('https://api-dev.getroamify.com/api/insurance/apply', { policyId: 'b06036c5-eadc-4f88-aa31-8c207fec91ec', startDate: '01/08/2026', endDate: '08/08/2026', mobile: '966512345678', email: 'traveller@example.com', insurer: { firstName: 'Fahad', lastName: 'Al Otaibi', nationality: 'Saudi Arabia', dateOfBirth: '24/06/1990', idNumber: '1012345678' } }, { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "policyId": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "orderId": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "packageId": "insurance-saudi-arabia-individual-7days", "package": "Individual - 7 Days", "countryName": "Saudi Arabia", "type": "insurance", "status": "ISSUED", "startDate": "01/08/2026", "endDate": "08/08/2026", "insurerName": "Fahad Al Otaibi", "requirements": { "phoneNumber": true, "dateOfBirth": true, "idNumber": true, "passportNumber": false }, "coverage": { "insuredType": "individual", "day": 7, "benefits": [ "Emergency medical cover up to USD 50,000", "24/7 assistance hotline" ] }, "policy": { "policyNo": "TRV-2026-000123", "policyIssuanceDate": "2026-07-10", "policyLink": "https://provider.example.com/policies/TRV-2026-000123.pdf" }, "createdAt": 1731837535, "updatedAt": 1731837987, "expiredAt": 1765238400 } } ``` > **Note:** Policy responses never echo back traveller PII. The identification number, passport number, date of birth, nationality, mobile, and email you submit are not returned. Only `insurerName` and the issued policy document are exposed. ### Response fields - `policyId`: The ID of the policy. - `orderId`: The ID of the order the policy was reserved through. - `packageId`: The ID of the insurance package. - `package`: The display name of the package. - `countryName`: The country the policy is associated with. - `type`: The service type. Always `insurance`. - `status`: The status of the policy. See [Policy Statuses](#policy-statuses). - `startDate`: The coverage start date, `DD/MM/YYYY`. - `endDate`: The coverage end date, `DD/MM/YYYY`. - `insurerName`: The insured traveller's full name. - `requirements`: The traveller fields this policy's package requires. - `coverage`: The coverage summary, containing `insuredType`, `day`, and `benefits`. - `policy`: The issued policy document, or `null` if the policy has not been issued yet. Contains: - `policyNo`: The provider's policy number. - `policyIssuanceDate`: The date the provider issued the policy. - `policyLink`: A link to the policy document. Pass this to your customer. - `createdAt`: The Unix timestamp of when the policy was reserved. - `updatedAt`: The Unix timestamp of when the policy was last updated. - `expiredAt`: The Unix timestamp of when coverage expires, or `-1` if not applicable. ### Errors | HTTP status | Message | | --- | --- | | `400` | This insurance package requires idNumber, please provide insurer.idNumber | | `400` | This insurance package requires passportNumber, please provide insurer.passportNumber | | `400` | This insurance package requires dateOfBirth, please provide insurer.dateOfBirth in the format DD/MM/YYYY (e.g. 24/06/1990) | | `400` | This insurance package requires phoneNumber, please provide mobile in international format without the leading '+' (e.g. 966512345678) | | `400` | Invalid traveller name, please provide both insurer.firstName and insurer.lastName | | `400` | Invalid nationality, please provide insurer.nationality | | `400` | Invalid idNumber, please provide a valid identification number for the traveller | | `400` | Invalid passportNumber, please provide a valid passport number for the traveller | | `400` | Invalid dateOfBirth, please provide a past date in the format DD/MM/YYYY (e.g. 24/06/1990) | | `400` | Invalid mobile, please provide 8 to 15 digits in international format without the leading '+' (e.g. 966512345678) | | `400` | Invalid email, please provide a valid email address for the traveller | | `400` | Invalid startDate, please provide the coverage start date in the format DD/MM/YYYY (e.g. 01/08/2026) | | `400` | Invalid endDate, please provide the coverage end date in the format DD/MM/YYYY (e.g. 08/08/2026) and make sure it is after startDate | | `400` | Coverage cannot start today, please provide a startDate at least 1 day in the future | | `400` | The requested coverage window is longer than the purchased package allows, please shorten it or buy a longer package | | `400` | Insurance details failed validation, please make sure you have provided all the required traveller fields | | `404` | Insurance policy not found, please make sure you have provided the correct policyId | | `409` | This insurance policy has already been issued and cannot be applied again | | `409` | This insurance policy is currently being issued, please retry with GET /insurance/policy?policyId=id after some time | | `409` | A policy has already been issued for this reference, please contact support if you believe this is an error | | `502` | Failed to issue the insurance policy, please try again later or contact support | | `502` | The insurance provider rejected the request, please verify the traveller details and try again | > **Warning:** Applying to a policy that is already `ISSUED`, `ACTIVE`, or `EXPIRED` returns `409`. Issuance is not idempotent — do not blindly retry `POST /api/insurance/apply` on a `409`. Read the message: if the policy is being issued, poll `GET /api/insurance/policy` instead. ## Get Insurance Policy To get a specific insurance policy, you need to send a `GET` request to the `/api/insurance/policy` endpoint. You are required to provide the following query parameter: - `policyId`: The ID of the insurance policy you want to retrieve. This is a required field of type `string`. Use this endpoint to read the `requirements` for a policy you already hold, and to poll for the issued policy document after a `409` telling you issuance is in progress. ### Usage - fetch an insurance policy **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/policy?policyId=Policy ID' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/policy?policyId=Policy ID', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "policyId": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "orderId": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "packageId": "insurance-saudi-arabia-individual-7days", "package": "Individual - 7 Days", "countryName": "Saudi Arabia", "type": "insurance", "status": "NEW", "startDate": "", "endDate": "", "insurerName": "", "requirements": { "phoneNumber": true, "dateOfBirth": true, "idNumber": true, "passportNumber": false }, "coverage": { "insuredType": "individual", "day": 7, "benefits": [ "Emergency medical cover up to USD 50,000", "24/7 assistance hotline" ] }, "policy": null, "createdAt": 1731837535, "updatedAt": 1731837535, "expiredAt": -1 } } ``` ### Errors | HTTP status | Message | | --- | --- | | `404` | Insurance policy not found, please make sure you have provided the correct policyId | > **Note:** A policy belonging to another partner is reported as `404`, not `403`. ## Get Insurance Policies To get a list of insurance policies, you need to send a `GET` request to the `/api/insurance/policies` endpoint. There are no required parameters for this endpoint, but you can provide the following optional parameters: - `orderId`: Return only the policies reserved through this order. This is an optional field of type `string`. - `status`: The status of the policies to return. This is an optional field of type `string`. Matching is exact and case-sensitive. - `limit`: The number of policies to return. This is an optional field of type `number`. The default value is `50`. Policies are returned newest first, sorted by `createdAt` descending. ### Usage - fetch a list of insurance policies **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/insurance/policies?status=ISSUED&limit=10' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/insurance/policies?status=ISSUED&limit=10', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ### Sample Response ```json { "status": "success", "data": { "policies": [ { "policyId": "b06036c5-eadc-4f88-aa31-8c207fec91ec", "orderId": "67bbabb7-e893-410a-b0a8-4fa62d347b6c", "packageId": "insurance-saudi-arabia-individual-7days", "package": "Individual - 7 Days", "countryName": "Saudi Arabia", "type": "insurance", "status": "ISSUED", "startDate": "01/08/2026", "endDate": "08/08/2026", "insurerName": "Fahad Al Otaibi", "requirements": { "phoneNumber": true, "dateOfBirth": true, "idNumber": true, "passportNumber": false }, "coverage": { "insuredType": "individual", "day": 7, "benefits": [ "Emergency medical cover up to USD 50,000", "24/7 assistance hotline" ] }, "policy": { "policyNo": "TRV-2026-000123", "policyIssuanceDate": "2026-07-10", "policyLink": "https://provider.example.com/policies/TRV-2026-000123.pdf" }, "createdAt": 1731837535, "updatedAt": 1731837987, "expiredAt": 1765238400 } ] } } ``` ## Policy Statuses A policy's `status` can be any of the following values: | Status | Meaning | | --- | --- | | `NEW` | Reserved by an order, not yet issued. Ready for `POST /api/insurance/apply`. | | `PENDING` | Issuance is in progress. | | `ISSUED` | The provider has issued the policy. The `policy` object is populated. | | `ACTIVE` | Coverage is currently in effect. | | `EXPIRED` | Coverage has ended. | | `CANCELLED` | The policy was cancelled. | | `REQUIRE_ACTION` | The policy needs attention before it can proceed. | | `UNKNOWN` | The status could not be determined. | > **Warning:** `POST /api/insurance/apply` only accepts a policy that is not already `ISSUED`, `ACTIVE`, or `EXPIRED`. Those three states return `409`. --- # Roamify Webhooks Overview > Receive Roamify event notifications for eSIM usage, eSIM status changes, partner balance, affiliate orders and refunds. Canonical: https://docs.getroamify.com/webhooks/overview/ Roamify Webhooks lets API users receive event notifications from the Roamify platform. Currently, it supports the following features: - **eSIM Usage**: Receive notifications about eSIM data usage at configurable intervals - **eSIM Status**: Receive notifications when an eSIM's status changes (activated, deactivated, suspended, etc.) - **Partner Balance**: Receive notifications about changes in your partner account balance - **Affiliate Orders**: Receive notifications when orders are placed through your affiliate links - **Refunds**: Receive notifications when a refund request is refunded or rejected - **More Features**: We are constantly working on adding more features to Roamify Webhooks. ## Mandatory Requirements To use Roamify Webhooks, you must meet the following requirements: - **HTTPS Endpoint**: Your webhook endpoint must be accessible over HTTPS. - **Valid SSL Certificate**: The endpoint must have a valid SSL certificate to ensure secure communication. - **Signature Verification**: Each webhook request will include a signature in the `X-Roamify-Signature` header. You must verify this signature using your secret key to ensure that the request is legitimate and has not been tampered with. - **Response Handling**: Your endpoint must respond with a 200 OK status code quickly (within 3 seconds) to acknowledge receipt of the webhook prior to any complex processing that may take longer. ## Recommended Practices Here is a high level overview of the recommended practices for using Roamify Webhooks: - **Use a Queue**: If your webhook processing involves complex logic or external API calls, consider using a message queue to handle the webhook processing asynchronously. This allows you to respond quickly to the webhook request while processing the data in the background. - **Logging**: Log all incoming webhook requests and their responses to help with debugging and monitoring. - **Error Handling**: Implement error handling to gracefully handle any issues that may arise during webhook processing. This includes logging errors and sending notifications if necessary. - **Testing**: Test your webhook endpoint thoroughly to ensure that it can handle various scenarios, including valid and invalid requests, network issues, and other edge cases. - **Security**: Keep your webhook endpoint secure by implementing authentication and authorization mechanisms. This can include IP whitelisting, API keys, or other security measures to ensure that only authorized requests are processed. ### Sample Webhook Flow Below are minimal examples that verify the webhook signature and acknowledge the request quickly. **javascript** ```javascript const crypto = require('crypto'); function verifySignature(rawBody, signature, secret) { const computed = crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex'); return signature.length === computed.length && crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(signature)); } // Example Express handler app.post('/webhooks/roamify', (req, res) => { const secret = process.env.ROAMIFY_WEBHOOK_SECRET; const signature = req.header('x-roamify-signature') || ''; const rawBody = req.rawBody || JSON.stringify(req.body); if (!verifySignature(rawBody, signature, secret)) { return res.status(401).json({ code: 401, message: 'invalid signature' }); } const { event_category, event_type } = req.body; // Queue your async processing here // queueWebhook({ event_category, event_type, payload: req.body }); return res.status(200).json({ code: 200, message: 'success' }); }); ``` **php** ```php 401, 'message' => 'invalid signature']); exit; } $payload = json_decode($rawBody, true); $eventCategory = $payload['event_category'] ?? null; $eventType = $payload['event_type'] ?? null; // Queue your async processing here http_response_code(200); echo json_encode(['code' => 200, 'message' => 'success']); ``` **python** ```python import hashlib import hmac import os from flask import request, jsonify def verify_signature(raw_body: str, signature: str, secret: str) -> bool: computed = hmac.new(secret.encode('utf-8'), raw_body.encode('utf-8'), hashlib.sha256).hexdigest() return hmac.compare_digest(computed, signature) def roamify_webhook_handler(): secret = os.environ['ROAMIFY_WEBHOOK_SECRET'] raw_body = request.get_data(as_text=True) signature = request.headers.get('X-Roamify-Signature', '') if not verify_signature(raw_body, signature, secret): return jsonify({'code': 401, 'message': 'invalid signature'}), 401 payload = request.get_json(silent=True) or {} event_category = payload.get('event_category') event_type = payload.get('event_type') # Queue your async processing here return jsonify({'code': 200, 'message': 'success'}), 200 ``` ### Architecture Diagram ```mermaid graph TD; subgraph Roamify [Roamify Platform] A[Event Occurs] B[Generate Webhook Payload] C[Sign with Secret Key] D[Send HTTPS POST Request] end subgraph YourEndpoint [Your Webhook Endpoint] E[Receive Webhook Request] F{Verify Signature} G[Return 200 OK] H[Return 4xx/5xx Error] I[Queue for Processing] end subgraph Processing [Async Processing] J{Event Category?} K[Process eSIM Events] L[Process Partner Events] M[Process Affiliate Events] Q[Process Refund Events] N[eSIM Usage/Status] O[Low/Insufficient Balance
Top-up] P[Affiliate Order] R[Refunded/Rejected] end A --> B; B --> C; C --> D; D --> E; E --> F; F -->|Valid| G; F -->|Invalid| H; G --> I; I --> J; J -->|esim| K; J -->|partner| L; J -->|affiliate| M; J -->|refund| Q; K --> N; L --> O; M --> P; Q --> R; ``` --- # Roamify Webhooks Verification > To ensure the security and reliability of Roamify Webhooks, we require that your webhook endpoint meets specific criteria. Canonical: https://docs.getroamify.com/webhooks/verification/ To ensure the security and reliability of Roamify Webhooks, we require that your webhook endpoint meets specific criteria. This document outlines the verification process and requirements for using Roamify Webhooks. Also, this is to protect against unauthorized access and ensure that only legitimate requests are processed. ## Pre-requisites 1. **Endpoint Accessibility**: Your webhook endpoint must be accessible over HTTPS. This ensures that all data transmitted between Roamify and your endpoint is encrypted and secure. 2. **SSL Certificate**: The endpoint must have a valid SSL certificate. This is crucial for establishing trust and ensuring that the data is not tampered with during transmission. 3. **Response Handling**: Your endpoint must respond with a 200 OK status code quickly (within 3 seconds) to acknowledge receipt of the webhook. This response is essential to confirm that the webhook was received successfully. If your endpoint does not respond within this time frame, Roamify will retry the webhook up to 3 times at increasing intervals. ## Verification Process As soon as the request hits your endpoint, you must verify the request to ensure it is coming from Roamify. This verification process includes: 1. **Signature Verification**: Each webhook request will include a signature in the `X-Roamify-Signature` header. You must verify this signature using your secret key to ensure that the request is legitimate and has not been tampered with. ### Sample Verification Code **javascript** ```javascript const crypto = require('crypto'); function isValidSignature(rawBody, signature, secret) { const computed = crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex'); return signature.length === computed.length && crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(signature)); } const secret = process.env.ROAMIFY_WEBHOOK_SECRET; const signature = req.headers['x-roamify-signature'] || ''; const rawBody = req.rawBody || JSON.stringify(req.body); if (!isValidSignature(rawBody, signature, secret)) { throw new Error('Invalid signature'); } ``` **php** ```php # eSIM Status Event > The eSIM Status Event webhook sends notifications when an eSIM's status changes. This allows you to monitor and respond to status transitions such as activation, deactivation, suspension, or other state changes. Canonical: https://docs.getroamify.com/webhooks/types/esim-status/ > **Warning:** Before you start using the eSIM status event webhook, you need to configure your webhook URL in the Roamify dashboard. This URL will be used to receive notifications about eSIM status changes. ## Overview The eSIM Status Event webhook sends notifications when an eSIM's status changes. This allows you to monitor and respond to status transitions such as activation, deactivation, suspension, or other state changes. ## Event Payload The webhook payload contains the following fields: - `event_category`: The category of event. For esim events, this will be `esim`. - `event_type`: The type of event. For esim status events, this will be `status`. - `data`: The data object containing the eSIM status information, which includes: - `timestamp`: The time when the event was triggered, in unix timestamp format (seconds since epoch). - `iccid`: The Integrated Circuit Card Identifier of the eSIM. - `esim_id`: The unique identifier of the eSIM. - `reference_id`: A custom referenceId provided by the user when creating the order if applicable. - `start`: The start time of the usage period, in unix timestamp format (seconds since epoch). - `end`: The end time of the usage period, in unix timestamp format (seconds since epoch). - `status`: The status of the eSIM usage event. Possible values include: - `NEW`: The eSIM is newly generated and not yet installed. - `INSTALLED`: The eSIM has been installed on a device. - `ACTIVATED`: The eSIM has been activated and is currently in use. - `INACTIVE`: The eSIM is inactive and not currently in use. - `DELETED`: The eSIM has been deleted from the device. - `REVOKED`: The eSIM has been revoked and is no longer valid. - `EXPIRED`: The eSIM has expired and is no longer valid. - `UNKNOWN`: The status of the eSIM is unknown or not specified. ### Example Payload Here is an example of the eSIM Status Event webhook payload: ```json { "event_category": "esim", "event_type": "status", "data": { "timestamp": 1700000000, "iccid": "8991234567890123456", "esim_id": "c2a9cdae-430e-4251-bf65-a84df4562ed9", "reference_id": "ref_1234567890", "start": 1699996800, "end": 1700000400, "status": "ACTIVATED" } } ``` ## Mandatory Response You must respond with a 200 OK status code to acknowledge receipt of the webhook. We recommend responding quickly (within 3 seconds) to avoid overloading Roamify's webhook server. We recommend you send response first and then process the webhook data in the background to ensure that you do not miss any webhook events due to processing delays. ### Response Example ```json { "code": 200, "status": "success" } ``` --- # eSIM Usage Event > The eSIM Usage Event webhook sends notifications about eSIM data usage at configurable intervals. This allows you to monitor data consumption and take appropriate actions based on usage patterns. Canonical: https://docs.getroamify.com/webhooks/types/esim-usage/ > **Warning:** Before you start using the eSIM usage event webhook, you need to configure your webhook URL in the Roamify dashboard. This URL will be used to receive notifications about eSIM status changes. ## Overview The eSIM Usage Event webhook sends notifications about eSIM data usage at configurable intervals. This allows you to monitor data consumption and take appropriate actions based on usage patterns. The event will be triggered for every activated eSIM every 4 hours, and it will also be triggered when data usage reaches the following thresholds: - 25% of the total data allocation - 50% of the total data allocation - 75% of the total data allocation - 90% of the total data allocation - 100% of the total data allocation ## Event Payload The webhook payload contains the following fields: - `event_category`: The category of event. For esim events, this will be `esim`. - `event_type`: The type of event. For esim usage events, this will be `usage`. - `data`: The data object containing the eSIM usage information, which includes: - `timestamp`: The time when the event was triggered, in unix timestamp format (seconds since epoch). - `iccid`: The Integrated Circuit Card Identifier of the eSIM. - `esim_id`: The unique identifier of the eSIM (UUID format). - `reference_id`: A custom referenceId provided by the user when creating the order if applicable. - `start`: The start time of the usage period, in unix timestamp format (seconds since epoch). - `end`: The end time of the usage period, in unix timestamp format (seconds since epoch). - `status`: The status of the eSIM usage event. Possible values include: - `NEW`: The eSIM is newly generated and not yet installed. - `INSTALLED`: The eSIM has been installed on a device. - `ACTIVATED`: The eSIM has been activated and is currently in use. - `INACTIVE`: The eSIM is inactive and not currently in use. - `DELETED`: The eSIM has been deleted from the device. - `REVOKED`: The eSIM has been revoked and is no longer valid. - `EXPIRED`: The eSIM has expired and is no longer valid. - `UNKNOWN`: The status of the eSIM is unknown or not specified. - `package_id`: The unique identifier of the eSIM package. - `package_name`: The name of the eSIM package. - `data_units`: The units used for data allocation, typically "MB" (megabytes). - `voice_units`: The units used for voice allocation, typically "minutes". - `sms_units`: The units used for SMS allocation, typically "sms". - `usage_data`: Object containing usage data, which includes: - `isUnlimited`: Indicates whether the data allocation is unlimited (true/false). - `allowed_data`: The total data allocation in megabytes (MB). - `remaining_data`: The remaining data allocation in megabytes (MB). - `allowed_sms`: The total SMS allocation. - `remaining_sms`: The remaining SMS allocation. - `allowed_voice`: The total voice allocation in minutes. - `remaining_voice`: The remaining voice allocation in minutes. - `usage_list`: An array of usage records, each containing: - `date`: The date of the usage record in ISO 8601 format (YYYY-MM-DD). - `data_used`: The amount of data used in megabytes (MB). - `location`: The location where the usage occurred, represented as a string (e.g., "Afghanistan"). ### Example Payload Here is an example of the eSIM Usage Event webhook payload: ```json { "event_category": "esim", "event_type": "usage", "data": { "timestamp": 1700000000, "iccid": "8991234567890123456", "esim_id": "c2a9cdae-430e-4251-bf65-a84df4562ed9", "reference_id": "ref_1234567890", "start": 1699996800, "end": 1700000400, "status": "ACTIVATED", "package_id": "esim-afghanistan-7days-1gb-all", "package_name": "1 GB - 7 Days", "data_units": "MB", "voice_units": "minutes", "sms_units": "sms", "usage_data": { "isUnlimited": false, "allowed_data": 1024, "remaining_data": 512, "allowed_sms": 100, "remaining_sms": 50, "allowed_voice": 500, "remaining_voice": 250 }, "usage_list": [ { "date": "2023-11-01", "data_used": 100, "location": "Afghanistan" }, { "date": "2023-11-02", "data_used": 200, "location": "Afghanistan" } ] } } ``` ## Mandatory Response You must respond with a 200 OK status code to acknowledge receipt of the webhook. We recommend responding quickly (within 3 seconds) to avoid overloading Roamify's webhook server. We recommend you send response first and then process the webhook data in the background to ensure that you do not miss any webhook events due to processing delays. ### Response Example ```json { "code": 200, "status": "success" } ``` --- # Partner Balance Event > The Partner Balance Event webhook sends notifications about changes in your partner account balance. This allows you to monitor your account balance in real-time and track transactions that affect your balance. Canonical: https://docs.getroamify.com/webhooks/types/partner-balance/ > **Warning:** Before you start using the partner balance event webhook, you need to configure your webhook URL in the Roamify dashboard. This URL will be used to receive notifications about eSIM status changes. ## Overview The Partner Balance Event webhook sends notifications about changes in your partner account balance. This allows you to monitor your account balance in real-time and track transactions that affect your balance. The `event_type` for these events will be either -`low_balance`: this event is triggered when your balance falls below the configured threshold, allowing you to take action to replenish your balance. -`insufficient_balance`: this event is triggered when a transaction is attempted that exceeds your current balance, indicating that the balance is insufficient to cover the transaction. -`top_up`: this event is triggered when you add funds to your partner account, allowing you to track when your balance is replenished. The event will be triggered when: - Your balance falls below the configured threshold. - 75% of the threshold is reached, which can be used to proactively manage your balance. - 50% of the threshold is reached, allowing you to take action before reaching the critical low balance. - 25% of the threshold is reached, which can be useful for early warnings. - A transaction is attempted that exceeds your current balance, resulting in an insufficient balance event. - Funds are added to your partner account, resulting in a top-up event. ## Event Payload The webhook payload contains the following fields: - `event_category`: The category of event. For partner events, this will be `partner`. - `event_type`: The type of event. For partner balance events, this will be any of `low_balance` or `insufficient_balance` or `top_up`. - `data`: The data object containing the partner balance information, which includes: - `timestamp`: The time when the event was triggered, in unix timestamp format (seconds since epoch). - `balance`: The currency balance amount. - `threshold`: The threshold amount that triggered the event, if applicable. - `threshold_percentage`: The percentage of the threshold relative to the total balance, if applicable. - `currency`: The currency of the balance, in ISO 4217 format (e.g., USD, EUR). ### Example Payload Here is an example of the Partner Balance Event webhook payload: ```json { "event_type": "low_balance", "event_category": "partner", "data": { "timestamp": 1673123456, "balance": 50.00, "threshold": 100.00, "threshold_percentage": 50, "currency": "USD" } } ``` ## Mandatory Response You must respond with a 200 OK status code to acknowledge receipt of the webhook. We recommend responding quickly (within 3 seconds) to avoid overloading Roamify's webhook server. We recommend you send response first and then process the webhook data in the background to ensure that you do not miss any webhook events due to processing delays. ### Response Example ```json { "code": 200, "status": "success" } ``` --- # Affiliate Orders Event > The Affiliate Orders Event webhook sends notifications when orders are placed through your affiliate links. This allows you to track conversions, monitor affiliate performance, and calculate commissions in real-time. Canonical: https://docs.getroamify.com/webhooks/types/affiliate-orders/ > **Warning:** Before you start using the affiliate orders event webhook, you need to configure your webhook URL in the Roamify dashboard. This URL will be used to receive notifications about eSIM status changes. ## Overview The Affiliate Orders Event webhook sends notifications when orders are placed through your affiliate links. This allows you to track conversions, monitor affiliate performance, and calculate commissions in real-time. ## Event Payload The webhook payload contains the following fields: - `event_category`: The category of event. For affiliate events, this will be `affiliate`. - `event_type`: The type of event. For affiliate order events, this will be `order`. - `data`: The data object containing the affiliate order information, which includes: - `timestamp`: The time when the event was triggered, in unix timestamp format (seconds since epoch). - `partner_name`: The name of the affiliate partner. - `partner_id`: The unique identifier of the affiliate account. - `commission_rate`: The commission rate for the order, expressed as a percentage. - `commission_amount`: The total commission amount earned from the order. - `order_data`: Object containing order information: - `order_id`: The unique identifier of the order. - `order_total`: The total amount of the order. - `order_adjustment`: The adjusted total amount of the order after any redeemable credits or adjustments. - `order_subtotal`: The subtotal amount of the order before any discounts. - `order_discount`: The discount applied to the order, if any. - `order_currency`: The currency of the order, in ISO 4217 format (e.g., USD, EUR). - `order_status`: The status of the order. Possible values include: - `completed`: The order has been completed successfully. - `pending`: The order is pending processing. - `failed`: The order has failed. - `refunded`: The order has been refunded. - `order_items`: Array of items included in the order, each containing: - `item_id`: The unique identifier of the item. - `item_name`: The name of the item. - `item_quantity`: The quantity of the item ordered. - `item_price`: The price of the item in USD. ### Example Payload Here is an example of the Affiliate Orders Event webhook payload: ```json { "event_category": "affiliate", "event_type": "order", "data": { "timestamp": 1700000000, "partner_name": "ACME Affiliate", "partner_id": "6ImuFenqQbPjYaYyhWRIvkYdsF42", "commission_rate": 10.0, "commission_amount": 5.00, "order_data": { "order_id": "c2a9cdae-430e-4251-bf65-a84df4562ed9", "order_total": 49.99, "order_adjustment": 0.00, "order_subtotal": 49.99, "order_discount": 0.00, "order_currency": "USD", "order_status": "completed", "order_items": [ { "item_id": "esim-afghanistan-7days-1gb-all", "item_name": "1 GB - 7 Days", "item_quantity": 1, "item_price": 49.99 } ] } } } ``` ## Mandatory Response You must respond with a 200 OK status code to acknowledge receipt of the webhook. We recommend responding quickly (within 3 seconds) to avoid overloading Roamify's webhook server. We recommend you send response first and then process the webhook data in the background to ensure that you do not miss any webhook events due to processing delays. ### Response Example ```json { "code": 200, "status": "success" } ``` --- # Refund Event > The Refund Event webhook tells you when Roamify support has decided one of your refund requests, so you do not need to poll GET /api/refund. Canonical: https://docs.getroamify.com/webhooks/types/refund/ > **Warning:** Before you start using the refund event webhook, you need to configure your webhook URL in the Roamify dashboard. This URL will be used to receive notifications about your [refund requests](https://docs.getroamify.com/esims/refunds/). ## Overview The Refund Event webhook tells you when Roamify support has decided one of your refund requests, so you do not need to poll [`GET /api/refund`](https://docs.getroamify.com/esims/refunds/#get-a-refund-request). The `event_type` for these events will be either: - `refunded`: the request was approved and the refund is complete. - `rejected`: the request was rejected. `rejection_reason` tells you why. No event is sent when a request is created, approved for processing or cancelled. ## Event Payload The webhook payload contains the following fields: - `event_id`: A stable identifier of this event, in the form `rfd_{refund_id}_{event_type}`. It is the same every time the same event is delivered. - `event_category`: The category of event. For refund events, this will be `refund`. - `event_type`: `refunded` or `rejected`. - `data`: The data object containing the refund request, which includes: - `timestamp`: The time when the event was sent, in unix timestamp format (seconds since epoch). - `refund_id`: The ID of the refund request. - `status`: The status of the request: `REFUNDED` or `REJECTED`. - `product_type`: The refunded product. Always `esim` for now. - `esim_id`: The unique identifier of the eSIM. - `iccid`: The ICCID of the eSIM, or an empty string if the eSIM was never redeemed. - `reference_id`: The `referenceId` of the order, or an empty string. - `order_id`: The ID of the order the eSIM belongs to. - `reason`: The [reason](https://docs.getroamify.com/esims/refunds/#refund-reasons) you gave. - `amount`: The amount refunded, in `currency`. `null` for `rejected`. - `currency`: The currency of `amount`. `null` for `rejected`. - `balance_credit_usd`: The amount credited to your balance in USD, when the order was paid with your balance. Otherwise `null`. - `transaction_id`: The ID of the refund of the payment. `null` for `rejected`. - `payment_method`: How the order was paid, in lowercase. `null` for `rejected`. - `rejection_reason`: Why support rejected the request, for `rejected` only. Otherwise `null`. - `rejection_note`: Support's explanation, for `rejected` only, when given. Otherwise `null`. - `requested_at`: When the request was created, in unix timestamp format. - `processed_at`: When the request was decided, in unix timestamp format, or `null`. ## Example Payloads A refunded request: ```json { "event_id": "rfd_9f3c1c0e-6a52-4b8e-9d0a-0d3f1e2a7c11_refunded", "event_type": "refunded", "event_category": "refund", "data": { "timestamp": 1762571000, "refund_id": "9f3c1c0e-6a52-4b8e-9d0a-0d3f1e2a7c11", "status": "REFUNDED", "product_type": "esim", "esim_id": "74b73655-0fa7-49e4-bab6-eb80b5522104", "iccid": "8935303411619613", "reference_id": "ref_1234567890", "order_id": "ee9357c2-0000-0000-0000-000000000000", "reason": "UNABLE_TO_INSTALL", "amount": 1.39, "currency": "USD", "balance_credit_usd": 1.39, "transaction_id": "rf_0000000000", "payment_method": "balance", "rejection_reason": null, "rejection_note": null, "requested_at": 1762570000, "processed_at": 1762570990 } } ``` A rejected request: ```json { "event_id": "rfd_2b1d7e40-0000-0000-0000-000000000000_rejected", "event_type": "rejected", "event_category": "refund", "data": { "timestamp": 1762571000, "refund_id": "2b1d7e40-0000-0000-0000-000000000000", "status": "REJECTED", "product_type": "esim", "esim_id": "c2a9cdae-430e-4251-bf65-a84df4562ed9", "iccid": "8991234567890123456", "reference_id": "", "order_id": "ee9357c2-0000-0000-0000-000000000000", "reason": "CUSTOMER_REQUEST", "amount": null, "currency": null, "balance_credit_usd": null, "transaction_id": null, "payment_method": null, "rejection_reason": "DATA_USED", "rejection_note": null, "requested_at": 1762570000, "processed_at": 1762570990 } } ``` ## Handling Duplicates A delivery can be repeated, for example when Roamify support resends it or after a crash is recovered. Use `event_id` to drop an event you have already processed. ## Mandatory Response You must respond with a 200 OK status code to acknowledge receipt of the webhook. We recommend responding quickly (within 3 seconds) and processing the data in the background. ### Response Example ```json { "code": 200, "status": "success" } ``` --- # Affiliate API Overview > How to integrate the Roamify Affiliate API and earn commissions on every sale made through your referral links. Canonical: https://docs.getroamify.com/affiliate/overview/ ## Affiliate Overview Roamify Affiliate API for a partner is straightforward and easy to use. You only need to follow a few steps to integrate Roamify Affiliate API into your application and start earning commissions on every sale you make. > **Warning:** Customer must click your referral link to be able to track the sale and reward you with the commission regardless of promo code used. - Step 1: Authentication: You need to have your Access Token to make requests to Roamify eSIM API. You can get your Access Token from [Roamify Partner Portal](https://partner.getroamify.com/sign-in). - Step 2: Get eSIM Packages: You will be assigned a dedicated endpoint to get a list of eSIM packages, which includes all available eSIM packages and your referral link for each package. - Step 3: Present to Customers: You can present the eSIM packages to your customers using the referral link you received in the previous step. - Step 4: Track rewards: You can track your rewards in the Roamify Partner Portal. You will see how many sales you made, how much commission you earned, and more. ## Recommended steps to use Affiliate API ### Step 1: Authentication You need to have your Access Token to make requests to Roamify eSIM API. You can get your Access Token from [Roamify Partner Portal](https://partner.getroamify.com/sign-in). If you don't have an account, you can sign up [here](https://partner.getroamify.com/sign-up) or contact us at [support@getroamify.com](mailto:support@getroamify.com). Our token is a Bearer token, which means you need to include it in the `Authorization` header of your requests. ### Step 2: Get eSIM Packages With your dedicated endpoint, you can get a list of eSIM packages that are available for you to promote. You need to send a `GET` request to the `/api/esim/{partnerSlug}/packages` endpoint. This endpoint will return a list of all available eSIM packages with referral links for each package. The referral link will include your unique identifier, which will be used to track the sales you make. #### Usage - how to get eSIM packages Here is an example of how you can get a list of eSIM packages using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/esimradar/packages' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/esimradar/packages', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` You can read more about this endpoint [here](https://docs.getroamify.com/affiliate/affiliate-links/). ### Step 3: Present to Customers Once you have the list of eSIM packages, you can then find each country's eSIM options in the packages array of the response. - Red box indicates the country name and code. - Yellow box indicates the eSIM packages available for that country. - Blue box indicates the referral link for each package. ![eSIM Packages](https://imagedelivery.net/uUE6LNs_ci8SFBH4_n66dQ/docs/affiliate-response/public) ### Step 4: Track rewards You can track your rewards in the Roamify Partner Portal. You will see how many sales you made, how much commission you earned, and more. - Red box click to withdraw your earnings. - Yellow box click to view your earnings history. - Blue box click to add or update your bank account details for payouts. ![eSIM Packages](https://imagedelivery.net/uUE6LNs_ci8SFBH4_n66dQ/docs/affiliate-reward/public) ### The End That's it! You have successfully integrated Roamify Affiliate API into your application. If you have any questions or need help, feel free to contact us at [support@getroamify.com](mailto:support@getroamify.com). --- # Affiliate API - Get Affiliate Links > Once onboarded, you will be assigned an dedicated API endpoint to fetch your affiliate links. Canonical: https://docs.getroamify.com/affiliate/affiliate-links/ ## Get Affiliate Links Once onboarded, you will be assigned an dedicated API endpoint to fetch your affiliate links. You need to send a `GET` request to the `/api/esim/{unique name}/packages` endpoint. This endpoint will return a list of all available eSIM packages with referral links for each package. The referral link will include your unique identifier, which will be used to track the sales you make. #### Usage - how to get eSIM packages Here is an example of how you can get a list of eSIM packages using Roamify eSIM API. **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/esim/esimradar/packages' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/esim/packages', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ## Sample Response > **Warning:** The response included will be in a > - `packages`: the response data will contain an array of packages for each country. > - `countryName`: the name of the country. > - `packages`: for each country object, there will be an array of packages available for the destination. > - `link`: the referral link for each package, which includes your unique identifier. ```json { "status": "success", "data": { "packages": [ { "id": "c21a8a20-5646-4b26-8df3-f5d37147790f", "countryName": "Afghanistan", "countryCode": "AF", "region": "local", "packages": [ { "id": "515e279d-0c78-4d65-a45b-e7c12ca2ebea", "name": "1 GB - 7 Days", "price": 5.5, "validity": 7, "plan": "data-only", "dataAmount": 1024, "dataUnit": "MB", "data": "1 GB", "isUnlimited": false, "isActivated": true, "withKyc": false, "withTopUp": true, "withSMS": false, "withCall": false, "withHotspot": true, "smsUnit": "", "callUnit": "", "smsLabel": "", "callLabel": "", "smsAmount": -1, "callAmount": -1, "coverages": [ "Afghanistan" ], "networks": [ "3G", "4G" ], "signals": [ { "code": "AF", "name": "Afghanistan", "networks": [ "3G", "4G" ], "carriers": [ "Roshan" ] } ], "link": "https://www.getroamify.com/esim/afghanistan/?referrer=5Spg1dU0XRhHeUJGx6JFak4zccH2" }, { "id": "92014f96-e231-4ea8-afbc-f5434dd78eee", "name": "3 GB - 15 Days", "price": 14.5, "validity": 15, "plan": "data-only", "dataAmount": 3072, "dataUnit": "MB", "data": "3 GB", "isUnlimited": false, "isActivated": true, "withKyc": false, "withTopUp": true, "withSMS": false, "withCall": false, "withHotspot": true, "smsUnit": "", "callUnit": "", "smsLabel": "", "callLabel": "", "smsAmount": -1, "callAmount": -1, "coverages": [ "Afghanistan" ], "networks": [ "3G", "4G" ], "signals": [ { "code": "AF", "name": "Afghanistan", "networks": [ "3G", "4G" ], "carriers": [ "Roshan" ] } ], "link": "https://www.getroamify.com/esim/afghanistan/?referrer=5Spg1dU0XRhHeUJGx6JFak4zccH2" }, { "id": "b20dbf4f-415a-4545-9109-3ba9739f873b", "name": "3 GB - 30 Days", "price": 15.5, "validity": 30, "plan": "data-only", "dataAmount": 3072, "dataUnit": "MB", "data": "3 GB", "isUnlimited": false, "isActivated": true, "withKyc": false, "withTopUp": true, "withSMS": false, "withCall": false, "withHotspot": true, "smsUnit": "", "callUnit": "", "smsLabel": "", "callLabel": "", "smsAmount": -1, "callAmount": -1, "coverages": [ "Afghanistan" ], "networks": [ "3G", "4G" ], "signals": [ { "code": "AF", "name": "Afghanistan", "networks": [ "3G", "4G" ], "carriers": [ "Roshan" ] } ], "link": "https://www.getroamify.com/esim/afghanistan/?referrer=5Spg1dU0XRhHeUJGx6JFak4zccH2" } ] } ] } } ``` ### The End That's it! You have successfully integrated Roamify Affiliate API into your application. If you have any questions or need help, feel free to contact us at [support@getroamify.com](mailto:support@getroamify.com). --- # Exchange Rates Overview > Roamify Exchange Rates API provides a simple and reliable way to access real-time and historical exchange-rate data across a wide range of currencies. Canonical: https://docs.getroamify.com/exchange-rates/overview/ Roamify Exchange Rates API provides a simple and reliable way to access real-time and historical exchange-rate data across a wide range of currencies. We support hundreds of currencies worldwide, making it easier to build financial applications, run market analysis, and perform currency conversion. We offer a variety of endpoints to suit your needs, including: - **Supported Currencies**: Get a list of all currencies supported by the Roamify Exchange Rates API. - **Latest Exchange Rates**: Get the most up-to-date exchange rates for a specific currency or a list of currencies. - **Historical Exchange Rates**: Access exchange rates for a specific date in the past. - **Currency Conversion**: Convert an amount from one currency to another using the latest exchange rates. - **Time-Series Data**: Retrieve exchange rates over a specified time period for trend analysis. --- # Get Supported Currencie(s) > To get a list of all supported currencies, you need to send a GET request to the /api/currencies endpoint. Canonical: https://docs.getroamify.com/exchange-rates/get-currencies/ ## Get Supported Currencies To get a list of all supported currencies, you need to send a `GET` request to the `/api/currencies` endpoint. The request parameters does not require any parameters. ## Usage - fetch all supported currencies Here is an example of how to fetch all supported currencies using curl and JavaScript (Axios): **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/currencies' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/currencies', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` --- # Get Exchange Rates > To get the latest exchange rates, you need to send a GET request to the /api/rates endpoint. Canonical: https://docs.getroamify.com/exchange-rates/get-exchange-rates/ ## Get latest exchange rates To get the latest exchange rates, you need to send a `GET` request to the `/api/rates` endpoint. The request body should contain the following parameters: - `source`: The source currency code. This is a required field of type `string`. - `target`: The target currency code. This is a required field of type `string`. - `datetime`: optional, The date and time for which you want the exchange rate. This is an optional field of type `string` in ISO 8601 format. If not provided, the latest exchange rate will be used. - `from`: optional, The start date for the historical data. This is an optional field of type `string` in ISO 8601 format. - `to`: optional, The end date for the historical data. This is an optional field of type `string` in ISO 8601 format. - `interval`: optional, The interval for the historical data. This is an optional field of type `string`. Possible values are `day`, `hour`, `minute`. > **Warning:** If datetime was provided, please do not provide from or to or interval parameters. > OTHERWISE, the API will return an error. > **Warning:** If from or to were provided, please also provide interval parameter. > And please do not provide datetime parameter. > OTHERWISE, the API will return an error. ## Usage - fetch all latest exchange rates Here is an example of how to fetch all latest exchange rates using curl and JavaScript (Axios): **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/rates' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/rates', { headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ## Convert Currency To convert an amount from one currency to another, you need to send a `GET` request to the `/api/rates` endpoint. The request parameters should contain the following: - `source`: The source currency code. This is a required field of type `string`. - `target`: The target currency code. This is a required field of type `string`. - `datetime`: optional, The date and time for which you want the exchange rate. This is an optional field of type `string` in ISO 8601 format. If not provided, the latest exchange rate will be used. ## Usage - convert currency Here is an example of how to convert currency using curl and JavaScript (Axios): **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/rates?source=USD&target=EUR&datetime=2023-01-01T00:00:00Z' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/rates', { params: { source: 'USD', target: 'EUR', datetime: '2023-01-01T00:00:00Z' }, headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` ## Get Historical Exchange Rates To get historical exchange rates for a specific date, you need to send a `GET` request to the `/api/rates` endpoint. The request parameters should contain the following: - `source`: The source currency code. This is a required field of type `string`. - `target`: The target currency code. This is a required field of type `string`. - `from`: The start date for the historical data. This is a required field of type `string` in ISO 8601 format. - `to`: The end date for the historical data. This is a required field of type - `interval`: The interval for the historical data. This is an optional field of type `string`. Possible values are `day`, `hour`, `minute`. ## Usage - get historical exchange rates Here is an example of how to get historical exchange rates using curl and JavaScript (Axios): **curl** ```bash curl --request GET \ --url 'https://api-dev.getroamify.com/api/rates?source=USD&target=EUR&from=2023-01-01T00:00:00Z&to=2023-01-07T00:00:00Z&interval=day' \ --header 'Authorization: Bearer {{token}}' \ --header 'Content-Type: application/json' ``` **javascript** ```javascript const axios = require('axios'); axios.get('https://api-dev.getroamify.com/api/rates', { params: { source: 'USD', target: 'EUR', from: '2023-01-01T00:00:00Z', to: '2023-01-07T00:00:00Z', interval: 'day' }, headers: { 'Authorization': 'Bearer {access token here}', 'Content-Type': 'application/json' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error(error); }); ``` --- # API Errors > Reference of Roamify API errors by HTTP status and module: authentication errors, refund error codes, voucher and insurance errors, with what each means and which ones to retry. Canonical: https://docs.getroamify.com/errors/ This page collects the errors documented across the Roamify API in one place, grouped by HTTP status and module. Each module page has the full error table next to its endpoints. Use this page to find an error message you received and decide what to do next. ## Reading an error Refund errors have the form `{ "message": "...", "status": "failed", "code": "..." }`, so you can branch on `code`. The voucher and insurance pages document the HTTP status and the `message` text for each error. Match on the status first and treat the message as human-readable text. ## Authentication errors These apply to every endpoint. See [Authentication](https://docs.getroamify.com/authentication/#response-and-error-handling). | HTTP | Meaning | What to do | | --- | --- | --- | | `401` | Missing token, malformed `Authorization` header, or invalid or rotated token. | Send `Authorization: Bearer ` with the current token from the Partner Portal. A rotated token stops working immediately. | | `403` | The token is valid but does not have permission for the requested action. | Check that the feature is enabled for your API key. Contact support if it should be. | | `500` | Internal server error. | Retry with backoff, or contact support if it persists. | ## Which errors to retry - **`400`, `404`, `422`** describe the request itself. Fix the request instead of retrying it unchanged. - **`500`** and **`503`** are temporary: retry with backoff. The refund `500` codes say "Try again later", and the voucher `503` says to retry shortly or contact support. - **`502`** on `POST /api/insurance/apply` says in its message whether to try again later or to verify the traveller details. Issuance is not idempotent, so check the policy first; see [Retries and idempotency](https://docs.getroamify.com/guides/retries-and-idempotency/#insurance-issuance-is-not-idempotent). - **`409`** means the state of a resource conflicts with the request. Read the message before retrying. In particular, issuing an insurance policy is not idempotent: do not blindly retry `POST /api/insurance/apply` after a `409`. See [Retries and idempotency](https://docs.getroamify.com/guides/retries-and-idempotency/). ## Refund errors From [Refunds](https://docs.getroamify.com/esims/refunds/#errors). | HTTP | `code` | Meaning | | --- | --- | --- | | `400` | `INVALID_PARAMETER` | A parameter is missing or invalid. For example both or neither of `iccid` and `esimId`, more than 20 bulk entries, or an invalid `limit`. | | `400` | `REFUND_INVALID_REASON` | `reason` is not one of the supported reasons. | | `400` | `REFUND_NOTES_REQUIRED` | `reason` is `OTHER` without `notes` of at least 10 characters. | | `404` | `ESIM_NOT_FOUND` | No eSIM of your account has that `iccid` or `esimId`. | | `404` | `REFUND_REQUEST_NOT_FOUND` | No request matches the `refundId`, `esimId` or `iccid`. | | `409` | `REFUND_REQUEST_ALREADY_EXISTS` | The eSIM already has an open request. The body carries its `refundId`. | | `409` | `REFUND_INVALID_STATUS` | Only a `PENDING` request can be cancelled. | | `422` | `REFUND_NOT_ELIGIBLE` | The eSIM cannot be refunded. `reasons` lists why, for example `ESIM_ALREADY_REFUNDED`. | | `422` | `REFUND_PRODUCT_TYPE_NOT_SUPPORTED` | Only `esim` is supported. | | `500` | `REFUND_CREATE_FAILED`, `REFUND_GET_FAILED`, `REFUND_CANCEL_FAILED`, `REFUND_SERVICE_NOT_CONFIGURED` | A temporary failure. Try again later. | ## Voucher errors From [Vouchers](https://docs.getroamify.com/esims/vouchers/). The voucher endpoints are enabled per API key for selected partners. | HTTP | Message | Endpoints | | --- | --- | --- | | `400` | "packageId" is required | Create vouchers | | `400` | "quantity" must be a number, must be greater than or equal to 1, or must be less than or equal to 10 | Create vouchers | | `400` | Invalid quantity, please provide a whole number of at least 1 | Create vouchers | | `400` | "status" must be one of [ACTIVE, REDEEMED, CANCELLED] | List vouchers | | `400` | "limit" must be greater than or equal to 1 | List vouchers | | `401` | Unauthorized, please make sure your have provided the correct api key | All voucher endpoints | | `403` | Voucher creation is not enabled for this API key, please contact support to have it enabled | All voucher endpoints | | `403` | This packageId is not allowed for voucher creation with this API key, please use one of the packages allowed for your account | Create vouchers | | `404` | Voucher not found, please make sure you have provided the correct voucher ID or code | Get, get by code, revoke | | `409` | Vouchers with the same referenceId already exist, please make sure you have provided a unique referenceId | Create vouchers | | `409` | Voucher has already been redeemed and can no longer be revoked | Revoke | | `409` | Only active vouchers can be revoked, this voucher is no longer active | Revoke | | `500` | Failed to create, get or revoke vouchers, please try again later | All voucher endpoints | | `503` | Vouchers cannot be issued at the moment, please retry shortly or contact support | Create vouchers | A voucher that belongs to another partner is reported as `404`, not `403`. ## Insurance errors From [Insurance orders](https://docs.getroamify.com/insurance/orders/#errors), [policies](https://docs.getroamify.com/insurance/policies/#errors) and [packages](https://docs.getroamify.com/insurance/packages/#errors). Insurance is available to travellers who are citizens or residents of Saudi Arabia. ### Orders | HTTP | Message | | --- | --- | | `400` | Only insurance packages can be ordered through this endpoint. Every packageId must start with 'insurance-'; use POST /esim/order for eSIM packages | | `400` | Invalid quantity, please make sure your have provided the correct quantity | | `400` | Exceed maximum total quantity of 5, please make sure your have provided the correct quantity and the quantity does not exceed the maximum allowed quantity 5 | | `400` | Missing required fields, please provide all required fields according to the documentation | | `404` | Order not found, please make sure your have provided the correct orderId or referenceId | | `409` | Order with the same referenceId already exists, please make sure your have provided a unique referenceId | | `409` | Insufficient partner balance, please recharge it first before placing order | An eSIM order ID returns `404` on the insurance order endpoint, even though the order exists. ### Applying and reading policies | HTTP | Message | | --- | --- | | `400` | This insurance package requires idNumber, passportNumber, dateOfBirth or phoneNumber, please provide insurer.idNumber, insurer.passportNumber, insurer.dateOfBirth (DD/MM/YYYY) or mobile (international format without the leading '+'). Each message names the one field that the package requires and you left out | | `400` | Invalid traveller name, nationality, idNumber, passportNumber, dateOfBirth, mobile or email | | `400` | Invalid startDate or endDate. Use DD/MM/YYYY, and make sure endDate is after startDate | | `400` | Coverage cannot start today, please provide a startDate at least 1 day in the future | | `400` | The requested coverage window is longer than the purchased package allows, please shorten it or buy a longer package | | `400` | Insurance details failed validation, please make sure you have provided all the required traveller fields | | `404` | Insurance policy not found, please make sure you have provided the correct policyId | | `409` | This insurance policy has already been issued and cannot be applied again | | `409` | This insurance policy is currently being issued, please retry with GET /insurance/policy?policyId=id after some time | | `409` | A policy has already been issued for this reference, please contact support if you believe this is an error | | `502` | Failed to issue the insurance policy, please try again later or contact support | | `502` | The insurance provider rejected the request, please verify the traveller details and try again | A policy that belongs to another partner is reported as `404`, not `403`. ### Packages | HTTP | Message | | --- | --- | | `404` | Insurance package not found, please make sure you have provided a valid packageId | | `500` | Failed to get insurance packages, please try again later | --- # Frequently Asked Questions > Answers to common questions about the Roamify API: base URLs, authentication, supported countries, webhook verification, refund requests and insurance eligibility. Canonical: https://docs.getroamify.com/faq/ Short answers to the questions partners ask most often. Each answer links to the page with the full details. ## Getting started ### What is the Roamify API? Roamify API is a REST API that lets you buy eSIMs, manage eSIM lifecycle events, issue travel insurance policies, check your partner balance and work with exchange rates. Start with the [introduction](https://docs.getroamify.com/). ### How many countries does Roamify eSIM cover? Roamify eSIM covers 200+ countries. Call `GET /api/esim/countries` to list the countries you can query packages for. See [Countries](https://docs.getroamify.com/esims/countries/). ### What are the Roamify API base URLs? The development base URL is `https://api-dev.getroamify.com` and the production base URL is `https://api.getroamify.com`. See [Authentication](https://docs.getroamify.com/authentication/). ### How do I check whether the Roamify API is up? Send a `GET` request to `/api/health` on either environment. The response contains the status of the API. See [Health Check](https://docs.getroamify.com/health/). ## Authentication ### How do I authenticate with the Roamify API? Send your access token in the `Authorization` header as `Bearer ` on every authenticated request. You get the token, called an API Key in the Partner Portal, from the Roamify Partner Portal. See [Authentication](https://docs.getroamify.com/authentication/). ### Do Roamify access tokens expire? No. An access token is long-lived and stays valid until you rotate it in the Partner Portal under API Keys. After rotation, the old token is invalidated immediately. See [Authentication](https://docs.getroamify.com/authentication/#token-rotation). ## Webhooks ### How do I verify that a webhook came from Roamify? Each webhook request includes a signature in the `X-Roamify-Signature` header. Compute an HMAC-SHA256 of the raw request body with your secret key and compare it to the header value. See [Webhook Verification](https://docs.getroamify.com/webhooks/verification/). ### What must my webhook endpoint do? It must be reachable over HTTPS with a valid SSL certificate and answer `200 OK` within 3 seconds. If it does not, Roamify retries the webhook up to 3 times at increasing intervals. See [Webhook Verification](https://docs.getroamify.com/webhooks/verification/#pre-requisites). ## Refunds and insurance ### Does a refund request refund my order immediately? No. A refund request asks Roamify support to refund one eSIM of one of your orders. The refund happens only after support approves it, and submitting a request never moves money by itself or revokes the eSIM. See [Refunds](https://docs.getroamify.com/esims/refunds/). ### Who can buy Roamify travel insurance? Insurance packages may only be sold to travellers who are citizens or residents of Saudi Arabia. See [Insurance Overview](https://docs.getroamify.com/insurance/overview/). --- # Changelog > Dated changelog of the Roamify API documentation: new modules, endpoints and webhook events, from the first eSIM docs to refunds, vouchers, insurance and the AI-readable docs. Canonical: https://docs.getroamify.com/changelog/ What changed in the Roamify API documentation, newest first. Dates are when the change was published to the docs. Each entry links to the page it affects. ## November 2026 - **AI-readable docs.** Every page is available as markdown by adding `.md` to its URL (for example `/esims/orders.md`) or by sending `Accept: text/markdown`. Pages have a "Copy page" menu. The docs publish [`/llms.txt`](https://docs.getroamify.com/llms.txt), [`/llms-full.txt`](https://docs.getroamify.com/llms-full.txt) and an [OpenAPI description](https://docs.getroamify.com/openapi.json). Added the [FAQ](https://docs.getroamify.com/faq/), [API Errors](https://docs.getroamify.com/errors/) and two [guides](https://docs.getroamify.com/guides/sell-esims-in-your-app/). - **2026-11-08: Refunds.** New [Refunds](https://docs.getroamify.com/esims/refunds/) page for creating, bulk-creating, getting, listing and cancelling refund requests, and a new [Refund webhook event](https://docs.getroamify.com/webhooks/types/refund/) (`refunded` and `rejected`). ## September 2026 - **2026-09-01: Vouchers.** New [Vouchers](https://docs.getroamify.com/esims/vouchers/) page: mint batches of redeemable voucher codes, list, get, get by code and revoke. Available to selected partners, enabled per API key. ## July 2026 - **2026-07-10: Insurance module.** New [Insurance](https://docs.getroamify.com/insurance/overview/) module with [countries](https://docs.getroamify.com/insurance/countries/), [packages](https://docs.getroamify.com/insurance/packages/), [orders](https://docs.getroamify.com/insurance/orders/) and [policies](https://docs.getroamify.com/insurance/policies/). ## June 2026 - **2026-06-09: Package pagination and top-up flow.** [eSIM packages](https://docs.getroamify.com/esims/packages/) are paginated with `page` and `pageSize`, and the country parameters are clarified. New [Top-up flow](https://docs.getroamify.com/esims/topup-flow/) page, and [Send SMS](https://docs.getroamify.com/esims/esims/#send-sms) to an eSIM was added to the eSIM management docs. Webhook and authentication pages were reworked. - **2026-06-09: Gift card API removed.** The gift card API documentation and its references were removed. ## January 2026 - **2026-01-07: eSIMs Link.** New [eSIMs Link](https://docs.getroamify.com/esims/esims-link/) page for generating shareable installation links. - **2026-01-09:** Added the Android quick install URL. ## December 2025 - **2025-12-03: Exchange rates.** New [Exchange Rates](https://docs.getroamify.com/exchange-rates/overview/) module with [supported currencies](https://docs.getroamify.com/exchange-rates/get-currencies/) and [exchange rates](https://docs.getroamify.com/exchange-rates/get-exchange-rates/). ## August 2025 - **2025-08-29: eSIM events.** New [eSIM Events](https://docs.getroamify.com/esims/events/) page for the lifecycle event history of an eSIM. ## July 2025 - **2025-07-08: Webhooks.** New [Webhooks](https://docs.getroamify.com/webhooks/overview/) module with [eSIM status](https://docs.getroamify.com/webhooks/types/esim-status/), [eSIM usage](https://docs.getroamify.com/webhooks/types/esim-usage/), [partner balance](https://docs.getroamify.com/webhooks/types/partner-balance/) and [affiliate order](https://docs.getroamify.com/webhooks/types/affiliate-orders/) events. - **2025-07-30: Webhook verification.** New [Webhook verification](https://docs.getroamify.com/webhooks/verification/) page for the `X-Roamify-Signature` header. ## June 2025 - **2025-06-18: Affiliate module.** New [Affiliate](https://docs.getroamify.com/affiliate/overview/) module with [affiliate links](https://docs.getroamify.com/affiliate/affiliate-links/). ## November 2024 - **2024-11-01: First version.** First published version of the Roamify API documentation.