Skip to main content

Executing Queries

A query must be stored before it can run: store it once, then execute it by id. Storing is what validates the query against your plan, records its version history, and gives it the identity an Access Management grant matches. Full request and response schemas are in the TagoIO API reference under SQL Queries.

Run a stored query

POST /sql/{id}/execute
profile-token: <profile-token>

{ "params": [{ "key": "$1", "value": "40" }] }

The body is optional: a bare POST runs the query with its stored defaults and the cache enabled. Values you send override the stored defaults per parameter (see Parameters).

While developing, add "test": true to probe the real pipeline with the cache fully out of the picture. No cached result is read, and the probe never pollutes the cache for real consumers. Test mode is not a limit bypass: rate limits and usage metering apply normally.

Exploring and iterating

There is no unsaved execution. To explore, store a scratch query and iterate on it with PUT /sql/{id}: each content change snapshots a new version, so nothing is lost while you refine it. Combine with test mode to keep the cache untouched while you experiment.

Query many devices

The fleet function device_data_by_tag returns the latest reading per device across every device matching your tags (see Available Tables). Last temperature of every sensor, in one request:

SELECT device, device_name, variable, value, time
FROM device_data_by_tag('type', 'sensor') AS f
WHERE variable = 'temperature' AND time > $1
ORDER BY device

Store it once, then execute with the time bound as a parameter ({ "params": [{ "key": "$1", "value": "2026-07-11T00:00:00Z" }] }).

One row per device comes back, each carrying that device's newest reading. Swap temperature for location or battery to build the other fleet widgets.

Fleet queries return up to your plan's device cap per request. For larger fleets, page with the after_device body field: order by device, then pass the last device id you received to fetch the next page.

{
"params": [{ "key": "$1", "value": "2026-07-11T00:00:00Z" }],
"after_device": "LAST_DEVICE_ID_FROM_PREVIOUS_PAGE"
}

Each page is cached independently, so a dashboard paging a warm fleet stays on cache hits.

What you get back

{
"status": true,
"result": {
"columns": [
{ "name": "variable", "type": "string" },
{ "name": "value", "type": "number" },
{ "name": "time", "type": "timestamp" }
],
"rows": [{ "variable": "temperature", "value": 41.2, "time": "2026-03-30T10:00:00+00:00" }],
"row_count": 1,
"execution_ms": 12,
"served_from_cache": false
}
}

columns gives you the typed header, ready for a table or chart; rows holds the data keyed by column name. Results are never silently shortened: row_count is the true size of what you asked for, and a query whose result would exceed your plan's row cap fails with a clear error instead.

Caching

For queries that run often with the same inputs, like a dashboard widget or a public report, enable the cache on the stored query (cache_enabled plus a cache_ttl_seconds). Then:

  • Repeat executions with the same parameter values are served from the cache: fast, free of data-output usage, and allowed far more requests per minute than fresh executions.
  • Each distinct set of parameter values gets its own cache entry, so different inputs never mix.
  • Editing the query drops its cached results automatically.
  • When many callers hit an expired entry at once, only one execution runs against the database and everyone shares its result.

The response's served_from_cache field tells you which path a result took.

Rate limits

Each plan caps how many fresh executions per minute a profile can run, and cached reads have their own much higher allowance. On top of that, a stored query can set its own rate_limit_rpm so one heavily-exposed query cannot exhaust the whole profile's budget. Over-limit requests return 429 with a Retry-After header. The numbers per plan are in Limits.

Who can execute

  • A profile token can execute any of the profile's queries.
  • An Analysis or Run-user token can execute a stored query when granted the Execute permission through an Access Management policy, matched by the query's id or tags. The query then runs under the owning profile's plan and scope.

A grant authorizes running the query as-is: it returns exactly what the query selects, no more and no less. The full permission model, including listing and management grants, is on the Access Management page.