Usage and spend caps
Viewer-hours and GiB, the monthly spending cap, and the alerts with their signatures.
What your streams used, what it costs this month, and the cap that keeps a surprise from becoming a bill.
What is counted
| Unit | From | Notes |
|---|---|---|
| Viewer-hours | each viewer session's length | sessions under 10 s count no time (a join that never saw a picture) |
| GiB | the bytes our relays sent, every role | the bill can be checked against it: egress is most of the cost |
| Sessions | each closed session | a page opens a new session about every 11 minutes, so one viewer is several sessions |
MoQ viewers are recorded by the relays, LL-HLS viewers by our HLS edge, per seat, into the same totals; the answer shows the two apart. A session is counted on the UTC day it ended, so a viewer watching now shows up when their session closes.
Reading it
curl -s "$API/v1/usage?from=2026-10-01&to=2026-10-03" -H "Authorization: Bearer $SK"from and to are UTC days (default: the 1st of this month to today, at
most 92 days a call); stream=st_... narrows it to one stream.
Illustrative example. The values below are made up to show the shape; they are not a measurement.
{
"tenant": "tn_...", "from": "2026-10-01", "to": "2026-10-03",
"days": [{
"date": "2026-10-03", "viewer_hours": 41.2, "gib": 69.1, "bytes": 74196824064, "sessions": 412, "viewer_sessions": 398,
"moq": { "viewer_hours": 39.8, "viewer_sessions": 380, "bytes": 71635353600 },
"hls": { "viewer_hours": 1.4, "viewer_sessions": 18, "bytes": 2561470464 },
"streams": [{ "stream": "st_8f2c...", "namespace": "live/st_8f2c...", "viewer_hours": 41.2, "...": "..." }]
}],
"total": { "viewer_hours": 41.2, "gib": 69.1, "...": "..." },
"month": {
"month": "2026-10", "viewer_hours": 41.2, "gib": 69.1, "bytes": 74196824064, "sessions": 412,
"free_viewer_hours": "...", "free_gib": "...",
"prices": { "per_gib_usd": "...", "per_viewer_hour_usd": "..." },
"spend_usd": "...", "spend_cap_usd": 50, "percent_of_cap": "...", "cap_reached": false,
"alerts": [{ "percent": 50, "at": "2026-10-12T08:14:03Z" }]
}
}(The usage numbers are an illustration of the shape, not a measurement;
month also carries the free allowance, the prices and the spend, which
are the pricing decision above.) A stream you deleted shows as
ns:<namespace>. GET /v1/tenant shows your account: its limits, its cap
(spend_cap_usd) and the same month block. month.alerts lists the
spending alerts sent this month, oldest first, with when each went out (a
new cap re-arms the levels, so a level can appear twice).
On a self-hosted deployment all of this
comes from nearglass-meter shipping each relay's session log. Without it every
number stays at zero and no cap ever triggers.
The spending cap
Every account has a monthly cap (50 USD by default during the pilot; a
self-hoster sets NEARGLASS_API_DEFAULT_SPEND_CAP). Set your own, up to the
most your account allows (500 USD unless the operator raised it, or
NEARGLASS_API_MAX_SPEND_CAP); above that is 409 above_max_spend_cap:
curl -s -X PATCH $API/v1/tenant -H "Authorization: Bearer $SK" -d '{"spend_cap_usd": 80}'0 allows the free allowance only: any spend at all reaches it. A new cap
starts the alerts over. At the cap (spend equal to or over it):
- New viewers are refused:
viewer-tokensand the public waiting room answer402 spend_cap_reached(with illustrative figures:this month's spending cap is reached (50.12 of 50.00 USD): no new viewers until it is raised (PATCH /v1/tenant) or the month ends; viewers already in keep watching), and the player shows "Cannot play". - Viewers already watching keep watching, to the end: a seat already held still gets its tokens.
- Publishers and controllers are not refused: the viewers already in need the stream.
So the spend can pass the cap by what the viewers already in go on to watch, plus up to a minute of reporting delay. Raising the cap lets new viewers in again at once; a new month starts from zero.
Alerts
Set a webhook and we POST to it at 50 %, 80 % and 100 % of the cap, once each a month (a new cap re-arms them):
curl -s -X PATCH $API/v1/tenant -H "Authorization: Bearer $SK" -d '{"webhook_url": "https://example.com/hooks/video"}'The URL must be a public address. The answer holds webhook_secret once.
Each alert:
Illustrative example. The values below are made up to show the shape; they are not a measurement.
{ "type": "spend.threshold", "tenant": "tn_...", "month": "2026-10", "percent": 80,
"spend_usd": 40.1, "spend_cap_usd": 50, "at": "2026-10-21T09:14:03Z", "note": "" }type is spend.cap_reached at 100 %, and its note says new viewers are
refused until the cap is raised or the month ends. The header
X-Nearglass-Signature: t=<unix seconds>,v1=<hex> signs it. We try three
times (at once, after 2 s, after 10 s). The signature check, with working
code in Node.js and Python, the retry rules and the other events are on
Webhooks.
"webhook_url": null removes the webhook.
Live numbers
GET /v1/streams/{id}/stats is the stream now: seats taken, people in line,
the cap, the peak, LL-HLS seats, turns, and when the ingest last connected.
It is for a dashboard polling every few seconds; the player never calls it.
Its fields are on Monitoring.
What was run, and what was not
Run on one host from the packages: a cap set to 0, a publisher session
shipped by nearglass-meter, GET /v1/usage and GET /v1/tenant showing the
spend and cap_reached, 402 spend_cap_reached on viewer-tokens, the
alerts at 50, 80 and 100 % arriving signed, 409 above_max_spend_cap for a
cap over the maximum, a new cap re-arming the alerts, and a webhook on a
loopback address refused.