Skip to content

Saved queries API

A saved query is a PQL query with a name, an optional description, and a visualization that says how its result is drawn. It belongs to the organization, not to a project. Each one has a server-assigned ULID, a creator, and a visibility: public (every member) or private (its creator, the members and user groups granted access, and organization owners and admins).

Saved queries are available on the Pro and Enterprise plans, the same plans as queries and dashboards. On any other plan every endpoint on this page returns 403 forbidden.

Method and pathWhat it does
GET /api/v1/queriesLists the saved queries you can see, most recently updated first.
POST /api/v1/queriesCreates a saved query.
GET /api/v1/queries/{savedQueryUlid}Reads one saved query, with its text and drawing.
PUT /api/v1/queries/{savedQueryUlid}Replaces the whole state: text, drawing, name, description, visibility and grants.
DELETE /api/v1/queries/{savedQueryUlid}Deletes the saved query and its grants. Answers 204.
POST /api/v1/queries/{savedQueryUlid}/runRuns the saved text with your own project access.
GET /api/v1/queries/{savedQueryUlid}/usageLists the dashboards that draw the query, for its delete confirmation.

A member sees a saved query when any of these holds:

  • it is public;
  • they created it;
  • they, or one of their user groups, were granted access to a private one.

Organization owners and admins see every saved query.

Creating a query needs the queries.write permission: owners, admins and members have it, and viewers can read queries but not create them. Only the creator and organization owners and admins can change, delete, or change the visibility and grants of a query, whatever other access a member holds. If the creator’s account is deleted, the query stays and only owners and admins can change it.

A query you cannot see answers 404 not_found on every call, so a private query never confirms that it exists. A query you can see but cannot change answers 403 forbidden on PUT and DELETE.

A name is unique in the organization, ignoring case: Staging failures and staging FAILURES are the same name. The rule covers every saved query of the organization, including private ones you cannot see, so a 409 conflict on create or on a rename can show that a name is taken.

The name is 1 to 120 characters after trimming spaces at both ends.

POST /api/v1/queries takes:

FieldRule
nameRequired, 1 to 120 characters.
descriptionOptional, up to 2,000 characters.
pqlRequired, 1 to 4,000 characters of PQL.
visualizationOptional. { "render": "table" } by default. See below.
accessModepublic (default) or private.
membersOptional. The user ULIDs of organization members who may see a private query.
groupsOptional. The ULIDs of user groups who may see a private query.

members and groups are up to 200 entries each, without duplicates, and must be empty on a public query. A private query can start with its first grants, saved in the same request.

PUT /api/v1/queries/{savedQueryUlid} takes the same fields, and every one is required; send description as null to clear it. A missing members or groups list is refused, so it never clears grants by accident. A public query takes empty lists, which removes every grant it held.

The visualization object has a render and an optional series:

  • render is one of table, bar, line, donut or value.
  • series names the result column that splits a chart into one series per value, or is null.

The query text is checked when you save it, with the same checks a run makes against your own view of the organization. A text that does not parse or validate answers 422 pql_invalid, with every error and its span, as the query endpoint does. A construct the engine does not run yet answers 422 pql_unsupported. A reference to another saved query, in query("name"), is checked the same way: a name the author cannot see answers 422 pql_invalid, and a save that would close a cycle of references is refused. Either way nothing is saved.

A query returns its grants, members and groups, only to a caller who can edit it. Anyone else receives null for both. Every response carries canEdit, so a client knows whether to offer changes.

GET /api/v1/queries pages the queries you can see, most recently updated first. Use page (from 1) and pageSize (up to 200, 50 by default). q keeps the queries whose name contains it, ignoring case. A list item carries the summary fields: ulid, name, description, ownerUserUlid, accessMode, createdAt, updatedAt and canEdit. Read one query for its pql, visualization and grants.

POST /api/v1/queries/{savedQueryUlid}/run takes an optional offset, from 0 to 10,000, in a JSON body; send {} to run the first page. It answers the page the query endpoint answers for the same text: the same rows or groups, computed with your own project access, with scope.hiddenProjectCount counting the projects you cannot open. A text that no longer passes the checks, because the organization’s fields changed, answers 422 pql_invalid or 422 pql_unsupported.

POST /api/v1/queries/{savedQueryUlid}/export answers the rows of the saved query as CSV, for the caller, under the caller’s own project access. It takes no body. The CSV follows the rules of the query export. A query the caller cannot see answers 404, and a text that no longer passes the checks answers 422.

GET /api/v1/queries/{savedQueryUlid}/usage answers the dashboards with a widget that draws the query, so you can see what a deletion would leave behind. dashboards names the ones you can open, by ULID and name; hiddenDashboardCount counts the others, which are never named. A query you cannot see answers 404. Deleting the query leaves those widgets in their “query deleted” state.

StatusCodeWhen
403forbiddenThe plan does not include queries, your role does not include queries.write on create, or you may see a query but not change it.
404not_foundNo saved query with this ULID that you can see.
409conflictAnother saved query of the organization has this name, ignoring case.
422validation_failedThe body or a path parameter is malformed, or a limit is broken.
422pql_invalidThe query text does not parse or validate. details.errors lists each error.
422pql_unsupportedThe query text uses a construct a later release adds. details.construct names it.

Every change writes an audit event, visible in the audit log:

  • saved_query.created when a query is created, with its visibility.
  • saved_query.updated when its name, description, text or drawing changes. It names the fields that changed, never their values.
  • saved_query.access_changed for each visibility change and grant, with its reason: mode_toggle, member_granted, member_cleared, group_granted or group_revoked.
  • saved_query.deleted when a query is deleted.

A request that changes nothing writes no event.