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>"
}
| Field | Type | Description |
|---|
type | string | Discriminator. Must be NODE. |
query | object | The filter body. See Node filters and Access filters. |
first | integer | Maximum results to return. Defaults to 200. |
after | string | Pagination 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.
| Field | Type | Description |
|---|
entityTypes | string[] | Top-level type. One or more of USER, RESOURCE, GROUP. |
entityItemTypes | enum[] | Granular subtype. E.g. AWS_IAM_ROLE, OKTA_GROUP, GIT_HUB_REPO. The full list is enumerated in the OpenAPI spec (EntityItemTypeEnum). |
entityName | object | Match by display name. See Name match. |
entityTag | object | Match by tag key/value. See Tag match. |
entityIDs | uuid[] | Restrict to specific entity IDs. |
importedFromApp | uuid[] | Restrict to entities imported from one or more app IDs. |
accessLevelRemoteIds | string[] | Match the access level remote ID (the role/permission on the entity). |
allOf | object[] | Nested filters — all must match (logical AND). |
anyOf | object[] | Nested filters — at least one must match (logical OR). |
not | object | A 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.
| Field | Direction | Question it answers |
|---|
isAccessibleBy | Inbound edge | The returned entity is accessible by ≥ 1 entity matching this filter. |
hasAccessTo | Outbound edge | The 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.
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.