Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/skills/frontend-standards/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ Small shared primitives already exist. Reuse or extend them instead of duplicati
| Info/warning callout | `InfoBanner` (`tone="info" \| "warning"`) |
| Page title + description + action | `PageHeader` |
| Destructive action without a modal | `ConfirmButton` (two-click arm/confirm) |
| Filter over a small fixed option set | `FilterSelect` (token-styled native `<select>`) |
| Filter over a large or open option set | `FilterMultiComboBox` (type-to-filter, holds a set of values; `allowsCustom` when the value space is not enumerable) |
| Applied filters, each removable | `FilterChips` (`components/FilterChips.tsx`); one chip per value, and pass `clearLabel` so several chips of one dimension stay distinguishable |
| Form field wrapper | `Field` (`components/Field.tsx`) |
| Tabular data | `Table` (`components/Table.tsx`) |
| Tabular data | `DataTable` (`components/DataTable.tsx`) |

`errorMessage(error)` centralizes turning an `ApiError`/`Error`/unknown into a display string;
use it rather than reaching into `error.message` yourself.
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ The per-request flow (auth → budget → dispatch → reconciliation) spans sev
bugs suite-wide. Mark a genuinely flaky test with
`@pytest.mark.flaky(reruns=...)` (from `pytest-rerunfailures`) and say why,
rather than reintroducing a global retry.
- Integration tests can use `TEST_DATABASE_URL` (if provided), or Testcontainers PostgreSQL (`postgres:17`) if not. Running integration tests without Docker may fail when Testcontainers is required.
- Integration tests need PostgreSQL: `TEST_DATABASE_URL` if set, otherwise a Testcontainers `postgres:17`, so without Docker the suite cannot start. SQLite is not a fallback even though `_to_async_url` accepts one: the fixtures tear down with `DROP TABLE ... CASCADE`, which SQLite rejects, so every test errors in teardown. With no Docker, point `TEST_DATABASE_URL` at any reachable PostgreSQL instead.
- Two tests assert the provider-error sanitization by making a real outbound call (`test_error_detail_leakage.py::test_provider_error_does_not_leak_details`, `test_streaming_error_event.py::test_streaming_creation_error_returns_http_error`). With no network egress the upstream fails differently and both report a status mismatch, so treat them as environment noise rather than a regression, and confirm a change against the rest of the suite.

## Generated Artifacts
- The Postman collection is generated **from** `docs/public/openapi.json`, so it goes stale
Expand Down
9 changes: 5 additions & 4 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,11 @@ targets, which is what a policy exists to keep off the wire.

