Skip to main content
Utility modules provide common functionality available to all OpalScript types. These modules help you query Opal’s data and make informed decisions.

Query Opal’s access graph

access Module

The access 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

The notifications 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

The tickets 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
A dictionary keyed by project key (e.g. "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_projects
string
required
The title or summary of the ticket
string
required
The description or body of the ticket
A 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.providers
string
required
The ticket’s identifier in the remote provider (e.g. "ENG-42")
A 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.providers
string
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.providers
string
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

The risk 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
A string representing the risk level: "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
A string representing the risk level: "UNKNOWN", "NONE", "LOW", "MEDIUM", "HIGH", or "CRITICAL"

Query request history

requests Module

The requests 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 skip
string
Opaque page cursor from a previous call’s next_cursor. Pass "" for the first page
int
Page size. Must be positive. Omit to return all matches in a single call
A dictionary keyed by request UUID, with a 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 skip
string
One of "PENDING", "APPROVED", "DENIED", "CANCELED". Pass "" to skip
string
Opaque page cursor from a previous call’s next_cursor. Pass "" for the first page
int
Page size. Must be positive. Omit to return all matches in a single call
A dictionary keyed by request UUID, with a 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 skip
string
One of "PENDING", "APPROVED", "DENIED", "CANCELED". Pass "" to skip
string
Opaque page cursor from a previous call’s next_cursor. Pass "" for the first page
int
Page size. Must be positive. Omit to return all matches in a single call
A dictionary keyed by request UUID, with a next_cursor attribute for pagination. See Request object for the value shape.

Work with time

time Module

The time 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
A string in RFC3339 format

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
An integer representing the difference in seconds

Convert time intervals to seconds

Use time.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
An integer representing the number of seconds

Important notes

  • Timestamps are UTC only: All time functions 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

The entity 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 a tags dictionary that maps tag keys to string values (None if the tag has no value).

Reach external services

The http 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

The http 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.
A Response. A 4xx or 5xx status is returned as a normal Response, not raised, so you can branch on 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 every http 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, so stripe.com.evil.com does not match *.stripe.com, and it never matches the apex domain on its own.
  • Scheme is part of the entry. https is the default and the recommendation. Add an http entry 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

The secrets 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.
An opaque 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 opaque Secret, 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 http URL 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.
Use a 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.
For a complete Request Review script that combines http, secrets, and entity to enforce device posture before approving a request, see Deny based on device posture with FleetDM.
Last modified on July 9, 2026