# Create an eSIM order

> Create one eSIM, debit your agency balance (live only) and return the eSIM with its activation data.

Endpoint: `POST /esims/orders`

Source: https://fx-port.com/docs/api/create-esim-order

`POST /api/v1/esims/orders`

Requires an API key with **Read+Write** permission: a Read key is refused with `read_write_permission_required`.

Prices are computed on the server. `quantity` and every price field of the body are ignored (the response lists them in `warnings`), and every order creates exactly one eSIM: call the endpoint once per eSIM. The names of the traveler are optional and are not restricted to ASCII.

Send `Content-Type: application/json` (in any case). A form-encoded body also works, but a JSON body sent without the JSON content type is read as an empty form and answered with `missing_field`.

There is no idempotency key. While an order for a package is being processed, another order for the same package is refused with `duplicate_request`; on live so is an order for a package when the most recent confirmed order of your agency is for that same package and is less than 60 seconds old, whoever the traveler is. If a call times out, look for the booking with `GET /esims/bookings?search=<traveler email>` before you send the order again.

If the supplier refuses the order the response has the supplier's status, nothing is debited and a `failed` booking is saved. In the sandbox the supplier creates a real test eSIM with a dummy activation code, no balance is debited, and the response (including its error bodies) carries `balanceDebited`, `transactionCreated` and `payoutCreated`, all `false`, with a snake_case twin each.

## Request

### Body

Required, JSON.

- `package_id` (string, required): The `id` of a package from the catalog (its `package_id`). At most 128 characters.
- `email` (string, required): The traveler's email address. FX-Port asks the supplier to send the QR code and a sharing link to it. At most 254 characters.
- `phone` (string, required): The traveler's phone number: at least 3 digits. Kept on the booking: it is not sent to the supplier. At most 32 characters.
- `first_name` (string, optional): The traveler's first name. Any characters are accepted. Kept on the booking only. At most 100 characters.
- `last_name` (string, optional): The traveler's last name. Any characters are accepted. Kept on the booking only. At most 100 characters.
- `description` (string, optional): A note for your own records. It is sent to the supplier with the order and returned as `order.description`. At most 500 characters.
- `agent_id` (string (uuid), optional): The id of the user of your agency who places the order. Optional: it appears on the booking as `agent_id`. A user of another agency is refused.
- `user_id` (string (uuid), deprecated, optional): Legacy name: use `agent_id`. Previous name of `agent_id`.
- `customer_email` (string, deprecated, optional): Legacy name: use `email`. The name the booking rows use for `email`. Still accepted.
- `customer_phone` (string, deprecated, optional): Legacy name: use `phone`. The name the booking rows use for `phone`. Still accepted.
- `customer_first_name` (string, deprecated, optional): Legacy name: use `first_name`. The name the booking rows use for `first_name`. Still accepted.
- `customer_last_name` (string, deprecated, optional): Legacy name: use `last_name`. The name the booking rows use for `last_name`. Still accepted.

### Example request

```bash
curl https://api.fx-port.com/api/v1/esims/orders \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "package_id": "france-in-7days-1gb",
  "first_name": "John",
  "last_name": "Smith",
  "email": "traveler@example.com",
  "phone": "+15555550123",
  "description": "Order for John Smith"
}'
```

## Response

### 200: The order was placed. The response has status 200, not 201.

