Skip to main content

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.

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.

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.
Each section is best served by a different endpoint. A common source of bugs is using an endpoint optimized for one section in another — for example, using the upcoming invoice for a per-workspace breakdown, or running 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.
Discounts, minimums, and maximums don’t decompose by group, so avoid showing these in any breakdown concept. A 10% account-level discount can’t be attributed to “this workspace contributed Xtothediscount,"andaX to the discount," and a 1,000 minimum can’t be split across regions in any non-arbitrary way. A breakdown that shows “amount due by group” will produce numbers that don’t reconcile to the invoice total. In practice:
  • 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 sectionEndpointCacheable on first paint
Current bill totalUpcoming invoiceYes (cache header)
Credit balanceCredit ledger / credit blocksYes (cache header)
Breakdown by groupGrouped costs (private preview)Yes (precomputed)
Cost by licenseLicense usageNo
Time series (cost)Subscription costs / customer costsYes (cache header)
Time series (usage)Subscription usageYes (cache header)
Freeform analyticsprices/evaluateNo (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:
{
  "grouping_keys": ["workspace_id"],
  "timeframe_start": "2026-05-01T00:00:00Z",
  "timeframe_end":   "2026-05-23T00:00:00Z",
  "currency": "usd",
  "limit": 10
}
You should expect two additions to this endpoint before it is generally available:
  • 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 endpoint has several constraints; these are quotas that may be adjusted:
  • The window between timeframe_start and timeframe_end cannot exceed 32 days by default.
  • timeframe_start cannot 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).
Please reach out to Orb support if you have a use case that requires extended quotas. When the number of distinct groups exceeds 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 a group_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, use GET /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’s timeframe_start is the start of the billing period, and timeframe_end advances 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.
Use 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.
For non-decomposable metrics where arbitrary grouping is required, use grouped costs (which operates in subtotal-space) instead. Day buckets are demarcated by the customer’s local midnight, including daylight savings. Both endpoints handle this server-side; do not re-bucket UTC timestamps on the client.

Freeform analytics: prices/evaluate

POST /v1/prices/evaluate runs rating math dynamically over ingested events. The request body accepts:
  • A customer_id or external_customer_id.
  • A timeframe_start and timeframe_end (start cannot be more than 100 days in the past).
  • A price_evaluations list of up to 100 prices. Each entry specifies one of price_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_keys list of properties or computed-property expressions (e.g. hour_floor_timestamp_millis(timestamp_millis) to bucket hourly).
  • Optional metric_parameter_overrides for parameterized metrics.
Because evaluation is dynamic, there is no precompute and no cache. High-cardinality grouping or long timeframes can take on the order of seconds. Place this endpoint behind an explicit “Run query” affordance in the UI (a tab or button), not on the first-paint path. For what-if flows that should evaluate against synthetic events rather than ingested usage (for example, proposal or quoting tools), use 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-Control: cache
Orb-Cache-Max-Age-Seconds: 300
When sent on a supported endpoint, Orb returns a precomputed snapshot if one is fresher than 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 behaviorEndpoints
Honors the cache headerUpcoming invoice, subscription costs, subscription usage, customer costs, credit ledger, and credit balance.
Fast for a different reasonGrouped 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 designprices/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=cumulative or periodic (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).
This gives roughly five or six cache-aware or precompute-backed calls on first paint, no dynamic rating queries on the load path, and no breakdown card attributing amount due.

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}/costs is 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, and invoice.payment_succeeded are 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_exceeded and subscription.cost_exceeded fire 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/evaluate on 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.