Skip to content

Data buckets, top-ups, and expiration

A data ledger answers three questions: how much data was granted, how much remains, and until when it can be used. It lets a customer buy again without extending every earlier purchase. Start with the accounting model if orders and child quotas are new to you.

New traffic does not extend the life of an older purchase
  1. Day 1 · Buy 10 GiBLedger A: 10 GiB, expires day 31

    A is active. Use 4 GiB: A has 6 GiB left.

  2. Day 20 · Buy another 10 GiBLedger B: 10 GiB, expires day 50

    B becomes active. Total usable balance is 16 GiB. A keeps its original deadline.

  3. Day 25 · Use 3 GiBA: 6 GiB · B: 7 GiB

    With B still active, this traffic is charged to B. Total usable balance is 13 GiB.

  4. Day 31 · A expiresUsable balance: 7 GiB

    A's unused 6 GiB stops being usable. This is expiration, not an extra 6 GiB of traffic.

The example assumes finite data, a 30-day cycle, no other traffic, and no intervening recovery or ledger switch.

The customer used 7 GiB in this example, not 13 GiB. Six more GiB became unusable through expiration. Your UI should distinguish “used” from “expired”; a fall in remaining balance is not always usage.

PurchaseResult
Finite data with an expirationA separate ledger for this purchase, on the same user/package order
Finite data without expirationMerge into a compatible non-expired, non-expiring ledger when one exists; otherwise create one
Unlimited packageReuse an eligible non-expired ledger when possible, including cycle-based purchases; update its expiration to the newly calculated date

“One invoice = one ledger” is therefore not a safe database assumption. Keep your own invoice/purchase records, and treat ledger IDs as accounting bucket identifiers. An exhausted ledger that is not marked expired can be reused by a compatible merged top-up; one already marked expired is not a merge candidate.

An explicit invoice expires must be a future Unix timestamp in seconds. Otherwise, a positive package billing cycle produces an expiration from the payment date plus the configured number of days (creation time is a fallback). With no explicit expiration and a zero cycle, there is no automatic expiration.

A normal repeat purchase does not add days to every older bucket. For unlimited packages the reused bucket takes the latest calculated expiration; this is not “add one period to the old deadline.” The order’s expires is also updated, so it is not a substitute for inspecting each ledger’s deadline.

A paid top-up makes its new or updated ledger active. Virtual children share their parent’s active ledger. When accountant replaces an exhausted or expired active ledger, it selects the oldest-created eligible remaining bucket, with the ID as a tie-breaker.

The API’s ledger-recovery path orders eligible buckets by expiration, creation time and ID instead. Consequently, do not promise a universal earliest-expiring-first or FIFO rule for every transition. Do not infer the active bucket from the first item in REST ledgers: response ordering is not spending priority.

Read your own GET /orders, or a managed customer’s GET /users/{id}/orders. For a purchased order, REST ledgers includes buckets with positive remaining data, not marked expired, and with no deadline or a deadline still in the future. It is not a full historical ledger export. Zero-byte unlimited buckets are not evidence that unlimited access is unavailable.

Read the detailed GET /orders/{id} only with an account allowed to see that order. List and detail serializers expose different fields; do not assume is_active, expires, or an active-ledger ID exists in every response.

Selected fields from a finite root order after the second purchase:

{
"data": 21474836480,
"data_spent": 4294967296,
"data_remaining": 17179869184,
"ledgers": [
{ "id": "550e8400-e29b-41d4-a716-446655440005", "data": 10737418240, "data_remaining": 6442450944, "expires": "2030-01-31T12:00:00Z" },
{ "id": "550e8400-e29b-41d4-a716-446655440009", "data": 10737418240, "data_remaining": 10737418240, "expires": "2030-02-19T12:00:00Z" }
]
}

These are illustrative selected fields, not a promise about array order or a complete response. For shared pools, data_spent would only include direct consumption by the root order’s user.

  • If one ledger stops being usable, another eligible bucket can keep the order tree supplied.
  • If the pool runs out, unused child quota does not supply new data. Refill the root pool and recheck access; changing only the child quota is insufficient.
  • If only a child quota runs out, other children can continue using the pool. Increase that child’s quota to restore its allowance.
  • A new purchase does not erase historical data_spent or reset all child limits for a new subscription month. Your billing system must implement its own period policy.

Keep invoice history for purchases, a local operation journal for allocations, and analytics for usage. Expired buckets disappear from the public usable-ledger list; do not reconstruct a complete expiry history from that list or from webhook counts. There is no public ledger CRUD endpoint in this contract.