| Method | Path | Description | Auth |
|--------|------|-------------|------|
| `GET` | `/v1/usage` | List usage logs. Filters: `start_date`, `end_date`, `user_id`, `status`, `status_code`, `model`, `endpoint`, `provider`, `source`, `source_label`, `api_key_id`, `request_group_id`. `status_code` is the HTTP status classifying a failure (e.g. 429 provider rate limit, 402 missing pricing); only error rows carry one, so filtering by it also restricts to `status=error` unless `status` is passed explicitly. `request_group_id` is repeatable and returns a routed request's whole attempt plan (see [Routing](routing.md)). | Master key |
| `GET` | `/v1/usage/count` | Total rows matching the filters (paginator total). | Master key |
| `GET` | `/v1/usage/summary` | Aggregated spend/volume: totals, breakdowns by model/user/key/source/session/endpoint/provider, the failure taxonomy in `errors_by_status_code` (failures grouped by `status_code` with a coarse `error_class`), and a time series. `dimensions` narrows which breakdowns are computed (each one is a separate `GROUP BY`, including `status_code` for the taxonomy); `dimensions=none` returns totals and series only. | Master key |
| `GET` | `/v1/usage/summary.csv` | Every breakdown as a CSV download. | Master key |
| `GET` | `/v1/usage` | List usage logs. Filters: `start_date`, `end_date`, `user_id`, `status`, `status_code`, `model`, `endpoint`, `provider`, `source`, `source_label`, `api_key_id`, `request_group_id`. `user_id`, `model`, and `api_key_id` are repeatable (up to 50 values each) and match any of the values given. `status_code` is the HTTP status classifying a failure (e.g. 429 provider rate limit, 402 missing pricing); only error rows carry one, so filtering by it also restricts to `status=error` unless `status` is passed explicitly. `request_group_id` is repeatable and returns a routed request's whole attempt plan (see [Routing](routing.md)). | Master key |
| `GET` | `/v1/usage/count` | Total rows matching the filters (paginator total). Same filters as `GET /v1/usage`, so a multi-value filter counts the same rows the list returns. | Master key |
| `GET` | `/v1/usage/summary` | Aggregated spend/volume: totals, breakdowns by model/user/key/source/session/endpoint/provider, the failure taxonomy in `errors_by_status_code` (failures grouped by `status_code` with a coarse `error_class`), and a time series. `dimensions` narrows which breakdowns are computed (each one is a separate `GROUP BY`, including `status_code` for the taxonomy); `dimensions=none` returns totals and series only. `model`, `user_id`, and `api_key_id` are repeatable (up to 50 values each) and match any of the values given, so one call can compare a set of models, users, or keys. | Master key |
| `GET` | `/v1/usage/summary.csv` | Every breakdown as a CSV download. Same filters as `/v1/usage/summary`, including the repeatable `model` / `user_id` / `api_key_id`. | Master key |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
| `GET` | `/v1/usage/series` | One time series per group, for stacked charts. `group_by` is required (`model`, `user_id`, `api_key_id`, or `source`). Same filters and window bounds as `/v1/usage/summary`, including the repeatable `model` / `user_id` / `api_key_id` (up to 50 values each, matching any of them). Returns the window's top eight groups by spend, with everything past them folded into one `other` series per bucket, so the stack reconciles with the summary totals. Points are sparse (populated cells only), and an `hour` bucket over a window of more than 1000 buckets is rejected with a 422 rather than returning an oversized payload. | Master key |
| `POST` | `/v1/usage/external-events` | Import externally-observed usage (e.g. Claude Code) as source-tagged rows, priced at API rates, never counted toward budget. An API key (must be budget-exempt) attributes to its own user; the master key may name any user. Idempotent by `(source, source_event_id)`. See [Importing external usage](external-usage.md). | API key (budget-exempt) or master key |
| `POST` | `/v1/traces` | OTLP receiver for GenAI usage **spans** (protobuf or JSON). Maps the OpenTelemetry GenAI conventions (`gen_ai.*`, `otari.*`) onto external usage ingestion. Any instrumented app can ship here. See [Importing external usage](external-usage.md). | API key (budget-exempt); master key refused |
| `POST` | `/v1/logs` | OTLP receiver for GenAI usage **log events** (protobuf or JSON), including Claude Code's `api_request` and Codex's `codex.sse_event` / `codex.api_request`. Same mapping as `/v1/traces`. See [Importing external usage](external-usage.md). | API key (budget-exempt); master key refused |
14 changes: 12 additions & 2 deletions docs/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,20 @@ gateway.
number of calls, and the **Tool** filter narrows the log to one of them. The
request detail lists the calls, how many failed, and what they cost. Query text
is never stored: the log records counts and names only.
The **User**, **Model**, and **API key** filters here take several values too, so
a drill-down from Usage arrives intact and a comparison can be read as one list.
The Model box also accepts a name that is not in its suggestions: press Enter to
add it, since the suggestions only cover models with traffic in the window. When
"select all N matching" is used for a bulk delete or reprice, the selection is
scoped to exactly the values shown in the chips.
- **Usage**: aggregate usage and analytics, showing spend and volume over time,
broken down by model and by user, plus a switchable breakdown by session,
endpoint, provider, or source. Clicking any row opens the Activity log scoped
to that group, so "spend went up" leads straight to the requests behind it.
endpoint, provider, or source. The **User**, **Model**, and **API key** pickers
each take several values, so a chart can compare a set ("these two models across
this team's keys") rather than one entity at a time; every pick becomes its own
chip, and the chip's ✕ removes just that value. Clicking any row opens the
Activity log scoped to that group, carrying the whole selection with it, so
"spend went up" leads straight to the requests behind it.
When the window contains gateway-run tool calls, a **Gateway-run tools** table
shows calls, failures, and spend per tool, so "what did search cost me last
week" has an answer that is not one request at a time. MCP tools are excluded
Expand Down
Loading
Loading