Request logs
Package: agentrouter.insights.v1 Service: RequestLogsService
Endpoints
Query request logs
Changed in v0.11.0 (behaviour changes): RequestLogsService.QueryRequestLogs now matches only the full request ID, comparing the UUID part without regard to case.
Changed in v0.11.0 (new features): Request logs show the project, provider, and the attribute routing rule that picked the model or the rate limit that refused it, and flag unexplained model changes. QueryRequestLogs filters by policy (policy_filter, policy_type), except for requests in long-term storage (GetRequestLog still shows their policies). Credential headers (authorization, cookie, x-tars-api-key) are masked on every request log read.
Changed in v0.9.0 (new features): Each request log carries caller_type, idp for an identity-provider application and api_key for any other caller, and QueryRequestLogs and GetRequestLogStats take a caller_type filter with the same values.
Changed in v0.5.0 (deprecations): The microdollar cost fields are deprecated. They have always returned 0, because nothing ever wrote them, and the decimal string fields carry the real amount at full precision. Read total_cost for one request, or cost for a stats group. The fields stay in the API and keep returning 0.
Changed in v0.2.0 (new features): Request-log statistics can now separate spend the organization was actually billed for from cost that is only estimated. Group or filter by usage_mode (managed, byok, passthrough) to keep BYOK and passthrough traffic out of a spend total. Group or filter by usage_source (actual, partial, estimated) to see how much of a figure rests on locally estimated token counts rather than counts the provider reported. Both work on the admin and customer stats endpoints and can be combined with an existing dimension, for example per-model billed spend. usage_mode is available for historical requests; usage_source is recorded from this release onward and is empty for earlier requests, for requests served by a data plane that has not been upgraded, and for rejected or budget-blocked requests. Individual request logs now carry usage_source too, which answers whether a request's token counts were measured or estimated, a question the usage_measured flag cannot answer.
Changed in v0.1.5 (bug fixes): A management API key that holds the request_logs_reader scope can now read request logs and request-log stats. Every scoped key was previously refused on these endpoints with "scoped api key cannot call an operation without a tenant boundary", so the scope granted nothing. Results are confined to the key's own organization or project, and the three read endpoints accept an optional customer_id filter. Admin credentials and console sessions are unchanged.
What it does: Retrieves request logs with filtering and pagination. Requires request_logs_reader (admin implies it).
Request fields:
| Field | Required | Description |
|---|---|---|
user_id | no | Filter by user ID |
api_key_id | no | Filter by API key ID. Superseded by api_key_ids when non-empty. |
api_key_ids | no | Filter by any of these API key IDs (UUIDs). |
endpoint | no | Filter by request endpoint (e.g. "/v1/chat/completions"). |
model_name | no | Filter by model name (e.g., "claude-3-opus", "gpt-4") |
status | no | Filter by status (e.g., "success", "error", "timeout") |
status_code | no | Filter by HTTP status code |
start_time | no | Start of time range (inclusive) |
end_time | no | End of time range (exclusive) |
min_duration_ms | no | Minimum duration in milliseconds |
max_duration_ms | no | Maximum duration in milliseconds |
page_size | no | Page size (default: 50, max: 1000) |
page_token | no | Page token for pagination |
order_by | no | Order by field (default: "timestamp") Valid values: timestamp, duration_ms, input_tokens, output_tokens, total_tokens, total_cost, status_code, model_name, endpoint, api_key_name |
order_direction | no | Order direction: "asc" or "desc" (default: "desc") |
search | no | Exact match on the full request ID as returned in the x-request-id response header. The UUID part is case-insensitive; the workspace part remains case-sensitive. A bare UUID matches only a stored ID without a workspace. |
dataplane_id | no | Filter by the data plane that served the request (the workspace id the reporting dataplane's telemetry is authenticated as) -- see RequestLog.dataplane_id. |
customer_id | no | Tenant boundary / filter. Restricts results to logs recorded by API keys of this customer. A scoped (non-admin) management key is always confined to its own customer whether or not this is set; admin and session callers query all customers when empty (fraser#6341). |
caller_type | no | Optional filter by caller type, with the values of group_by "caller_type" on GetRequestLogStats. "idp" keeps the requests whose API key serves an application, which is a caller that authenticated with its identity provider's token and is served as its own API key. "api_key" keeps every other request, including one that never reached a key, such as a rejected request. Empty means no filter, and any other value is refused with InvalidArgument. |
policy_filter | no | Optional filter by the policies that acted on a request, read from the decision records the data plane writes (see RequestLog.policy_decisions). Unspecified keeps every request. |
policy_type | no | The policy type to keep when policy_filter is REQUEST_LOG_POLICY_FILTER_TYPE, and refused with InvalidArgument otherwise. |
request_id_prefix | no | Deprecated: no longer supported. A non-empty value is refused with InvalidArgument. Find a request by its full ID with search, or with GetRequestLog. |
Response fields:
| Field | Required | Description |
|---|---|---|
logs | no | List of request logs matching the query |
next_page_token | no | Token for retrieving the next page (empty if no more pages) |
total_count | no | Total count of matching logs (may be approximate for large datasets) |
{"signatures":{"go":"func (x *RequestLogsClient) QueryRequestLogs(ctx context.Context, req *insightsv1.QueryRequestLogsRequest) (*insightsv1.QueryRequestLogsResponse, error)","python":"query_request_logs(req: request_logs_pb2.QueryRequestLogsRequest) -\u003e QueryRequestLogsResponse","typescript":"queryRequestLogs(req: MessageInitShape\u003ctypeof tars_insights_v1_request_logs_pb.QueryRequestLogsRequestSchema\u003e): Promise\u003cQueryRequestLogsResponse\u003e","curl":"curl \"${AGENTROUTER_BASE_URL}/v1/request-logs\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"examples":{"go":{"files":[{"name":"main.go","content":"// Command example is a runnable example for the AgentRouter Go SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then `go run .`.\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tagentrouter \"github.com/tetrateio/agentrouter-go\"\n\tinsightsv1 \"github.com/tetrateio/agentrouter-go/genapi/api/tars/insights/v1\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\tclient, err := agentrouter.New(ctx,\n\t\tagentrouter.WithBaseURL(os.Getenv(\"AGENTROUTER_BASE_URL\")),\n\t\tagentrouter.WithAPIKey(os.Getenv(\"AGENTROUTER_API_KEY\")),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"client: %v\", err)\n\t}\n\n\t// Populate the request fields -- see the \"Request fields\" table above for\n\t// the available fields and which are required.\n\treq := \u0026insightsv1.QueryRequestLogsRequest{}\n\n\tresp, err := client.RequestLogs().QueryRequestLogs(ctx, req)\n\tif err != nil {\n\t\tlog.Fatalf(\"call: %v\", err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", resp)\n}\n"},{"name":"go.mod","content":"module github.com/tetrateio/agentrouter-go-examples/requestlogs/queryrequestlogs\n\ngo 1.26\n\nrequire github.com/tetrateio/agentrouter-go v0.1.1\n\n// Point this at the directory you extracted the downloaded Go SDK tarball into.\n// The directory name matches the tarball stem on the Download SDK page.\nreplace github.com/tetrateio/agentrouter-go =\u003e ./third_party/agentrouter-go-0.1.1\n"}]},"python":{"files":[{"name":"main.py","content":"\"\"\"Runnable example for the AgentRouter Python SDK.\n\nSet AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `python main.py`.\n\"\"\"\nimport os\n\nfrom tars.insights.v1 import request_logs_pb2\n\nfrom agentrouter_sdk import Client\n\nclient = Client(\n base_url=os.environ[\"AGENTROUTER_BASE_URL\"],\n api_key=os.environ[\"AGENTROUTER_API_KEY\"],\n)\n\n# Populate the request fields -- see the \"Request fields\" table above\n# for the available fields and which are required.\nreq = request_logs_pb2.QueryRequestLogsRequest()\ntry:\n result = client.request_logs.query_request_logs(req)\n print(result)\nexcept Exception as err:\n print(\"Error:\", err)\n"},{"name":"requirements.txt","content":"# Point this at the directory you extracted the downloaded Python SDK tarball\n# into. The directory name matches the tarball stem on the Download SDK page.\n# To install instead from PyPI once published, replace the line below with:\n# agentrouter-sdk\u003e=0.1.0\nagentrouter-sdk @ file:./third_party/agentrouter-python-0.1.1\n"}]},"typescript":{"files":[{"name":"index.ts","content":"// Runnable example for the AgentRouter TypeScript SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `npm install \u0026\u0026 npx tsx index.ts`.\nimport { Client } from '@tetrate/agentrouter-sdk'\n\nconst client = new Client({\n baseUrl: process.env.AGENTROUTER_BASE_URL,\n apiKey: process.env.AGENTROUTER_API_KEY,\n})\n\n// Populate the request fields -- see the \"Request fields\" table above\n// for the available fields and which are required.\nconst req = {}\ntry {\n const result = await client.requestLogs.queryRequestLogs(req)\n console.log(result)\n} catch (err) {\n console.error('Error:', err)\n}\n"},{"name":"package.json","content":"{\n \"name\": \"requestlogs\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": {\n \"@tetrate/agentrouter-sdk\": \"file:./third_party/agentrouter-typescript-0.1.1\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"typescript\": \"^5.4.0\"\n }\n}\n"},{"name":"tsconfig.json","content":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true\n }\n}\n"}]},"curl":"curl \"${AGENTROUTER_BASE_URL}/v1/request-logs\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"persona":"Admin","httpVerb":"GET","httpPath":"/v1/request-logs","slug":"query-request-logs"}
Get request log
Changed in v0.11.0 (new features): Request logs show the project, provider, and the attribute routing rule that picked the model or the rate limit that refused it, and flag unexplained model changes. QueryRequestLogs filters by policy (policy_filter, policy_type), except for requests in long-term storage (GetRequestLog still shows their policies). Credential headers (authorization, cookie, x-tars-api-key) are masked on every request log read.
Changed in v0.9.0 (new features): Each request log carries caller_type, idp for an identity-provider application and api_key for any other caller, and QueryRequestLogs and GetRequestLogStats take a caller_type filter with the same values.
Changed in v0.5.0 (deprecations): The microdollar cost fields are deprecated. They have always returned 0, because nothing ever wrote them, and the decimal string fields carry the real amount at full precision. Read total_cost for one request, or cost for a stats group. The fields stay in the API and keep returning 0.
Changed in v0.2.0 (new features): Request-log statistics can now separate spend the organization was actually billed for from cost that is only estimated. Group or filter by usage_mode (managed, byok, passthrough) to keep BYOK and passthrough traffic out of a spend total. Group or filter by usage_source (actual, partial, estimated) to see how much of a figure rests on locally estimated token counts rather than counts the provider reported. Both work on the admin and customer stats endpoints and can be combined with an existing dimension, for example per-model billed spend. usage_mode is available for historical requests; usage_source is recorded from this release onward and is empty for earlier requests, for requests served by a data plane that has not been upgraded, and for rejected or budget-blocked requests. Individual request logs now carry usage_source too, which answers whether a request's token counts were measured or estimated, a question the usage_measured flag cannot answer.
Changed in v0.1.5 (bug fixes): Fetching a request log by ID over REST now works when the ID contains a colon (for example default:<uuid>). Such a request previously returned an empty 200 response, because the route treated the text after the colon as a custom verb. Both the raw and the percent-encoded forms of the ID now resolve.
Changed in v0.1.5 (bug fixes): A management API key that holds the request_logs_reader scope can now read request logs and request-log stats. Every scoped key was previously refused on these endpoints with "scoped api key cannot call an operation without a tenant boundary", so the scope granted nothing. Results are confined to the key's own organization or project, and the three read endpoints accept an optional customer_id filter. Admin credentials and console sessions are unchanged.
What it does: Retrieves a single request log by request ID. Requires request_logs_reader (admin implies it).
Request fields:
| Field | Required | Description |
|---|---|---|
request_id | no | Request identifier |
customer_id | no | Tenant boundary. A scoped (non-admin) management key is confined to its own customer whether or not this is set: a log recorded outside that customer is NotFound. Admin and session callers ignore it when empty (fraser#6341). |
Response fields:
| Field | Required | Description |
|---|---|---|
id | output-only | Unique log identifier |
timestamp | no | Timestamp when the request was received |
user_id | no | User ID who made the request |
api_key_id | no | API key information UUID of the API key that authenticated the request. |
api_key_prefix | no | The key's UID, the 12 characters both consoles show to identify it. Set only for a pre-JWT sk-/hk- key; empty for every other key. |
api_key_hash | no | Hash of the API key used to look it up without storing the secret. |
api_key_address | no | On-chain/account address the API key is bound to. |
api_key_name | no | Human-readable label assigned to the API key. |
model_name | no | Model name used for the request |
input_tokens | no | Token usage. A zero here is meaningful only when usage_measured is true; when it is false the request never recorded usage (e.g. early failure) and consumers should render these as blank rather than 0. Number of prompt/input tokens consumed by the request. |
output_tokens | no | Number of completion/output tokens generated in the response. |
input_tokens_cost_microdollar | no | Cost fields. Every amount below is a decimal string, precision 18, scale 10. An amount is empty when the request never recorded usage. "0" is a real measured zero. total_cost is the field to read. It is the whole billed amount for this request: input + output + additional. Do not add the component fields together. input_tokens_cost already contains the two cache amounts, so a sum of them counts the same money twice. A *_cost field is the amount TARS billed, after the platform fee. The matching *_cost_before_fee field is the provider price, before that fee. The fee rate is a per-deployment setting. On byok and passthrough traffic the customer pays the provider directly, so the billed amount can be the fee alone. Deprecated: always 0. Use input_tokens_cost. |
input_tokens_cost | no | Input-token cost as a decimal string (precision: 18, scale: 10). Empty when usage was never recorded. |
input_tokens_cost_before_fee | no | Input-token cost before the platform fee/markup is applied. Decimal string. |
output_tokens_cost_microdollar | no | Deprecated: always 0. Use output_tokens_cost. |
output_tokens_cost | no | Output-token cost as a decimal string (precision: 18, scale: 10). Empty when usage was never recorded. |
output_tokens_cost_before_fee | no | Output-token cost before the platform fee/markup is applied. Decimal string. |
status | no | Request/Response status Logical outcome of the request (e.g. "success", "error", "timeout"). |
status_code | no | HTTP status code returned to the client. |
request_headers | no | Request metadata Captured request headers as a JSON object. |
request_body | no | Captured request payload as a JSON object. |
response_headers | no | Response metadata Captured response headers as a JSON object. |
response_body | no | Text response body captured from the upstream response. |
response_body_raw | no | Raw response body bytes (for binary payloads such as images). |
llm_parameters | no | LLM parameters used |
upstream_duration_ms | no | Timing information Duration of the upstream LLM call in milliseconds. |
duration_ms | no | Total end-to-end request duration in milliseconds. |
request_id | no | Request identifier |
created_at | no | Record creation timestamp |
storage_location | no | Object storage information (for Parquet-based storage) GCS path to the Parquet file when use_object_storage is true. |
storage_row_index | no | Row index within the Parquet file. |
storage_batch_id | no | Batch UUID grouping rows written in the same export batch. |
use_object_storage | no | True when the row is stored in object storage (GCS); false when in the DB. |
cached_input_tokens | no | Cache token breakdown. Meaningful only when cache_measured is true; when it is false the request did not record a cache breakdown and consumers should treat cache-hit as unknown rather than 0%. Cache presence is independent of usage_measured. Input tokens served from the prompt cache (cache reads), billed at the cached rate. |
cached_input_tokens_cost | no | Cached-input cost as a decimal string (precision: 18, scale: 10). Empty when cache was never recorded. Part of input_tokens_cost, not an addition to it. |
cached_input_tokens_cost_before_fee | no | Cached-input cost before the platform fee/markup is applied. Decimal string. |
cache_creation_input_tokens | no | Input tokens written to the prompt cache (cache-creation writes). |
cache_creation_input_tokens_cost | no | Cache-creation cost as a decimal string (precision: 18, scale: 10). Empty when cache was never recorded. Part of input_tokens_cost, not an addition to it. |
cache_creation_input_tokens_cost_before_fee | no | Cache-creation cost before the platform fee/markup is applied. Decimal string. |
endpoint | no | Request route (e.g. "/v1/chat/completions", "/v1/images/generations"). |
additional_cost | no | Request-level fee after token costs (decimal string, after fee). Empty only when token usage was not measured and no nonzero additional cost was recorded; "0" means a measured real zero fee. |
additional_cost_before_fee | no | Request-level fee before the platform fee/markup is applied. Empty only when token usage was not measured and no nonzero additional cost was recorded; "0" means a measured real zero fee. |
total_cost | no | Total billed cost after fee: input + output + additional. Empty only when token usage was not measured and no nonzero additional cost was recorded. |
model_requested | no | Model name the client requested (from request headers/body). |
model_effective | no | Model name that actually served the request (after routing/fallback). |
used_fallback_model | no | True when the gateway substituted a fallback model for the request. |
used_byok | no | True when the request used bring-your-own-key (customer-supplied provider key). |
used_passthrough | no | True when the request was forwarded in passthrough mode without rewriting. |
is_provider_fallback | no | True when the provider itself returned a fallback model. |
usage_measured | no | Presence signals for the nullable metric groups above. A real 0 is indistinguishable from "never recorded" on the integer fields, so these tell consumers whether those zero values are measured. True when the request recorded token usage (input/output tokens + costs). |
cache_measured | no | True when the request recorded a prompt-cache breakdown (cached and cache-creation tokens). Independent of usage_measured. |
dataplane_id | no | Identity of the DATA PLANE that served (and reported) this request: the workspace id the reporting dataplane's telemetry channel is authenticated as (project_gateways.workspace_id), stamped server-side at telemetry ingest from the MP-verified service-account binding -- not from request headers or reporter-supplied claims -- so attribution survives customer-managed DNS failover between a project's member gateways. NOTE: this is dataplane granularity, not per-gateway -- gateways co-located on one dataplane share a value (true per-gateway attribution requires the dataplane to export its per-request P10 gateway match and is tracked as a follow-up). Empty for rows recorded before dataplane attribution existed and for deployments that write logs locally without a reporting identity. |
usage_source | no | How this row's token counts were obtained: "actual" (the provider reported usage), "partial" (input real, output estimated), "estimated" (no usage at all, e.g. a cancelled stream). This is the field that answers "are these token counts real?", which usage_measured cannot: on an estimated row the estimator writes its guess into the usage fields, so usage_measured reports true wherever that guess is non-zero. (A zero estimate leaves the token fields unset and usage_measured false, so it is not a reliable inverse either.) Empty means no usage was resolved for this request. That covers rows predating the field and rows from a data plane not yet upgraded, and permanently covers rejected and budget-blocked requests. |
caller_type | no | Caller type of this request, with the values of group_by "caller_type" on GetRequestLogStats: "idp" when the request's API key serves an application, which is a caller that authenticated with its identity provider's token and is served as its own API key, and "api_key" otherwise, including a request that never reached a key. A management plane older than 0.9.0 leaves it empty. |
project_id | no | The project of the API key that sent the request. Empty for a request that never reached a key. |
project_name | no | The current name of project_id's project. Empty when it has none. |
provider | no | The provider that served the request, from the upstream backend. Empty when the request did not reach a provider. |
policy_decisions | no | The policy decisions recorded on the request, in the order the policies ran. Empty when no policy recorded a decision. This and the fields up to policy_decisions_unavailable are filled by RequestLogsService only. |
model_change_unexplained | no | True when the model that answered differs from the model the caller asked for and no recorded decision explains it. |
not_served | no | True when the request was refused before it reached a model: by a policy (a rate limit or a budget block) or by the gateway itself (for example an unsupported endpoint). policy_decisions may hold no step that explains it. |
policy_decisions_unavailable | no | True when the request's decision records are no longer stored (archived), so an empty policy_decisions does not mean no policy acted. On QueryRequestLogs an archived row reads as unavailable and the CHANGED and TYPE filters skip it; GetRequestLog recovers the records from the archive and shows the steps. |
{"signatures":{"go":"func (x *RequestLogsClient) GetRequestLog(ctx context.Context, req *insightsv1.GetRequestLogRequest) (*insightsv1.RequestLog, error)","python":"get_request_log(req: request_logs_pb2.GetRequestLogRequest) -\u003e RequestLog","typescript":"getRequestLog(req: MessageInitShape\u003ctypeof tars_insights_v1_request_logs_pb.GetRequestLogRequestSchema\u003e): Promise\u003cRequestLog\u003e","curl":"curl \"${AGENTROUTER_BASE_URL}/v1/request-logs/01H...\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"examples":{"go":{"files":[{"name":"main.go","content":"// Command example is a runnable example for the AgentRouter Go SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then `go run .`.\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tagentrouter \"github.com/tetrateio/agentrouter-go\"\n\tinsightsv1 \"github.com/tetrateio/agentrouter-go/genapi/api/tars/insights/v1\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\tclient, err := agentrouter.New(ctx,\n\t\tagentrouter.WithBaseURL(os.Getenv(\"AGENTROUTER_BASE_URL\")),\n\t\tagentrouter.WithAPIKey(os.Getenv(\"AGENTROUTER_API_KEY\")),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"client: %v\", err)\n\t}\n\n\t// Populate the request fields -- see the \"Request fields\" table above for\n\t// the available fields and which are required.\n\treq := \u0026insightsv1.GetRequestLogRequest{}\n\n\tresp, err := client.RequestLogs().GetRequestLog(ctx, req)\n\tif err != nil {\n\t\tlog.Fatalf(\"call: %v\", err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", resp)\n}\n"},{"name":"go.mod","content":"module github.com/tetrateio/agentrouter-go-examples/requestlogs/getrequestlog\n\ngo 1.26\n\nrequire github.com/tetrateio/agentrouter-go v0.1.1\n\n// Point this at the directory you extracted the downloaded Go SDK tarball into.\n// The directory name matches the tarball stem on the Download SDK page.\nreplace github.com/tetrateio/agentrouter-go =\u003e ./third_party/agentrouter-go-0.1.1\n"}]},"python":{"files":[{"name":"main.py","content":"\"\"\"Runnable example for the AgentRouter Python SDK.\n\nSet AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `python main.py`.\n\"\"\"\nimport os\n\nfrom tars.insights.v1 import request_logs_pb2\n\nfrom agentrouter_sdk import Client\n\nclient = Client(\n base_url=os.environ[\"AGENTROUTER_BASE_URL\"],\n api_key=os.environ[\"AGENTROUTER_API_KEY\"],\n)\n\n# Populate the request fields -- see the \"Request fields\" table above\n# for the available fields and which are required.\nreq = request_logs_pb2.GetRequestLogRequest()\ntry:\n result = client.request_logs.get_request_log(req)\n print(result)\nexcept Exception as err:\n print(\"Error:\", err)\n"},{"name":"requirements.txt","content":"# Point this at the directory you extracted the downloaded Python SDK tarball\n# into. The directory name matches the tarball stem on the Download SDK page.\n# To install instead from PyPI once published, replace the line below with:\n# agentrouter-sdk\u003e=0.1.0\nagentrouter-sdk @ file:./third_party/agentrouter-python-0.1.1\n"}]},"typescript":{"files":[{"name":"index.ts","content":"// Runnable example for the AgentRouter TypeScript SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `npm install \u0026\u0026 npx tsx index.ts`.\nimport { Client } from '@tetrate/agentrouter-sdk'\n\nconst client = new Client({\n baseUrl: process.env.AGENTROUTER_BASE_URL,\n apiKey: process.env.AGENTROUTER_API_KEY,\n})\n\n// Populate the request fields -- see the \"Request fields\" table above\n// for the available fields and which are required.\nconst req = {}\ntry {\n const result = await client.requestLogs.getRequestLog(req)\n console.log(result)\n} catch (err) {\n console.error('Error:', err)\n}\n"},{"name":"package.json","content":"{\n \"name\": \"requestlogs\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": {\n \"@tetrate/agentrouter-sdk\": \"file:./third_party/agentrouter-typescript-0.1.1\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"typescript\": \"^5.4.0\"\n }\n}\n"},{"name":"tsconfig.json","content":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true\n }\n}\n"}]},"curl":"curl \"${AGENTROUTER_BASE_URL}/v1/request-logs/01H...\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"persona":"Admin","httpVerb":"GET","httpPath":"/v1/request-logs/{request_id}","slug":"get-request-log"}
Get request log stats
Changed in v0.9.0 (new features): Request log statistics can be grouped by caller_type, which separates identity-provider callers from API key callers, and by application, which shows the usage of each application under its key's current name. GetCustomerRequestLogStats lists only the caller's own requests, so application usage appears in GetRequestLogStats.
Changed in v0.9.0 (new features): Each request log carries caller_type, idp for an identity-provider application and api_key for any other caller, and QueryRequestLogs and GetRequestLogStats take a caller_type filter with the same values.
Changed in v0.5.0 (bug fixes): Request log stats now report the full billed amount. The group cost left out per-image generation cost, so a group of image generation traffic could read 0.00 while those requests were billed. The response-level total cost was never populated and was always empty. Both now count every cost component, so figures for image-heavy workloads rise to the amount that was always charged.
Changed in v0.5.0 (deprecations): The microdollar cost fields are deprecated. They have always returned 0, because nothing ever wrote them, and the decimal string fields carry the real amount at full precision. Read total_cost for one request, or cost for a stats group. The fields stay in the API and keep returning 0.
Changed in v0.2.0 (new features): Request-log statistics can now separate spend the organization was actually billed for from cost that is only estimated. Group or filter by usage_mode (managed, byok, passthrough) to keep BYOK and passthrough traffic out of a spend total. Group or filter by usage_source (actual, partial, estimated) to see how much of a figure rests on locally estimated token counts rather than counts the provider reported. Both work on the admin and customer stats endpoints and can be combined with an existing dimension, for example per-model billed spend. usage_mode is available for historical requests; usage_source is recorded from this release onward and is empty for earlier requests, for requests served by a data plane that has not been upgraded, and for rejected or budget-blocked requests. Individual request logs now carry usage_source too, which answers whether a request's token counts were measured or estimated, a question the usage_measured flag cannot answer.
Changed in v0.1.5 (bug fixes): A management API key that holds the request_logs_reader scope can now read request logs and request-log stats. Every scoped key was previously refused on these endpoints with "scoped api key cannot call an operation without a tenant boundary", so the scope granted nothing. Results are confined to the key's own organization or project, and the three read endpoints accept an optional customer_id filter. Admin credentials and console sessions are unchanged.
What it does: Returns aggregated statistics for request logs. Requires request_logs_reader (admin implies it).
Request fields:
| Field | Required | Description |
|---|---|---|
user_id | no | Filter by user ID |
model_name | no | Filter by model name |
start_time | no | Start of time range (inclusive) |
end_time | no | End of time range (exclusive) |
group_by | no | Group by dimension Valid values: "model", "user", "api_key", "status", "dataplane", "usage_mode", "usage_source", "hour", "day", "caller_type", "application". Grouping by "dataplane" keys each StatEntry by the reporting dataplane's identity (see RequestLog.dataplane_id -- gateways co-located on one dataplane share a bucket); rows recorded before dataplane attribution existed group under the empty key "". Grouping by "usage_mode" keys each StatEntry by how the request was paid for: "managed" (billed by TARS per token), "byok" (the customer's own provider credential) or "passthrough" (the caller's own subscription). Cost on a byok or passthrough group is a token-priced ESTIMATE, not a charge. Derived from the upstream-backend response header, so it is available for the full history; a row whose header is absent (client cancel, upstream error, or a gateway-side 4xx before routing) currently groups under "managed" -- see tetrateio/fraser#5166. Cost on a byok or passthrough group means what TARS billed, which is deployment-dependent and differs from what the usage dashboard shows for the same requests -- see the StatEntry.cost comment before comparing the two. Grouping by "usage_source" splits a group into measured versus estimated usage: "actual" (the provider reported usage), "partial" (input real, output estimated), "estimated" (no usage at all, e.g. a cancelled stream). Rows for which no usage was resolved group under the empty key "". That bucket does NOT drain over time: as well as rows predating this dimension and rows from a data plane not yet upgraded, it permanently contains rejected and budget-blocked requests, which never reach usage resolution. Grouping by "caller_type" keys each StatEntry "idp" when the request's API key is the key of an application (an externally authenticated caller) and "api_key" otherwise. Requests that never reached a key, such as rejected ones, count as "api_key". Grouping by "application" keys each StatEntry by the application's principal id, and sets StatEntry.label to the application's name; requests whose key is not an application's key group under the empty key "". |
metric | no | Metric to calculate Valid values: "count", "tokens", "cost", "latency", "errors" |
dataplane_id | no | Optional filter restricting stats to logs served by this data plane (see RequestLog.dataplane_id). Empty means "no filter" -- the unattributed "" bucket surfaced by group_by="dataplane" cannot be selected here. |
usage_mode | no | Optional filter restricting stats to one billing mode: "managed", "byok" or "passthrough" (see group_by). Empty means "no filter"; any other value is rejected with InvalidArgument rather than matching nothing, because an empty result reads as "no traffic in this mode". Combine with group_by to cross-cut, e.g. group_by="model" with usage_mode="managed" for per-model actual spend. |
usage_source | no | Optional filter restricting stats to one usage source: "actual", "partial" or "estimated" (see group_by). Empty means "no filter"; any other value is rejected with InvalidArgument. The "" bucket surfaced by group_by="usage_source" cannot be selected here. |
customer_id | no | Tenant boundary / filter. Restricts aggregation to logs recorded by API keys of this customer. A scoped (non-admin) management key is always confined to its own customer whether or not this is set; admin and session callers aggregate all customers when empty (fraser#6341). |
caller_type | no | Optional filter by caller type: "idp" or "api_key", as defined by group_by "caller_type". Empty means no filter, and any other value is refused with InvalidArgument. Combine it with group_by "application" to list only applications, or with group_by "model" to see which models applications use. |
Response fields:
| Field | Required | Description |
|---|---|---|
stats | no | Statistics grouped by the requested dimension |
total_count | no | Total counts across all groups Total number of requests across all groups. |
total_input_tokens | no | Sum of input tokens across all groups. |
total_output_tokens | no | Sum of output tokens across all groups. |
total_cost | no | Total billed cost across all groups as a decimal string. |
avg_duration_ms | no | Mean request duration in milliseconds across all groups. |
error_count | no | Total number of error requests across all groups. |
total_cached_input_tokens | no | Sum of input tokens served from the prompt cache (cache reads) across all groups. |
total_cache_creation_input_tokens | no | Sum of input tokens written to the prompt cache (cache-creation writes) across all groups. |
{"signatures":{"go":"func (x *RequestLogsClient) GetRequestLogStats(ctx context.Context, req *insightsv1.GetRequestLogStatsRequest) (*insightsv1.RequestLogStatsResponse, error)","python":"get_request_log_stats(req: request_logs_pb2.GetRequestLogStatsRequest) -\u003e RequestLogStatsResponse","typescript":"getRequestLogStats(req: MessageInitShape\u003ctypeof tars_insights_v1_request_logs_pb.GetRequestLogStatsRequestSchema\u003e): Promise\u003cRequestLogStatsResponse\u003e","curl":"curl \"${AGENTROUTER_BASE_URL}/v1/request-logs/stats\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"examples":{"go":{"files":[{"name":"main.go","content":"// Command example is a runnable example for the AgentRouter Go SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then `go run .`.\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tagentrouter \"github.com/tetrateio/agentrouter-go\"\n\tinsightsv1 \"github.com/tetrateio/agentrouter-go/genapi/api/tars/insights/v1\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\tclient, err := agentrouter.New(ctx,\n\t\tagentrouter.WithBaseURL(os.Getenv(\"AGENTROUTER_BASE_URL\")),\n\t\tagentrouter.WithAPIKey(os.Getenv(\"AGENTROUTER_API_KEY\")),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"client: %v\", err)\n\t}\n\n\t// Populate the request fields -- see the \"Request fields\" table above for\n\t// the available fields and which are required.\n\treq := \u0026insightsv1.GetRequestLogStatsRequest{}\n\n\tresp, err := client.RequestLogs().GetRequestLogStats(ctx, req)\n\tif err != nil {\n\t\tlog.Fatalf(\"call: %v\", err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", resp)\n}\n"},{"name":"go.mod","content":"module github.com/tetrateio/agentrouter-go-examples/requestlogs/getrequestlogstats\n\ngo 1.26\n\nrequire github.com/tetrateio/agentrouter-go v0.1.1\n\n// Point this at the directory you extracted the downloaded Go SDK tarball into.\n// The directory name matches the tarball stem on the Download SDK page.\nreplace github.com/tetrateio/agentrouter-go =\u003e ./third_party/agentrouter-go-0.1.1\n"}]},"python":{"files":[{"name":"main.py","content":"\"\"\"Runnable example for the AgentRouter Python SDK.\n\nSet AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `python main.py`.\n\"\"\"\nimport os\n\nfrom tars.insights.v1 import request_logs_pb2\n\nfrom agentrouter_sdk import Client\n\nclient = Client(\n base_url=os.environ[\"AGENTROUTER_BASE_URL\"],\n api_key=os.environ[\"AGENTROUTER_API_KEY\"],\n)\n\n# Populate the request fields -- see the \"Request fields\" table above\n# for the available fields and which are required.\nreq = request_logs_pb2.GetRequestLogStatsRequest()\ntry:\n result = client.request_logs.get_request_log_stats(req)\n print(result)\nexcept Exception as err:\n print(\"Error:\", err)\n"},{"name":"requirements.txt","content":"# Point this at the directory you extracted the downloaded Python SDK tarball\n# into. The directory name matches the tarball stem on the Download SDK page.\n# To install instead from PyPI once published, replace the line below with:\n# agentrouter-sdk\u003e=0.1.0\nagentrouter-sdk @ file:./third_party/agentrouter-python-0.1.1\n"}]},"typescript":{"files":[{"name":"index.ts","content":"// Runnable example for the AgentRouter TypeScript SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `npm install \u0026\u0026 npx tsx index.ts`.\nimport { Client } from '@tetrate/agentrouter-sdk'\n\nconst client = new Client({\n baseUrl: process.env.AGENTROUTER_BASE_URL,\n apiKey: process.env.AGENTROUTER_API_KEY,\n})\n\n// Populate the request fields -- see the \"Request fields\" table above\n// for the available fields and which are required.\nconst req = {}\ntry {\n const result = await client.requestLogs.getRequestLogStats(req)\n console.log(result)\n} catch (err) {\n console.error('Error:', err)\n}\n"},{"name":"package.json","content":"{\n \"name\": \"requestlogs\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": {\n \"@tetrate/agentrouter-sdk\": \"file:./third_party/agentrouter-typescript-0.1.1\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"typescript\": \"^5.4.0\"\n }\n}\n"},{"name":"tsconfig.json","content":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true\n }\n}\n"}]},"curl":"curl \"${AGENTROUTER_BASE_URL}/v1/request-logs/stats\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"persona":"Admin","httpVerb":"GET","httpPath":"/v1/request-logs/stats","slug":"get-request-log-stats"}
Count project requests
Changed in v0.7.0 (new features): A new call counts one project's requests on each of several data planes in one request, naming the project rather than every API key it holds. Reading these counts through the request-log list cost a discarded row and a scan of the whole data plane's window.
What it does: Counts one project's requests on each of several data planes, in one call and without reading a row. Requires request_logs_reader (admin implies it). QueryRequestLogs answers this today with page_size 1, which costs a COUNT(*) plus a row the caller discards, and takes the project as every API key id the project has ever held. This takes the project itself and scopes through api_keys.project_id, so the caller sends no key ids and a key added mid-window is counted (fraser#10363).
Request fields:
| Field | Required | Description |
|---|---|---|
customer_id | no | Tenant boundary / filter, as on QueryRequestLogs. Required, because a project name is unique only per customer: a project named without a customer would otherwise count every customer's project of that name. A scoped (non-admin) management key is always confined to its own customer whether or not this is set. |
project_id | no | Project whose requests are counted. Required: this counts one project, so a request that names none is refused rather than answered with a customer-wide total. A project-tier key carries its own project and may leave this empty; naming a different one is denied. request_logs carries no tenancy columns, so the count scopes through the recording key (request_logs.api_key_id -> api_keys.project_id), which is what lets the caller name the project rather than list its key ids. |
dataplane_ids | no | Data planes to count, one count returned per id. An id that served nothing in the window comes back with count 0 rather than being left out, so the caller can tell "no traffic" from "not asked". |
start_time | no | Start of the window, inclusive. Required. |
end_time | no | End of the window, exclusive. Unset means now, which the service applies as an upper bound rather than leaving the count open-ended, so a row stamped in the future is not counted in a window that ends at the present. |
Response fields:
| Field | Required | Description |
|---|---|---|
counts | no | One entry per requested data plane, in the order they were requested. |
{"signatures":{"go":"func (x *RequestLogsClient) CountProjectRequests(ctx context.Context, req *insightsv1.CountProjectRequestsRequest) (*insightsv1.CountProjectRequestsResponse, error)","python":"count_project_requests(req: request_logs_pb2.CountProjectRequestsRequest) -\u003e CountProjectRequestsResponse","typescript":"countProjectRequests(req: MessageInitShape\u003ctypeof tars_insights_v1_request_logs_pb.CountProjectRequestsRequestSchema\u003e): Promise\u003cCountProjectRequestsResponse\u003e","curl":"curl \"${AGENTROUTER_BASE_URL}/v1/request-logs/count\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"examples":{"go":{"files":[{"name":"main.go","content":"// Command example is a runnable example for the AgentRouter Go SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then `go run .`.\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tagentrouter \"github.com/tetrateio/agentrouter-go\"\n\tinsightsv1 \"github.com/tetrateio/agentrouter-go/genapi/api/tars/insights/v1\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\tclient, err := agentrouter.New(ctx,\n\t\tagentrouter.WithBaseURL(os.Getenv(\"AGENTROUTER_BASE_URL\")),\n\t\tagentrouter.WithAPIKey(os.Getenv(\"AGENTROUTER_API_KEY\")),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"client: %v\", err)\n\t}\n\n\t// Populate the request fields -- see the \"Request fields\" table above for\n\t// the available fields and which are required.\n\treq := \u0026insightsv1.CountProjectRequestsRequest{}\n\n\tresp, err := client.RequestLogs().CountProjectRequests(ctx, req)\n\tif err != nil {\n\t\tlog.Fatalf(\"call: %v\", err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", resp)\n}\n"},{"name":"go.mod","content":"module github.com/tetrateio/agentrouter-go-examples/requestlogs/countprojectrequests\n\ngo 1.26\n\nrequire github.com/tetrateio/agentrouter-go v0.1.1\n\n// Point this at the directory you extracted the downloaded Go SDK tarball into.\n// The directory name matches the tarball stem on the Download SDK page.\nreplace github.com/tetrateio/agentrouter-go =\u003e ./third_party/agentrouter-go-0.1.1\n"}]},"python":{"files":[{"name":"main.py","content":"\"\"\"Runnable example for the AgentRouter Python SDK.\n\nSet AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `python main.py`.\n\"\"\"\nimport os\n\nfrom tars.insights.v1 import request_logs_pb2\n\nfrom agentrouter_sdk import Client\n\nclient = Client(\n base_url=os.environ[\"AGENTROUTER_BASE_URL\"],\n api_key=os.environ[\"AGENTROUTER_API_KEY\"],\n)\n\n# Populate the request fields -- see the \"Request fields\" table above\n# for the available fields and which are required.\nreq = request_logs_pb2.CountProjectRequestsRequest()\ntry:\n result = client.request_logs.count_project_requests(req)\n print(result)\nexcept Exception as err:\n print(\"Error:\", err)\n"},{"name":"requirements.txt","content":"# Point this at the directory you extracted the downloaded Python SDK tarball\n# into. The directory name matches the tarball stem on the Download SDK page.\n# To install instead from PyPI once published, replace the line below with:\n# agentrouter-sdk\u003e=0.1.0\nagentrouter-sdk @ file:./third_party/agentrouter-python-0.1.1\n"}]},"typescript":{"files":[{"name":"index.ts","content":"// Runnable example for the AgentRouter TypeScript SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `npm install \u0026\u0026 npx tsx index.ts`.\nimport { Client } from '@tetrate/agentrouter-sdk'\n\nconst client = new Client({\n baseUrl: process.env.AGENTROUTER_BASE_URL,\n apiKey: process.env.AGENTROUTER_API_KEY,\n})\n\n// Populate the request fields -- see the \"Request fields\" table above\n// for the available fields and which are required.\nconst req = {}\ntry {\n const result = await client.requestLogs.countProjectRequests(req)\n console.log(result)\n} catch (err) {\n console.error('Error:', err)\n}\n"},{"name":"package.json","content":"{\n \"name\": \"requestlogs\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": {\n \"@tetrate/agentrouter-sdk\": \"file:./third_party/agentrouter-typescript-0.1.1\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"typescript\": \"^5.4.0\"\n }\n}\n"},{"name":"tsconfig.json","content":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true\n }\n}\n"}]},"curl":"curl \"${AGENTROUTER_BASE_URL}/v1/request-logs/count\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"persona":"Admin","httpVerb":"GET","httpPath":"/v1/request-logs/count","slug":"count-project-requests"}
Get an organization's usage over time
Changed in v0.11.0 (new features): GET /v1/usage/stats takes group_keys, which keeps only the named groups, and subgroup_by, which splits each group by model, user, api_key or usage_mode. Each split group carries its subgroup_keys and no series, and ordering, paging and total_groups count the split groups.
Changed in v0.10.0 (new features): GET /v1/usage/stats takes a caller_type filter: idp keeps the usage of identity-provider applications and api_key keeps every other caller's. Totals, the series, distinct counts and groups all count the kept usage only.
Changed in v0.9.0 (behaviour changes): Existing metrics_reader keys can read their organization's usage per user, team or API key.
Changed in v0.9.0 (new features): GET /v1/usage/stats reads an organization's usage over time, with distinct users, per user, team or API key. Groups sort by any token figure or by last use, and skip jumps straight to a page.
What it does: Returns an organization's usage over time, read from the usage rollups: totals, a time series, and groups by model, user, team, API key or usage mode. Unlike the request-log reads above, this is gated on metrics_reader and usage.read, the same grant as the metrics API. It returns aggregates only, never a request body or a client address. A request that names customer_id is authorized against that customer: an organization viewer of that customer, or a key of that customer, may call it. A request that names no customer reads every customer and needs a platform usage.read grant. A non-admin key is always confined to its own customer, and to its own project for a project-tier key.
Request fields:
| Field | Required | Description |
|---|---|---|
customer_id | no | Customer to read. Empty reads every customer and needs a platform grant. Rows recorded by permanently deleted API keys are left out of a one-customer read, because their customer can no longer be resolved. |
project_id | no | Narrows the read to one project of customer_id. Requires customer_id. |
start_time | no | Start of the window, inclusive. Unset means no lower limit. |
end_time | no | End of the window, exclusive. Unset means no upper limit. The window is at most 400 days. |
step | no | Bucket size of the series. At most 3000 buckets. |
time_zone | no | IANA time zone for HOUR, DAY and MONTH buckets (for example "Asia/Kolkata"). Empty means UTC. |
group_by | no | Dimension of groups: "model", "user", "api_key", "usage_mode" or "team". Empty returns no groups. Team keys are user-group ids, or "Untagged" for users with no team or a deleted team. |
limit | no | Maximum groups returned. 0 means 1000, the maximum. |
usage_modes | no | Usage modes to include: "managed", "byok", "passthrough". Empty includes all. |
user_ids | no | Users to include. Empty includes all. |
team_ids | no | Teams to include: user-group ids, and "Untagged" for users with no team or a deleted team. Empty includes all. |
api_key_tag_key | no | Includes only API keys that carry this tag key. Requires api_key_tag_value. |
api_key_tag_value | no | Tag value that goes with api_key_tag_key. |
search | no | Case-insensitive substring match. With group_by "model" it matches the model name; "user" the user name, email and team name; "team" the team name ("Untagged" for none); "api_key" the key name, key prefix, user name and email; no group the key name, key prefix, user name, email and team name. Ignored for "usage_mode". |
order_by | no | Group order: one of key, request_count, cost, total_tokens, input_tokens, output_tokens, prompt_tokens, completion_tokens, cached_input_tokens, cache_creation_input_tokens, error_count, avg_latency_ms, distinct_users, distinct_api_keys or last_used (start of the group's newest bucket), then optionally "asc" or "desc". Default "cost desc". |
page_size | no | Groups per page. Use this or limit, not both. |
page_token | no | Token from a previous response's next_page_token. |
skip | no | Groups to skip. Use this or page_token, not both, and not with limit. |
caller_type | no | Optional filter by caller type, with the values of caller_type on QueryRequestLogsRequest and GetRequestLogStatsRequest. "idp" keeps the usage of applications, which are callers that authenticated with their identity provider's token and are served as their own API key; the rollups know such a row by its user, the application's owner, whose id starts with "principal-". "api_key" keeps every other row. Totals, the series, groups and their paging all count the kept rows only. Empty means no filter, and any other value is refused with InvalidArgument. |
group_keys | no | Keeps only the rows whose group_by key is one of these values, with the key rules of UsageGroup.key: an empty user id is no user, and "Untagged" is no team. API key ids must be UUIDs. Requires group_by. At most 1000 values. |
subgroup_by | no | Splits each group by these dimensions, in this order: "model", "user", "api_key" or "usage_mode". Each value at most once, and not the group_by dimension. Requires group_by. Totals and the series do not change. Ordering, paging and total_groups count the split groups, which have no buckets. |
Response fields:
| Field | Required | Description |
|---|---|---|
totals | no | Figures over the whole window. |
buckets | no | Series over the window, oldest first. Buckets with no usage are left out. |
groups | no | Groups in the requested order. |
truncated | no | More groups matched than this response returns. |
next_page_token | no | Token for the next page of groups. GetUsageStats only. |
total_groups | no | Number of groups that matched. |
source_interval_seconds | no | Bucket interval, in seconds, of the fine-grained rollups this response read. 0 when it read the daily rollups or found no rows. A SOURCE bucket spans this interval. |
{"signatures":{"go":"func (x *RequestLogsClient) GetUsageStats(ctx context.Context, req *insightsv1.GetUsageStatsRequest) (*insightsv1.UsageStatsResponse, error)","python":"get_usage_stats(req: request_logs_pb2.GetUsageStatsRequest) -\u003e UsageStatsResponse","typescript":"getUsageStats(req: MessageInitShape\u003ctypeof tars_insights_v1_request_logs_pb.GetUsageStatsRequestSchema\u003e): Promise\u003cUsageStatsResponse\u003e","curl":"curl \"${AGENTROUTER_BASE_URL}/v1/usage/stats\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"examples":{"go":{"files":[{"name":"main.go","content":"// Command example is a runnable example for the AgentRouter Go SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then `go run .`.\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tagentrouter \"github.com/tetrateio/agentrouter-go\"\n\tinsightsv1 \"github.com/tetrateio/agentrouter-go/genapi/api/tars/insights/v1\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\tclient, err := agentrouter.New(ctx,\n\t\tagentrouter.WithBaseURL(os.Getenv(\"AGENTROUTER_BASE_URL\")),\n\t\tagentrouter.WithAPIKey(os.Getenv(\"AGENTROUTER_API_KEY\")),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"client: %v\", err)\n\t}\n\n\t// Populate the request fields -- see the \"Request fields\" table above for\n\t// the available fields and which are required.\n\treq := \u0026insightsv1.GetUsageStatsRequest{}\n\n\tresp, err := client.RequestLogs().GetUsageStats(ctx, req)\n\tif err != nil {\n\t\tlog.Fatalf(\"call: %v\", err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", resp)\n}\n"},{"name":"go.mod","content":"module github.com/tetrateio/agentrouter-go-examples/requestlogs/getusagestats\n\ngo 1.26\n\nrequire github.com/tetrateio/agentrouter-go v0.1.1\n\n// Point this at the directory you extracted the downloaded Go SDK tarball into.\n// The directory name matches the tarball stem on the Download SDK page.\nreplace github.com/tetrateio/agentrouter-go =\u003e ./third_party/agentrouter-go-0.1.1\n"}]},"python":{"files":[{"name":"main.py","content":"\"\"\"Runnable example for the AgentRouter Python SDK.\n\nSet AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `python main.py`.\n\"\"\"\nimport os\n\nfrom tars.insights.v1 import request_logs_pb2\n\nfrom agentrouter_sdk import Client\n\nclient = Client(\n base_url=os.environ[\"AGENTROUTER_BASE_URL\"],\n api_key=os.environ[\"AGENTROUTER_API_KEY\"],\n)\n\n# Populate the request fields -- see the \"Request fields\" table above\n# for the available fields and which are required.\nreq = request_logs_pb2.GetUsageStatsRequest()\ntry:\n result = client.request_logs.get_usage_stats(req)\n print(result)\nexcept Exception as err:\n print(\"Error:\", err)\n"},{"name":"requirements.txt","content":"# Point this at the directory you extracted the downloaded Python SDK tarball\n# into. The directory name matches the tarball stem on the Download SDK page.\n# To install instead from PyPI once published, replace the line below with:\n# agentrouter-sdk\u003e=0.1.0\nagentrouter-sdk @ file:./third_party/agentrouter-python-0.1.1\n"}]},"typescript":{"files":[{"name":"index.ts","content":"// Runnable example for the AgentRouter TypeScript SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `npm install \u0026\u0026 npx tsx index.ts`.\nimport { Client } from '@tetrate/agentrouter-sdk'\n\nconst client = new Client({\n baseUrl: process.env.AGENTROUTER_BASE_URL,\n apiKey: process.env.AGENTROUTER_API_KEY,\n})\n\n// Populate the request fields -- see the \"Request fields\" table above\n// for the available fields and which are required.\nconst req = {}\ntry {\n const result = await client.requestLogs.getUsageStats(req)\n console.log(result)\n} catch (err) {\n console.error('Error:', err)\n}\n"},{"name":"package.json","content":"{\n \"name\": \"requestlogs\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": {\n \"@tetrate/agentrouter-sdk\": \"file:./third_party/agentrouter-typescript-0.1.1\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"typescript\": \"^5.4.0\"\n }\n}\n"},{"name":"tsconfig.json","content":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true\n }\n}\n"}]},"curl":"curl \"${AGENTROUTER_BASE_URL}/v1/usage/stats\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"persona":"App Developer (API key)","httpVerb":"GET","httpPath":"/v1/usage/stats","slug":"get-an-organizations-usage-over-time"}
Summarize MCP calls
Changed in v0.10.0 (new features): GET /v1/mcp-operations and GET /v1/mcp-operations/stats list and summarize an organization's MCP calls by session, profile, backend, tool, client or method. Calls recorded before the upgrade belong to no organization, so these reads leave them out until they age out after 30 days; only platform readers see them.
What it does: Summarizes MCP calls: totals and groups by session, profile, backend, tool, client or method. It counts operations, one row for each MCP call, and leaves out the backend fan-out rows of older data planes. For a session that has both, the current rows win over the older root row, chosen inside the requested window. ListMcpOperations returns the same operations. Authorization is the same as GetUsageStats: metrics_reader and usage.read. A request that names customer_id is authorized against that customer and reads only its calls. A request that names no customer reads every customer, including calls recorded before the tenant was stored, and needs a platform usage.read grant.
Request fields:
| Field | Required | Description |
|---|---|---|
customer_id | no | Customer to read. Empty reads every customer and needs a platform grant. |
project_id | no | Narrows the read to one project of customer_id. Requires customer_id. |
start_time | no | Start of the window, inclusive. Unset means no lower limit (MCP calls are kept for 30 days). |
end_time | no | End of the window, exclusive. Unset means no upper limit. |
profile_id | no | MCP profile id. |
session_id | no | Hashed MCP session id. |
tool_name | no | Tool name, exact match. |
backend | no | Backend MCP server, exact match. |
client_id | no | Client id, exact match. |
method | no | JSON-RPC method, for example "tools/call". |
outcome | no | Includes only operations with this outcome. Unset includes all. |
operation_kind | no | Includes only operations of this kind. Unset includes all. |
search | no | Case-insensitive substring match. With group_by set it matches the group key. With no group_by it matches the profile URL, client, session, backend, tool and method, including those of the backend events. |
page_size | no | Groups per page. 0 means 50; at most 1000. |
page_token | no | Token from a previous response's next_page_token. |
skip | no | Groups to skip. Use this or page_token, not both. |
group_by | no | Dimension of the groups. Unset returns totals only. |
order_by | no | Order of the groups. Unset means operation count, most first. |
descending | no | Reverses order_by. Ignored when order_by is unset. |
Response fields:
| Field | Required | Description |
|---|---|---|
totals | no | Figures over every operation that the groups count. With no group_by, over every matching operation. |
groups | no | Groups in the requested order. |
next_page_token | no | Token for the next page of groups. |
total_count | no | Number of groups that matched. |
{"signatures":{"go":"func (x *RequestLogsClient) GetMcpOperationStats(ctx context.Context, req *insightsv1.GetMcpOperationStatsRequest) (*insightsv1.GetMcpOperationStatsResponse, error)","python":"get_mcp_operation_stats(req: request_logs_pb2.GetMcpOperationStatsRequest) -\u003e GetMcpOperationStatsResponse","typescript":"getMcpOperationStats(req: MessageInitShape\u003ctypeof tars_insights_v1_request_logs_pb.GetMcpOperationStatsRequestSchema\u003e): Promise\u003cGetMcpOperationStatsResponse\u003e","curl":"curl \"${AGENTROUTER_BASE_URL}/v1/mcp-operations/stats\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"examples":{"go":{"files":[{"name":"main.go","content":"// Command example is a runnable example for the AgentRouter Go SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then `go run .`.\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tagentrouter \"github.com/tetrateio/agentrouter-go\"\n\tinsightsv1 \"github.com/tetrateio/agentrouter-go/genapi/api/tars/insights/v1\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\tclient, err := agentrouter.New(ctx,\n\t\tagentrouter.WithBaseURL(os.Getenv(\"AGENTROUTER_BASE_URL\")),\n\t\tagentrouter.WithAPIKey(os.Getenv(\"AGENTROUTER_API_KEY\")),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"client: %v\", err)\n\t}\n\n\t// Populate the request fields -- see the \"Request fields\" table above for\n\t// the available fields and which are required.\n\treq := \u0026insightsv1.GetMcpOperationStatsRequest{}\n\n\tresp, err := client.RequestLogs().GetMcpOperationStats(ctx, req)\n\tif err != nil {\n\t\tlog.Fatalf(\"call: %v\", err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", resp)\n}\n"},{"name":"go.mod","content":"module github.com/tetrateio/agentrouter-go-examples/requestlogs/getmcpoperationstats\n\ngo 1.26\n\nrequire github.com/tetrateio/agentrouter-go v0.1.1\n\n// Point this at the directory you extracted the downloaded Go SDK tarball into.\n// The directory name matches the tarball stem on the Download SDK page.\nreplace github.com/tetrateio/agentrouter-go =\u003e ./third_party/agentrouter-go-0.1.1\n"}]},"python":{"files":[{"name":"main.py","content":"\"\"\"Runnable example for the AgentRouter Python SDK.\n\nSet AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `python main.py`.\n\"\"\"\nimport os\n\nfrom tars.insights.v1 import request_logs_pb2\n\nfrom agentrouter_sdk import Client\n\nclient = Client(\n base_url=os.environ[\"AGENTROUTER_BASE_URL\"],\n api_key=os.environ[\"AGENTROUTER_API_KEY\"],\n)\n\n# Populate the request fields -- see the \"Request fields\" table above\n# for the available fields and which are required.\nreq = request_logs_pb2.GetMcpOperationStatsRequest()\ntry:\n result = client.request_logs.get_mcp_operation_stats(req)\n print(result)\nexcept Exception as err:\n print(\"Error:\", err)\n"},{"name":"requirements.txt","content":"# Point this at the directory you extracted the downloaded Python SDK tarball\n# into. The directory name matches the tarball stem on the Download SDK page.\n# To install instead from PyPI once published, replace the line below with:\n# agentrouter-sdk\u003e=0.1.0\nagentrouter-sdk @ file:./third_party/agentrouter-python-0.1.1\n"}]},"typescript":{"files":[{"name":"index.ts","content":"// Runnable example for the AgentRouter TypeScript SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `npm install \u0026\u0026 npx tsx index.ts`.\nimport { Client } from '@tetrate/agentrouter-sdk'\n\nconst client = new Client({\n baseUrl: process.env.AGENTROUTER_BASE_URL,\n apiKey: process.env.AGENTROUTER_API_KEY,\n})\n\n// Populate the request fields -- see the \"Request fields\" table above\n// for the available fields and which are required.\nconst req = {}\ntry {\n const result = await client.requestLogs.getMcpOperationStats(req)\n console.log(result)\n} catch (err) {\n console.error('Error:', err)\n}\n"},{"name":"package.json","content":"{\n \"name\": \"requestlogs\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": {\n \"@tetrate/agentrouter-sdk\": \"file:./third_party/agentrouter-typescript-0.1.1\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"typescript\": \"^5.4.0\"\n }\n}\n"},{"name":"tsconfig.json","content":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true\n }\n}\n"}]},"curl":"curl \"${AGENTROUTER_BASE_URL}/v1/mcp-operations/stats\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"persona":"App Developer (API key)","httpVerb":"GET","httpPath":"/v1/mcp-operations/stats","slug":"summarize-mcp-calls"}
List MCP calls
Changed in v0.10.0 (behaviour changes): Existing metrics_reader keys can read their organization's individual MCP calls. Client addresses and error messages stay hidden unless the key also has the admin or request_logs_reader scope.
Changed in v0.10.0 (new features): GET /v1/mcp-operations and GET /v1/mcp-operations/stats list and summarize an organization's MCP calls by session, profile, backend, tool, client or method. Calls recorded before the upgrade belong to no organization, so these reads leave them out until they age out after 30 days; only platform readers see them.
What it does: Lists MCP calls, newest first: the operations that GetMcpOperationStats counts. Authorization is the same as GetMcpOperationStats. client_ip and error_message are returned only to a signed-in user that a console acts for, and to a key with the admin or request_logs_reader scope. Other callers get them empty.
Request fields:
| Field | Required | Description |
|---|---|---|
customer_id | no | Customer to read. Empty reads every customer and needs a platform grant. |
project_id | no | Narrows the read to one project of customer_id. Requires customer_id. |
start_time | no | Start of the window, inclusive. Unset means no lower limit (MCP calls are kept for 30 days). |
end_time | no | End of the window, exclusive. Unset means no upper limit. |
profile_id | no | MCP profile id. |
session_id | no | Hashed MCP session id. |
tool_name | no | Tool name, exact match. |
backend | no | Backend MCP server, exact match. |
client_id | no | Client id, exact match. |
method | no | JSON-RPC method, for example "tools/call". |
outcome | no | Includes only operations with this outcome. Unset includes all. |
operation_kind | no | Includes only operations of this kind. Unset includes all. |
search | no | Case-insensitive substring match on the profile URL, client, session, backend, tool and method, including those of the backend events. |
page_size | no | Operations per page. 0 means 50; at most 1000. |
page_token | no | Token from a previous response's next_page_token. |
skip | no | Operations to skip. Use this or page_token, not both. |
include_backend_events | no | Adds backend_events to each operation that an older data plane recorded as one root call fanned out to backend calls. |
Response fields:
| Field | Required | Description |
|---|---|---|
operations | no | Operations, newest first. |
next_page_token | no | Token for the next page. |
total_count | no | Number of operations that matched. |
{"signatures":{"go":"func (x *RequestLogsClient) ListMcpOperations(ctx context.Context, req *insightsv1.ListMcpOperationsRequest) (*insightsv1.ListMcpOperationsResponse, error)","python":"list_mcp_operations(req: request_logs_pb2.ListMcpOperationsRequest) -\u003e ListMcpOperationsResponse","typescript":"listMcpOperations(req: MessageInitShape\u003ctypeof tars_insights_v1_request_logs_pb.ListMcpOperationsRequestSchema\u003e): Promise\u003cListMcpOperationsResponse\u003e","curl":"curl \"${AGENTROUTER_BASE_URL}/v1/mcp-operations\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"examples":{"go":{"files":[{"name":"main.go","content":"// Command example is a runnable example for the AgentRouter Go SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then `go run .`.\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tagentrouter \"github.com/tetrateio/agentrouter-go\"\n\tinsightsv1 \"github.com/tetrateio/agentrouter-go/genapi/api/tars/insights/v1\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\n\tclient, err := agentrouter.New(ctx,\n\t\tagentrouter.WithBaseURL(os.Getenv(\"AGENTROUTER_BASE_URL\")),\n\t\tagentrouter.WithAPIKey(os.Getenv(\"AGENTROUTER_API_KEY\")),\n\t)\n\tif err != nil {\n\t\tlog.Fatalf(\"client: %v\", err)\n\t}\n\n\t// Populate the request fields -- see the \"Request fields\" table above for\n\t// the available fields and which are required.\n\treq := \u0026insightsv1.ListMcpOperationsRequest{}\n\n\tresp, err := client.RequestLogs().ListMcpOperations(ctx, req)\n\tif err != nil {\n\t\tlog.Fatalf(\"call: %v\", err)\n\t}\n\n\tfmt.Printf(\"%+v\\n\", resp)\n}\n"},{"name":"go.mod","content":"module github.com/tetrateio/agentrouter-go-examples/requestlogs/listmcpoperations\n\ngo 1.26\n\nrequire github.com/tetrateio/agentrouter-go v0.1.1\n\n// Point this at the directory you extracted the downloaded Go SDK tarball into.\n// The directory name matches the tarball stem on the Download SDK page.\nreplace github.com/tetrateio/agentrouter-go =\u003e ./third_party/agentrouter-go-0.1.1\n"}]},"python":{"files":[{"name":"main.py","content":"\"\"\"Runnable example for the AgentRouter Python SDK.\n\nSet AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `python main.py`.\n\"\"\"\nimport os\n\nfrom tars.insights.v1 import request_logs_pb2\n\nfrom agentrouter_sdk import Client\n\nclient = Client(\n base_url=os.environ[\"AGENTROUTER_BASE_URL\"],\n api_key=os.environ[\"AGENTROUTER_API_KEY\"],\n)\n\n# Populate the request fields -- see the \"Request fields\" table above\n# for the available fields and which are required.\nreq = request_logs_pb2.ListMcpOperationsRequest()\ntry:\n result = client.request_logs.list_mcp_operations(req)\n print(result)\nexcept Exception as err:\n print(\"Error:\", err)\n"},{"name":"requirements.txt","content":"# Point this at the directory you extracted the downloaded Python SDK tarball\n# into. The directory name matches the tarball stem on the Download SDK page.\n# To install instead from PyPI once published, replace the line below with:\n# agentrouter-sdk\u003e=0.1.0\nagentrouter-sdk @ file:./third_party/agentrouter-python-0.1.1\n"}]},"typescript":{"files":[{"name":"index.ts","content":"// Runnable example for the AgentRouter TypeScript SDK.\n// Set AGENTROUTER_BASE_URL and AGENTROUTER_API_KEY in the environment, then run `npm install \u0026\u0026 npx tsx index.ts`.\nimport { Client } from '@tetrate/agentrouter-sdk'\n\nconst client = new Client({\n baseUrl: process.env.AGENTROUTER_BASE_URL,\n apiKey: process.env.AGENTROUTER_API_KEY,\n})\n\n// Populate the request fields -- see the \"Request fields\" table above\n// for the available fields and which are required.\nconst req = {}\ntry {\n const result = await client.requestLogs.listMcpOperations(req)\n console.log(result)\n} catch (err) {\n console.error('Error:', err)\n}\n"},{"name":"package.json","content":"{\n \"name\": \"requestlogs\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"type\": \"module\",\n \"dependencies\": {\n \"@tetrate/agentrouter-sdk\": \"file:./third_party/agentrouter-typescript-0.1.1\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"typescript\": \"^5.4.0\"\n }\n}\n"},{"name":"tsconfig.json","content":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true\n }\n}\n"}]},"curl":"curl \"${AGENTROUTER_BASE_URL}/v1/mcp-operations\" \\\n -H \"Authorization: Bearer ak-${AGENTROUTER_API_KEY}\""},"persona":"App Developer (API key)","httpVerb":"GET","httpPath":"/v1/mcp-operations","slug":"list-mcp-calls"}