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.
| Status | Meaning |
|---|---|
HIT | Served from cache. No warehouse execution. |
MISS | A 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. |
BYPASS | A rule matched and declined to serve or store this execution from cache. |
PASSTHROUGH | No 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
| Method | Path | Returns |
|---|---|---|
| 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 01–12,
day 01–31, hour 00–23.
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" }
| Field | Required | Description |
|---|---|---|
sql | Yes | A 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
}
| Field | Type | Description |
|---|---|---|
tenantId | string | Tenant the query ran against. |
columns | array | Result columns in order, each with a name and a DuckDB type. |
rows | array | Result rows as objects keyed by column name. |
rowCount | integer | Rows returned, after the enforced cap. |
truncated | boolean | true 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
| Surface | Grain | Reach for it when |
|---|---|---|
| Summaries API | Day, month, year, user | You want a dashboard number or a trend, already aggregated. |
proxy_logs SQL | One row per request | You have a question no rollup answers, and would rather ask it in SQL. |
| Raw objects | One object per request | You are exporting into your own pipeline, or need the untouched record. |
See also
- Query the raw log — the workflow, with worked examples.
- Analytics API — the pre-aggregated summaries built from this log.
- Investigate a statement — the same record, read through the App.
- Security posture — what is and isn’t retained, and why.
- Response headers — the per-request view, in real time, before anything is aggregated.