Skip to main content
GET /v1/usage is a forward-only cursor pagination — pass limit for the page size and use the response’s next_page token to walk forward. The cursor encodes the query fingerprint so filters cannot drift between pages.

Basic walk

When page_token is provided, you do NOT need to re-send start_time, end_time, bucket_width, group_by, or filter parameters — they’re encoded in the cursor. In fact, sending them with different values mismatches the fingerprint and returns:
This is intentional. A cursor that silently re-resolves params (e.g. resolves a new now for end_time on page 2) would return different data than page 1 sees — silent skew. The fingerprint check is Stripe-grade and we don’t relax it.

Cursor lifetime

  • Tokens expire 24 hours after the page-1 response that minted them. Walking pages slower than 24 hours apart returns 400 invalid_page_token with token_expired detail.
  • Tokens are scoped to your team — using a cursor from team A’s response against team B’s API key returns 400 invalid_page_token.

Cursor opacity

The next_page value is base64url-encoded JSON with version + query fingerprint + last-bucket-start + expiry. Don’t parse it yourself — treat it as opaque. Future versions of the platform may add fields or change the encoding entirely.

Known edge case — limit smaller than per-bucket group count

The cursor encodes last_bucket_start (the timestamp of the last bucket returned on the current page). If a bucket has N groups and your limit is less than N:
  • Page 1 returns the bucket with the first limit groups; has_more: true; next_page encodes that bucket’s bucket_start
  • Page 2 — using the cursor — starts AFTER bucket_start, so the remaining groups in that bucket are LOST
In practice this matters when:
  • Your group_by is multi-dimensional (e.g. group_by=type,model could produce 8 groups per bucket: 4 types × 2 models)
  • Your limit is unusually small (< the maximum expected groups-per-bucket)
Recommended workaround: set limit ≥ the max possible groups per bucket. For a single group_by like type (≤ 4 groups), limit ≥ 4 is safe. For multi-dim group_by, multiply: group_by=type,modellimit ≥ 16 is safe up to 8 models. The defaults (limit=100) are generous enough that most production callers never hit this. The proper fix is to paginate at the bucket level (return whole buckets, never split a bucket’s groups across pages). It’s on the v1.1 roadmap — see the pagination plan in launch-week followups.

Stable ordering

Within a response, buckets are ordered by bucket_start ascending (oldest first). Within a bucket, groups are ordered by total credit charge descending (highest spend first), then by dimensions alpha-sort tie-break. The order is stable across page walks for a given fingerprint — paging forward through a 1000-row window deterministically returns the same row order whether you do it in one shot or in five 200-row pages.

Sample code

Where to next?