Skip to main content
Each script type has its own context and actions modules tailored to that automation scenario.

Overview

Request Review scripts run when an access request is assigned to a service user for review. The script can automatically approve, deny, or add comments to the request. This guide details the context and action modules you can use to get started, as well as the best practices and limitations when using OpalScript. For use case specific sample scripts, see our examples.

Quick Start

The simplest script approves all requests:
actions.approve()
A more practical script evaluates the request:
request = context.get_request()

if "urgent" in request.reason.lower():
    actions.approve("Auto-approved: urgent request")
else:
    actions.comment("Flagged for manual review")

context Module

The context module provides read-only access to the request being reviewed.

Get the request object

context.get_request() returns the Request object being reviewed. None
Request
object
Contains information about the access request
request = context.get_request()
print(request.reason)
print(request.requester_id)

actions Module

The actions module provides methods to take action on the request.

Approve a request

actions.approve([comment], [duration_minutes]) approves the request.
comment
string
Optional comment to add to the approval.
duration_minutes
int
Optional override for the access duration. Must be a positive integer. If different from the originally requested duration, a notice is automatically appended to the comment.
actions.approve()
actions.approve("Auto-approved: meets all criteria")
actions.approve("Approved with reduced duration", duration_minutes=60)
actions.approve(duration_minutes=480)

Deny a request

actions.deny(comment) denies a request. A comment is required to explain the denial.
comment
string
required
Comment explaining the denial.
actions.deny("Access to production databases requires manager approval")

Comment on a request

actions.comment(comment, [duration_minutes]) adds a comment without changing the request status. Useful for flagging requests or adding context for manual reviewers.
comment
string
required
Comment to add.
duration_minutes
int
Optional override for the access duration. Must be a positive integer. If different from the originally requested duration, a notice is automatically appended to the comment.
actions.comment("Flagged: unusual access pattern detected")
actions.comment("Duration adjusted for policy compliance", duration_minutes=60)

Pause execution

actions.pause(minutes) pauses script execution and resumes from the same point after the specified time. Any actions already taken before the pause are skipped on replay.
minutes
int
required
Number of minutes to pause. Must be between 1 and 1440 (24 hours).
# Wait 60 minutes, then check a condition and decide
actions.pause(60)
request = context.get_request()
if request.custom_fields.get("manager_approved") == "true":
    actions.approve("Manager approved during review window")
else:
    actions.deny("No manager approval received within 60 minutes")

Poll a condition

actions.poll(function, minutes, max_iterations) repeatedly calls function at the given interval until it returns True or the maximum iterations are reached. Returns True if the function succeeded, False if it ran out of iterations.
function
callable
required
A function that takes no arguments and returns a boolean. Execution resumes when it returns True or max_iterations is exhausted.
minutes
int
required
Interval between calls in minutes. Must be between 1 and 1440.
max_iterations
int
required
Maximum number of times to call the function. Must be between 1 and 50.
def check_ticket_closed():
    ticket = ticketslib.get_ticket(ticketslib.providers.JIRA, "PROJ-123")
    return ticket.status == "CLOSED"

approved = actions.poll(check_ticket_closed, 60, 24)
if approved:
    actions.approve("Associated ticket was resolved")
else:
    actions.deny("Ticket was not resolved within 24 hours")

Objects

Request object

Returned by context.get_request() and contains information about the access request.
AttributeTypeDescription
idString (UUID)Unique identifier for the request
reasonStringThe reason provided by the requester
requester_idString (UUID)ID of the user who created the request
target_user_idString or NoneID of the user being granted access (if applicable)
target_group_idString or NoneID of the group being granted access (if applicable)
requested_duration_minutesint or NoneRequested access duration in minutes
statusStringCurrent status (e.g., "PENDING", "APPROVED", "DENIED")
requested_resourcesList[RequestedResource]Resources included in this request
requested_groupsList[RequestedGroup]Groups included in this request
custom_fieldsDict[String, value]Custom field values from the request template
request = context.get_request()