- `success` (boolean): `true` when the order was placed.
- `data` (object): The order and its eSIM. `sim` is null when the supplier has not returned the eSIM yet: ask for the setup instructions later.
  - `order` (object): The order, with the amounts in your agency currency.
    - `code` (string, deprecated): Legacy name: use `order_id`. The supplier's order code, for example `20261101-106852`. Booking rows call it `order_id`. The older name of `order_id`: same value, use `order_id`.
    - `order_id` (string): The supplier's order code, for example `20261101-106852`. Booking rows call it `order_id`. The snake_case twin of `code`, with the same value: use this name.
    - `package_id` (string): The package that was ordered.
    - `type` (string): Always `sim`.
    - `description` (string, nullable): The `description` you sent.
    - `esim_type` (string): Kind of eSIM plan, for example `local`.
    - `created_at` (string): When the supplier created the order: `YYYY-MM-DD HH:MM:SS`, UTC, without a zone.
    - `package` (string): Name of the package: operator, data allowance and validity.
    - `data` (string): Data allowance as text, for example `1 GB`.
    - `validity` (integer): Validity in days.
    - `price` (number): What the traveler pays, in your agency currency. Equal to `total_price` here (in the catalog, `price` is a different amount: see the pricing guide).
    - `currency` (string): Currency of the amounts: the settlement currency of your agency.
    - `total_price` (number): The price to charge the traveler, in your agency currency. Your commission is `total_price` minus the amount debited from your balance.
    - `retail_price` (number): The price to charge the traveler, in your agency currency. Your commission is `total_price` minus the amount debited from your balance. Another name of `total_price`, with the same value.
    - `agency_currency` (string): Same as `currency`.
    - `supplier_currency` (string): Currency the supplier prices the package in.
    - `exchange_rate` (number): Rate used to convert `supplier_currency` to your agency currency.
    - `voice` (integer, nullable): Voice minutes included, or null.
    - `text` (integer, nullable): SMS included, or null.
    - `coverage` (string): Coverage text taken from the installation instructions. Can be an empty string.
  - `sim` (object, nullable): The eSIM that was created. Treat the activation data as a secret of the traveler.
    - `iccid` (string): The ICCID of the eSIM. Keep it: usage, topups and instructions are asked by ICCID.
    - `created_at` (string, nullable): When the supplier created the eSIM: `YYYY-MM-DD HH:MM:SS`, UTC, without a zone.
    - `lpa` (string, nullable): Host of the SM-DP+ server (the LPA address).
    - `imsis` (string, nullable): IMSI of the profile, when the supplier returns it.
    - `qrcode` (string, nullable): The activation string the QR code encodes: `LPA:1$<host>$<activation code>`. Sensitive: show it only to the traveler.
    - `qrcode_url` (string, nullable): Link to the QR code image.
    - `direct_apple_installation_url` (string, nullable): Link that starts the installation on an iPhone.
    - `apn_type` (string, nullable): `automatic` or `manual`.
    - `apn_value` (string, nullable): The APN to enter on a manual setup, or `N/A`.
    - `apn` (object, nullable): APN settings per operating system.
      - `ios` (object): Access point name (APN) settings for one operating system.
        - `apn_type` (string): `automatic` when the device configures the access point name by itself, `manual` when the traveler has to enter it.
        - `apn_value` (string, nullable): The access point name to enter on a manual setup; null or `N/A` when none is needed.
      - `android` (object): Access point name (APN) settings for one operating system.
        - `apn_type` (string): `automatic` when the device configures the access point name by itself, `manual` when the traveler has to enter it.
        - `apn_value` (string, nullable): The access point name to enter on a manual setup; null or `N/A` when none is needed.
    - `is_roaming` (boolean, nullable): Whether the eSIM works through roaming.
    - `confirmation_code` (string, nullable): Confirmation code to enter at installation, when the operator requires one.
    - `msisdn` (string, nullable): Phone number of the eSIM, for the rare plans that include one.
    - `sharing` (object, nullable): A shareable installation page for the eSIM.
      - `access_code` (number): Code the traveler enters on the sharing page.
      - `link` (string): Page where the traveler can open the eSIM details and QR code.
  - `iccid` (string): The ICCID of the eSIM: the same value as `sim.iccid`. When the supplier has not returned the eSIM yet it is the placeholder `PENDING-` followed by the order code.
- `booking_id` (string): The FX-Port booking reference, `FX-` followed by a number. Keep it: it is the key of the booking in every other endpoint.
- `message` (string): A confirmation sentence.
- `balanceDebited` (boolean, deprecated): Legacy name: use `balance_debited`. Sandbox only, always `false`: no balance was debited. The older name of `balance_debited`: same value, use `balance_debited`.
- `balance_debited` (boolean): Sandbox only, always `false`: no balance was debited. The snake_case twin of `balanceDebited`, with the same value: use this name.
- `transactionCreated` (boolean, deprecated): Legacy name: use `transaction_created`. Sandbox only, always `false`: no balance transaction was created. The older name of `transaction_created`: same value, use `transaction_created`.
- `transaction_created` (boolean): Sandbox only, always `false`: no balance transaction was created. The snake_case twin of `transactionCreated`, with the same value: use this name.
- `payoutCreated` (boolean, deprecated): Legacy name: use `payout_created`. Sandbox only, always `false`: no supplier payout was created. The older name of `payout_created`: same value, use `payout_created`.
- `payout_created` (boolean): Sandbox only, always `false`: no supplier payout was created. The snake_case twin of `payoutCreated`, with the same value: use this name.

