/admin/robotics/billing
and /admin/games/billing share the same shell and differ only by a vertical
discriminator (robotics | games). Everything on the page — the KPI strip, the
metered rows, the cost-by-tag split, the period history — is driven by that one
value.
That matters for reading the page: the rate plan is per vertical. The metric
list on the robotics screen and the metric list on the games screen come from two
separate server-side rate plans, and neither is a subset of the other. A vertical
whose rate plan is empty renders as “No billable metrics yet — rate plan is empty
for <vertical>” rather than as a zero-dollar bill.
How the rate plan is served per vertical
Three procedures back the screen.getUsage returns the rate plan and the metered usage together. There is no
separate “list the rate plan” call — the plan is the row set, with units and
cost_usd filled in for the current period. A metric that exists in the plan but
has not been used this period still comes back as a row, with zero units. The
Metrics billed KPI is exactly this distinction: it counts rows where
units > 0 against the total number of rows in the plan.
The current-period query refetches on a 120-second interval, and the header shows
the last successful aggregation time. Aggregation is computed inline on each query
rather than precomputed, so the Refresh button re-runs it. History is cached
with a 5-minute stale time and is requested for 6 months.
Reading a metered usage row
Each row in This period · metered usage is one metric from the vertical’s rate plan. The fields are:cost_usd is computed server-side and rendered as returned; the console does not
multiply rate by units itself. The quantity and the dollar figures are rounded for
display only — quantities below 1 are shown to four decimal places, quantities
between 1 and 100 to two, and larger quantities are progressively rounded. Do not
reconcile a spreadsheet against the rendered strings; reconcile against the
units and cost_usd values from getUsage.
The panel header shows the response’s currency field, defaulting to USD. All
cost fields in the payload are _usd-suffixed.
What each unit label counts and over what window
Theunit_label is the meter’s unit, and it is authoritative in two places at
once: it is the denominator of the price ($0.02/unit_label) and the unit of the
quantity (1,204 unit_label). Because both come from the same field, a row is
always internally consistent — you never have to guess whether a rate is per hour
while the quantity is in minutes.
What the unit counts for a given metric is stated in that metric’s
rate_plan_description, which the screen renders verbatim under the row title.
TeleQuick treats that description as the definition of record for the meter;
this page deliberately does not restate per-metric semantics, because the plan is
served from the control plane and can add metrics without a docs change.
The window is the billing period, not a rolling window. getUsage returns
periodStart and periodEnd, which the screen prints in the page subtitle, and
every units value in the response is the total accumulated inside that period
for the queried vertical. The KPI strip additionally surfaces
projected_end_of_period_usd as Projected end-of-period — a server-side
projection for the same window, not a forecast beyond it.
Because the period is closed at periodEnd, the current-period row set is a
moving target and the history table is not. Once a period closes, its totals land
as a row in fleet_usage_period and are served by fleetBilling.history.
Which metrics are resource-attributed and which are unattributable
A chargeback split only works for metrics that carry a per-resource dimension — that is, metrics whose usage the meter can attribute to a specific robot, site, server, or fleet. Metrics without that dimension cannot be divided by any tag, no matter how thoroughly you tag.fleetBilling.costByTag makes the division explicit and returns three buckets:
The console renders all three, and the distinction is the point. Unallocated is
a tagging gap you can close. Unattributable is a property of the metric — the
reason field tells you which, and the screen prints it next to the metric name
for any unattributable metric with non-zero cost.
Two further details in the group list:
- Groups arrive ordered by cost, largest first; the bars are scaled against the first group.
- A group whose
valueis the empty string is rendered as(no value). This is not the same as unallocated: it means the tag key is present on those resources with an empty value, whereas unallocated means the key is absent entirely.
Reconciling the split against the period total
The panel prints a reconciliation line rather than asking you to trust the bars:unattributable
entries above the line, since that is where the difference lives.
Check this line before you send a chargeback. A split that does not reconcile is
not wrong, but it is incomplete: the residual is real spend that the selected tag
key structurally cannot carry.
Tagging the objects that make a chargeback split work
The tag key selector is populated bytags.keys({ orgId }), which reads the
resource_tag table for the organization, counts occurrences per key, and returns
{ key, count } sorted by count descending with the key name as tiebreak. The key
list is built from up to 20,000 tag rows. The selector defaults to the first
entry, so the most-used key in your organization is the default grouping on
both billing screens.
If the organization has no tags at all, the panel does not render a split. Instead
it tells you what to tag, and the advice differs by vertical:
- Robotics — tag a site or a fleet.
- Games — tag a region or a server fleet.
Reading the period history
The Recent months panel lists closed periods fromfleet_usage_period, most
recent first, with three columns: the period (month and year), the comma-joined
list of metric identifiers billed in that period, and the period total.
The metric list per row is what makes this panel useful beyond the totals: if a
metric appears in one month and not the next, the rate plan for that vertical
changed, or nothing metered against it. A freshly onboarded organization sees “No
history yet. This month’s row lands once usage is aggregated.”
Current limits of the rate plan
Three constraints are stated on the screen itself and are worth carrying into any integration you build on these procedures:- Rates are platform defaults. Per-tenant rate overrides are not implemented;
every organization on a given vertical sees the same
rate_per_unit. - Aggregation is inline. Usage is computed on each query, not served from a precomputed rollup. Re-querying is how you refresh.
- The period is fixed by the server. The screen holds a period selector in
state but does not currently pass it to
getUsage, so the current-period view is always the server’s active period.
Related
- Telemetry — the metric and CDR streams that feed usage
- Authentication — org-scoped API keys for the control plane