Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.opal.dev/llms.txt

Use this file to discover all available pages before exploring further.

The POST /queries/run endpoint runs an ad-hoc OpalQuery and returns matched entities. It is the programmatic equivalent of the OpalQuery builder in the dashboard — useful for scripted audits, reporting pipelines, and one-off investigations.
POST /queries/run is in beta and limited to organizations in the OpalQuery beta. Contact Opal support to be added.

Requirements

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

Endpoint

POST https://api.opal.dev/v1/queries/run
Authorization: Bearer <YOUR_API_TOKEN>
Content-Type: application/json
The full schema and an interactive playground are available under API Reference → Opal API in the navigation.

Request anatomy

Every request has the same outer shape. Only NODE queries are supported today — they return entities (users, resources, or groups) that match your filters.
{
  "type": "NODE",
  "query": {
    "nodeFilters": { /* AccessEntityFilters — narrow which entities to return */ },
    "accessFilters": { /* AccessRelationshipFilters — traverse access edges (optional) */ }
  },
  "first": 200,
  "after": "<cursor from a previous response>"
}
FieldTypeDescription
typestringDiscriminator. Must be NODE.
queryobjectThe filter body. See Node filters and Access filters.
firstintegerMaximum results to return. Defaults to 200.
afterstringPagination cursor from a previous response’s pageInfo.endCursor.
nodeFilters narrows the set of entities returned. accessFilters (optional) further restricts the set to entities connected by an access edge to entities matching another filter — for example, “resources accessible by contractors.”

Node filters

nodeFilters is an AccessEntityFilters object. All fields are optional, and multiple sibling fields are combined with AND.
FieldTypeDescription
entityTypesstring[]Top-level type. One or more of USER, RESOURCE, GROUP.
entityItemTypesenum[]Granular subtype. E.g. AWS_IAM_ROLE, OKTA_GROUP, GIT_HUB_REPO. The full list is enumerated in the OpenAPI spec (EntityItemTypeEnum).
entityNameobjectMatch by display name. See Name match.
entityTagobjectMatch by tag key/value. See Tag match.
entityIDsuuid[]Restrict to specific entity IDs.
importedFromAppuuid[]Restrict to entities imported from one or more app IDs.
accessLevelRemoteIdsstring[]Match the access level remote ID (the role/permission on the entity).
allOfobject[]Nested filters — all must match (logical AND).
anyOfobject[]Nested filters — at least one must match (logical OR).
notobjectA nested filter — exclude entities that match it (logical NOT).

Name match

{
  "entityName": {
    "stringMatchType": "CONTAINS",
    "string": "prod"
  }
}
stringMatchType is one of EQUALS, CONTAINS, STARTS_WITH, ENDS_WITH.

Tag match

{
  "entityTag": {
    "key": "env",
    "value": "prod",
    "connectionId": "9b1c33a4-..."
  }
}
  • key is required.
  • value is optional — omit it to match any value for that key.
  • connectionId is optional — set it to restrict the match to tags sourced from a specific connection.

Logical composition

allOf, anyOf, and not each take the same shape as the outer filter and can be nested to any depth. Use them to combine constraints.
{
  "entityTypes": ["RESOURCE"],
  "allOf": [
    { "entityTag": { "key": "env", "value": "prod" } },
    { "entityTag": { "key": "team", "value": "platform" } }
  ],
  "not": {
    "entityItemTypes": ["AWS_IAM_ROLE"]
  }
}
This reads as: resources tagged env=prod and team=platform, excluding AWS IAM roles.

Access filters

accessFilters traverses the access graph. Use it to constrain results by who can access them, or what they can access. Both fields are AccessEntityFilters objects with the same shape as nodeFilters.
FieldDirectionQuestion it answers
isAccessibleByInbound edgeThe returned entity is accessible by ≥ 1 entity matching this filter.
hasAccessToOutbound edgeThe returned entity has access to ≥ 1 entity matching this filter.
If both are provided, both must be satisfied. Direct and indirect (through groups, nested groups, etc.) access edges are considered.
{
  "nodeFilters": { "entityTypes": ["RESOURCE"] },
  "accessFilters": {
    "isAccessibleBy": {
      "entityTypes": ["USER"],
      "entityTag": { "key": "contractor" }
    }
  }
}
This finds resources accessible by any user tagged contractor (any value).

Response shape

