Skip to main content
POST /queries/run is in beta and limited to organizations in the OpalQuery beta. Contact Opal support to be added.
The POST /queries/run endpoint is the programmatic equivalent of the OpalQuery builder — useful for scripted audits, reporting pipelines, and one-off investigations.

Requirements

See Requirements in the Overview. You’ll also need an Opal API token — see Authentication.

Endpoint

The full schema and an interactive playground are available under API Reference → Opal API in the navigation.

Query types

Every request sets type to one of two modes: Both modes use the same filter vocabulary — AccessEntityFilters to describe entities, and AccessRelationshipFilters to traverse access edges. The Entity filters and Relationship filters sections below document these shared building blocks; the NODE queries and ACCESS queries sections show how each mode uses them.

Entity filters

An AccessEntityFilters object describes a set of entities by their own properties. It is used in both modes: as nodeFilters / accessFilters.isAccessibleBy / accessFilters.hasAccessTo in NODE queries, and as principalFilter / entitlementFilter / the nested filters inside principalAccessFilters and entitlementAccessFilters in ACCESS queries. All fields are optional. Multiple sibling fields are combined with AND.

Name match

stringMatchType is one of EQUALS, CONTAINS, STARTS_WITH, ENDS_WITH.

Description match

Matches resources and groups whose description contains string (case-insensitive substring match). User entities have no description and never match this filter in either polarity. Set not: true to return resources and groups whose description does not contain string (including entities with an empty description).

Tag match

key is required. value is optional — if omitted, matches only tags with no value (use entityTagKey to match any value for a key). connectionId is optional — set it to restrict to tags sourced from a specific connection.

Tag key match

Matches entities that have at least one tag whose key is in keys, regardless of the tag’s value (including tags with no value).

IDP status match

Matches users by HR/IDP lifecycle status. statuses is one or more of ACTIVE, SUSPENDED, DEPROVISIONED, DELETED, NOT_FOUND, combined with OR. Groups and resources never match this filter, in either polarity. Set not: true to invert within the user domain (e.g. “users whose IDP status is NOT active”).

Admin owner match

Matches resources and groups by admin owner. ownerIDs is a list of owner (group) UUIDs; an entity matches if its admin owner is any of them. User entities never match this filter, in either polarity. Set not: true to return resources/groups not owned by the given owners.

Logical composition

allOf, anyOf, and not each take the same shape as the outer filter and can be nested arbitrarily deep.
Resources tagged env=prod and team=platform, excluding AWS IAM roles.

Relationship filters

An AccessRelationshipFilters object traverses access edges. It is used in both modes: as accessFilters in NODE queries to narrow returned entities by their access relationships, and as principalAccessFilters / entitlementAccessFilters in ACCESS queries to scope one side of the path by its own access edges. Both fields take an AccessEntityFilters object. Both direct and indirect (through nested group memberships) edges are considered. When both are set, both must be satisfied. hasAccessTo additionally accepts two fields that constrain the role on the access edge: These are only honored inside hasAccessTo — they have no effect in isAccessibleBy or entity filters.

NODE queries

A NODE query returns a paginated list of entities (users, resources, or groups) that match your filters.

Request

At least one of nodeFilters or accessFilters is required.

Response

node.id is the entity’s Opal UUID — pass it to other API endpoints (e.g. GET /resources/{resource_id}) to fetch full details.

Examples

All users
AWS IAM roles tagged env=prod, accessible by contractors Combines entityItemTypes, entityTag, and accessFilters.
Users with write access to a specific GitHub repo Uses roleRemoteIds inside hasAccessTo to filter by role.
Resources owned by a specific admin, accessible by suspended users (deprovisioning gap) Combines entityAdminOwner and hrIdpStatus.
Full curl request

ACCESS queries

An ACCESS query returns paginated access grants — each result represents one (principal, entitlement, access level) grant, along with whether it is direct, its effective expiration, and all intermediate paths connecting the principal to the entitlement.

Request

At least one of principalFilter or entitlementFilter is required.

Edge filter

  • directOnly — when true, return only direct (depth-1) principal → entitlement edges.
  • accessDurationType — EXPIRING_ONLY or PERMANENT_ONLY, to constrain by whether the terminal access expires.

Advanced access filters

principalAccessFilters and entitlementAccessFilters scope one side of the path by its own access edges, using the same AccessRelationshipFilters shape as NODE accessFilters:
  • principalAccessFilters.hasAccessTo — keep only principals that also have access to a matching entity.
  • principalAccessFilters.isAccessibleBy — keep only principals that are also accessible by a matching entity.
  • entitlementAccessFilters — the same, applied to the entitlement side.
Each access filter only takes effect when that side also carries an entity filter: principalAccessFilters requires principalFilter, and entitlementAccessFilters requires entitlementFilter. Supplied without a matching entity filter, it is silently ignored.

Response

  • isDirect — true if any path in paths is a direct 2-node grant ([principalId, entitlementId]).
  • effectiveExpiration — when this grant expires across all paths; null if at least one path is permanent.
  • paths — all paths through which principalId holds this access level on entitlementId. Each entry includes the path array of entity IDs from principal to entitlement and that path’s expiration (null if permanent).

Examples

All access grants for a specific user
Separation of duties — users who can reach billing-prod and also hold admin access Uses principalAccessFilters.hasAccessTo to scope principals to those with an additional access relationship.
Deprovisioning gap — direct access still held by suspended users Combines hrIdpStatus on the principal side with directOnly on the edge.

Pagination

Both query types use cursor-based pagination. Set first to control the page size (default 200). When pageInfo.hasNextPage is true, pass pageInfo.endCursor as after on the next request.
Last modified on October 27, 2026