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:

FieldRequiredDescription
user_idnoFilter by user ID
api_key_idnoFilter by API key ID. Superseded by api_key_ids when non-empty.
api_key_idsnoFilter by any of these API key IDs (UUIDs).
endpointnoFilter by request endpoint (e.g. "/v1/chat/completions").
model_namenoFilter by model name (e.g., "claude-3-opus", "gpt-4")
statusnoFilter by status (e.g., "success", "error", "timeout")
status_codenoFilter by HTTP status code
start_timenoStart of time range (inclusive)
end_timenoEnd of time range (exclusive)
min_duration_msnoMinimum duration in milliseconds
max_duration_msnoMaximum duration in milliseconds
page_sizenoPage size (default: 50, max: 1000)
page_tokennoPage token for pagination
order_bynoOrder 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_directionnoOrder direction: "asc" or "desc" (default: "desc")
searchnoExact 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_idnoFilter by the data plane that served the request (the workspace id the reporting dataplane's telemetry is authenticated as) -- see RequestLog.dataplane_id.
customer_idnoTenant 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_typenoOptional 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_filternoOptional 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_typenoThe policy type to keep when policy_filter is REQUEST_LOG_POLICY_FILTER_TYPE, and refused with InvalidArgument otherwise.
request_id_prefixnoDeprecated: 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:

FieldRequiredDescription
logsnoList of request logs matching the query
next_page_tokennoToken for retrieving the next page (empty if no more pages)
total_countnoTotal 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:

FieldRequiredDescription
request_idnoRequest identifier
customer_idnoTenant 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:

FieldRequiredDescription
idoutput-onlyUnique log identifier
timestampnoTimestamp when the request was received
user_idnoUser ID who made the request
api_key_idnoAPI key information UUID of the API key that authenticated the request.
api_key_prefixnoThe 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_hashnoHash of the API key used to look it up without storing the secret.
api_key_addressnoOn-chain/account address the API key is bound to.
api_key_namenoHuman-readable label assigned to the API key.
model_namenoModel name used for the request
input_tokensnoToken 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_tokensnoNumber of completion/output tokens generated in the response.
input_tokens_cost_microdollarnoCost 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_costnoInput-token cost as a decimal string (precision: 18, scale: 10). Empty when usage was never recorded.
input_tokens_cost_before_feenoInput-token cost before the platform fee/markup is applied. Decimal string.
output_tokens_cost_microdollarnoDeprecated: always 0. Use output_tokens_cost.
output_tokens_costnoOutput-token cost as a decimal string (precision: 18, scale: 10). Empty when usage was never recorded.
output_tokens_cost_before_feenoOutput-token cost before the platform fee/markup is applied. Decimal string.
statusnoRequest/Response status Logical outcome of the request (e.g. "success", "error", "timeout").
status_codenoHTTP status code returned to the client.
request_headersnoRequest metadata Captured request headers as a JSON object.
request_bodynoCaptured request payload as a JSON object.
response_headersnoResponse metadata Captured response headers as a JSON object.
response_bodynoText response body captured from the upstream response.
response_body_rawnoRaw response body bytes (for binary payloads such as images).
llm_parametersnoLLM parameters used
upstream_duration_msnoTiming information Duration of the upstream LLM call in milliseconds.
duration_msnoTotal end-to-end request duration in milliseconds.
request_idnoRequest identifier
created_atnoRecord creation timestamp
storage_locationnoObject storage information (for Parquet-based storage) GCS path to the Parquet file when use_object_storage is true.
storage_row_indexnoRow index within the Parquet file.
storage_batch_idnoBatch UUID grouping rows written in the same export batch.
use_object_storagenoTrue when the row is stored in object storage (GCS); false when in the DB.
cached_input_tokensnoCache 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_costnoCached-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_feenoCached-input cost before the platform fee/markup is applied. Decimal string.
cache_creation_input_tokensnoInput tokens written to the prompt cache (cache-creation writes).
cache_creation_input_tokens_costnoCache-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_feenoCache-creation cost before the platform fee/markup is applied. Decimal string.
endpointnoRequest route (e.g. "/v1/chat/completions", "/v1/images/generations").
additional_costnoRequest-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_feenoRequest-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_costnoTotal billed cost after fee: input + output + additional. Empty only when token usage was not measured and no nonzero additional cost was recorded.
model_requestednoModel name the client requested (from request headers/body).
model_effectivenoModel name that actually served the request (after routing/fallback).
used_fallback_modelnoTrue when the gateway substituted a fallback model for the request.
used_byoknoTrue when the request used bring-your-own-key (customer-supplied provider key).
used_passthroughnoTrue when the request was forwarded in passthrough mode without rewriting.
is_provider_fallbacknoTrue when the provider itself returned a fallback model.
usage_measurednoPresence 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_measurednoTrue when the request recorded a prompt-cache breakdown (cached and cache-creation tokens). Independent of usage_measured.
dataplane_idnoIdentity 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_sourcenoHow 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_typenoCaller 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_idnoThe project of the API key that sent the request. Empty for a request that never reached a key.
project_namenoThe current name of project_id's project. Empty when it has none.
providernoThe provider that served the request, from the upstream backend. Empty when the request did not reach a provider.
policy_decisionsnoThe 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_unexplainednoTrue when the model that answered differs from the model the caller asked for and no recorded decision explains it.
not_servednoTrue 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_unavailablenoTrue 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:

FieldRequiredDescription
user_idnoFilter by user ID
model_namenoFilter by model name
start_timenoStart of time range (inclusive)
end_timenoEnd of time range (exclusive)
group_bynoGroup 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 "".
metricnoMetric to calculate Valid values: "count", "tokens", "cost", "latency", "errors"
dataplane_idnoOptional 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_modenoOptional 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_sourcenoOptional 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_idnoTenant 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_typenoOptional 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:

FieldRequiredDescription
statsnoStatistics grouped by the requested dimension
total_countnoTotal counts across all groups Total number of requests across all groups.
total_input_tokensnoSum of input tokens across all groups.
total_output_tokensnoSum of output tokens across all groups.
total_costnoTotal billed cost across all groups as a decimal string.
avg_duration_msnoMean request duration in milliseconds across all groups.
error_countnoTotal number of error requests across all groups.
total_cached_input_tokensnoSum of input tokens served from the prompt cache (cache reads) across all groups.
total_cache_creation_input_tokensnoSum 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:

FieldRequiredDescription
customer_idnoTenant 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_idnoProject 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_idsnoData 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_timenoStart of the window, inclusive. Required.
end_timenoEnd 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:

FieldRequiredDescription
countsnoOne 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:

FieldRequiredDescription
customer_idnoCustomer 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_idnoNarrows the read to one project of customer_id. Requires customer_id.
start_timenoStart of the window, inclusive. Unset means no lower limit.
end_timenoEnd of the window, exclusive. Unset means no upper limit. The window is at most 400 days.
stepnoBucket size of the series. At most 3000 buckets.
time_zonenoIANA time zone for HOUR, DAY and MONTH buckets (for example "Asia/Kolkata"). Empty means UTC.
group_bynoDimension 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.
limitnoMaximum groups returned. 0 means 1000, the maximum.
usage_modesnoUsage modes to include: "managed", "byok", "passthrough". Empty includes all.
user_idsnoUsers to include. Empty includes all.
team_idsnoTeams to include: user-group ids, and "Untagged" for users with no team or a deleted team. Empty includes all.
api_key_tag_keynoIncludes only API keys that carry this tag key. Requires api_key_tag_value.
api_key_tag_valuenoTag value that goes with api_key_tag_key.
searchnoCase-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_bynoGroup 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_sizenoGroups per page. Use this or limit, not both.
page_tokennoToken from a previous response's next_page_token.
skipnoGroups to skip. Use this or page_token, not both, and not with limit.
caller_typenoOptional 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_keysnoKeeps 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_bynoSplits 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:

FieldRequiredDescription
totalsnoFigures over the whole window.
bucketsnoSeries over the window, oldest first. Buckets with no usage are left out.
groupsnoGroups in the requested order.
truncatednoMore groups matched than this response returns.
next_page_tokennoToken for the next page of groups. GetUsageStats only.
total_groupsnoNumber of groups that matched.
source_interval_secondsnoBucket 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:

FieldRequiredDescription
customer_idnoCustomer to read. Empty reads every customer and needs a platform grant.
project_idnoNarrows the read to one project of customer_id. Requires customer_id.
start_timenoStart of the window, inclusive. Unset means no lower limit (MCP calls are kept for 30 days).
end_timenoEnd of the window, exclusive. Unset means no upper limit.
profile_idnoMCP profile id.
session_idnoHashed MCP session id.
tool_namenoTool name, exact match.
backendnoBackend MCP server, exact match.
client_idnoClient id, exact match.
methodnoJSON-RPC method, for example "tools/call".
outcomenoIncludes only operations with this outcome. Unset includes all.
operation_kindnoIncludes only operations of this kind. Unset includes all.
searchnoCase-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_sizenoGroups per page. 0 means 50; at most 1000.
page_tokennoToken from a previous response's next_page_token.
skipnoGroups to skip. Use this or page_token, not both.
group_bynoDimension of the groups. Unset returns totals only.
order_bynoOrder of the groups. Unset means operation count, most first.
descendingnoReverses order_by. Ignored when order_by is unset.

Response fields:

FieldRequiredDescription
totalsnoFigures over every operation that the groups count. With no group_by, over every matching operation.
groupsnoGroups in the requested order.
next_page_tokennoToken for the next page of groups.
total_countnoNumber 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:

FieldRequiredDescription
customer_idnoCustomer to read. Empty reads every customer and needs a platform grant.
project_idnoNarrows the read to one project of customer_id. Requires customer_id.
start_timenoStart of the window, inclusive. Unset means no lower limit (MCP calls are kept for 30 days).
end_timenoEnd of the window, exclusive. Unset means no upper limit.
profile_idnoMCP profile id.
session_idnoHashed MCP session id.
tool_namenoTool name, exact match.
backendnoBackend MCP server, exact match.
client_idnoClient id, exact match.
methodnoJSON-RPC method, for example "tools/call".
outcomenoIncludes only operations with this outcome. Unset includes all.
operation_kindnoIncludes only operations of this kind. Unset includes all.
searchnoCase-insensitive substring match on the profile URL, client, session, backend, tool and method, including those of the backend events.
page_sizenoOperations per page. 0 means 50; at most 1000.
page_tokennoToken from a previous response's next_page_token.
skipnoOperations to skip. Use this or page_token, not both.
include_backend_eventsnoAdds backend_events to each operation that an older data plane recorded as one root call fanned out to backend calls.

Response fields:

FieldRequiredDescription
operationsnoOperations, newest first.
next_page_tokennoToken for the next page.
total_countnoNumber 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"}