Query Opal’s access graph
access Module
Theaccess module provides functions to query Opal’s access graph. Use it to check existing permissions when making automation decisions.
access.check_access(principal_id, entity_id, [access_level_remote_id]) checks whether a principal (user or group) currently has access to an entity (resource or group).
string (UUID)
required
The ID of the user or group to check
string (UUID)
required
The ID of the resource or group to check access to
string (UUID)
Filter by specific access level (e.g.,
"admin", "viewer")True if access exists, False otherwise
Send notifications
notifications Module
Thenotifications module provides functions to send notifications to users, admins, and owners from within a script.
Notify a user
notifications.notify_user(user_id, title, body) sends a notification to a specific user via email and Slack (if configured).
string (UUID)
required
The ID of the user to notify
string
required
Notification title
string
required
Notification body
True if the notification was sent successfully, False otherwise
Notify all admins
notifications.notify_admins(title, body) sends a notification to all Opal admins.
string
required
Notification title
string
required
Notification body
True if the notification was sent successfully, False otherwise
Notify an owner
notifications.notify_owner(owner_id, title, body) sends a notification to an owner. If the owner has a Slack message channel configured, the notification is sent to that channel only. Otherwise it is sent to all individual users in the owner.
string (UUID)
required
The ID of the owner to notify
string
required
Notification title
string
required
Notification body
True if the notification was sent successfully, False otherwise
Manage support tickets
tickets Module
Thetickets module provides functions to create, retrieve, comment on, and close tickets in connected ticket providers. Use it to automatically file or update tickets as part of your access automation workflows.
Supported providers: Jira, Linear, ServiceNow, Notion, FreshService, and Shortcut. Only providers that are installed and connected to your Opal organization are available at runtime.
Tickets created via OpalScript are independent of tickets created via ticket propagation. If ticket propagation is also enabled, a separate ticket will be created after the request is approved. Creating a ticket via OpalScript does not replace or affect that flow.
Access a provider
tickets.providers.<PROVIDER> exposes the ticket providers installed for your organization as a namespace. Reference a provider by name to pass it to other tickets functions.
Available provider names: JIRA, LINEAR, SERVICE_NOW, NOTION, FRESH_SERVICE, SHORTCUT
List projects
tickets.list_projects(provider) returns all projects available on the given provider.
ticket_provider
required
A provider from
tickets.providers"ENG"). Each value is a ticket_project object:
Create a ticket
tickets.create_ticket(project, title, description) creates a new ticket on the remote provider and stores it in Opal.
ticket_project
required
A project object returned by
tickets.list_projectsstring
required
The title or summary of the ticket
string
required
The description or body of the ticket
ticket object:
Get a ticket
tickets.get_ticket(provider, remote_ticket_id) fetches an existing ticket from the remote provider by its identifier.
ticket_provider
required
A provider from
tickets.providersstring
required
The ticket’s identifier in the remote provider (e.g.
"ENG-42")ticket object with the same attributes as returned by create_ticket above.
Comment on a ticket
tickets.comment_ticket(provider, remote_ticket_id, comment) adds a comment to an existing ticket. Does not change the ticket’s status.
ticket_provider
required
A provider from
tickets.providersstring
required
The ticket’s identifier in the remote provider (e.g.
"ENG-42")string
required
The text of the comment to add
None
Close a ticket
tickets.close_ticket(provider, remote_ticket_id, [comment]) closes an existing ticket on the remote provider and updates its stored status in Opal to CLOSED. No-op if the ticket is already closed.
ticket_provider
required
A provider from
tickets.providersstring
required
The ticket’s identifier in the remote provider (e.g.
"ENG-42")string
An optional closing comment to add before closing
None
Assess risk
risk Module
Therisk module provides functions to look up the risk sensitivity classification of resources and groups.
Get resource risk sensitivity
risk.resource_sensitivity(resource_id) returns the risk sensitivity level of a resource.
string (UUID)
required
The ID of the resource
"UNKNOWN", "NONE", "LOW", "MEDIUM", "HIGH", or "CRITICAL"
Get group risk sensitivity
risk.group_sensitivity(group_id) returns the risk sensitivity level of a group.
string (UUID)
required
The ID of the group
"UNKNOWN", "NONE", "LOW", "MEDIUM", "HIGH", or "CRITICAL"
Query request history
requests Module
Therequests module provides functions to query Opal’s historical access requests — by user, by resource, or by group.
Request history lookups return a dictionary keyed by stringified request UUID. The values are request objects with the same shape as the one returned by context.get_request(). If no requests match, the dictionary is empty. Looking up a missing ID raises a key error, so guard with if request_id in requests: when needed. Pass empty strings ("") to skip any optional string argument.
Results are ordered by (updated_at DESC, id DESC) and filtered to the caller’s organization. The returned value also exposes a next_cursor attribute — an opaque string to pass back as the cursor argument on the next call. It is "" when there are no further pages. When both cursor and requests_per_page are omitted, every match is returned in a single call and next_cursor is "".
Get requests for a user
requests.by_user(user_id, [request_status], [cursor], [requests_per_page]) returns every request where the given user is either the requester (submitted it) or the target user (recipient of the access). A self-request (requester == target) appears once.
string (UUID)
required
The ID of the user
string
One of
"PENDING", "APPROVED", "DENIED", "CANCELED". Pass "" to skipstring
Opaque page cursor from a previous call’s
next_cursor. Pass "" for the first pageint
Page size. Must be positive. Omit to return all matches in a single call
next_cursor attribute for pagination. See Request object for the value shape. Looking up a missing ID raises a key error — guard with if request_id in requests: when needed.
Get requests for a resource
requests.by_resource(resource_id, [user_id], [request_status], [cursor], [requests_per_page]) returns every request whose requested_resources includes resource_id. The optional user_id filter narrows to requests submitted by that user (requester_id), not the target user.
A single request that asked for multiple resources will appear in queries for each one.
string (UUID)
required
The resource being queried
string (UUID)
Narrow to requests submitted by this user. Pass
"" to skipstring
One of
"PENDING", "APPROVED", "DENIED", "CANCELED". Pass "" to skipstring
Opaque page cursor from a previous call’s
next_cursor. Pass "" for the first pageint
Page size. Must be positive. Omit to return all matches in a single call
next_cursor attribute for pagination. See Request object for the value shape.
Get requests for a group
requests.by_group(group_id, [user_id], [request_status], [cursor], [requests_per_page]) returns every request whose requested_groups includes group_id. This filters on what was asked for — a request with target_group_id = X but no X in requested_groups does not match.
string (UUID)
required
The group being queried
string (UUID)
Narrow to requests submitted by this user. Pass
"" to skipstring
One of
"PENDING", "APPROVED", "DENIED", "CANCELED". Pass "" to skipstring
Opaque page cursor from a previous call’s
next_cursor. Pass "" for the first pageint
Page size. Must be positive. Omit to return all matches in a single call
next_cursor attribute for pagination. See Request object for the value shape.
Work with time
time Module
Thetime module provides functions to work with Unix timestamps (seconds since epoch) and time intervals for access duration validation and temporal logic.
Get current time
time.now() returns the current Unix timestamp (seconds since epoch).
An integer representing the current time as a Unix timestamp
Convert timestamp to string
time.from_unix(timestamp) converts a Unix timestamp to an RFC3339 formatted string in UTC (e.g., 2024-01-15T10:30:45Z).
int
required
Unix timestamp to convert
Compare timestamps
time.is_before(timestamp1, timestamp2) checks if the first timestamp is before the second.
time.is_after(timestamp1, timestamp2) checks if the first timestamp is after the second.
int
required
First timestamp
int
required
Second timestamp
True or False
Calculate time differences
time.seconds_since(timestamp1, timestamp2) returns the number of seconds between two timestamps (positive if timestamp1 is after timestamp2).
int
required
First timestamp
int
required
Second timestamp
Convert time intervals to seconds
Usetime.minutes(n), time.hours(n), and time.days(n) to convert human-readable time intervals to seconds. This is useful for time comparisons and avoids hardcoding magic numbers.
int
required
Number of time units
Important notes
- Timestamps are UTC only: All
timefunctions work in UTC. There is no timezone conversion support. - Second precision: Timestamps have second-level precision. Sub-second differences are not available.
- No test mode: OpalScript automations execute on real access requests. Test your logic with low-risk parameters first.
- Review before deploying: All OpalScript automations should be reviewed by a human before deployment to catch logic errors.
Look up entity information
entity Module
Theentity module provides functions to look up users, groups, and resources by their IDs. Use it to access entity properties and tags when making automation decisions.
Get a user
entity.get_user(user_id) retrieves a user by their UUID.
string (UUID)
required
The ID of the user to fetch
Get a group
entity.get_group(group_id) retrieves a group by its UUID.
string (UUID)
required
The ID of the group to fetch
Get a resource
entity.get_resource(resource_id) retrieves a resource by its UUID.
string (UUID)
required
The ID of the resource to fetch
Tags
All entity objects include atags dictionary that maps tag keys to string values (None if the tag has no value).
Reach external services
Thehttp module lets a script call external HTTP APIs, and the secrets module lets it authenticate those calls without ever exposing a credential to the script. Both are governed by a per-organization egress allowlist: a script can only reach hosts an admin has explicitly allowed.
Use these together to enrich an access decision with data from another system. For example, check a device-posture API before approving a request, open a ticket when access is granted, or post to a chat webhook.
The
http and secrets modules are currently available in Request Review scripts.http Module
Thehttp module performs outbound requests through Opal’s SSRF-safe client. Every request is checked against the egress allowlist and the scheme before it leaves, on the initial request and on every redirect.
http.request(url, method=..., headers=..., params=..., json=..., body=..., timeout=...) sends a request and returns a Response. There is a convenience method for each verb: http.get, http.post, http.put, http.patch, and http.delete.
string | Secret
required
The absolute URL to call. Must use a scheme and host that are on the egress allowlist. Can be a Secret when the URL itself is sensitive, such as a webhook URL.
string
The HTTP method, for example
"GET" or "POST". Only for http.request (the verb methods set it for you).dict
Request headers. Values can be strings or Secrets, so you can pass a token as
{"Authorization": "Bearer " + secrets.get("api_token")} without the script ever seeing the value.dict
Query-string parameters, appended to the URL.
dict | list
A JSON request body. Serialized for you and sends
Content-Type: application/json. Available on request, post, put, and patch.string
A raw string request body, as an alternative to
json. Available on request, post, put, and patch.int
Per-call timeout in seconds. Defaults to 10 and is capped at 15.
status_code. A network failure, a timeout, or a blocked (non-allowlisted) host raises an error instead.
On a redirect to a different origin, Opal drops your request headers and body so a credential meant for the original host is never sent to another host. Each redirect hop is re-checked against the egress allowlist.
Response
The value returned by everyhttp method.
response.json() parses the body as JSON and returns the result (a dict or list). It raises if the body is not valid JSON.
Egress allowlist
OpalScript egress is default-deny. Out of the box a script cannot reach any external host. An admin adds the hosts a script is allowed to call, so a script cannot be used to send your data to an arbitrary destination. Go to Settings → Advanced → Egress allowlist (admin only). Add each host a script needs to reach. A blocked call raises an error the script can catch, so you find out at test time rather than in production. Each entry is a hostname pattern and a scheme. No port or path.- Matching is case-insensitive and normalizes internationalized (IDN) hosts, so a Unicode host and its punycode form are treated as the same entry.
- The
*.wildcard is a subdomain wildcard only. It is suffix-anchored, sostripe.com.evil.comdoes not match*.stripe.com, and it never matches the apex domain on its own. - Scheme is part of the entry.
httpsis the default and the recommendation. Add anhttpentry only if a host genuinely requires it. A host can be allowlisted for both schemes with two entries.
A shared-suffix host such as
*.s3.amazonaws.com allows every bucket on that suffix. Prefer the most specific host you can.The allowlist is one of two independent gates. Every request also goes through an SSRF-safe client that blocks private and loopback addresses (for example
localhost, 127.0.0.1, 10.x, 192.168.x, and link-local metadata IPs), even if you allowlist them. A target must be reachable at a public address.secrets Module
Thesecrets module gives a script credentials to use in http calls without ever revealing their values. An admin stores a named secret once, and the script references it by name.
secrets.get(name) returns an opaque Secret for the named secret in your organization.
string
required
The name of the secret, as configured by an admin in Settings.
Secret. It is designed so the plaintext can never leak into your script or its output:
- It renders as
***everywhere it could be printed:str,print, logs, and comments. - It composes with
+."Bearer " + secrets.get("token")returns a new opaqueSecret, so you can build an auth header without ever seeing the value. Combining two secrets, or a secret and a string, both stay opaque. - It cannot be coerced to a plaintext string and cannot be serialized. There is no attribute that returns the value, and it refuses to be turned into JSON.
- Its plaintext is materialized only inside Opal when the Secret is sent as an
httpURL or header value. It is never written to the run trace, logs, or replay data, and any secret value echoed back in a response is scrubbed from anything the script persists or shows.
Secret, or a Secret composed with +, directly as an http URL or header value.
Go to Settings → Advanced → Automation secrets (admin only) to create, rotate, and delete secrets. A secret value is write-only: it is encrypted at rest and is never shown again after you save it. To change a value, rotate it.
http, secrets, and entity to enforce device posture before approving a request, see Deny based on device posture with FleetDM.