Usage and billing
Querying usage, grouping by end user, quotas, spend caps, invoices and credits.
GET /v1/usage aggregated usage, grouped and filtered
GET /v1/usage/timeseries bucketed over time
GET /v1/usage/export CSV or JSONL for a period
GET /v1/quotas current limits and consumption
PATCH /v1/quotas change limits (admin)
GET /v1/invoices list
GET /v1/invoices/{id} read
GET /v1/invoices/{id}/pdf download
GET /v1/credits balance and grants
POST /v1/spend-caps set a cap
Usage
curl -sS "$WB_BASE_URL/usage?period=month_to_date&group_by=meter,tier" \ -H "Authorization: Bearer $WB_API_KEY"
{
"period": { "start": "2026-10-01T00:00:00Z", "end": "2026-10-06T09:40:00Z" },
"currency": "USD",
"groups": [
{ "meter": "tier2.session_s", "tier": 2, "quantity": 184420, "unit": "s", "amount_minor": 3688 },
{ "meter": "tier1.session_s", "tier": 1, "quantity": 902100, "unit": "s", "amount_minor": 1804 },
{ "meter": "cpu_ms", "tier": null, "quantity": 4.21e9, "unit": "ms", "amount_minor": 842 },
{ "meter": "egress_gb", "tier": null, "quantity": 412.8, "unit": "GiB", "amount_minor": 4128 },
{ "meter": "tool_call", "tier": 0, "quantity": 1842011, "unit": "count", "amount_minor": 184 }
],
"subtotal_minor": 10646,
"credits_applied_minor": 5000,
"total_minor": 5646
}
| Parameter | Values |
|---|---|
period | today, yesterday, month_to_date, last_month, or start/end |
group_by | Any of meter, tier, project_id, end_user_id, region, label.<key> |
meter | Filter to specific meters, repeatable |
project_id, end_user_id, region, tier | Filters |
Grouping dimensions combine. group_by=end_user_id,tier gives per-customer, per-tier cost in one
query, which is the shape a reseller needs for its own invoices.
Per end user
curl -sS "$WB_BASE_URL/usage?period=last_month&group_by=end_user_id&order=amount_desc&limit=20" \ -H "Authorization: Bearer $WB_API_KEY"
{
"groups": [
{ "end_user_id": "eu_customer_4821", "external_id": "cust-4821", "amount_minor": 184220, "quantity_by_meter": { "tier2.session_s": 84110, "egress_gb": 42.1 } },
{ "end_user_id": "eu_customer_1190", "external_id": "cust-1190", "amount_minor": 92410, "quantity_by_meter": { "tier3.session_s": 18220, "browser_page_s": 41022 } }
],
"has_more": true
}
Only usage tagged with end_user_id at emission time appears. Untagged usage is grouped under
null and cannot be assigned retroactively — which is why the recommendation is to tag everything
from day one.
Time series
curl -sS "$WB_BASE_URL/usage/timeseries?period=today&bucket=1h&group_by=tier" \ -H "Authorization: Bearer $WB_API_KEY"
{
"bucket": "1h",
"series": [
{ "tier": 2, "points": [
{ "t": "2026-10-06T08:00:00Z", "quantity": 8420, "amount_minor": 168 },
{ "t": "2026-10-06T09:00:00Z", "quantity": 9112, "amount_minor": 182 }
]}
]
}
Buckets: 1m (last 24 h), 1h (last 90 d), 1d (last 2 y).
Export
curl -sS "$WB_BASE_URL/usage/export?period=last_month&format=jsonl&granularity=session" \ -H "Authorization: Bearer $WB_API_KEY" -o usage.jsonl
granularity is session, run or hour. Session granularity is the one to use for rebilling —
one row per session with its meters, tags and cost.
Freshness
| Stage | Lag |
|---|---|
| Node emission to minute bucket | < 30 s typical, < 2 m p99 |
| Minute bucket to rated usage | < 30 s |
| Rated usage to the usage API | < 10 s |
| End to end | < 60 s typical |
usage_to_date on a live session object comes directly from the node's last flush and is fresher —
under 15 seconds — but it is an estimate, not the billed figure.
Quotas
curl -sS "$WB_BASE_URL/quotas" -H "Authorization: Bearer $WB_API_KEY"
{
"organization_id": "org_2kQ9xw",
"limits": {
"concurrent_sessions": { "tier0": null, "tier1": 500, "tier2": 100, "tier3": 50, "tier4": 10 },
"session_creates_per_minute": 600,
"storage_gib": 10000,
"egress_gb_per_day": 2000
},
"current": {
"concurrent_sessions": { "tier1": 42, "tier2": 18, "tier3": 3, "tier4": 1 },
"storage_gib": 1842
}
}
Quotas are concurrency and rate ceilings, enforced synchronously at admission. They are distinct from spend caps, which are monetary and eventually consistent.
Raise them with PATCH /v1/quotas up to the plan's ceiling; beyond that, it is a sales conversation,
because the ceiling reflects capacity the platform has actually provisioned.
Per end user
curl -sS -X PATCH "$WB_BASE_URL/end-users/eu_customer_4821" \
-H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{
"quotas": { "concurrent_sessions": { "tier2": 5, "tier4": 1 } },
"spend_cap_minor": 50000
}'
This is how a reseller stops one of its customers from consuming the whole organization's quota.
Spend caps
curl -sS "$WB_BASE_URL/spend-caps" \
-H "Authorization: Bearer $WB_API_KEY" -H "Content-Type: application/json" \
-d '{
"scope": "organization",
"period": "monthly",
"amount_minor": 500000,
"currency": "USD",
"action": "block_and_stop",
"alerts": [ { "threshold_pct": 50 }, { "threshold_pct": 80 }, { "threshold_pct": 95 } ]
}'
action | Effect at the cap |
|---|---|
alert_only | Webhook only; nothing is stopped |
block_new | New sessions refused with spend_cap_reached; running sessions continue |
block_and_stop | New sessions refused and running sessions stopped |
Alerts fire as quota.threshold_reached webhooks. Caps can be scoped to the organization, a project
or an end user.
Caps are enforced against rated usage, which lags real time by up to 60 seconds, so an explosive burst can overshoot by roughly one minute of maximum burn. Pair a cap with a concurrency quota for a hard ceiling — quotas are synchronous.
Invoices
curl -sS "$WB_BASE_URL/invoices?limit=12" -H "Authorization: Bearer $WB_API_KEY"
{
"object": "list",
"data": [
{
"id": "inv_8Pq2Vd",
"period_start": "2026-09-01", "period_end": "2026-09-30",
"status": "paid",
"subtotal_minor": 1284200,
"credits_applied_minor": 100000,
"tax_minor": 213156,
"total_minor": 1397356,
"currency": "USD"
}
]
}
curl -sS "$WB_BASE_URL/invoices/inv_8Pq2Vd?expand=lines" -H "Authorization: Bearer $WB_API_KEY"
{
"lines": [
{ "meter": "tier2.session_s", "quantity": 18442000, "unit_price_minor": 0.002, "amount_minor": 368840,
"breakdown_by": "project", "breakdown": [
{ "project_id": "proj_7fTn21", "quantity": 14002000, "amount_minor": 280040 },
{ "project_id": "proj_3Lm9Kd", "quantity": 4440000, "amount_minor": 88800 }
]}
]
}
A finalized invoice is immutable. Corrections are credit notes against it, never edits — which keeps every invoice explainable from the rated usage that produced it.
Credits
curl -sS "$WB_BASE_URL/credits" -H "Authorization: Bearer $WB_API_KEY"
{
"balance_minor": 420000,
"currency": "USD",
"grants": [
{ "id": "cred_4Jd8Pq", "amount_minor": 500000, "remaining_minor": 420000,
"reason": "annual_commit", "expires_at": "2027-01-01T00:00:00Z" }
]
}
Credits apply before any invoiceable usage accrues, oldest-expiring first. A prepaid organization
with zero balance gets insufficient_credits on create.
Webhooks worth subscribing to
| Event | Why |
|---|---|
quota.threshold_reached | Act before the cap, not after |
quota.exceeded | Sessions are being refused right now |
invoice.finalized | Pull the invoice for your own accounting |
invoice.payment_failed | Fix the payment method before suspension |
credit.low | Prepaid balance below threshold |
See webhooks.