Query log

Every statement that crosses the Gateway is written down once, in a fixed shape, into your own bucket. This page is the record itself: what an entry contains, how the objects are laid out, and the three ways to read them — raw objects, a columnar SQL view, or the pre-aggregated summaries built on top.

What is recorded

One entry per request. The entry describes the statement and its outcome — who ran it, what they ran, which rule matched, whether it was served from cache, how long it took, and how many bytes came back.

Result rows are never recorded. The contents of a response are not read and not retained. Neither are raw credentials: the caller’s own token passes through to your engine, and none of it is held. See Trust & security for the full statement and security posture for how that is enforced.

Cache status

Every entry carries a cache status. Four values are emitted, and the distinction matters when you aggregate — the summary endpoints count non-HIT as a miss, which is a wider set than the strict MISS.

StatusMeaning
HITServed from cache. No warehouse execution.
MISSA cache rule matched, but no valid entry existed — nothing cached yet, TTL expired, or invalidated by a marker or rule. Executed on the warehouse and stored.
BYPASSA rule matched and declined to serve or store this execution from cache.
PASSTHROUGHNo cache rule matched. Forwarded to the warehouse untouched.

Wherever a summary field is named cacheMisses, it counts MISS + BYPASS + PASSTHROUGH. If you want strict misses only, filter on the cache status yourself in a SQL query.

Storage layout

Raw logs are date-partitioned objects in your bucket, one JSON object per file, under an hourly prefix:

logs/YYYY/MM/DD/HH/<file>.json

The same tree is browsable over the API as a one-level listing. Any partial prefix lists its immediate children — a year lists its months, a month lists its days, a day lists its hours, and an hour lists that hour’s log files. A path ending in .json is an object read instead of a listing.

Reading raw objects

MethodPathReturns
GET/tenants/{tenant}/logs/{year}The months with data.
GET/tenants/{tenant}/logs/{year}/{month}The days with data.
GET/tenants/{tenant}/logs/{year}/{month}/{day}The hours with data.
GET/tenants/{tenant}/logs/{year}/{month}/{day}/{hour}The log files for that hour.
GET/tenants/{tenant}/logs/{year}/{month}/{day}/{hour}/{file}One raw log object.

Path segments are zero-padded strings: month 0112, day 0131, hour 0023.

Example

curl -H "Authorization: Bearer $AIRBRX_PAT" \
  "https://api.airbrx.ai/tenants/your-slug/logs/2026/05/19/14"
{
  "tenantId": "your-slug",
  "_links": {
    "self":   { "href": "/tenants/your-slug/logs/2026/05/19/14" },
    "parent": { "href": "/tenants/your-slug/logs/2026/05/19" },
    "items": [
      { "name": "a1b2c3d4.json", "href": "/tenants/your-slug/logs/2026/05/19/14/a1b2c3d4.json" }
    ]
  }
}

Follow _links.items[].href down to a leaf and read the object:

curl -H "Authorization: Bearer $AIRBRX_PAT" \
  "https://api.airbrx.ai/tenants/your-slug/logs/2026/05/19/14/a1b2c3d4.json"

Access control

The caller must be a member of the tenant. Every read of a raw log is itself audit-logged — reading the record leaves a record. Mint a personal access token and scope it to GET on the log paths of the one tenant you mean to read.

Querying the log with SQL

Walking objects hour by hour is the wrong tool for a question like “which uncached statements ran most often last month.” For that, the same log is exposed as a columnar view named proxy_logs, queryable with read-only SQL:

POST /tenants/{tenant}/query

Queries execute on a locked-down DuckDB service against that one fixed view. The workflow — scoping, dialect, limits, and worked examples — is in Query the raw log.

Request

{ "sql": "SELECT * FROM proxy_logs LIMIT 10" }
FieldRequiredDescription
sqlYesA single read-only SELECT against proxy_logs. Multiple statements are rejected, as are file and extension functions. No other field is accepted.

Response

{
  "tenantId": "your-slug",
  "columns": [
    { "name": "cacheStatus", "type": "VARCHAR" },
    { "name": "executions",  "type": "BIGINT" }
  ],
  "rows": [
    { "cacheStatus": "HIT", "executions": 9234 }
  ],
  "rowCount": 1,
  "truncated": false
}
FieldTypeDescription
tenantIdstringTenant the query ran against.
columnsarrayResult columns in order, each with a name and a DuckDB type.
rowsarrayResult rows as objects keyed by column name.
rowCountintegerRows returned, after the enforced cap.
truncatedbooleantrue if the cap cut the result set short. Aggregate or narrow the query rather than paging blindly.

Columns

proxy_logs is a fixed view, and the authoritative column list is the one the service itself reports. Ask it directly:

curl -X POST \
  -H "Authorization: Bearer $AIRBRX_PAT" \
  -H "Content-Type: application/json" \
  -d '{"sql":"DESCRIBE proxy_logs"}' \
  "https://api.airbrx.ai/tenants/your-slug/query"

The per-statement fields on the daily summary query record — statement, hash, cache key, matched rule, cache status, response code, timings, bytes, warehouse execution time — are the same facts, rolled up per statement per day.

Three ways to read the same log

SurfaceGrainReach for it when
Summaries APIDay, month, year, userYou want a dashboard number or a trend, already aggregated.
proxy_logs SQLOne row per requestYou have a question no rollup answers, and would rather ask it in SQL.
Raw objectsOne object per requestYou are exporting into your own pipeline, or need the untouched record.

See also