Balance history
The complete financial ledger of one currency balance: top-ups, booking debits and refunds.
GET/api/v1/balances/{balance_id}/history
GET /
Permission: Read
The money movements recorded against one balance: top-ups, booking debits and refunds, newest first. Call
GET / first to get the balance_id of the currency you want.
Sandbox: the answer is always empty (pagination.total is 0) and meta says why.
Live: a balance_id that is not a balance of your agency, or that is not a UUID, is answered
404 balance_not_found.
The source field
Each entry has a source:
source | What it represents |
|---|---|
"transactions" | Authoritative balance movements. Recorded every time the balance changes: top-up, booking charge or refund. Use these for reconciliation totals. eSIM orders and topups have no audit source: their debits are transactions rows. Transfers have no audit source either |
"flight_balance_operations" | Per-booking flight audit records. Granular events of a booking (initial debit, exchange fee, adjustment, refund). One booking can produce several rows |
"hotel_balance_operations" | Per-booking hotel audit records. The same idea as the flight operations |
A single action, for example confirming a flight, produces both a transactions row and a
flight_balance_operations row, and both appear in this answer. Sum only the transactions rows for
financial totals. The description of an eSIM debit is eSIM Order <order code> (a topup: eSIM TopUp <order code>),
where the order code is the order_id of the booking.
Parameters
| Parameter | Type | Description |
|---|---|---|
balance_id (path) | string | The id of a balance from GET / |
date_from | string | Inclusive start. Any ISO 8601 spelling is read: a date (2026-07-01 or 20260701, 00:00:00 UTC) or a datetime (2026-07-01T00:00:00Z) with 1 to 9 fraction digits and an offset written Z, +01:00 or +0100 (in a URL write the + as %2B). Without an offset a datetime is UTC |
date_to | string | Inclusive end, in the same spellings. A bare date means the end of that day (23:59:59.999999 UTC). Not before date_from |
type | string | Keep one type. Exact and case sensitive: CREDIT, DEBIT or REFUND match transactions; credit, debit or refund match the two *_balance_operations sources. Up to 32 characters |
page | integer | The page, starting at 1 (at most 10,000). Default 1. An empty value means not sent |
limit | integer | Entries per page, 1 to 100. Default 20. A larger value is refused with 422 validation_error; an empty value means not sent |
Legacy names (still work; the response carries a deprecated_field warning; date_from and date_to win when both are sent):
| Legacy | Use instead |
|---|---|
from, from_date | date_from |
to, to_date | date_to |
Never send agency_id: an API key that does is refused with 400 agency_id_not_allowed. A value that is not an ISO 8601
date (01/, an epoch number, yesterday) is 400 invalid_date, and date_from after date_to is 400 invalid_date_range.
Movement types
| Type | Direction | When it appears |
|---|---|---|
CREDIT / credit | Funds in | Manual top-ups, deposits returned when a hold is released |
DEBIT / debit | Funds out | Booking confirmations, exchange fees, manual debit adjustments |
REFUND / refund | Funds in | Full or partial refund after a booking is cancelled or voided |
Upper case values appear in transactions rows and lower case values in *_balance_operations rows.
Request example
curl --get \
--url https://api.fx-port.com/api/v1/balances/db002f7a-c136-4f32-b19d-38a2542f2af9/history \
--header 'Authorization: Bearer fxp_test_YOUR_KEY' \
--data-urlencode 'date_from=2026-10-01' \
--data-urlencode 'date_to=2026-10-31' \
--data-urlencode 'limit=50'const balanceId = "db002f7a-c136-4f32-b19d-38a2542f2af9";
const params = new URLSearchParams({ date_from: "2026-10-01", date_to: "2026-10-31", limit: "50" });
const response = await fetch(`https://api.fx-port.com/api/v1/balances/${balanceId}/history?${params}`, {
headers: { Authorization: "Bearer fxp_test_YOUR_KEY" },
});
const { data: entries, pagination } = await response.json();
// reconciliation: only the authoritative movements
const net = entries
.filter((entry) => entry.source === "transactions")
.reduce((sum, entry) => sum + (entry.type === "DEBIT" ? -entry.amount : entry.amount), 0);import requests
balance_id = "db002f7a-c136-4f32-b19d-38a2542f2af9"
response = requests.get(
f"https://api.fx-port.com/api/v1/balances/{balance_id}/history",
params={"date_from": "2026-10-01", "date_to": "2026-10-31", "limit": 50},
headers={"Authorization": "Bearer fxp_test_YOUR_KEY"},
timeout=30,
)
body = response.json()Response example
The amounts are examples. A live answer:
{
"environment": "live",
"success": true,
"data": [
{
"source": "transactions",
"id": "3e8b1c02-d7a4-4f5e-9b2a-1c0d8e7f3a91",
"type": "CREDIT",
"amount": 2500.0,
"currency": "DZD",
"description": "Balance top-up",
"balanceId": "db002f7a-c136-4f32-b19d-38a2542f2af9",
"balance_id": "db002f7a-c136-4f32-b19d-38a2542f2af9",
"createdAt": "2026-10-11T09:15:00.000000+00:00",
"created_at": "2026-10-11T09:15:00.000000+00:00"
},
{
"source": "flight_balance_operations",
"id": "658185df-234c-43ba-8175-ba9a5abe6007",
"type": "debit",
"amount": 87.5,
"currency": "DZD",
"description": "Exchange fee debit for flight booking Y7PAEU",
"bookingRecordId": "711371c3-00aa-4be2-b790-9f0ca2ccfdb7",
"booking_record_id": "711371c3-00aa-4be2-b790-9f0ca2ccfdb7",
"createdAt": "2026-10-09T14:30:00.000000+00:00",
"created_at": "2026-10-09T14:30:00.000000+00:00"
},
{
"source": "transactions",
"id": "31936df0-299d-429e-b0f5-056a6c0587cc",
"type": "DEBIT",
"amount": 4925.44,
"currency": "DZD",
"description": "Booking confirmation: flight Y7PAEU (ALG → ORN)",
"balanceId": "db002f7a-c136-4f32-b19d-38a2542f2af9",
"balance_id": "db002f7a-c136-4f32-b19d-38a2542f2af9",
"createdAt": "2026-10-01T15:42:34.272792+00:00",
"created_at": "2026-10-01T15:42:34.272792+00:00"
}
],
"pagination": { "page": 1, "limit": 20, "total": 3, "total_pages": 1, "hasMore": false, "has_more": false },
"meta": {
"agencyId": "0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
"agency_id": "0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
"balanceId": "db002f7a-c136-4f32-b19d-38a2542f2af9",
"balance_id": "db002f7a-c136-4f32-b19d-38a2542f2af9",
"balanceCurrency": "DZD",
"balance_currency": "DZD",
"environment": "live"
}
}Field reference
| Field | Present when | Description |
|---|---|---|
source | Always | Where the entry comes from: see the table above |
id | Always | The entry UUID |
type | Always | CREDIT, DEBIT or REFUND (upper case in transactions, lower case in operations) |
amount | Always | The amount, always positive: type gives the direction |
currency | Always | ISO 4217 code: always the currency of the balance |
description | Always | A label with the booking reference and the action. It can be null |
balanceId / balance_id | transactions only | The balance the movement was recorded against |
bookingRecordId / booking_record_id | operations only | The internal id of the booking: use it to match the entry with the booking history |
createdAt / created_at | Always | ISO 8601 datetime, UTC |
pagination.hasMore / has_more | Always | true when another page can be served |
meta.balanceId, meta.balanceCurrency | Live | The balance of the history and its currency. Twins: balance_id, balance_currency |
How far back the pages go
pagination.total is the real number of entries that match. The pages are built from the newest entries of
each source (up to 500 per source), so the oldest entries of a long history cannot be reached by paging:
follow has_more and narrow the list with date_from and date_to to read an older period.
Errors
| HTTP | code | Cause |
|---|---|---|
400 | agency_id_not_allowed | An API key sent agency_id |
400 | invalid_date, invalid_date_range | A date is not valid, or date_from is after date_to |
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), or is longer than 64 characters (any environment) |
422 | validation_error | page is below 1 or above 10,000, or limit is above 100 |
429 | rate_limit_exceeded | Over 6,000 requests per minute for this key |
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) |