Analytics API
Pre-aggregated summaries of every query that flowed through the Gateway,
served by the Airbrx API at api.airbrx.ai. Use these
endpoints to build custom dashboards, monitor cache effectiveness, or
track per-user query patterns.
Authentication
All requests require a Personal Access Token in the Authorization
header. The token must include the relevant analytics scope; see the
API reference for the full scope
list.
curl -H "Authorization: Bearer YOUR_PAT" \
https://api.airbrx.ai/tenants/your-slug/summaries/2026
Endpoints
| Method | Path | Returns |
|---|---|---|
| GET | /tenants/{tenant}/summaries/{year} |
Yearly summary with daily activity timeline |
| GET | /tenants/{tenant}/summaries/{year}/{month} |
Monthly summary |
| GET | /tenants/{tenant}/summaries/{year}/{month}/{day} |
Daily summary with per-query details |
| GET | /tenants/{tenant}/summaries/{year}/rules |
Rule effectiveness for the year |
| GET | /tenants/{tenant}/summaries/{year}/opportunities |
Cache opportunities for the year |
| GET | /tenants/{tenant}/users |
All users summary for the tenant |
| GET | /tenants/{tenant}/users/{userId}/{year} |
User yearly summary |
| GET | /tenants/{tenant}/users/{userId}/{year}/{month} |
User monthly summary, scoped to one user |
| GET | /tenants/{tenant}/users/{userId}/{year}/{month}/{day} |
User daily summary (same shape as daily summary, scoped to one user) |
Yearly summary
GET /tenants/{tenant}/summaries/{year}
{
"tenantSummaryReportID": "a1b2c3d4-...",
"tenantId": "acme-corp",
"reportyears": ["2026"],
"totalUsers": 45,
"totalQueries": 12847,
"totalCacheHits": 9234,
"totalHits": 52341,
"cacheSize": 1073741824,
"cacheElements": 12847,
"last365daysQueryActivity": [
{
"date": "2026/01/15",
"queries": 234,
"users": 12,
"cacheHits": 189,
"totalHits": 1205,
"dataTransfer": 52428800,
"cacheSize": 2097152,
"cacheElements": 234
}
]
}
Top-level fields
| Field | Type | Description |
|---|---|---|
tenantSummaryReportID | string | Unique report identifier. |
tenantId | string | Tenant identifier. |
totalUsers | integer | Cumulative unique users for the year. |
totalQueries | integer | Unique SQL statements cached. |
totalCacheHits | integer | Cache hits across all queries. |
totalHits | integer | Total query executions (hits + misses). |
cacheSize | integer (bytes) | Total cached data size. |
cacheElements | integer | Number of cached query results. |
last365daysQueryActivity | array | Daily activity timeline (see below). |
Daily activity entry
| Field | Type | Description |
|---|---|---|
date | string | YYYY/MM/DD format. |
queries | integer | Unique statements on this day. |
users | integer | Unique users on this day. |
cacheHits | integer | Cache hits on this day. |
totalHits | integer | Total executions on this day. |
dataTransfer | integer (bytes) | Bytes served from cache. |
cacheSize | integer (bytes) | New cache data added. |
cacheElements | integer | New cache entries added. |
Daily summary
GET /tenants/{tenant}/summaries/{year}/{month}/{day}
{
"queries": [
{
"queryHash": "a1b2c3d4...",
"statement": "SELECT * FROM customers WHERE region = ?",
"cacheKey": "abc123...",
"matchedRule": "cache-customers",
"cacheStatus": "HIT",
"cacheHits": 47,
"cacheMisses": 5,
"responseCode": 200,
"averageResponseTime": 234,
"responseSize": 15360,
"count": 52,
"warehouseExecutionTimeMs": 4210,
"firstRequestTime": "2026-01-15T08:23:45Z",
"lastRequestTime": "2026-01-15T17:45:12Z"
}
],
"uniqueUsers": 23,
"cacheHits": 892,
"cacheMisses": 313,
"totalHits": 1205,
"cachedBytes": 13684736,
"passThroughBytes": 48234496,
"averageResponseTime": 187,
"cacheHitAverageResponseTime": 41,
"cacheMissAverageResponseTime": 604,
"minResponseTime": 12,
"maxResponseTime": 3450,
"hourlyActivity": [
{
"hour": 0,
"queries": 4,
"totalHits": 11,
"cacheHits": 9,
"cacheMisses": 2,
"cachedBytes": 138240,
"passThroughBytes": 409600,
"cacheHitAverageResponseTime": 38,
"cacheMissAverageResponseTime": 588,
"users": 2
}
]
}
Query record fields
One entry per distinct statement per day — a rollup of the
underlying query log, not
the log itself. For one row per execution, query
proxy_logs
directly.
The summaries and proxy_logs are derived from the raw logs
in parallel, so a field name here does not always match the one there,
and a few fields mean different things — statement
is standardizedSql, userId is
userName, and cacheStatus below is a sample
rather than a rollup.
The three logs maps
them against each other.
| Field | Type | Description |
|---|---|---|
queryHash | string | Hash of the standardized SQL. |
statement | string | The standardized SQL. |
cacheKey | string | Cache storage key, or filename. |
matchedRule | string | null | Rule that matched this statement. null if no rule matched. |
cacheStatus | string | The status of the most recent execution of this statement that day — a sample, not a rollup, and any of the twelve statuses. For the distribution, count it in proxy_logs. |
cacheHits | integer | Times served from cache. |
cacheMisses | integer | Non-HIT events: MISS + BYPASS + PASSTHROUGH, not strict misses. |
responseCode | integer | HTTP status (200, 404, 500, …). |
averageResponseTime | integer (ms) | Mean response time. |
responseSize | integer (bytes) | Response size. |
count | integer | Total execution count. |
warehouseExecutionTimeMs | integer | null | Total warehouse execution time. null if the statement never reached the warehouse. |
firstRequestTime | ISO 8601 | First execution. |
lastRequestTime | ISO 8601 | Most recent execution. |
Day-level fields
hourlyActivity holds 24 entries, index 0 being midnight
UTC. On both the day and the hour, cachedBytes sums
responseSize across HIT events and
passThroughBytes sums it across everything else.
Two cautions when charting these. The split averages
(cacheHitAverageResponseTime,
cacheMissAverageResponseTime) emit 0 for an
empty bucket, which means no data rather than a real 0 ms
response — plot them as gaps, not as zeroes. And the five fields
cacheMisses, cachedBytes,
passThroughBytes and the two split averages were added
after the first summary files were written, so treat them as optional
when reading far enough back.
Monthly summary
GET /tenants/{tenant}/summaries/{year}/{month}
The month’s totals, in the same vocabulary as the daily summary:
totalQueries, totalUsers,
totalCacheHits, totalCacheMisses,
totalHits, totalCachedBytes,
totalPassThroughBytes, and the response-time set
(cacheHitAverageResponseTime,
cacheMissAverageResponseTime,
averageResponseTime, minResponseTime,
maxResponseTime).
Two shape details worth knowing before you parse it. The URL takes a
zero-padded month (04) but the body emits
month as an integer (4). And an empty hourly
bucket emits null for the two split averages here, where
the daily summary emits 0 — so a client that handles one
does not automatically handle the other.
Rule effectiveness
GET /tenants/{tenant}/summaries/{year}/rules
Per-rule performance for the year: totalQueries,
uncachedQueries and bypassedQueries at the
top level, then a rules array carrying
ruleId, ruleName,
queriesMatched, totalExecutions,
cacheHits, cacheMisses, hitRate
(0.0–1.0), and warehouseTimeSavedMs. This is the
data behind the Insights page’s rule table.
Cache opportunities
GET /tenants/{tenant}/summaries/{year}/opportunities
Ranked candidates for caching that isn’t happening yet, in three
arrays — repeatMisses,
highCostUncached, and lowHitRate. Each entry
carries queryHash, statement,
executionCount, cacheMisses,
totalWarehouseTimeMs, matchedRule, and a
suggestedAction.
All users summary
GET /tenants/{tenant}/users
{
"tenantId": "acme-corp",
"generatedAt": "2026-01-15T18:30:00Z",
"totalUsers": 45,
"users": [
{
"userId": "alice@acme.com",
"uniqueQueries": 234,
"cacheHits": 189,
"totalHits": 567,
"averageResponseTime": 145,
"minResponseTime": 12,
"maxResponseTime": 2340
}
]
}
User yearly summary
GET /tenants/{tenant}/users/{userId}/{year}
Returns the same shape as the tenant yearly summary, scoped to a single
user. The corresponding daily endpoint
(/tenants/{tenant}/users/{userId}/{year}/{month}/{day})
returns the same shape as the daily summary, also scoped to that user.
A monthly endpoint
(/tenants/{tenant}/users/{userId}/{year}/{month}) is
available too.
When a summary isn’t enough
Everything on this page is pre-aggregated by day, month, year, or user.
That is deliberate: the payloads stay small and the questions stay
cheap. When your question doesn’t fit one of those grains —
one row per execution, an arbitrary GROUP BY, a filter on
strict MISS rather than every non-hit — the same
traffic is available as a
query log you can hit with
read-only SQL. See
Query the
raw log.
See also
- Query log — the per-statement record these summaries are built from.
- Platform Savings Pillar — what these metrics enable you to measure.
- Response headers — per-request, real-time cache info (vs. these aggregated summaries).
- API reference — the live OpenAPI spec for these and all other endpoints.