```json
{
  "environment": "sandbox",
  "success": true,
  "data": {
    "order": {
      "code": "20261101-106852",
      "order_id": "20261101-106852",
      "package_id": "france-in-7days-1gb",
      "type": "sim",
      "description": "Order for John Smith",
      "esim_type": "local",
      "created_at": "2026-11-01 15:23:22",
      "package": "KPN France Data only-1 GB - 7 days",
      "data": "1 GB",
      "validity": 7,
      "price": 468.84,
      "currency": "DZD",
      "total_price": 468.84,
      "retail_price": 468.84,
      "agency_currency": "DZD",
      "supplier_currency": "USD",
      "exchange_rate": 250,
      "voice": null,
      "text": null,
      "coverage": ""
    },
    "sim": {
      "iccid": "8900000540463077957",
      "created_at": "2026-11-01 15:23:22",
      "lpa": "lpa.airalo.com",
      "imsis": null,
      "qrcode": "LPA:1$lpa.airalo.com$DUMMY-2610301358-iKFxr-44741",
      "qrcode_url": "https://sandbox.airalo.com/qr?expires=1879860202&id=473564&signature=...",
      "direct_apple_installation_url": "https://esimsetup.apple.com/esim_qrcode_provisioning?carddata=LPA:1$lpa.airalo.com$DUMMY-2610301358-iKFxr-44741",
      "apn_type": "automatic",
      "apn_value": "N/A",
      "apn": {
        "ios": {
          "apn_type": "automatic",
          "apn_value": "N/A"
        },
        "android": {
          "apn_type": "automatic",
          "apn_value": "N/A"
        }
      },
      "is_roaming": true,
      "confirmation_code": null,
      "msisdn": null,
      "sharing": {
        "access_code": 8803,
        "link": "https://esims.cloud/v8hy7-usgkq6wg"
      }
    },
    "iccid": "8900000540463077957"
  },
  "booking_id": "FX-1793546602916",
  "message": "eSIM order placed successfully",
  "balanceDebited": false,
  "balance_debited": false,
  "transactionCreated": false,
  "transaction_created": false,
  "payoutCreated": false,
  "payout_created": false
}
```

## Errors

| Status | Code | Example message |
| --- | --- | --- |
| 400 | `missing_field` | package_id is required |
| 400 | `invalid_email` | email is not a valid email address |
| 400 | `missing_field` | phone is required |
| 400 | `invalid_agent_id` | agent_id is not a user of your agency. |
| 402 | `insufficient_balance` | Agency balance is insufficient. Available: 300.0 DZD, Required: 418.6 DZD. |
| 403 | `read_write_permission_required` | This endpoint requires an API key with read_write permission. |
| 404 | `not_found` | Package no-such-package-1gb not found in cache. Packages may still be loading — retry in a moment. |
| 409 | `duplicate_request` | An identical order is already being processed. Please wait before retrying. |
| 429 | `rate_limit_exceeded` | Too many requests hit the API too quickly. Please retry your request after the time specified in the `ratelimit-reset` header. |
| 429 | `concurrency_limit_exceeded` | Too many requests of this kind are already running for this API key; wait for one to finish and retry. |
| 503 | `service_unavailable` | Airalo token unavailable |
| 503 | `balance_lock_timeout` | Another operation on this balance is in progress. Please retry in a few seconds. |

See [Errors](https://fx-port.com/docs/errors.md) for every code and how to react.

## Related guides

- [Create an eSIM order](https://fx-port.com/docs/esims/orders.md)
- [eSIM pricing](https://fx-port.com/docs/esims/pricing.md)
