Skip to content

Usage and data accounting

ProxyRequest accounts traffic in integer bytes. A reseller integration must distinguish the physical byte pool owned by a root order from the commercial limit assigned to each sub-user. They change during the same request, but they are not the same balance.

DATA INPUT HELPER

Convert allocation units to bytes

API UNIT: BYTES
Send this integer1073741824

Binary and decimal units are shown separately so the allocation is explicit.

Root order

The package purchase owned by the main user. It owns every data ledger for that user/package pair and is the parent of reseller-created child orders.

Data ledger

A physical pool with total bytes, remaining bytes, and an optional expiry. Only root orders own ledgers.

Active ledger

The one ledger currently used by proxy authentication and accounting. A child order points to the same active ledger as its parent.

Child order

A sub-user’s independently assigned limit. Its data and data_spent fields enforce that customer limit without creating another physical pool.

TRAFFIC ACCOUNTING PIPELINEOne usage batch updates a shared pool and one consumer order
01 / PROXYCount transferred bytesHTTP, CONNECT, SOCKS5 TCP, and UDP traffic
02 / BATCHAggregate usageUser + order + active ledger + package
03 / ACCOUNTANTApply the same byte deltaFast balance in Redis, durable totals in PostgreSQL
04 / OUTCOMEEmit balance eventsContinue, switch ledger, or disable one child order
ROOT ORDER / SHARED POOL

Parent package

ACTIVELedger C4.0 GB left
NEXTLedger A2.5 GB left
DONELedger Bexpired / 0 B
CHILD ORDERS / COMMERCIAL LIMITS
Sub-user A3 GB assigned
remaining = 3 GB − data_spent
Sub-user B5 GB assigned
remaining = 5 GB − data_spent

Both consume the active parent ledger. Their assigned limits do not reserve bytes from that ledger.

The proxy counts transferred bytes while it handles HTTP requests, CONNECT tunnels, SOCKS5 TCP streams, and UDP traffic. For metered packages, it aggregates positive byte deltas with the consuming user, order, package, and active ledger identifiers before sending them to accountant.

Accountant applies each batch to two projections:

  1. Redis decrements the active ledger balance used by the live proxy path and emits a balance-change or run-out signal.
  2. PostgreSQL aggregates the batch by ledger and order, then updates both durable values in one transaction:
data_ledgers.data_remaining = data_ledgers.data_remaining - bytes
orders.data_spent = orders.data_spent + bytes

The ledger update is grouped by ledger_id; the order update is grouped by the order that actually generated traffic. This is what makes root and sub-user accounting behave differently.

Traffic ownerPhysical balance that decreasesdata_spent that increasesPersonal remaining balance
Main userRoot order’s active ledgerRoot orderNot separately calculated
Sub-userParent root order’s active ledgerThat sub-user’s child orderchild.data - child.data_spent
Unlimited packageNo byte-ledger decrementTraffic is still captured for analyticsNot byte-enforced

Assume the active parent ledger starts with 10 GB. Sub-user A is assigned 3 GB and consumes 1.2 GB; sub-user B is assigned 5 GB and consumes 0.8 GB.

Parent active ledger: 10.0 - 1.2 - 0.8 = 8.0 GB remaining
Sub-user A: 3.0 - 1.2 = 1.8 GB remaining
Sub-user B: 5.0 - 0.8 = 4.2 GB remaining
Parent order data_spent = 0 GB

The parent order’s own data_spent stays unchanged because the child orders were the consumers. The shared parent ledger still pays for both. Direct traffic made with the parent’s credentials would increment the parent order’s data_spent as well as decrement the same ledger.

POST /users/{id}/data/add and the matching subtract operation change the child order’s data allowance. They do not decrement the parent ledger, create a child ledger, or reserve bytes from the shared pool.

This makes overcommit possible: the sum of all child limits can be greater than the bytes currently available across parent ledgers. It is useful when not every customer consumes their full allowance, but your billing system should monitor both values:

  • child remaining tells you whether one customer may continue;
  • active ledger remaining tells you whether the whole package tree can continue.

Subtracting allocation cannot remove more than the child order’s current assigned data. It also synchronizes that child to the parent’s current active ledger when necessary.

A root order can keep more than one valid ledger. This lets separate purchases retain their own expiry and lets accountant move the whole order tree without rebuilding credentials.

  1. A paid, expiring, limited-data invoice creates a separate ledger so that purchase can expire independently.
  2. Unlimited packages and non-expiring purchases merge new data into a compatible ledger instead of creating another expiring pool.
  3. The ledger created or updated by the latest paid invoice becomes active immediately.
  4. The API synchronizes the root order and all child orders to that active ledger.
  5. When the active ledger reaches zero or expires, accountant marks it expired and selects the oldest-created remaining valid ledger.
  6. The replacement ledger ID is written to the entire root/child tree and projected into proxy caches.
  7. If no replacement exists, active_ledger becomes empty for the whole tree and metered proxy access cannot continue.

A ledger is eligible as a replacement only when it belongs to the root order, is not marked expired, has data_remaining > 0, and has no expiry or an expiry in the future. Created time and ledger ID provide deterministic FIFO ordering between eligible replacements.

ConditionAccountant actionWho loses accessEvent meaning
Active ledger remains above zeroKeep the current ledgerNobodyuser.data.change reports its latest remaining bytes
Active ledger reaches zero or belowExpire it and transition the whole treeNobody if a replacement exists; otherwise root and childrenuser.data.run_out identifies the exhausted ledger
Finite child limit reaches zero or belowClear only that child order’s active ledger and invalidate its cacheThat sub-user onlyorder.data.run_out reports the child limit exhaustion
Ledger expires by timeRun the same whole-tree transitionDepends on replacement availabilityExpiry and byte run-out share the transition model

Usage is processed in batches, so a balance can cross zero rather than stop exactly at zero. Both webhook order.data and order.data_remaining can therefore be negative and represent overdraft from the final batch.

order.data

Remaining bytes in the ledger named by order.ledger_id after the usage batch. It describes the shared physical pool, even when a child user generated the traffic.

order.data_remaining

Remaining bytes in a finite child order. It is null for root and unlimited orders; null does not mean zero.

Webhooks are optimized for reacting to balance changes, transitions, and run-out. They may be coalesced, delayed, or retried. Use analytics and reconciliation for closed reporting windows and the webhook event reference for exact payload fields.