# Usage analytics

> Query estimated provider cost, requests, errors and latency with GET /v1/usage, grouped by model, provider, route, project, key, feature, workflow or end user.

Source: https://infro.io/docs/observability/analytics

> **Note:** INFRO is in private early access. Running these examples requires an early-access account, an active trial or subscription entitlement, and your own provider connection. Self-serve purchasing is not generally open. See [Early access](https://infro.io/early-access).

Usage analytics summarize traffic through your own provider connections. Cost values estimate provider spend; INFRO software subscriptions are billed separately. Unpriced requests are counted explicitly so an incomplete estimate is not mistaken for a complete bill.

Totals cover only what the calling key may read: its project or its own requests by default, or the whole organization when an owner or admin granted it. See [Request traces](https://infro.io/docs/observability/traces) for how history access works.

## Query usage

```bash
curl "https://api.infro.io/v1/usage?granularity=day&group_by=provider" \
  -H "Authorization: Bearer $INFRO_API_KEY"
```

| Parameter | Supported values |
| --- | --- |
| `granularity` | `hour`, `day` (default), or `month`. |
| `group_by` | `model`, `modality`, `project`, `key`, `endpoint`, `provider`, `connection`, `route`, `environment`, `surface`, `workflow`, `step`, `feature`, `user`, or `tag:<key>` for a [promoted tag](https://infro.io/docs/features/attribution); omit for ungrouped totals. `surface=gateway\|playground\|studio` narrows any grouping to requests from gateway keys, the console Playground or INFRO Studio. `workflow`, `step`, `feature`, `user` and `tag:<key>` return the hundred largest groups plus one `__other__` row. |
| `from`, `to` | ISO timestamps. Default is the last 7 days; a single requested range may not exceed 400 days and remains subject to plan history limits. |
| `format` | `json` (default) or `csv`. |

## Response fields

Each bucket contains `bucket`, `group`, `requests`, `units`, `cost`, `unpriced_requests`, `error_rate`, `p50_ms`, `p95_ms` and `p99_ms`. Cost is USD; latency is milliseconds, measured on calls that reached your provider and answered inside the request (a refused request has no latency and is left out, and so is an async render: an image or video job's wall time from submission to result is not a request latency, and its row counts in `requests` and `cost` only). Each percentile is exact for its bucket; percentiles do not average, so a week's p95 is not the mean of seven daily ones — ask for one bucket over the window (`granularity=month` on a window inside one month) when you need the period's own figure. The response reports `from`, `to`, `requested_from` and `retention_days` so you can see whether the requested window was narrowed.

A query returns its buckets together: `has_more` is false and `next_cursor` is null. Split large analyses into time windows with `from` and `to`; there is no cursor pagination for usage buckets.

### Response cache fields

Each bucket also carries `cache_hits`, `cache_lookups` (hits plus misses, the denominator of a hit rate), `estimated_provider_cost_avoided` (USD, the sum of each hit's source estimate), `cache_hits_unpriced` (hits whose source had no price, left out of that sum), and `cache_hit_p50_ms`, `cache_hit_p95_ms` and `cache_hit_p99_ms`. `requests` includes hits; `p50_ms`, `p95_ms` and `p99_ms` cover provider calls only, so a cache never makes provider latency look faster than it is. See [Response cache](https://infro.io/docs/features/caching).

## Download a series as CSV

`format=csv` returns the `data` array as a file, one row per bucket and group, with the columns `bucket`, `group`, `requests`, `units`, `estimated_provider_cost_usd`, `unpriced_requests`, `error_rate`, `p50_ms`, `p95_ms`, with `p99_ms` as the last column. Columns are only ever added at the end, so a reader that takes them by position keeps working. The cost column is the same estimated provider cost as `cost` in JSON and excludes unpriced requests, which is why `unpriced_requests` sits beside it. `group` is empty on an ungrouped query and on requests that carried no value for the dimension; on a truncated grouping, `__other__` is the remainder of every group outside the hundred largest.

The envelope's facts are response headers rather than lines in the file: `X-INFRO-Granularity`, `X-INFRO-Group-By`, `X-INFRO-From`, `X-INFRO-To`, `X-INFRO-Requested-From`, `X-INFRO-Groups-Truncated` and `X-INFRO-Retention-Days`. The console offers this as **Chart data (CSV)** beside each Analytics chart. It is a different dataset from the [FOCUS cost dataset](https://infro.io/docs/observability/focus-export), which is charge-grain and built for FinOps tools.

The CSV gains two columns, after `p95_ms` and before `p99_ms`: `cache_hits` and `estimated_provider_cost_avoided_usd`.

## Attribution and history

Developer includes 7 days of queryable history, Startup 14, Team 30, Business 90 and Enterprise 365. The API reports the applied window. Optional payload logging is a separate opt-in with plan limits; see [Privacy](https://infro.io/docs/privacy).

`group_by=user` groups by the `user_id` you attach in request metadata, and `group_by=feature` by `metadata.feature` or `X-INFRO-Feature`. A tag groups only once it is promoted (up to five keys, under Cost centers → Attribution settings); an unpromoted `tag:<key>` is a 400 `tag_not_promoted`. Session ids and unpromoted tags are filters on [request history](https://infro.io/docs/observability/traces). In the console, Analytics → Where the spend went offers the same groupings, shows what share of estimated spend carries the chosen field, and opens a row's requests. For custom provider rates and cost reconciliation, see [Billing](https://infro.io/docs/org/billing).
