The async query model

This is the one concept every integrator needs before calling anything under /api/queries.

Two possible responses

POST /api/queries/{id}/refresh (ad-hoc cache-or-enqueue) can return either:

  • an immediate result, already available:

    { "query_result": { "id": 501, "data": { "...": "..." }, "runtime": 0.42 } }
  • or a freshly enqueued job, because the result needed a new run:

    { "job": { "id": 9001, "status": 1, "query_result_id": null } }

Which one you get depends on whether a fresh run was actually needed — you don’t choose, and your code needs to branch on the response shape (presence of "query_result" vs "job").

If you get a job: poll it

Poll GET /api/jobs/{id} until status reaches a terminal value:

status Meaning
1 Pending
2 Started
3 Success
4 Failure
5 Cancelled

[!TIP] 1 and 2 are the only non-terminal states — stop polling as soon as you see anything else.

Once it succeeds: fetch the result

When status is 3 (Success), the job response includes a query_result_id. Fetch the actual data with:

GET /api/query_results/{query_result_id}

Sequence, end to end

  1. POST /api/queries/{id}/refresh (or run, see the Queries resource).
  2. If the response has "query_result", you’re done — that’s the data.
  3. If it has "job" instead, poll GET /api/jobs/{id} until status is 3, 4, or 5.
  4. On 3, fetch GET /api/query_results/{query_result_id} for the data. On 4 or 5, the run failed or was cancelled — there’s no result to fetch.