{
  "type": "NODE",
  "edges": [
    {
      "node": {
        "id": "8b0f6e0c-4b71-4f3b-9c61-1d1d5e6a51e4",
        "name": "prod-readonly",
        "entityType": "RESOURCE",
        "entityItemType": "AWS_IAM_ROLE"
      },
      "cursor": "b3BhcXVlLWN1cnNvci0x"
    }
  ],
  "pageInfo": {
    "hasNextPage": true,
    "endCursor": "b3BhcXVlLWN1cnNvci0x",
    "hasPreviousPage": false,
    "startCursor": "b3BhcXVlLWN1cnNvci0w"
  }
}
Each edge is one matched entity. The node.id is the entity’s Opal UUID — use it with other API endpoints (e.g. GET /resources/{resource_id}) to fetch full details.

Pagination

Set first to control the page size (default 200). When pageInfo.hasNextPage is true, pass pageInfo.endCursor as after on the next request to fetch the following page.
{
  "type": "NODE",
  "query": { "nodeFilters": { "entityTypes": ["USER"] } },
  "first": 500,
  "after": "b3BhcXVlLWN1cnNvci0x"
}

Examples

1. List all users

{
  "type": "NODE",
  "query": {
    "nodeFilters": { "entityTypes": ["USER"] }
  }
}

2. AWS IAM roles tagged env=prod

{
  "type": "NODE",
  "query": {
    "nodeFilters": {
      "entityItemTypes": ["AWS_IAM_ROLE"],
      "entityTag": { "key": "env", "value": "prod" }
    }
  }
}

3. Groups whose name starts with “engineering”

{
  "type": "NODE",
  "query": {
    "nodeFilters": {
      "entityTypes": ["GROUP"],
      "entityName": { "stringMatchType": "STARTS_WITH", "string": "engineering" }
    }
  }
}

4. Resources accessible by a specific user

Use isAccessibleBy with that user’s entityIDs.
{
  "type": "NODE",
  "query": {
    "nodeFilters": { "entityTypes": ["RESOURCE"] },
    "accessFilters": {
      "isAccessibleBy": {
        "entityIDs": ["29827fb8-f2dd-4e80-9576-28e31e9934ac"]
      }
    }
  }
}

5. Users with access to any AWS IAM role

{
  "type": "NODE",
  "query": {
    "nodeFilters": { "entityTypes": ["USER"] },
    "accessFilters": {
      "hasAccessTo": {
        "entityItemTypes": ["AWS_IAM_ROLE"]
      }
    }
  }
}

6. Users with write access to a specific GitHub repo

Combine entityName, entityItemTypes, and accessLevelRemoteIds inside hasAccessTo.
{
  "type": "NODE",
  "query": {
    "nodeFilters": { "entityTypes": ["USER"] },
    "accessFilters": {
      "hasAccessTo": {
        "entityItemTypes": ["GIT_HUB_REPO"],
        "entityName": { "stringMatchType": "EQUALS", "string": "payments-service" },
        "accessLevelRemoteIds": ["write"]
      }
    }
  }
}

7. Production resources excluding IAM roles, accessible by contractors

Combines allOf, not, and accessFilters.
{
  "type": "NODE",
  "query": {
    "nodeFilters": {
      "entityTypes": ["RESOURCE"],
      "allOf": [
        { "entityTag": { "key": "env", "value": "prod" } },
        { "entityTag": { "key": "team", "value": "platform" } }
      ],
      "not": { "entityItemTypes": ["AWS_IAM_ROLE"] }
    },
    "accessFilters": {
      "isAccessibleBy": {
        "entityTypes": ["USER"],
        "entityTag": { "key": "contractor" }
      }
    }
  },
  "first": 50
}

8. Full request with curl

curl -X POST https://api.opal.dev/v1/queries/run \
  -H "Authorization: Bearer $OPAL_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "NODE",
    "query": {
      "nodeFilters": {
        "entityTypes": ["RESOURCE"],
        "entityTag": { "key": "env", "value": "prod" }
      },
      "accessFilters": {
        "isAccessibleBy": {
          "entityTypes": ["USER"],
          "entityTag": { "key": "contractor" }
        }
      }
    },
    "first": 100
  }'

Limitations

The API has the same constraints as the OpalQuery UI. See Limitations in the Overview.
Last modified on May 13, 2026