Skip to content

How purchases and traffic fit together

There are two ways to give a customer proxy access: buy data for their own order, or let them use part of a parent’s shared pool. Both can look like “add 10 GiB” in your UI, but they are different API workflows.

ResourceIn plain languageWhat it is not
UserThe account that owns credentials and has permissionsA traffic purchase
PackageThe product: routing, pricing, capabilities, limits and billing cycleData already paid for
InvoiceThe record of a purchase and its payment stateProof that access is ready just because it exists
OrderOne user’s access to one package, reused across top-upsA new subscription record for every payment
Data ledgerA purchased bucket with its own remaining bytes and expirationA complete financial or traffic transaction journal
Child quotaThe most a managed customer may consume from a shared poolReserved data or money transferred to that customer

A user’s wallet holds money. Ledger balances and child quotas hold bytes. Paying to top up a wallet does not itself buy a proxy package.

WORKFLOW

From payment to usable traffic

  1. Create the purchase

    Choose the recipient, package and integer-byte amount.

    POST /invoices
  2. Confirm payment

    A provider or wallet settles the invoice. With external billing, a superuser records an already-paid manual purchase.

  3. Check the entitlement

    The paid purchase creates or tops up the recipient’s order and its ledger. Verify the order before delivering access.

    GET /users/{id}/orders
  4. Use the proxy

    Traffic consumes the active purchased ledger and updates the consuming order’s usage.

Repeated purchases for the same user and package reuse the order. Finite, expiring purchases create separate buckets; compatible non-expiring top-ups can merge. Your database should keep separate purchase/invoice records even when they point to the same order.

Start with your own billing quickstart or built-in checkout and wallet payments.

The parent first needs its own purchased root order for the package. Creating a sub-user alone does not create that pool. Then POST /users/{id}/data/add creates or increases the child’s virtual order, using package_id and data.

Assigning a quota does not move traffic. Using a proxy does.
  1. 1 · Before allocationParent: 100 GiB

    Purchased data available to the whole order tree.

  2. 2 · Assign two quotasA: 80 GiB · B: 80 GiB

    Parent still has 100 GiB. The 160 GiB of quotas is not reserved.

  3. 3 · A uses 10 GiBParent: 90 GiB

    A has 70 GiB left. B still has 80 GiB. Both depend on the same parent pool.

Access needs personal quota and usable shared data. Either can run out first.

Overcommit is intentional: assigned limits are independent ceilings, not reservations. The API does not cap the sum of those limits at the parent’s remaining data. If your business promises every assigned byte, enforce that reservation policy in your own backend and keep the shared pool funded. Do not implement it as an unprotected read-then-write balance check across concurrent purchases.

When a child uses 10 GiB, the same usage reduces the shared ledger and increases that child’s data_spent. It does not count as 10 GiB of the parent’s own direct traffic. Parent direct traffic also uses the shared pool.

A sub-user is not necessarily a child order

Section titled “A sub-user is not necessarily a child order”

There are two independent relationships:

  • User parent: who manages the account.
  • Order parent: whose purchased pool supplies this package’s traffic.

A managed sub-user can instead receive an independent paid purchase. That order owns its own ledgers and does not use the parent’s pool. Choose the model before provisioning a user/package pair. The allocation endpoint rejects an existing independently purchased order; it is not a conversion API. Do not use an invoice as a way to convert an existing virtual child order either.

For finite-data packages:

FieldPurchased root orderVirtual child order
dataGranted/purchased data counter; not the usable balance after expirationTotal assigned quota
data_spentTraffic consumed directly by this order’s user, not all childrenTraffic consumed by this child
REST data_remainingSum of usable ledger balancesmax(data - data_spent, 0)
REST ledgersUsable buckets, not complete historyEmpty array: the parent owns the buckets

Do not use root.data - root.data_spent to display a shared pool. That misses both child consumption and expired data. Do not interpret an empty ledger array alone as proof of a virtual order: a purchased order can also have no usable buckets.

Webhook fields are a different contract: shared and personal balances have distinct fields, exhausted finite balances can be negative, and unlimited capacity can be null. Use the webhook field definitions rather than copying REST formulas. Unlimited access can still expire or be disabled.

Check the user and order state, the child’s personal quota (if virtual), and usable purchased data. Expiration, an empty pool, a depleted child quota, disabled access, credentials, source-IP rules and connection limits are different failures. More child quota cannot repair an empty parent pool.

Usage arrives in batches. A final batch can cross a limit, and dashboards may lag live traffic. Use closed-window analytics for reconciliation, not an instantaneous balance subtraction.

Next: assign and reduce quotas or understand ledger expiration.