Query the raw log

The dashboard answers the questions we anticipated. For everything else, every statement that crossed the Gateway is one row in a columnar view called proxy_logs, and you can ask it anything in read-only SQL — no export, no parsing project, no warehouse credit spent.

The endpoint

POST /tenants/{tenant}/query

The body is a single field. The response is columns, rows, a count, and a truncation flag. Full shapes are in the query log reference.

{ "sql": "SELECT * FROM proxy_logs LIMIT 10" }

Scope a token first

This runs outside the App, so it needs a non-interactive credential. Mint a personal access token and scope it tightly — the log is the most sensitive thing the API will hand back, because it contains the SQL your people and your models actually wrote.

Scope, in plain language for this use case:

The caller must be a member of the tenant regardless of scope, and reads against the raw log are audit-logged.

Find out what the columns are

proxy_logs is a fixed view. Rather than trusting a list that can drift, ask the service what it holds — this is the first query to run against a new tenant:

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 facts available are the ones on the record: who ran the statement, the standardized SQL and its hash, the cache key, the rule that matched, the cache status, the response code, the timings (Gateway-side and warehouse-side), and the bytes returned. Result rows are never recorded, so no query you write here can reach the contents of anyone’s data.

What the executor allows

Queries run on a locked-down DuckDB service, so the dialect is DuckDB — window functions, QUALIFY, GROUP BY ALL and the rest are available. Four limits apply:

Results are capped

Every result set carries an enforced LIMIT. The response tells you whether you hit it:

"rowCount": 500,
"truncated": true

truncated: true means you are looking at an arbitrary subset, not the top of a ranking — a fact worth being blunt about, because a truncated result still looks like an answer. The fix is almost never to page through raw rows; it is to make the database do the work. Aggregate, filter to a narrower window, or rank and take the top N, so the rows you get back are the rows you meant to see.

Worked questions

Column names below follow the record as described above; confirm them against DESCRIBE proxy_logs for your tenant before saving anything into a pipeline.

What is the cache actually doing?

SELECT cacheStatus, count(*) AS executions
FROM proxy_logs
GROUP BY ALL
ORDER BY executions DESC

Four rows, and the shape of your traffic. PASSTHROUGH is the interesting one: those statements matched no rule at all, so they are the population your next cache rule would be drawn from. Note that this is stricter than the summary endpoints, which fold MISS, BYPASS and PASSTHROUGH together as misses.

Which uncached statements repeat the most?

SELECT queryHash,
       any_value(statement)          AS statement,
       count(*)                      AS executions,
       count(DISTINCT userId)        AS users,
       sum(warehouseExecutionTimeMs) AS warehouse_ms
FROM proxy_logs
WHERE cacheStatus = 'PASSTHROUGH'
GROUP BY queryHash
HAVING count(*) > 1
ORDER BY warehouse_ms DESC
LIMIT 25

This is the ranked list of work your warehouse did more than once and did not need to. Sorting by warehouse time rather than execution count puts the expensive repeats first, which is the order you want to write rules in.

What has AI been asking our data?

Agents, copilots and text-to-SQL tools are just another client, so they land in the same record. Whichever field identifies the caller in your setup — a service account, a dedicated user, an application name — the question becomes a WHERE clause rather than a project:

SELECT userId,
       count(*)               AS executions,
       count(DISTINCT queryHash) AS distinct_statements,
       min(timestamp)         AS first_seen,
       max(timestamp)         AS last_seen
FROM proxy_logs
GROUP BY ALL
ORDER BY executions DESC
LIMIT 50

Is a rule earning its keep?

SELECT matchedRule,
       count(*)                                             AS executions,
       count(*) FILTER (WHERE cacheStatus = 'HIT')          AS hits,
       round(100.0 * count(*) FILTER (WHERE cacheStatus = 'HIT') / count(*), 1) AS hit_rate_pct
FROM proxy_logs
WHERE matchedRule IS NOT NULL
GROUP BY ALL
ORDER BY executions DESC

A rule with high executions and a low hit rate is usually a cache key that is too specific — every execution computes a different key, so nothing is ever read back. Investigate one of its statements to see the key inputs, then narrow the key.

When to use something else

Where to go next

Mint a token

Querying the log starts with a least-privilege PAT.

Create a personal access token