Suspend & approval / Prompts
Flows can be suspended until resumed or canceled event(s) are received. This feature is most useful for implementing approval steps.
An approval step is a normal script with the Suspend option enabled in the step’s advanced settings. This suspends the execution of a flow until it has been approved through the resume endpoints or the approval page, by and solely by the recipients of the secret URLs.
Suspending a flow in Orvanta
Section titled “Suspending a flow in Orvanta”Other ways to pause a workflow include:
- Early stop/Break: if defined, at the end of the step, the predicate expression will be evaluated to decide if the flow should stop early.
- Sleep: if defined, at the end of the step, the flow will sleep for a number of seconds before scheduling the next job (if any; no effect if the step is the last one).
- Retry a step until it succeeds.
- Schedule the trigger of a script or flow.
An event can be:
- a cancel
- a pre-set number of approvals that is met.
The approval step generates a unique URL for each required approval using orvanta.getResumeUrls() (or orvanta.get_resume_urls() in Python). The approval step works like a webhook mechanism — the flow remains suspended until the required number of approval events are received via HTTP requests to these generated URLs. Each approval event is an HTTP request to one of these URLs, which then resumes or cancels the flow execution.
Add approval script
Section titled “Add approval script”Consider a scenario where only specific people can resume or cancel a flow. To achieve this, they would need to receive a personalized URL via some external communication channel (like email, SMS, or chat message).
When adding a step to a flow, pick Approval, and write a new approval script or pick one from the Orvanta Hub. This will create a step where the option in tab “Advanced” — “Suspend” is enabled.
Use orvanta.getResumeUrls() in TypeScript or orvanta.get_resume_urls() in Python from the Orvanta client to generate secret URLs.
Flow-level resume URLs (pre-approvals)
Section titled “Flow-level resume URLs (pre-approvals)”By default, resume URLs are tied to a specific step. With flowLevel: true (TypeScript) or flow_level=True (Python), you can generate resume URLs for the parent flow instead. These “pre-approvals” can be consumed by any later suspend step in the same flow.
This is useful when you want to request approval early in the flow (e.g. in the first step) and have it automatically satisfy a suspend step that comes later:
// TypeScriptconst urls = await orvanta.getResumeUrls("approver1", true) // flowLevel = true# Pythonurls = orvanta.get_resume_urls(approver="approver1", flow_level=True)When a flow-level resume is received, the approval is stored at the flow level and matched when the worker checks for resumes at any subsequent suspend step.
Number of approvals/events required for resuming a flow
Section titled “Number of approvals/events required for resuming a flow”The number of required approvals can be customized. This allows flexibility and security for cases where you either require approvals from all authorized people or only from one.
Note that approval steps can have the same configurations applied as regular steps (Retries, Early stop/Break, or Suspend).
Timeout
Section titled “Timeout”Set a custom timeout after which the flow will be automatically canceled if no approval is received.
Continue on disapproval/timeout
Section titled “Continue on disapproval/timeout”If set, instead of failing the flow and bubbling up the error, continue to the next step, which would allow you to put a branch one right after to handle both cases separately. If any disapproval/timeout event is received, the resume payload will be similar to every error result in Orvanta: an object containing an error field which you can use to distinguish between approvals and disapprovals/timeouts.
You can add an arbitrary schema form to be provided and displayed on the approval page. Users opening the approval page would then be offered to fill arguments you can use in the flow.
In the Advanced menu of a step, go to the “Suspend/Approval” tab and enable the Add a form to the approval page button.
Add properties and define their Name, Description, Type, Default Value, and Advanced settings. That will then be displayed on the approval page.
Use arguments
Section titled “Use arguments”The approval form argument values can be accessed in the subsequent step by connecting input fields to either resume["argument_name"] for a specific argument, or simply resume to obtain the complete payload.
This is a way to introduce human-in-the-loop workflows and condition branches on approval step inputs.
Prompts
Section titled “Prompts”A prompt is simply an approval step that can be self-approved. To do this, include the resume URL in the returned payload of the step. The UX will automatically adapt and show the prompt to the operator when running the flow. e.g:
TypeScript (Bun)
import * as orvanta from "orvanta-client"export async function main() { const resumeUrls = await orvanta.getResumeUrls("approver1") return { resume: resumeUrls['resume'], default_args: {}, // optional enums: {} // optional }}TypeScript (Deno)
import * as orvanta from "npm:orvanta-client@^1.158.2"export async function main() { const resumeUrls = await orvanta.getResumeUrls("approver1") return { resume: resumeUrls['resume'], default_args: {}, // optional enums: {} // optional }}Python
import orvantadef main(): urls = orvanta.get_resume_urls() return { "resume": urls["resume"], "default_args": {}, # optional "enums": {} # optional }Go
package innerimport ( orvanta "github.com/Orvanta-Cloud/orvanta-go-client")func main() (map[string]interface{}, error) { urls, err := orvanta.GetResumeUrls("approver1") if err != nil { return nil, err } return map[string]interface{}{ "resume": urls.Resume, "default_args": make(map[string]interface{}), // optional "enums": make(map[string]interface{}), // optional }, nil}This approval-prompt script can be found on the Orvanta Hub.
An operator (operator, since they are only “Viewer” in the folder of the flow) running the flow will see the prompt automatically shown when running it.
Default args
Section titled “Default args”As one of the return keys of this step, return an object default_args that contains the default arguments of the form argument. e.g:
//this assumes the Form tab has a string field named "foo" and a checkbox named "bar"import * as orvanta from 'npm:orvanta-client@^1.158.2';export async function main() { // if no argument is passed, if user is logged in, it will use the user's username const resumeUrls = await orvanta.getResumeUrls('approver1'); // send the resumeUrls to the recipient or see Prompt section above return { default_args: { foo: 'foo', bar: true } };}This default-arguments script can be found on the Orvanta Hub.
Dynamic enums
Section titled “Dynamic enums”As one of the return keys of this step, return an object enums that contains the default options of the form argument. e.g:
//this assumes the Form tab has a string field named "foo"import * as orvanta from 'npm:orvanta-client@^1.158.2';export async function main() { // if no argument is passed, if user is logged in, it will use the user's username const resumeUrls = await orvanta.getResumeUrls('approver1'); // send the resumeUrls to the recipient or see Prompt section above return { enums: { foo: ['choice1', 'choice2'] } };}This dynamic-enums script can be found on the Orvanta Hub.
That’s a powerful way of having dynamic enums as flow inputs. You can have a dynamic list of choices as the first step of a flow, then run the flow and see the list of choices.
Below is the flow YAML used for this example:
summary: ""value: modules: - id: a value: type: rawscript content: >- import * as orvantaClient from "orvanta-client" export async function main() { // Constant array, but could come from dynamic source const customers: string[] = [ "New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia", "San Antonio", "San Diego", "Dallas", "San Jose" ]; const resumeUrls = await orvantaClient.getResumeUrls("approver1"); // Remove duplicates and sort the customers array in alphabetical order const sortedCustomers = Array.from(new Set(customers)).sort(); return { resume: resumeUrls['resume'], enums: { "Customers to send to": sortedCustomers }, default_args: { "Customers to send to": sortedCustomers } } } language: bun input_transforms: {} is_trigger: false continue_on_error: false suspend: required_events: 1 timeout: 1800 hide_cancel: false resume_form: schema: properties: Customers to send to: items: type: string type: array description: "" required: [] order: - Customers to send to summary: Approval step with dynamic enum - id: b summary: Use the selected arguments value: type: rawscript content: |- # import orvanta def main(x): return x language: python3 input_transforms: x: type: javascript expr: resume["Customers to send to"] is_trigger: false same_worker: falseschema: $schema: https://json-schema.org/draft/2020-12/schema properties: {} required: [] type: objectDescription
Section titled “Description”You can add a description to give clear instructions that support the whole range of rich display rendering (including markdown).
import * as orvanta from "orvanta-client@^1.158.2"export async function main(approver?: string) { const urls = await orvanta.getResumeUrls(approver) // send the urls to their intended recipients // if the resumeUrls are part of the response, they will be available to any persons having access // to the run page and allowed to be approved from there, even from non owners of the flow // self-approval is disablable in the suspend options return { ...urls, default_args: {}, enums: {}, description: { render_all: [ { markdown: "# We have located the secret vault with thousands of H100" }, { map: { lat: -30, lon: 10, markers: [{lat: -30, lon: 0, title: "It's here"}]} }, "Just kidding" ] } // supports all formats from rich display rendering such as simple strings, // but also markdown, html, images, tables, maps, render_all, etc... // https://docs.orvanta.cloud/concepts/rich-display-rendering/ }}Hide cancel button on approval page
Section titled “Hide cancel button on approval page”By enabling this option, the cancel button will not be displayed on the approval page, to force more complex patterns using forms with enums processed in later steps.
Alternatively, adding the cancel URL as a result of the step will also render a cancel button, providing the operator with an option to cancel the step. e.g:
import * as orvanta from "orvanta-client"export async function main() { const urls = await orvanta.getResumeUrls("approver1") return { resume: urls['resume'], cancel: urls['cancel'], }}Permissions
Section titled “Permissions”Require approvers to be logged in
Section titled “Require approvers to be logged in”By enabling this option, only users logged in to Orvanta can approve the step.
Disable self-approval
Section titled “Disable self-approval”The user who triggered the flow will not be allowed to approve it. This restriction is enforced both on the approval page and on the flow resume API endpoint, preventing flow owners from bypassing approval requirements programmatically.
Require approvers to be members of a group
Section titled “Require approvers to be members of a group”By enabling this option, only logged-in users who are members of the specified group can approve the step.
You can also dynamically set the group by connecting it to another node’s output.
Get the users who approved the flow
Section titled “Get the users who approved the flow”The input approvers is an array of the users who approved the flow.
To get the list of users, just have the step after the approval step return the approvers key. For example, by taking an input connected to the approvers key.
The step could be as simple as:
export async function main(list_of_approvers) { return list_of_approvers}With input list_of_approvers taking the JavaScript expression approvers.
Slack approval step
Section titled “Slack approval step”The Orvanta Python and TypeScript clients both have a helper function to request an interactive approval on Slack. An interactive approval is a Slack message that can be approved or rejected directly from Slack without having to go back to the Orvanta UI.
The following Hub scripts can be used:
- Python: Request Interactive Slack Approval
- TypeScript: Request Interactive Slack Approval
If you define a form on the approval step, the form will be displayed in the Slack message as a modal.
Both of these scripts use the Orvanta client helper function:
Python
orvanta.request_interactive_slack_approval( slack_resource_path="/u/username/my_slack_resource", channel_id="admins-slack-channel", message="Please approve this request", approver="approver123", default_args_json={"key1": "value1", "key2": 42}, dynamic_enums_json={"foo": ["choice1", "choice2"], "bar": ["optionA", "optionB"]},)Bun
await orvanta.requestInteractiveSlackApproval({ slackResourcePath: "/u/username/my_slack_resource", channelId: "admins-slack-channel", message: "Please approve this request", approver: "approver123", defaultArgsJson: { key1: "value1", key2: 42 }, dynamicEnumsJson: { foo: ["choice1", "choice2"], bar: ["optionA", "optionB"] }, });Where dynamic_enums can be used to dynamically set the options of enum form arguments and default_args can be used to dynamically set the default values of form arguments.
If multiple approvals are required, you can use the client helper directly and send approval requests to different channels:
Python
import orvanta
def main(): # Send approval request to customers orvanta.request_interactive_slack_approval( 'u/username/slack_resource', 'customers', ) # Send approval request to admins orvanta.request_interactive_slack_approval( 'u/username/slack_resource', 'admins', )Bun
import * as orvanta from "orvanta-client"export async function main() { await orvanta.requestInteractiveSlackApproval({ slackResourcePath: "/u/username/slack_resource", channelId: "customers" }) await orvanta.requestInteractiveSlackApproval({ slackResourcePath: "/u/username/slack_resource", channelId: "admins" })}Microsoft Teams approval step
Section titled “Microsoft Teams approval step”The Orvanta TypeScript client exposes helper functions to request an approval on Microsoft Teams. The interactive approval is a Teams message that can be approved or rejected directly from Teams without having to go back to the Orvanta UI, whereas the non-interactive approval will be a simple link that opens the approval page in the Orvanta UI in your browser.
The following Hub scripts can be used:
- Request Interactive Teams Approval
- Request Basic Teams Approval
If you define a form on the approval step, the form will be displayed in the Teams message as a modal.
Both of these scripts use the Orvanta client helper function:
TypeScript Interactive
await orvanta.requestInteractiveTeamsApproval({ teamName: "Orvanta", channelName: "General", message: "Please approve this request", approver: "approver123", defaultArgsJson: { key1: "value1", key2: 42 }, dynamicEnumsJson: { foo: ["choice1", "choice2"], bar: ["optionA", "optionB"] }, });TypeScript Basic
const card_block = { "type": "message", "attachments": [ { "contentType": "application/vnd.microsoft.card.adaptive", "content": { "type": "AdaptiveCard", "$schema": "https://adaptivecards.io/schemas/adaptive-card.json", "version": "1.6", "body": [ ... // card body ], }, } ], "conversation": {"id": `${conversation_id}`}, } await orvanta.TeamsService.sendMessageToConversation({ requestBody: { conversation_id, text: "A workflow has been suspended and is waiting for approval!", card_block } })Where dynamic_enums can be used to dynamically set the options of enum form arguments and default_args can be used to dynamically set the default values of form arguments.
If multiple approvals are required, you can use the client helper directly and send approval requests to different channels (same as the Slack example above).
Tutorial: a Slack approval step conditioning flow branches
Section titled “Tutorial: a Slack approval step conditioning flow branches”The answer to the arguments of an approval page can be used as an input to condition branches in human-in-the-loop workflows.
For the sake of the example, we made this flow simple with a manual trigger. Two inputs were used: “User email” and “Order number”, both strings.
Then, we picked an approval step on the Hub to Request Interactive Slack Approval. With inputs:
- “slackResourcePath”: the path to your Slack resource.
- “channel”: Slack channel to publish the message, as a string.
- “text”:
Refund request by _${flow_input["User email"]}_ on order ${flow_input["Order number"]}.
This allows the user to approve or reject the refund request directly from Slack without having to go back to the Orvanta UI.
In the Advanced settings of the step, for “Suspend/Approval”, we added properties to the form. This leads to the approval page and Slack form.
This approval page will generate two keys you can use for further steps: resume["Action"] and resume["Message"]. resume is the resume payload.
Those are the keys you can use as predicate expressions for your branches.
The content of each branch is of little importance for this tutorial, as it depends on each operation and tech stack. For the example, we used two Hub scripts: Send Email with Gmail and Send Message to Channel with Slack.
Automated trigger version
Section titled “Automated trigger version”You could use the Mailchimp Mandrill integration to trigger this flow by an email reception.
This flow can be found and forked on the Orvanta Hub.