Billing in the TeleQuick console is assembled from two independent systems that are presented side by side: They are not the same ledger, they are not aggregated by the same job, and they do not have to agree with each other. Most confusion on the billing screens comes from reading a number from one system against a number from the other.

What a billing period is and when it closes

The fleet billing screen works on one period at a time. fleetBilling.getUsage returns periodStart and periodEnd, which the header renders as Period: <start> → <end> and the KPI strip renders as a month label. A period becomes history when a row for it exists in fleet_usage_period. fleetBilling.history reads those rows — the screen asks for the last 6 periods — and renders one table line per period with the metric names it contains and the period total. The empty state is explicit about the ordering:
No history yet. This month’s row lands once usage is aggregated.
So: the current period is a live calculation, and the history table only shows what has been aggregated into a period row. A month you are still inside may be absent from Recent months while being fully populated in This period · metered usage. For voice, the period is the current calendar month. cdr.monthStats filters CDRs with toStartOfMonth(starting_time) = toStartOfMonth(now()), and billing.invoices.generate snapshots the current calendar month’s metered CDR cost. There is no configurable billing anniversary on either screen.

Rate plans and what a metered row contains

Each vertical has its own rate plan. The plan is a list of metrics, and one metered row is rendered per metric in the plan — including metrics that metered nothing. Each row carries: If the plan itself has no entries, the panel reads No billable metrics yet — rate plan is empty for <vertical>. That is a plan-configuration state, not a usage state: it means nothing is priced for that vertical in this deployment, so nothing can be charged. The Metrics billed KPI counts only rows with units > 0, shown as n of <total rows>. A low n against a large total is normal — it means most of the plan’s metrics were not exercised this period. Units are formatted at different precision depending on magnitude (whole numbers with thousands separators for large values, four decimal places below 1), so a row can legitimately show a very small non-zero quantity. The quantity you see is the quantity that was multiplied by rate_per_unit.

Month-to-date total vs the end-of-period projection

Two different figures sit next to each other in the KPI strip:
  • MTD cost — total_cost_usd. Money already metered in this period. It is also repeated as Period total at the foot of the metered rows, and it is the figure the tag split reconciles against.
  • Projected end-of-period — projected_end_of_period_usd, computed server-side and returned alongside the total.
The projection is an estimate produced by the usage aggregation, not a commitment and not an invoice. Only the MTD total corresponds to usage that has actually been metered. Do not chargeback, alert on, or reconcile against the projection. Both figures are formatted for readability, not for accounting: values at or above 1000 are rounded to whole currency units and thousand-separated, values at or above 100 are shown without decimals, and only smaller values show cents. A displayed $1,204 is a rounded render of the underlying number.

Aggregation timing and refresh behaviour

Fleet aggregation is not a background job you wait on. As the footnote on the screen states:
Aggregation runs inline on each query — re-render to refresh.
Concretely:
  • fleetBilling.getUsage re-runs on an interval of 120 s while the screen is open, and the header shows either Refreshing… or Updated <relative time> from the last successful fetch.
  • The Refresh button re-runs the same query, and is disabled while a fetch is in flight.
  • fleetBilling.history is cached for 5 minutes, so a period row that has just landed may take until the next fetch to appear in Recent months.
  • tags.keys is likewise cached for 5 minutes.
The voice side has no polling. The portal refetches the account balance and the payment list when the Add-credits modal settles, and nothing else refreshes on its own.

Cost allocation by tag: allocated, unallocated, unattributable

