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

FieldTypeDescription
tenantSummaryReportIDstringUnique report identifier.
tenantIdstringTenant identifier.
totalUsersintegerCumulative unique users for the year.
totalQueriesintegerUnique SQL statements cached.
totalCacheHitsintegerCache hits across all queries.
totalHitsintegerTotal query executions (hits + misses).
cacheSizeinteger (bytes)Total cached data size.
cacheElementsintegerNumber of cached query results.
last365daysQueryActivityarrayDaily activity timeline (see below).

Daily activity entry

FieldTypeDescription
datestringYYYY/MM/DD format.
queriesintegerUnique statements on this day.
usersintegerUnique users on this day.
cacheHitsintegerCache hits on this day.
totalHitsintegerTotal executions on this day.
dataTransferinteger (bytes)Bytes served from cache.
cacheSizeinteger (bytes)New cache data added.
cacheElementsintegerNew 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.

FieldTypeDescription
queryHashstringHash of the standardized SQL.
statementstringThe standardized SQL.
cacheKeystringCache storage key, or filename.
matchedRulestring | nullRule that matched this statement. null if no rule matched.
cacheStatusstringThe 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.
cacheHitsintegerTimes served from cache.
cacheMissesintegerNon-HIT events: MISS + BYPASS + PASSTHROUGH, not strict misses.
responseCodeintegerHTTP status (200, 404, 500, …).
averageResponseTimeinteger (ms)Mean response time.
responseSizeinteger (bytes)Response size.
countintegerTotal execution count.
warehouseExecutionTimeMsinteger | nullTotal warehouse execution time. null if the statement never reached the warehouse.
firstRequestTimeISO 8601First execution.
lastRequestTimeISO 8601Most 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