# Top up an eSIM

> Add a topup package to an eSIM of your agency and debit your agency balance (live only).

Endpoint: `POST /esims/topups`

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

`POST /api/v1/esims/topups`

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

Call `GET /esims/topup-packages/{iccid}` first and send one of its `package_id` values: a package that was not returned for this eSIM is refused. `email` and `phone` are required and kept on the booking; the email is not sent to the supplier on a topup. The ICCID must be an eSIM that your agency ordered.

The same rules as for an order apply: JSON body, server-side prices, no idempotency key, `duplicate_request` while the same topup is running or (live) when a topup of the same ICCID was confirmed in the last 60 seconds. If the supplier refuses the topup nothing is debited and a `failed` booking is saved. The sandbox debits nothing, returns a `balance` of `0.0`, and adds the `balanceDebited`, `transactionCreated` and `payoutCreated` flags.

The booking of a topup is listed by `GET /esims/bookings/{iccid}/topup-history`, not by `GET /esims/bookings`.

## Request

### Body

Required, JSON.

- `package_id` (string, required): The `id` of a topup package returned for this ICCID by the topup packages endpoint (its `package_id`). At most 128 characters.
- `iccid` (string, required): The ICCID of the eSIM to top up: 18 to 22 digits, which may be written in groups of spaces or hyphens (`8944 4654 0000 0123 456`). It must be an eSIM that your agency ordered.
- `email` (string, required): The traveler's email address. Kept on the booking: it is not sent to the supplier on a topup. At most 254 characters.
- `phone` (string, required): The traveler's phone number: at least 3 digits. Kept on the booking. At most 32 characters.
- `first_name` (string, optional): The traveler's first name. Any characters are accepted. At most 100 characters.
- `last_name` (string, optional): The traveler's last name. Any characters are accepted. At most 100 characters.
- `agent_id` (string (uuid), optional): The id of the user of your agency who places the topup. Optional. 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/topups \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "package_id": "france-in-7days-1gb-topup",
  "iccid": "8900000540463077957",
  "first_name": "John",
  "last_name": "Smith",
  "email": "traveler@example.com",
  "phone": "+15555550123"
}'
```

## Response

### 200: The topup was applied. The response has status 200, not 201.

- `success` (boolean): `true` when the topup was applied.
- `data` (object): The topup, the ICCID and the balance change.
  - `topup` (object): The topup, with the amounts in your agency currency.
    - `code` (string, deprecated): Legacy name: use `order_id`. The supplier's order code of the topup. 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 of the topup. Booking rows call it `order_id`. The snake_case twin of `code`, with the same value: use this name.
    - `package_id` (string): The topup package that was bought.
    - `package` (string): Name of the topup package.
    - `data` (string): Data allowance added, as text.
    - `validity` (integer): Validity of the topup 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.
  - `iccid` (string): The ICCID that was topped up.
  - `balance` (object): The agency balance around the topup.
    - `previous` (number): Agency balance in this currency before the topup. Always `0.0` in the sandbox.
    - `current` (number): Agency balance after the topup. Always `0.0` in the sandbox.
    - `currency` (string): Currency of the balance.
- `booking_id` (string): The FX-Port booking reference of the topup, `FX-` followed by a number.
- `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": {
    "topup": {
      "code": "20261101-106853",
      "order_id": "20261101-106853",
      "package_id": "france-in-7days-1gb-topup",
      "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
    },
    "iccid": "8900000540463077957",
    "balance": {
      "previous": 0,
      "current": 0,
      "currency": "DZD"
    }
  },
  "booking_id": "FX-1793546625391",
  "message": "eSIM topup completed 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 and iccid are required |
| 400 | `invalid_iccid` | iccid must be 18-22 digits (received '123'). |
| 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 | `iccid_not_found` | No eSIM with this ICCID was found for your account. |
| 404 | `not_found` | Package no-such-topup not found or has no valid price |
| 409 | `duplicate_request` | A duplicate topup was detected for this eSIM. If intentional, please wait 60 seconds 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 |

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

## Related guides

- [Top up an eSIM](https://fx-port.com/docs/esims/topups.md)
- [Supplier continuity](https://fx-port.com/docs/esims/supplier-continuity.md)