# Basic properties
reason = request.reason
requester = request.requester_id

# Check optional properties
if request.requested_duration_minutes:
    duration = request.requested_duration_minutes

# Iterate over requested resources
for resource in request.requested_resources:
    print(resource.resource_name)

# Access custom fields
ticket_number = request.custom_fields.get("ticket_number", "")

RequestedResource object

Represents a resource included in the request. Returned in the Request object.
AttributeTypeDescription
idString (UUID)Unique identifier for this requested resource entry
resource_idString (UUID)ID of the resource being requested
resource_nameString or NoneName of the resource
resource_typeString or NoneType of the resource (e.g., "AWS_IAM_ROLE")
access_level_nameStringDisplay name of the requested access level
access_level_remote_idStringRemote identifier for the access level
for resource in request.requested_resources:
    print(f"Resource: {resource.resource_name}")
    print(f"Type: {resource.resource_type}")
    print(f"Access Level: {resource.access_level_name}")

RequestedGroup object

Represents a group included in the request. Returned in the Request object.
AttributeTypeDescription
idString (UUID)Unique identifier for this requested group entry
group_idString (UUID)ID of the group being requested
group_nameString or NoneName of the group
group_typeString or NoneType of the group (e.g., "OKTA_GROUP")
access_level_nameStringDisplay name of the requested access level
access_level_remote_idStringRemote identifier for the access level
for group in request.requested_groups:
    print(f"Group: {group.group_name}")
    print(f"Type: {group.group_type}")

Custom Fields

The custom_fields attribute is a dictionary containing values from the request template’s custom fields. The keys are field names, and values depend on the field type:
Field TypeValue TypeExample
Short TextString"JIRA-1234"
Long TextString"Detailed justification..."
BooleanboolTrue
Multi-ChoiceString"Option A"
custom_fields = request.custom_fields

# Access with default value
ticket = custom_fields.get("ticket_number", "")
is_emergency = custom_fields.get("emergency_access", False)

# Check if field exists
if "justification" in custom_fields:
    justification = custom_fields["justification"]

Constraints & Limits

OpalScript enforces limits to ensure safe, predictable execution:
ConstraintLimitDescription
Script Size100 KBMaximum script length
Execution Time30 secondsScripts timeout after 30 seconds
Execution Steps1,000,000Maximum operations to prevent infinite loops

Unsupported Operations

For security and reliability, OpalScript does not support:
  • External HTTP calls: Scripts cannot make network requests
  • File I/O: Scripts cannot read or write files
  • Direct database access: All data access goes through provided modules
  • Import statements: All modules are pre-loaded
  • While loops: Use for loops with range() instead

Error Handling

Scripts can fail due to various errors. Understanding common error types helps you write more robust scripts.

Syntax Errors

# Missing colon
if True
    actions.approve()  # Error: missing colon

None Value Errors

request = context.get_request()

# Unsafe: duration might be None
if request.requested_duration_minutes < 60:  # Error if None
    actions.approve()

# Safe: check for None first
if request.requested_duration_minutes and request.requested_duration_minutes < 60:
    actions.approve()

Type Errors

# Error: concatenating string and int
message = "Count: " + 5

# Correct: convert to string
message = "Count: " + str(5)

Best Practices

  1. Check for None before using optional attributes
  2. Use .get() with defaults when accessing dictionary values
  3. Keep scripts focused - do one thing well
  4. Test with edge cases - empty strings, None values, missing fields
# Defensive coding example
request = context.get_request()

# Safe dictionary access
custom_fields = request.custom_fields
ticket = custom_fields.get("ticket_number", "")

# Safe optional attribute access
duration = request.requested_duration_minutes
if duration is not None and duration <= 240:
    actions.approve("Short duration approved")
elif duration is None:
    actions.comment("No duration specified")
else:
    actions.comment("Duration requires review")
Last modified on June 25, 2026