A usage dashboard typically combines several views of a customer’s account: the bill they’re on track to be charged, the credits they have left, where their spend is coming from, and how usage is trending. Each view maps to a different Orb endpoint, with different performance, caching, and grouping characteristics. Choosing the right endpoint for each view, and composing them carefully on the page, is what keeps the dashboard fast and the numbers consistent with the invoice.Documentation Index
Fetch the complete documentation index at: https://docs.withorb.com/llms.txt
Use this file to discover all available pages before exploring further.
What a usage dashboard usually contains
Common dashboard sections:- A current period total — the bill the customer is on track to be charged at the end of the period.
- A credit balance — for prepaid plans, the remaining credits and the rate of consumption.
- A breakdown by group — top-N workspaces, projects, regions, or any cost-driving dimension. A specialized dimension in Orb is the license scope.
- A time series — usage or cost over the current billing period.
- An ad-hoc analytics surface — a freeform query workspace for power users and support.
prices/evaluate on first paint.
Bill preview vs. cost breakdown
There are two framings for what a customer sees:- Bill preview shows what they’ll be charged: line items, discounts, minimums, taxes, credits drawn down. The relevant number is
amount_due, and the source of truth is the upcoming invoice. - Cost breakdown shows where spend is coming from: which prices, which groups, which days. The relevant number is the subtotal, not the amount due.
- The headline “what will I be charged” tile sources from the upcoming invoice.
- All breakdowns (by group, by day, by price) source from cost or subtotal endpoints, and the UI labels them as subtotals.
- Breakdown cards link out to the upcoming invoice for the full bill.
Endpoints by dashboard section
| Dashboard section | Endpoint | Cacheable on first paint |
|---|---|---|
| Current bill total | Upcoming invoice | Yes (cache header) |
| Credit balance | Credit ledger / credit blocks | Yes (cache header) |
| Breakdown by group | Grouped costs (private preview) | Yes (precomputed) |
| Cost by license | License usage | No |
| Time series (cost) | Subscription costs / customer costs | Yes (cache header) |
| Time series (usage) | Subscription usage | Yes (cache header) |
| Freeform analytics | prices/evaluate | No (runs live) |
Current bill total — upcoming invoice
GET /v1/invoices/upcoming?subscription_id=… returns the draft invoice for the current period: line items, subtotal, total, and amount due. This is the only endpoint that runs the full bill computation (discounts, minimums, maximums, taxes), so it is the only correct source for the headline total.
The endpoint is scoped to a single subscription. For customers with multiple active subscriptions, call it once per subscription and render the results separately; discount and minimum math does not compose across subscriptions either.
Calling against a subscription whose status is ended returns a 400. Calling against a subscription that has no pending invoice (for example, a plan with no recurring fees) returns a 404; render a “no upcoming invoice” empty state rather than an error.
Credit balance — credit ledger
GET /v1/customers/{id}/credits/ledger returns the customer’s credit ledger, scoped to a pricing unit, in newest-first order. The first entry’s ending_balance is the current balance. Each entry includes a type (increment, decrement, expiration_change, credits_expiry, void, void_initiated), an amount, and a timestamp, so the same response can power the headline balance, a recent-activity feed, and a simple burn-rate projection.
If the customer has no ledger for the requested pricing unit, the endpoint returns an empty paginated list. Treat this as “no credits,” not as an error.
For block-level detail — per-block expiry dates and cost basis — use GET /v1/customers/{id}/credits. This returns a paginated list of credit blocks, useful for warnings such as “X credits expiring on date Y.”
Cost breakdown: grouped costs
This feature is in private preview.Grouped costs are currently available to a limited set of customers. Reach out to your Orb contact to get access.
POST /v1/subscriptions/{id}/grouped_costs returns subtotals over a bounded window, grouped by a registered event property. Subtotals are read from precomputed data in Orb’s system, so the endpoint is fast enough to call on first paint even at moderate cardinality.
The request body for POST /v1/subscriptions/{id}/grouped_costs:
- Pagination is expected to be released to this endpoint shortly. Note that this endpoint does not group by day by default, unlike other cost endpoints.
- In addition to returning grouped costs, Orb will return the full subtotal for the queried period; this will help you display the total subtotal cost, the top groups, and any residual.
- The window between
timeframe_startandtimeframe_endcannot exceed 32 days by default. timeframe_startcannot be more than 100 days in the past.- Each grouping key needs to be enabled per-account; a key that isn’t allowlisted returns a 400.
- A single grouping key is allowed by default; composite grouping is supported but requires enablement.
- This endpoint will return a validation error if there are more than 5,000 distinct groups in the defined window for the subscription (regardless of how many are returned in the response).
limit (default 10, max 100), the response sets has_more: true. Surface this in the UI (“Showing top 10 of N+”) rather than truncating silently.
The fallback for a top-N by cost view is prices/evaluate — this endpoint is explicitly a free-form analytics endpoint so expect it to be slower. If the question can be reformulated as top-N by usage (for example, “top workspaces by API calls” rather than “top workspaces by spend”), the group_by parameter on /subscriptions/{id}/usage is also an option.
Cost by license
If you’re using Orb’s licenses feature, you should fetch usage per license using the dedicated, paginated API. Note that this endpoint accepts agroup_by, allowing you to group usage by license, day, or both.
Just like other cost data, license usage does not necessarily correlate to the amount_due on your invoice because of the presence of other adjustments and discounts. Relatedly, usage beyond a license’s allocation may be deducted from other grants on the credit ledger, accrue as overage on the invoice, or be written off entirely depending on your license configuration. Therefore, we recommend you use this endpoint to understand how much each license has used of its allocation, and consider displaying the amount owed for the full customer separately.
Time series — costs or usage
For a daily cost chart, useGET /v1/subscriptions/{id}/costs for a single subscription, or GET /v1/customers/{id}/costs to roll up across subscriptions that overlap the requested timeframe (overlapping days are summed). The response is one entry per day, each with subtotal, total, and a per-price breakdown.
The view_mode parameter controls how each datapoint is computed:
cumulative(default) — each datapoint’stimeframe_startis the start of the billing period, andtimeframe_endadvances by one day. Each datapoint represents “what the bill would be if the period ended today,” including any minimums and discounts.periodic— each datapoint is the increment for that day, with a one-day window.
cumulative when the pricing involves minimums or discounts. Use periodic for a day-over-day bar chart, when the pricing doesn’t have period-level adjustments that need to be reflected per-day.
For a daily usage chart (raw quantity, independent of pricing), use GET /v1/subscriptions/{id}/usage with granularity=day. Usage can be grouped by an event property if billable_metric_id is also passed.
Grouping behavior depends on whether the metric is decomposable:
- Decomposable metrics (sums, counts) can be grouped by any event property.
- Non-decomposable metrics (unique counts, percentiles) can only be grouped by the price’s
invoice_grouping_key. Per-day “uniques” do not sum to the period total, so arbitrary grouping is not mathematically meaningful.
Freeform analytics: prices/evaluate
POST /v1/prices/evaluate runs rating math dynamically over ingested events. The request body accepts:
- A
customer_idorexternal_customer_id. - A
timeframe_startandtimeframe_end(start cannot be more than 100 days in the past). - A
price_evaluationslist of up to 100 prices. Each entry specifies one ofprice_id,external_price_id, or an inline price (a floating-price definition for what-if scenarios). - A boolean filter expression per price evaluation, with computed properties supported.
- A
grouping_keyslist of properties or computed-property expressions (e.g.hour_floor_timestamp_millis(timestamp_millis)to bucket hourly). - Optional
metric_parameter_overridesfor parameterized metrics.
POST /v1/prices/evaluate_preview_events. The body is the same as evaluate, plus an events array of synthetic preview events.
Caching
Dashboards are read-heavy and first-paint-sensitive. The cache header is the main lever for keeping load times low:Orb-Cache-Max-Age-Seconds. The response includes an Orb-Cache-Updated-At header, which can be surfaced in the UI as “Updated N minutes ago.” Without Orb-Cache-Max-Age-Seconds, any precomputed value is acceptable.
Cache support varies by endpoint:
| Cache behavior | Endpoints |
|---|---|
| Honors the cache header | Upcoming invoice, subscription costs, subscription usage, customer costs, credit ledger, and credit balance. |
| Fast for a different reason | Grouped costs reads precomputed metric values internally, so it is first-paint-safe regardless of headers. The cache header itself does nothing on this route. |
| Never cached, by design | prices/evaluate runs live every time. |
Composing the dashboard
A typical per-customer dashboard, organized by load tier:- First paint, in parallel:
- Upcoming invoice for each active subscription (headline total).
- Credit ledger (current balance, recent activity).
- Subscription costs with
view_mode=cumulativeorperiodic(time series card). - Grouped costs with the relevant grouping key (top-N breakdown).
- “Updated N minutes ago” indicator sourced from
Orb-Cache-Updated-At.
- On user-initiated refresh:
- Re-issue the first-paint calls without the cache header (or with a small
Orb-Cache-Max-Age-Seconds).
- Re-issue the first-paint calls without the cache header (or with a small
Rate limits
Orb’s rate limits are documented separately. Think of these rate limits as the number of users who can concurrently do a fresh load of a usage dashboard, rather than those who can be browsing their usage. These rate limits are calibrated against our experience; in practice, we find that for accounts with millions of end-users, a small subset are active at any given time, a much smaller subset need a live view of their usage, and a fraction of those end users are re-loading their usage simultaneously.Notes on specific cases
- Multi-subscription customers. Most endpoints are subscription-scoped. Render per-subscription cards rather than collapsing across;
GET /v1/customers/{id}/costsis the exception for rolling up across subscriptions that overlap a timeframe. - Credit-denominated plans. A currency value can be a credit pricing unit rather than ISO 4217. Render credits as “1,200 credits,” not “$1,200.00.”
- Webhooks for lifecycle, cache for freshness.
invoice.issued,invoice.edited, andinvoice.payment_succeededare appropriate signals for refreshing the headline tile when discrete invoice events occur. There is no webhook that fires when the upcoming invoice total changes by a small amount; that kind of continuous freshness is the cache header’s job.subscription.usage_exceededandsubscription.cost_exceededfire when a configured threshold is crossed, and are appropriate for alert UIs rather than general invalidation.
Common pitfalls
- Do not display a grouped breakdown of
amount_due. Discounts, minimums, and maximums do not decompose by group. Breakdown cards work in subtotal-space; link out to the upcoming invoice for the bill. - Do not put
prices/evaluateon the first-paint path. It is a dynamic endpoint. Place it behind an explicit tab and “Run” button. - Do not re-bucket timestamps client-side. Day boundaries respect the customer’s timezone on the server. Client-side re-bucketing breaks during DST and for non-UTC customers.
- Do not expect grouped costs to honor the cache header. The endpoint is fast for a different reason; sending the headers has no effect.