--- title: Build a Workflow subtitle: Create, run, and test multi-step automations slug: multi-step-automations/build-a-workflow --- A **Workflow** chains multiple automation steps into a reusable template. While a Task handles a single goal on one page, a Workflow orchestrates an entire process: filling forms, validating data, downloading files, and sending results. You define the workflow once with blocks, then run it with different parameters each time. --- ## Tasks vs Workflows | Characteristic | Tasks | Workflows | |----------------|-------|-----------| | **Structure** | Single prompt, one execution | Multiple blocks in sequence | | **Scope** | Single goal, usually one page | Multi-step process across pages | | **Reusability** | Run ad-hoc or scheduled | Define once, run with different parameters | | **Parameterization** | Basic (prompt, URL, extraction schema) | Rich (input parameters, block outputs, Jinja templates) | | **Control Flow** | Linear | Loops, conditionals, validation gates | | **Data Passing** | Output returned at the end | Each block can pass data to subsequent blocks | | **Composition** | Single AI agent execution | Combine navigation, extraction, files, notifications | | **File Operations** | Download files during task | Download, parse, upload, email files across blocks | | **Best For** | Prototyping, simple extractions, one-offs | Production automations, bulk processing, complex logic | | **Run ID Format** | `tsk_*` | `wr_*` | --- ## Workflow structure A workflow is defined as an ordered list of **blocks**. Blocks execute one after another, and each block can access the outputs of previous blocks. Here's an IRS EIN registration workflow that fills out a government form, validates the summary, extracts the result, downloads the confirmation letter, and emails it to your team: IRS SS-4 Filing Workflow: Navigation → Validation → Extraction → File Download → Send Email Here's what each block does in this workflow: - **Navigation** takes the company information from `ein_info` and fills out the multi-page SS-4 form until it reaches the review page - **Validation** checks that the form summary matches the provided information and either continues (if correct) or terminates the workflow (if incorrect) - **Extraction** pulls the assigned EIN number and legal name from the confirmation page into structured JSON output - **File Download** navigates to and downloads the EIN confirmation letter as a PDF file - **Send Email** attaches the downloaded PDF and sends it to the specified recipients with company details in the email body For detailed documentation on all available block types and their parameters, see [Workflow Blocks Reference](/multi-step-automations/workflow-blocks-reference). --- ## Step 1: Understand the structure A workflow definition has two parts: **parameters** (the inputs) and **blocks** (the steps). Here's the complete shape: ```yaml title: Job Application Workflow # Display name workflow_definition: parameters: # ← Inputs: change per run (Step 2) - key: job_url parameter_type: workflow workflow_parameter_type: string blocks: # ← Steps: execute in order (Step 3) - label: apply_to_job block_type: navigation url: "{{ job_url }}" navigation_goal: Fill out the job application form ``` You send this definition to the API to create a reusable workflow: ```python workflow = await client.create_workflow( json_definition={ # ← or yaml_definition="..." "title": "...", "workflow_definition": { "parameters": [...], # Step 2 "blocks": [...] # Step 3 } } ) ``` The rest of this guide walks through each part, then shows the full API calls for creating, running, and monitoring workflows. --- ## Step 2: Define parameters The `parameters` array (inside `workflow_definition`) defines the inputs your workflow accepts. Instead of hardcoding a resume URL or job page into the workflow definition, you define named inputs that accept different values each time the workflow runs. Each parameter needs three fields: - `key` — the name you reference later with `{{ key }}` - `parameter_type` — always `"workflow"` for input parameters - `workflow_parameter_type` — the data type (`string`, `file_url`, `json`, etc.) ```json JSON { "parameters": [ { "key": "resume", "parameter_type": "workflow", "workflow_parameter_type": "file_url" }, { "key": "job_url", "parameter_type": "workflow", "workflow_parameter_type": "string" } ] } ``` ```yaml YAML parameters: - key: resume parameter_type: workflow workflow_parameter_type: file_url - key: job_url parameter_type: workflow workflow_parameter_type: string ``` This defines two inputs: a `resume` file and a `job_url` string. When you run the workflow later, you pass actual values for these parameters. **Available parameter types:** | Type | Description | Example value | |------|-------------|---------------| | `string` | Text | `"John Smith"` | | `integer` | Whole number | `42` | | `float` | Decimal number | `99.99` | | `boolean` | True or false | `true` | | `json` | JSON object or array | `{"key": "value"}` | | `file_url` | URL to a file | `"https://example.com/resume.pdf"` | | `credential_id` | Reference to a stored credential | `"cred_abc123"` | For the full parameter reference, see [Workflow Parameters](/multi-step-automations/workflow-parameters). --- ## Step 3: Define blocks The `blocks` array (inside `workflow_definition`) defines the steps your workflow executes. Each block has a `block_type` that determines what it does: navigate a page, extract data, download a file, send an email. Every block needs two fields: a `label` (a unique name) and a `block_type`. The remaining fields depend on the block type. ```json JSON { "blocks": [ { "label": "parse_resume", "block_type": "file_url_parser", "file_url": "{{ resume }}", "file_type": "pdf" }, { "label": "apply_to_job", "block_type": "navigation", "url": "{{ job_url }}", "navigation_goal": "Fill out the job application form using the candidate's information." } ] } ``` ```yaml YAML blocks: - label: parse_resume block_type: file_url_parser file_url: "{{ resume }}" file_type: pdf - label: apply_to_job block_type: navigation url: "{{ job_url }}" navigation_goal: Fill out the job application form using the candidate's information. ``` The first block (`parse_resume`) parses the resume file passed as an input parameter. The second block (`apply_to_job`) navigates to the job page and fills out the application. Notice `{{ resume }}` and `{{ job_url }}` — double curly braces reference the input parameters you defined in Step 2. Skyvern replaces these with actual values when the workflow runs. Skyvern supports 23 block types: navigation, extraction, validation, file download, for loops, conditionals, and more. See [Workflow Blocks Reference](/multi-step-automations/workflow-blocks-reference) for the full list. --- ## Step 4: Pass data between blocks When a block completes, its output becomes available to subsequent blocks. This is how you chain steps together: one block extracts data, the next block uses it. Reference a block's output with `{{ label_output }}` — the block's `label` followed by `_output`. Access nested fields with dot notation: `{{ parse_resume_output.name }}`. Here, the `apply_to_job` block uses specific fields from the `parse_resume` block's output: ```json JSON { "blocks": [ { "label": "parse_resume", "block_type": "file_url_parser", "file_url": "{{ resume }}", "file_type": "pdf" }, { "label": "apply_to_job", "block_type": "navigation", "url": "{{ job_url }}", "navigation_goal": "Fill out the job application form.\n\nUse this information:\n- Name: {{ parse_resume_output.name }}\n- Email: {{ parse_resume_output.email }}\n- Experience: {{ parse_resume_output.work_experience }}" } ] } ``` ```yaml YAML blocks: - label: parse_resume block_type: file_url_parser file_url: "{{ resume }}" file_type: pdf - label: apply_to_job block_type: navigation url: "{{ job_url }}" navigation_goal: | Fill out the job application form. Use this information: - Name: {{ parse_resume_output.name }} - Email: {{ parse_resume_output.email }} - Experience: {{ parse_resume_output.work_experience }} ``` `{{ parse_resume_output.name }}`, `{{ parse_resume_output.email }}`, and `{{ parse_resume_output.work_experience }}` are fields from the structured data the `parse_resume` block extracted from the PDF. ### Looping over output data When a block produces a list, use a `for_loop` block to process each item. Set `loop_over_parameter_key` to the output field containing the array, and reference the current item with `{{ loop_block_label.current_value }}`. ```json JSON { "blocks": [ { "label": "extract_orders", "block_type": "extraction", "url": "https://example.com/orders", "data_extraction_goal": "Extract all order IDs from the page", "data_schema": { "type": "object", "properties": { "order_ids": { "type": "array", "items": { "type": "string" } } } } }, { "label": "download_invoices", "block_type": "for_loop", "loop_over_parameter_key": "extract_orders_output.order_ids", "loop_blocks": [ { "label": "download_invoice", "block_type": "file_download", "url": "https://example.com/invoice/{{ download_invoice.current_value }}", "navigation_goal": "Download the invoice PDF" } ] } ] } ``` ```yaml YAML blocks: - label: extract_orders block_type: extraction url: "https://example.com/orders" data_extraction_goal: Extract all order IDs from the page data_schema: type: object properties: order_ids: type: array items: type: string - label: download_invoices block_type: for_loop loop_over_parameter_key: extract_orders_output.order_ids loop_blocks: - label: download_invoice block_type: file_download url: "https://example.com/invoice/{{ download_invoice.current_value }}" navigation_goal: Download the invoice PDF ``` The `for_loop` iterates over the `order_ids` array from the extraction block. Inside the loop, `{{ download_invoice.current_value }}` contains one order ID per iteration. --- ## Step 5: Create the workflow Send the complete workflow definition to the API. Skyvern validates the parameters, blocks, and template references, then returns a `workflow_permanent_id` you use to run the workflow. The API accepts two formats: pass a JSON object via `json_definition`, or pass a YAML string via `yaml_definition`. ### Using JSON ```python Python import os import asyncio from skyvern import Skyvern async def main(): client = Skyvern(api_key=os.getenv("SKYVERN_API_KEY")) workflow = await client.create_workflow( json_definition={ "title": "Job Application Workflow", "workflow_definition": { "parameters": [ { "key": "resume", "parameter_type": "workflow", "workflow_parameter_type": "file_url" }, { "key": "job_url", "parameter_type": "workflow", "workflow_parameter_type": "string" } ], "blocks": [ { "label": "parse_resume", "block_type": "file_url_parser", "file_url": "{{ resume }}", "file_type": "pdf" }, { "label": "apply_to_job", "block_type": "navigation", "url": "{{ job_url }}", "navigation_goal": ( "Fill out the job application form.\n\n" "Use this information:\n" "{{ parse_resume_output }}" ) } ] } } ) print(f"Workflow ID: {workflow.workflow_permanent_id}") asyncio.run(main()) ``` ```typescript TypeScript import { SkyvernClient } from "@skyvern/client"; async function main() { const client = new SkyvernClient({ apiKey: process.env.SKYVERN_API_KEY, }); const workflow = await client.createWorkflow({ body: { json_definition: { title: "Job Application Workflow", workflow_definition: { parameters: [ { key: "resume", parameter_type: "workflow", workflow_parameter_type: "file_url", }, { key: "job_url", parameter_type: "workflow", workflow_parameter_type: "string", }, ], blocks: [ { label: "parse_resume", block_type: "file_url_parser", file_url: "{{ resume }}", file_type: "pdf", }, { label: "apply_to_job", block_type: "navigation", url: "{{ job_url }}", navigation_goal: [ "Fill out the job application form.", "", "Use this information:", "{{ parse_resume_output }}", ].join("\n"), }, ], }, }, }, }); console.log(`Workflow ID: ${workflow.workflow_permanent_id}`); } main(); ``` ```bash cURL curl -X POST "https://api.skyvern.com/v1/workflows" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "json_definition": { "title": "Job Application Workflow", "workflow_definition": { "parameters": [ { "key": "resume", "parameter_type": "workflow", "workflow_parameter_type": "file_url" }, { "key": "job_url", "parameter_type": "workflow", "workflow_parameter_type": "string" } ], "blocks": [ { "label": "parse_resume", "block_type": "file_url_parser", "file_url": "{{ resume }}", "file_type": "pdf" }, { "label": "apply_to_job", "block_type": "navigation", "url": "{{ job_url }}", "navigation_goal": "Fill out the job application form.\n\nUse this information:\n{{ parse_resume_output }}" } ] } } }' ``` ### Using YAML Pass the same definition as a YAML string via `yaml_definition`. This is useful when you store workflow definitions in `.yaml` files. ```python Python workflow = await client.create_workflow( yaml_definition="""\ title: Job Application Workflow workflow_definition: parameters: - key: resume parameter_type: workflow workflow_parameter_type: file_url - key: job_url parameter_type: workflow workflow_parameter_type: string blocks: - label: parse_resume block_type: file_url_parser file_url: "{{ resume }}" file_type: pdf - label: apply_to_job block_type: navigation url: "{{ job_url }}" navigation_goal: | Fill out the job application form. Use this information: {{ parse_resume_output }} """ ) ``` ```typescript TypeScript const workflow = await client.createWorkflow({ body: { yaml_definition: ` title: Job Application Workflow workflow_definition: parameters: - key: resume parameter_type: workflow workflow_parameter_type: file_url - key: job_url parameter_type: workflow workflow_parameter_type: string blocks: - label: parse_resume block_type: file_url_parser file_url: "{{ resume }}" file_type: pdf - label: apply_to_job block_type: navigation url: "{{ job_url }}" navigation_goal: | Fill out the job application form. Use this information: {{ parse_resume_output }} `.trim(), }, }); ``` ```bash cURL curl -X POST "https://api.skyvern.com/v1/workflows" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "yaml_definition": "title: Job Application Workflow\nworkflow_definition:\n parameters:\n - key: resume\n parameter_type: workflow\n workflow_parameter_type: file_url\n - key: job_url\n parameter_type: workflow\n workflow_parameter_type: string\n blocks:\n - label: parse_resume\n block_type: file_url_parser\n file_url: \"{{ resume }}\"\n file_type: pdf\n - label: apply_to_job\n block_type: navigation\n url: \"{{ job_url }}\"\n navigation_goal: \"Fill out the job application form.\\n\\nUse this information:\\n{{ parse_resume_output }}\"\n" }' ``` To load from a file: `await client.create_workflow(yaml_definition=open("workflow.yaml").read())` **Example response:** ```json { "workflow_permanent_id": "wpid_123456789", "workflow_id": "wf_987654321", "version": 1, "title": "Job Application Workflow", "workflow_definition": { "parameters": [...], "blocks": [...] }, "created_at": "2026-01-20T12:00:00.000000", "modified_at": "2026-01-20T12:00:00.000000" } ``` Save the `workflow_permanent_id` — you need it to run the workflow. --- ## Step 6: Run the workflow Pass values for the parameters you defined in Step 2. The call returns immediately with a `run_id` — the workflow runs asynchronously in the background. ```python Python run = await client.run_workflow( workflow_id="wpid_123456789", parameters={ "resume": "https://example.com/resume.pdf", "job_url": "https://jobs.lever.co/company/position" } ) print(f"Run ID: {run.run_id}") ``` ```typescript TypeScript const run = await client.runWorkflow({ body: { workflow_id: "wpid_123456789", parameters: { resume: "https://example.com/resume.pdf", job_url: "https://jobs.lever.co/company/position", }, }, }); console.log(`Run ID: ${run.run_id}`); ``` ```bash cURL curl -X POST "https://api.skyvern.com/v1/run/workflows" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "wpid_123456789", "parameters": { "resume": "https://example.com/resume.pdf", "job_url": "https://jobs.lever.co/company/position" } }' ``` **Example response:** ```json { "run_id": "wr_486305187432193510", "status": "created" } ``` The response includes a `run_id`. Use this ID to check status and fetch results. --- ## Step 7: Get results Workflows run asynchronously, so you need to check back for results. You have two options: poll the API, or receive a webhook when the workflow completes. ### Option 1: Polling Poll `get_run` until the status reaches a terminal state. ```python Python run_id = run.run_id while True: result = await client.get_run(run_id) if result.status in ["completed", "failed", "terminated", "timed_out", "canceled"]: break await asyncio.sleep(5) print(f"Status: {result.status}") print(f"Output: {result.output}") ``` ```typescript TypeScript const runId = run.run_id; while (true) { const result = await client.getRun(runId); if (["completed", "failed", "terminated", "timed_out", "canceled"].includes(result.status)) { console.log(`Status: ${result.status}`); console.log(`Output: ${JSON.stringify(result.output)}`); break; } await new Promise((resolve) => setTimeout(resolve, 5000)); } ``` ```bash cURL #!/bin/bash RUN_ID="wr_486305187432193510" while true; do RESPONSE=$(curl -s -X GET "https://api.skyvern.com/v1/runs/$RUN_ID" \ -H "x-api-key: $SKYVERN_API_KEY") STATUS=$(echo "$RESPONSE" | jq -r '.status') echo "Status: $STATUS" if [[ "$STATUS" == "completed" || "$STATUS" == "failed" || "$STATUS" == "terminated" || "$STATUS" == "timed_out" || "$STATUS" == "canceled" ]]; then echo "$RESPONSE" | jq '.output' break fi sleep 5 done ``` ### Option 2: Webhooks Pass a `webhook_url` when running the workflow. Skyvern sends a POST request to your URL when the workflow reaches a terminal state. ```python Python run = await client.run_workflow( workflow_id="wpid_123456789", parameters={ "resume": "https://example.com/resume.pdf", "job_url": "https://jobs.lever.co/company/position" }, webhook_url="https://your-server.com/webhook" ) ``` ```typescript TypeScript const run = await client.runWorkflow({ body: { workflow_id: "wpid_123456789", parameters: { resume: "https://example.com/resume.pdf", job_url: "https://jobs.lever.co/company/position", }, webhook_url: "https://your-server.com/webhook", }, }); ``` ```bash cURL curl -X POST "https://api.skyvern.com/v1/run/workflows" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "wpid_123456789", "parameters": { "resume": "https://example.com/resume.pdf", "job_url": "https://jobs.lever.co/company/position" }, "webhook_url": "https://your-server.com/webhook" }' ``` The webhook payload contains the same data as the polling response. See [Webhooks](/going-to-production/webhooks) for authentication and retry options. --- ## Understand the response The response from polling contains the workflow run status and output from each block. ```json { "run_id": "wr_486305187432193510", "run_type": "workflow_run", "status": "completed", "output": { "parse_resume_output": { "name": "John Smith", "email": "john@example.com", "work_experience": [...] }, "apply_to_job_output": { "task_id": "tsk_123456", "status": "completed", "extracted_information": null } }, "screenshot_urls": [ "https://skyvern-artifacts.s3.amazonaws.com/.../screenshot_final.png" ], "recording_url": "https://skyvern-artifacts.s3.amazonaws.com/.../recording.webm", "failure_reason": null, "created_at": "2026-01-20T12:00:00.000000", "modified_at": "2026-01-20T12:05:00.000000" } ``` **Response fields:** | Field | Type | Description | |-------|------|-------------| | `run_id` | string | Unique identifier for this run (format: `wr_*`) | | `run_type` | string | Always `"workflow_run"` for workflow runs | | `status` | string | Current status: `created`, `queued`, `running`, `completed`, `failed`, `terminated`, `timed_out`, `canceled` | | `output` | object | Output from each block, keyed by `{label}_output` | | `screenshot_urls` | array | Final screenshots from the last blocks | | `recording_url` | string | Video recording of the browser session | | `failure_reason` | string \| null | Error description if the run failed | | `downloaded_files` | array | Files downloaded during the run | | `created_at` | datetime | When the run started | | `modified_at` | datetime | When the run was last updated | ### Block outputs Each block's output appears in the `output` object with the key `{label}_output`. The structure depends on the block type: **Navigation/Action blocks:** ```json { "apply_to_job_output": { "task_id": "tsk_123456", "status": "completed", "extracted_information": null, "failure_reason": null } } ``` **Extraction blocks:** ```json { "extract_data_output": { "product_name": "Widget Pro", "price": 99.99, "in_stock": true } } ``` **For Loop blocks:** ```json { "download_invoices_output": [ { "task_id": "tsk_111", "status": "completed" }, { "task_id": "tsk_222", "status": "completed" }, { "task_id": "tsk_333", "status": "completed" } ] } ``` --- ## Complete example This script creates a job application workflow, runs it, and polls until completion. Copy and run it to get started. ```python Python import os import asyncio from skyvern import Skyvern async def main(): client = Skyvern(api_key=os.getenv("SKYVERN_API_KEY")) # Create the workflow workflow = await client.create_workflow( json_definition={ "title": "Job Application Workflow", "workflow_definition": { "parameters": [ { "key": "resume", "parameter_type": "workflow", "workflow_parameter_type": "file_url" }, { "key": "job_url", "parameter_type": "workflow", "workflow_parameter_type": "string" } ], "blocks": [ { "label": "parse_resume", "block_type": "file_url_parser", "file_url": "{{ resume }}", "file_type": "pdf" }, { "label": "apply_to_job", "block_type": "navigation", "url": "{{ job_url }}", "navigation_goal": ( "Fill out the job application form.\n\n" "Use this information:\n" "{{ parse_resume_output }}" ) } ] } } ) print(f"Created workflow: {workflow.workflow_permanent_id}") # Run the workflow run = await client.run_workflow( workflow_id=workflow.workflow_permanent_id, parameters={ "resume": "https://example.com/resume.pdf", "job_url": "https://jobs.lever.co/company/position" } ) print(f"Started run: {run.run_id}") # Poll for results while True: result = await client.get_run(run.run_id) if result.status in ["completed", "failed", "terminated", "timed_out", "canceled"]: break print(f"Status: {result.status}") await asyncio.sleep(5) print(f"Final status: {result.status}") print(f"Output: {result.output}") asyncio.run(main()) ``` ```typescript TypeScript import { SkyvernClient } from "@skyvern/client"; async function main() { const client = new SkyvernClient({ apiKey: process.env.SKYVERN_API_KEY, }); // Create the workflow const workflow = await client.createWorkflow({ body: { json_definition: { title: "Job Application Workflow", workflow_definition: { parameters: [ { key: "resume", parameter_type: "workflow", workflow_parameter_type: "file_url", }, { key: "job_url", parameter_type: "workflow", workflow_parameter_type: "string", }, ], blocks: [ { label: "parse_resume", block_type: "file_url_parser", file_url: "{{ resume }}", file_type: "pdf", }, { label: "apply_to_job", block_type: "navigation", url: "{{ job_url }}", navigation_goal: [ "Fill out the job application form.", "", "Use this information:", "{{ parse_resume_output }}", ].join("\n"), }, ], }, }, }, }); console.log(`Created workflow: ${workflow.workflow_permanent_id}`); // Run the workflow const run = await client.runWorkflow({ body: { workflow_id: workflow.workflow_permanent_id, parameters: { resume: "https://example.com/resume.pdf", job_url: "https://jobs.lever.co/company/position", }, }, }); console.log(`Started run: ${run.run_id}`); // Poll for results while (true) { const result = await client.getRun(run.run_id); if (["completed", "failed", "terminated", "timed_out", "canceled"].includes(result.status)) { console.log(`Final status: ${result.status}`); console.log(`Output: ${JSON.stringify(result.output, null, 2)}`); break; } console.log(`Status: ${result.status}`); await new Promise((resolve) => setTimeout(resolve, 5000)); } } main(); ``` ```bash cURL #!/bin/bash # Create the workflow WORKFLOW=$(curl -s -X POST "https://api.skyvern.com/v1/workflows" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "json_definition": { "title": "Job Application Workflow", "workflow_definition": { "parameters": [ { "key": "resume", "parameter_type": "workflow", "workflow_parameter_type": "file_url" }, { "key": "job_url", "parameter_type": "workflow", "workflow_parameter_type": "string" } ], "blocks": [ { "label": "parse_resume", "block_type": "file_url_parser", "file_url": "{{ resume }}", "file_type": "pdf" }, { "label": "apply_to_job", "block_type": "navigation", "url": "{{ job_url }}", "navigation_goal": "Fill out the job application form.\n\nUse this information:\n{{ parse_resume_output }}" } ] } } }') WORKFLOW_ID=$(echo "$WORKFLOW" | jq -r '.workflow_permanent_id') echo "Created workflow: $WORKFLOW_ID" # Run the workflow RUN=$(curl -s -X POST "https://api.skyvern.com/v1/run/workflows" \ -H "x-api-key: $SKYVERN_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"workflow_id\": \"$WORKFLOW_ID\", \"parameters\": { \"resume\": \"https://example.com/resume.pdf\", \"job_url\": \"https://jobs.lever.co/company/position\" } }") RUN_ID=$(echo "$RUN" | jq -r '.run_id') echo "Started run: $RUN_ID" # Poll for results while true; do RESPONSE=$(curl -s -X GET "https://api.skyvern.com/v1/runs/$RUN_ID" \ -H "x-api-key: $SKYVERN_API_KEY") STATUS=$(echo "$RESPONSE" | jq -r '.status') echo "Status: $STATUS" if [[ "$STATUS" == "completed" || "$STATUS" == "failed" || "$STATUS" == "terminated" || "$STATUS" == "timed_out" || "$STATUS" == "canceled" ]]; then echo "$RESPONSE" | jq '.output' break fi sleep 5 done ``` --- ## List your workflows Retrieve all workflows in your organization. ```python Python workflows = await client.get_workflows() for workflow in workflows: print(f"{workflow.workflow_permanent_id}: {workflow.title}") ``` ```typescript TypeScript const workflows = await client.getWorkflows(); for (const workflow of workflows) { console.log(`${workflow.workflow_permanent_id}: ${workflow.title}`); } ``` ```bash cURL curl -X GET "https://api.skyvern.com/v1/workflows" \ -H "x-api-key: $SKYVERN_API_KEY" ``` --- ## Next steps Detailed documentation for all 23 block types Configure proxies, webhooks, and other run options Download, parse, and upload files in workflows