The Cost by tag panel splits this period’s spend by the values of one tag key. Tag keys come from tags.keys, which reads the resource_tag table for the org, counts rows per key, and returns keys ordered by usage count (ties broken alphabetically). The panel defaults to the most-used key; the dropdown selects another. Tag values are inherited. Tagging a site or fleet (robotics) or a region or server fleet (games) is enough — the resources under it inherit the value, which is why the split works without tagging every individual robot or server. fleetBilling.costByTag returns three distinct kinds of money, and the panel deliberately shows all three: The distinction matters before a chargeback goes out: unallocated is a tagging gap you can fix, unattributable is a property of the metric and will not improve by tagging more resources. Groups and unattributable entries are only rendered when their cost is non-zero; if nothing metered this period carries the key at all, the panel says so by name. When the organisation has no tags yet, the panel renders guidance instead of an empty chart — there is nothing to group by. Voice has an equivalent, billing.costByTag. It splits the period’s rated CDR cost by the values of one tag key along a chosen dimension — trunk (the default) or phone_number — with an optional periodStart. It exists because billing.account returns a single balance for the whole tenant, which cannot answer “which cost centre burned it”, whereas each CDR carries a rated cost and a trunk. It deliberately does not stamp a currency on its result; see Currency handling. If the allocation query fails, the procedure raises Voice cost allocation failed. rather than returning a plausible-looking partial split.

Reconciling a tag split against the period total

The panel renders a reconciliation line instead of asking you to trust the bars:
The two sides are compared with a tolerance of one cent. When they match, the line reads Allocated + unallocated matches the period total. When they do not, it reads Split does not match the period total — some usage is not resource-attributed, and the accounted-vs-total figures are printed beside it as <accounted> / <total>. A mismatch is expected whenever unattributable metrics carry cost: that money is in the period total but, by definition, in neither the groups nor the unallocated bucket. Read the unattributable lines to see how much, and for which metrics.

Per-tenant rate overrides in fleet billing

The fleet billing screen states its own limitation:
Rates are platform defaults. Per-tenant overrides land in V2.
Every rate_per_unit on that screen is the platform default for the vertical’s rate plan. There is no per-tenant override applied to fleet metering from this screen. (Voice-side prices are a separate mechanism — see Rate cards and who may change a price.)

The rating account and when it is created

Voice call rating happens in CGRateS. billing.account calls ApierV1.GetAccount with the org id as the CGRateS tenant and, by default, the account name default. It returns found, the account id, a disabled flag, and the monetary balance. The account is created lazily, on the first rated call. There is no provisioning step. This is why the console distinguishes four states, computed once and shared by the Account Balance tile and the Rating Engine card so they cannot contradict each other: pending is the state most often misread as a failure. “Has a rating account been auto-provisioned yet” and “did the org start the Voice product” are two different facts: the first comes from CGRateS, the second from organization.subscriptions.voice, which hub.setSubscription flips immediately. Voice being Active on the Hub while the rating engine reports no account is the normal state of an activated workspace that has not yet placed a call. The balance tile says active — bills on first call in that state.

The flat voice tariff: per call, per minute, per-second rounding

The portal’s Pricing card describes one tariff that applies to every call, inbound or outbound: The Rating Engine footer restates the same thing as Billing Mode: Per-second, flat tariff, with Rating Precision: 6 decimals. Two things to know about these two numbers:
  • They are display constants in the portal bundle that mirror what the CGRateS seed script writes (RT_CONNECT’s ConnectFee and RT_PER_MIN’s Rate). Changing the price means editing both the portal constant and the seed script, then re-running the seed. Editing only one makes the screen lie.
  • Trunk numbers are not part of this tariff. They are purchased separately from the Trunks page (one-time and/or monthly per number). Once a number is owned, calls on it rate at this flat plan.

Wallet balance vs metered spend

These are different measurements and are expected to disagree:
  • Account Balance is the CGRateS *monetary balance — a prepaid wallet figure, moved by top-ups and by rating.
  • Month-to-Date Cost / Spend this month is cdr.monthStats.cost, the sum of rated cost over this calendar month’s CDRs, read from ClickHouse.
cdr.monthStats deduplicates before summing: it groups the cdrs table by call_id, takes max(duration) and max(cost) per call, and then sums those. Multiple CDR rows for the same call therefore contribute once. The same query returns calls (distinct calls) and duration_sec, from which the screens derive Total Minutes / Minutes this month. Because the wallet spans all time and includes top-ups, while metered spend spans one month and includes only rated calls, the two numbers answer different questions. Use the balance to answer “can this tenant still place calls”, and metered spend to answer “what did this month cost”. The portal also renders a cross-vertical strip from hub.overview, summing cost_mtd_usd across voice, robotics and games. That strip refuses to print $0.00 when the query failed or is still loading — it shows