# Account API overview

> Agency balances, the financial ledger and cross-product booking history.

Source: https://fx-port.com/docs/account

Read-only endpoints for the state of your account: the balance of each currency, the money movements of a
balance, and the bookings of your agency across **all products** (flights, hotels, eSIMs and transfers).

**Permission:** Read (every endpoint in this section). **Rate limit:** the default group, 6,000 requests per
minute per key (see [Rate limits](https://fx-port.com/docs/rate-limits.md)).


> [!NOTE]
> These endpoints expose account-level data for reconciliation, dashboards and integration monitoring. They are
> **not** the booking operations API. For full traveler details, fare rules and booking actions, use the
> product-specific endpoints (for example `POST /api/v1/flights/get_booking`).


## Endpoints

| Method | Path | Description |
|---|---|---|
| `GET` | [`/api/v1/balances`](https://fx-port.com/docs/account/balances.md) | The balance of each currency your agency holds |
| `GET` | [`/api/v1/balances/{balance_id}/history`](https://fx-port.com/docs/account/balance-history.md) | The money movements of one balance |
| `GET` | [`/api/v1/history`](https://fx-port.com/docs/account/history.md) | Booking summaries across all products |
| `GET` | [`/api/v1/bookings/{booking_id}`](https://fx-port.com/docs/account/booking-detail.md) | One booking in detail, by its reference |

## Conventions of the account endpoints

- **The agency comes from the key.** Never send `agency_id`: an API key that does is refused with
  `400 agency_id_not_allowed`.
- **Environment.** A sandbox key reads sandbox data and a live key reads live data. Every body says which in
  `environment`.
- **Pagination.** `page` starts at 1 (at most 10,000) and `limit` is 1 to **100** (default 20); a larger `limit`
  is refused with `422 validation_error`, and an empty `page` or `limit` (`?page=`) means not sent. The answer has
  `pagination: { page, limit, total, total_pages, hasMore, has_more }`. `total` is the real number of entries that
  match and `total_pages` is `total` divided by `limit`, rounded up. **Follow `has_more`, not `total_pages`**: each page is
  served from the newest entries only (200 per product for the history, 500 per source for a balance history), so
  older entries are reached by narrowing the list with `date_from` and `date_to`. `hasMore` and `has_more` carry the same value.
- **Dates.** `date_from` and `date_to` take an ISO 8601 date or datetime in any common spelling: `2026-10-01` or
  `20261001`, a fraction of 1 to 9 digits, an offset written `Z`, `+01:00` or `+0100` (in a URL write the `+` as `%2B`).
  A bare date means 00:00:00 UTC for `date_from` and the end of the day (23:59:59.999999 UTC) for `date_to`; a
  datetime without an offset is read as UTC. Years 1900 to 2200. Something that is not an ISO 8601 date
  (`01/10/2026`, an epoch number, `yesterday`) is `400 invalid_date`, and a `date_from` after `date_to` is
  `400 invalid_date_range`. Both bounds are inclusive.
- **Product and status.** `product` takes any case and the singular names (`flight`, `hotel`, `esim`, `transfer`);
  `status` takes any case and reads spaces and hyphens as underscores (`On Hold` is `on_hold`).
- **Legacy names.** `from` and `from_date` are the previous names of `date_from`, and `to` and `to_date` of
  `date_to`. They still work and the response carries a `deprecated_field` warning; when both spellings are sent,
  `date_from` and `date_to` win.
- **Names in the answers.** The booking summaries use camelCase keys for historical reasons (`bookingId`,
  `totalPrice` ...). Every key that matters has a **snake_case twin** with the same value (`booking_id`,
  `total_price` ...), listed in the tables of each page, and `?naming=snake` returns every key in snake_case. The
  `record` of a booking and the balance rows are snake_case already. See [Conventions and naming](https://fx-port.com/docs/conventions.md).
- **Timestamps** are ISO 8601 with a zone, in UTC.
- **Amounts** are in your agency currency, rounded to two decimals.

## Sandbox behaviour

| Endpoint | In the sandbox |
|---|---|
| `GET /balances` | An envelope with an **empty** `data` and a `meta` that says why: `financialOperationsEnabled: false` and a message. It is not a bare array |
| `GET /balances/{balance_id}/history` | Always empty (`pagination.total` is 0), for any `balance_id` of up to 64 characters |
| `GET /history` | Sandbox bookings only |
| `GET /bookings/{booking_id}` | Sandbox bookings only. The balance operations of a flight or hotel are empty |

Sandbox bookings never touch the live ledger.

## Error responses

Every error has a stable `code` in `errors[0].code`. The older `detail.error` carries the same code.

| HTTP | `code` | Cause |
|---|---|---|
| `400` | `agency_id_not_allowed` | An API key sent `agency_id` |
| `400` | `invalid_date` | `date_from` or `date_to` is not an ISO 8601 value, or is outside 1900 to 2200 |
| `400` | `invalid_date_range` | `date_from` is after `date_to` |
| `400` | `invalid_product` | `product` is not one of `all`, `flights`, `hotels`, `esims`, `transfers` (or their singular names). `details.allowed` lists them |
| `400` | `invalid_status` | `status` is not a plain status name: letters, digits, `_`, spaces or hyphens, at most 32 characters |
| `400` | `invalid_booking_id` | The booking reference has more than 100 characters or characters other than letters, digits and `._:=+~-` |
| `401` | `authentication_required`, `invalid_api_key` | Missing or invalid API key |
| `404` | `balance_not_found` | The `balance_id` is not a balance of your agency (live) |
| `404` | `booking_not_found` | No booking matches in this environment |
| `422` | `validation_error` | `page` is below 1 or above 10,000, `limit` is above 100, or a value is too long |
| `429` | `rate_limit_exceeded` | Over 6,000 requests per minute for this key |
| `500` | `internal_error` | An error on our side. Retry; contact support if it persists |
| `503` | `service_overloaded` | The service is shedding load for a moment (its database pool is full). Nothing was read: retry after the seconds in `Retry-After` (2 to 5) |
