Run SQL and get rows back.
Send either sql (free-form) or query_id (a saved query to execute); if
both are present sql wins and query_id is recorded only as the run’s
origin. params is a JSON array of positional values substituted for
$1…$N in the statement.
Results are scoped to your organization no matter how the SQL is written — there is no way to phrase a query that reads another organization’s data.
One read-only statement is accepted and nothing else:
- it must start with
SELECTorWITH; - a
;outside a string literal is rejected — no multi-statement bodies; - 8000 characters maximum;
- a single statement may not join the analytics tables (
events,evaluations,agent_sessions) to the operational ones (dashboards,saved_queries,api_keys,users, …) — split it into two queries.
Use GET /queries/schema for the tables and columns you can read.
A successful run returns {columns, rows, truncated, elapsed_ms}, where
each entry in rows is an array of values positionally matching columns.
At most 10 000 rows come back; truncated is true when there were more,
so page with LIMIT/OFFSET in your own SQL rather than relying on the
cap. A run is cut off after 10 seconds.
A NUMERIC column comes back as null — cast it in the SELECT
(avg(x)::float8).
Every run, including a rejected or failed one, is recorded in the query audit log with its SQL, parameters, duration and outcome.
Authorizations
A scoped AgentEye API key. Mint one in the dashboard under Settings → API keys, or with POST /v1/keys. Each endpoint names the permission it requires; a key without it gets 403 and a required_permission field naming what was missing.
Body
Positional or named parameter values. For positional ($1..$N) pass an array; for named pass an object. Only string/number/bool/null/array JSON leaves are bound — anything else is sent as text.
Saved query id — sql_text is fetched and substituted in.
Free-form SQL — wins over query_id if both are present.
Response
columns, rows, truncated and elapsed_ms.

