Skip to content

Immutable ledger entries

GET
/billing/ledger
curl --request GET \
--url 'https://api.flotera.com/api/v1/billing/ledger?limit=50&kind=charge&range=today&from=2026-07-25T10%3A15%3A30.123456Z&to=2026-07-25T10%3A15%3A30.123456Z' \
--cookie __Host-erm_session=<__Host-erm_session>
cursor
string
>= 1 characters <= 512 characters

Opaque pagination cursor from a previous next_cursor.

limit
integer
default: 50 >= 1 <= 200

Page size.

kind

charge — hourly usage; topup — payments, including their reversals (negative amount); adjustment — manual credits and debits. topup is exactly the set excluded from spent_mtd_usd, so the tile equals minus the sum of the non-topup rows of the same period.

string
Allowed values: charge topup adjustment
range

Named consumption period, expanded server-side in the tenant timezone by the same code that produces the billing tiles.

string
Allowed values: today yesterday 7d month prev_month

Named consumption period, expanded server-side in the tenant timezone. Mutually exclusive with from/to (422 otherwise). Prefer it over client-computed bounds: the sum of the returned charge/adjustment rows then equals the matching GET /billing tile by construction.

from

RFC 3339 UTC with microsecond precision.

string format: date-time

Half-open interval start [from, to) on occurred_at (the consumed period), the same anchor the tiles aggregate by.

to

RFC 3339 UTC with microsecond precision.

string format: date-time

Half-open interval end [from, to) on occurred_at.

Page of ledger entries, newest first.

Media typeapplication/json
object
items
required
Array<object>
<= 200 items

Immutable ledger entry; amounts are negative for charges, positive for top-ups.

object
id
required

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
tenant_id
required

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
kind
required

charge — hourly usage; topup — payments, including their reversals (negative amount); adjustment — manual credits and debits. topup is exactly the set excluded from spent_mtd_usd, so the tile equals minus the sum of the non-topup rows of the same period.

string
Allowed values: charge topup adjustment
amount_usd
required

Decimal value serialized as a string, never a JSON number.

string
/^-?[0-9]+(\.[0-9]{1,6})?$/
balance_after_usd
required

Decimal value serialized as a string, never a JSON number.

string
/^-?[0-9]+(\.[0-9]{1,6})?$/
occurred_at
required

When the entry was consumed: start of the billed hour for hourly charges, effective_at otherwise. Rows are filtered, sorted and paginated by this instant and it is what the UI must display — created_at is the bookkeeping moment and runs ~1h15m later.

string format: date-time
charge_day
Any of:

UTC calendar day.

string format: date
charge_hour

Start of the billed UTC hour [H, H+1) for hourly charges; null otherwise.

string | null format: date-time
detail
required
string
<= 512 characters
calculation_id
Any of:

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
created_at
required

RFC 3339 UTC with microsecond precision.

string format: date-time
next_cursor
required

Opaque cursor for the next page; null when there are no more rows.

string | null
>= 1 characters <= 512 characters
Example
{
"items": [
{
"kind": "charge",
"amount_usd": "12.340000",
"balance_after_usd": "12.340000",
"occurred_at": "2026-07-25T10:15:30.123456Z",
"created_at": "2026-07-25T10:15:30.123456Z"
}
]
}

Missing/invalid credentials (code=unauthorized).

Media typeapplication/problem+json

RFC 9457 problem document with a stable machine code.

object
type
required
string format: uri
title
required
string
<= 256 characters
status
required
integer
>= 100 <= 599
code
required

Stable machine-readable error code (03 §2.4).

string
Allowed values: unauthorized forbidden csrf_rejected not_found conflict idempotency_conflict validation_failed rate_limited payload_too_large unsupported_agent_version temporarily_unavailable offline_queue_full online_queue_full plan_required server_limit_reached feature_not_entitled account_in_grace account_frozen payment_pending payment_expired payment_amount_mismatch change_already_pending change_already_applied change_effective reserve_not_covered direction_changed
detail
string
<= 2048 characters
instance
string
<= 512 characters
request_id

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
errors
Array<object>
<= 100 items
object
path
required

JSON Pointer to the offending field.

string
<= 512 characters
code
required
string
<= 64 characters
Example
{
"type": "https://ermeon.com/problems/validation",
"code": "unauthorized"
}

Authenticated but not permitted. code=forbidden — role/scope mismatch; code=feature_not_entitled — the plan does not include the capability; code=account_frozen — the account is frozen and this operation is declared x-ermeon-frozen: deny. The three are deliberately distinct: only the last one is fixed by a top-up (13 §5.6).

Media typeapplication/problem+json

RFC 9457 problem document with a stable machine code.

object
type
required
string format: uri
title
required
string
<= 256 characters
status
required
integer
>= 100 <= 599
code
required

Stable machine-readable error code (03 §2.4).

string
Allowed values: unauthorized forbidden csrf_rejected not_found conflict idempotency_conflict validation_failed rate_limited payload_too_large unsupported_agent_version temporarily_unavailable offline_queue_full online_queue_full plan_required server_limit_reached feature_not_entitled account_in_grace account_frozen payment_pending payment_expired payment_amount_mismatch change_already_pending change_already_applied change_effective reserve_not_covered direction_changed
detail
string
<= 2048 characters
instance
string
<= 512 characters
request_id

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
errors
Array<object>
<= 100 items
object
path
required

JSON Pointer to the offending field.

string
<= 512 characters
code
required
string
<= 64 characters
Example
{
"type": "https://ermeon.com/problems/validation",
"code": "unauthorized"
}

Request failed validation (code=validation_failed), with per-field errors.

Media typeapplication/problem+json

RFC 9457 problem document with a stable machine code.

object
type
required
string format: uri
title
required
string
<= 256 characters
status
required
integer
>= 100 <= 599
code
required

Stable machine-readable error code (03 §2.4).

string
Allowed values: unauthorized forbidden csrf_rejected not_found conflict idempotency_conflict validation_failed rate_limited payload_too_large unsupported_agent_version temporarily_unavailable offline_queue_full online_queue_full plan_required server_limit_reached feature_not_entitled account_in_grace account_frozen payment_pending payment_expired payment_amount_mismatch change_already_pending change_already_applied change_effective reserve_not_covered direction_changed
detail
string
<= 2048 characters
instance
string
<= 512 characters
request_id

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
errors
Array<object>
<= 100 items
object
path
required

JSON Pointer to the offending field.

string
<= 512 characters
code
required
string
<= 64 characters
Example
{
"type": "https://ermeon.com/problems/validation",
"code": "unauthorized"
}