docs: full documentation redesign with new content, screenshots, and navigation (#5235)

Co-authored-by: Kunal Mishra <kunalm2345@gmail.com>
This commit is contained in:
Naman 2026-03-25 23:27:58 +05:30 committed by GitHub
parent 2c604b6a4d
commit e4fd342746
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
72 changed files with 3193 additions and 559 deletions

View file

@ -1,8 +1,24 @@
---
title: "Changelog"
description: "New features, improvements, and fixes in Skyvern"
mode: custom
---
<div className="cl-page">
<div className="cl-sidebar">
<p className="cl-sidebar-title">TIMELINE</p>
<a href="#v1022--february-26-2026" className="cl-sidebar-link">v1.0.22 — Feb 26, 2026</a>
<a href="#v1021--february-24-2026" className="cl-sidebar-link">v1.0.21 — Feb 24, 2026</a>
<a href="#v1018--february-19-2026" className="cl-sidebar-link">v1.0.18 — Feb 19, 2026</a>
<a href="#v1017--february-19-2026" className="cl-sidebar-link">v1.0.17 — Feb 19, 2026</a>
<a href="#v1016--february-19-2026" className="cl-sidebar-link">v1.0.16 — Feb 19, 2026</a>
<a href="#v1015--february-17-2026" className="cl-sidebar-link">v1.0.15 — Feb 17, 2026</a>
</div>
<div className="cl-content">
<h1 className="cl-title">Changelog</h1>
<p className="cl-subtitle">New features, improvements, and fixes in Skyvern</p>
<Update label="v1.0.22 — February 26, 2026" description="Adaptive caching, Workflow Trigger block, CLI signup, and more">
## New features
@ -125,3 +141,6 @@ description: "New features, improvements, and fixes in Skyvern"
- **Anthropic Claude Opus 4.6** — Added Claude Opus 4.6 as an available LLM engine for web automation tasks. ([#4777](https://github.com/Skyvern-AI/skyvern/pull/4777), [#4778](https://github.com/Skyvern-AI/skyvern/pull/4778))
</Update>
</div>
</div>

View file

@ -21,7 +21,7 @@ Treat your API key like a password. Anyone with this key can create runs, read r
Every Skyvern API request requires the `x-api-key` header:
```bash
curl -X POST "https://api.skyvern.com/v1/runs" \
curl -X POST "https://api.skyvern.com/v1/run/tasks" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com", "prompt": "Extract the pricing table" }'

View file

@ -12,17 +12,77 @@ Open **Billing** in the sidebar to see available plans and your current balance.
<img src="/images/cloud/settings-billing-overview.png" alt="Billing page showing plan options and current balance" />
| Plan | Price | Credits | Includes |
|------|-------|---------|----------|
| **Hobby** | $29/month | 30,000 credits/month | Email support, priority queue |
| **Pro** | $149/month | 150,000 credits/month | Priority support, higher rate limits |
| Plan | Price | Credits/month | Estimated actions | Concurrent runs |
|------|-------|---------------|-------------------|-----------------|
| **Free** | $0 | 1,000 | ~170 | 1 |
| **Hobby** | $29/month | 30,000 | ~1,200 | 10 |
| **Pro** | $149/month | 150,000 | ~6,200 | 25 |
| **Enterprise** | Custom | Unlimited | Unlimited | Unlimited |
Click **Switch to Hobby** or **Switch to Pro** to change plans. Your remaining dollar balance is applied as a credit toward your first subscription payment.
**Free** is available to all new accounts with zero commitment. **Hobby** and **Pro** are monthly subscriptions. Click **Switch to Hobby** or **Switch to Pro** to change plans. Your remaining dollar balance is applied as a credit toward your first subscription payment.
<Note>
Need custom volumes or a dedicated plan? Use the **Book a call** link at the bottom of the Billing page, or visit [View full pricing details](https://skyvern.com) for a complete comparison.
Need HIPAA compliance, SOC-2 reports, custom code blocks, or dedicated support? Use the **Book a call** link at the bottom of the Billing page to discuss **Enterprise**, or visit [skyvern.com/pricing](https://skyvern.com/pricing) for a complete feature comparison.
</Note>
### What's included in each plan
<AccordionGroup>
<Accordion title="Free: Try Skyvern with zero commitment">
**1,000 credits/month** (~170 actions)
Perfect for evaluating Skyvern and prototyping automations.
- 1 concurrent run
- Webhooks
- Country geo-targeting
- Community support
</Accordion>
<Accordion title="Hobby: For side projects and small automations">
**30,000 credits/month** (~1,200 actions) • **$29/month**
- 10 concurrent runs
- Basic CAPTCHA solver
- Webhooks
- Stored credentials
- Country geo-targeting
- Email support
</Accordion>
<Accordion title="Pro: For teams shipping production automations">
**150,000 credits/month** (~6,200 actions) • **$149/month**
Everything in Hobby, plus:
- 25 concurrent runs
- Advanced CAPTCHA solver
- Team workspaces
- 2FA / TOTP support
- 1Password integration
- Residential proxy
- City-level geo-targeting
- Priority support
</Accordion>
<Accordion title="Enterprise: For regulated industries and large-scale operations">
**Unlimited credits** • **Custom pricing**
Everything in Pro, plus:
- Unlimited concurrent runs
- HIPAA compliant infrastructure
- SOC-2 Type II Report
- Dedicated Slack channel
- Azure Key Vault integration
- Bitwarden integration
- Custom code blocks
- Human-in-the-loop workflows
[Contact sales](https://skyvern.com/contact) to discuss Enterprise.
</Accordion>
</AccordionGroup>
## How credits are consumed
A credit maps to one discrete browser action: clicking a button, typing into a field, selecting a dropdown, or extracting data from a page. Navigation counts too.

View file

@ -0,0 +1,110 @@
---
title: Browser Profiles
subtitle: Save and reuse login state across sessions
slug: cloud/browser-management/browser-profiles
---
A **browser profile** is a saved snapshot of browser state — cookies, localStorage, and session files. Load a profile into a new [browser session](/cloud/browser-management/browser-sessions) and the browser starts with that saved state restored, skipping login flows you've already completed.
Profiles are ideal when you:
- Run the same automation repeatedly with the same account (daily reports, scheduled data extraction)
- Want to skip login steps that consume credits and time
- Need multiple workflows to share the same authenticated state
<Note>
Looking to manage profiles from code? See the [API & SDK guide](/optimization/browser-profiles) instead.
</Note>
---
## How profiles are created
Browser profiles are created automatically when you test a password credential with the **Save browser session** option enabled. There is no standalone "create profile" button — the profile is a byproduct of a successful login test.
### Creating a profile from a credential
<Steps>
<Step title="Go to the Credentials page">
Click **Credentials** in the left sidebar under **General**.
</Step>
<Step title="Create or edit a password credential">
Click **+ Add → Password** to create a new credential, or click the **pencil icon** on an existing one. Enter the username and password. See [Password Credentials](/cloud/managing-credentials/password-credentials) for details.
<img src="/images/cloud/credential-add-password-form.png" alt="Add password credential dialog with name, username, and password fields" />
</Step>
<Step title="Enable Save browser session">
Below the password fields, enable the **Save browser session** option. Enter the **login URL** for the site (e.g., `https://app.example.com/login`). You can also add optional context to help the AI navigate the login flow.
</Step>
<Step title="Test the credential">
Click **Test**. Skyvern opens a browser, navigates to the login URL, signs in with the credential, and — if the login succeeds — saves the browser state as a profile. The profile is linked to this credential automatically.
</Step>
<Step title="Confirm the profile was saved">
When the test completes, the credential row shows a **saved-profile** badge with the site hostname. This means the browser profile is ready to use.
<img src="/images/cloud/credential-save-profile.png" alt="Credential listed with a profile badge" />
</Step>
</Steps>
<Tip>
The **Save browser session** flow is the easiest way to create a profile. It handles the full lifecycle: open browser, log in, save state. No workflows or API calls required.
</Tip>
---
## When to refresh profiles
Cookies and session tokens expire. A profile that worked last week may not work today if the site's authentication tokens have a short lifespan.
**Signs a profile needs refreshing:**
- Tasks that use the profile start failing with login-related errors
- The browser loads a login page instead of the authenticated dashboard
- The site forces re-authentication after a set period
**To refresh:** go back to the credential, click **Test** again with **Save browser session** enabled. The new profile replaces the old one.
<Tip>
For sites with short-lived sessions (banks, healthcare portals), refresh profiles before each batch of runs. For sites with long-lived cookies (most SaaS apps), weekly or monthly refreshes are usually sufficient.
</Tip>
---
## Sessions vs profiles
| | Browser Session | Browser Profile |
|---|----------------|-----------------|
| **What it is** | Live browser instance | Saved snapshot of browser state |
| **Lifetime** | Minutes to hours | Days to months |
| **State** | Current page, cookies, open connections | Cookies, localStorage, session files |
| **Billing** | Charged while open | No cost when not in use |
| **Best for** | Back-to-back tasks, human-in-the-loop | Repeated logins, scheduled workflows |
---
## What's next
<CardGroup cols={3}>
<Card
title="Browser Sessions"
icon="browser"
href="/cloud/browser-management/browser-sessions"
>
Keep a browser open across multiple tasks
</Card>
<Card
title="Password Credentials"
icon="key"
href="/cloud/managing-credentials/password-credentials"
>
Store the login details that profiles are built from
</Card>
<Card
title="Browser Profiles (API)"
icon="code"
href="/optimization/browser-profiles"
>
Create and manage profiles programmatically
</Card>
</CardGroup>

View file

@ -0,0 +1,173 @@
---
title: Browser Sessions
subtitle: Keep a browser open across multiple tasks
slug: cloud/browser-management/browser-sessions
---
A **browser session** is a live browser instance that stays open between runs. Cookies, login state, cart contents, and page context all persist — like keeping a browser tab open. Use sessions when you need back-to-back tasks to share state, or when you want to interact with the browser yourself between automated steps.
<Note>
Looking to use browser sessions from code? See the [API & SDK guide](/optimization/browser-sessions) instead.
</Note>
---
## The Browsers page
Click **Browsers** in the left sidebar to open the Browsers page. It lists all browser sessions in your organization.
<img src="/images/cloud/browsers-page-overview.png" alt="Browsers page with the sessions table showing columns for ID, open status, occupied status, started time, timeout, and CDP URL" />
Each row shows:
| Column | What it shows |
|--------|--------------|
| **ID** | Session identifier (starts with `pbs_`) |
| **Open** | Whether the browser is currently running |
| **Occupied** | Whether a task or workflow is actively using the session |
| **Started** | When the session launched |
| **Timeout** | How long the session stays open before auto-closing |
| **CDP URL** | Chrome DevTools Protocol address for direct connection |
---
## Creating a session
Click the **Create Browser Session** button at the top of the Browsers page. A panel opens with the following settings:
<Frame>
<img src="/images/cloud/create-session-drawer.png" alt="Create Browser Session panel showing fields for timeout, proxy location, browser type, extensions, and browser profile" width="300" />
</Frame>
| Setting | What it does |
|---------|-------------|
| **Timeout** | How long the session stays open, in minutes. Min: 5, max: 1440 (24 hours). Default: 60. |
| **Proxy Location** | Route the browser through a residential proxy in a specific country. Default: US. |
| **Browser Type** | Chrome (default, widest compatibility) or Microsoft Edge. |
| **Extensions** | Optional add-ons: **Ad Blocker** (removes overlay ads that interfere with clicks) and **Captcha Solver** (auto-solves CAPTCHAs). |
| **Browser Profile** | Load a saved [browser profile](/cloud/browser-management/browser-profiles) to restore cookies and login state from a previous session. |
Click **Create** to launch the session. It appears in the table within a few seconds.
<Warning>
Sessions bill while open, even when idle. Set the timeout to match your use case and close sessions when you're done.
</Warning>
---
## Viewing a live session
Click any session row in the table to open its detail page. The detail page has three tabs:
### Stream
A live view of the browser. You can watch pages load and actions fire in real time.
Click on <kbd>Take Control</kbd> to interact with the browser yourself. Use this to log in, dismiss a popup, solve a CAPTCHA.
<img src="/images/cloud/session-detail-stream.png" alt="Browser session detail page with the Stream tab active, showing a live browser view and session controls" />
### Recordings
Video recordings of activity in the session. Each recording captures what happened during a task or workflow that ran inside this session.
### Downloads
Files downloaded by the browser during the session. Click any file to save it locally.
<img src="/images/cloud/browser-session-downloads.png" alt="Browser session download page with the preview or download links for anything downloaded during the session" />
---
## Using a session with a task
Once you have a running session, you can run tasks inside it from the [Discover page](/cloud/getting-started/run-a-task):
1. Copy the session ID (`pbs_...`) from the Browsers page
2. Go to the **Discover** page
3. Click the **gear icon** to open Advanced Settings
4. Paste the ID into the **Browser Session ID** field
5. Write your prompt and run
The task executes inside your existing session. Cookies, login state, and page context from previous tasks are still there.
<img src="/images/cloud/discover-session-id-field.png" alt="Advanced settings panel on the Discover page with a pbs_ session ID entered in the Browser Session ID field" />
---
## Using a session with a workflow
You can also run workflows inside an existing session:
1. Copy the session ID from the Browsers page
2. Go to **Workflows** and click **Run** on your workflow
3. On the parameters page, scroll to **Run Settings**
4. Paste the ID into the **Browser Session ID** field
5. Click **Run Workflow**
The workflow picks up where the browser left off — same page, same cookies, same form data.
<img src="/images/cloud/workflow-run-session-id-field.png" alt="Workflow run settings panel with a pbs_ session ID entered in the Browser Session ID field" />
---
## Closing a session
From the session detail page, click **Stop**. A confirmation dialog appears. The browser shuts down immediately and the session moves to `closed` status.
<img src="/images/cloud/session-stop-confirmation.png" alt="Stop browser session confirmation dialog with Cancel and Stop buttons" />
Sessions also close automatically when their timeout expires, **even if a task is still running**. Set timeouts with enough margin for your longest expected task.
<Warning>
Closed sessions cannot be reopened. If you need the same login state later, create a [browser profile](/cloud/browser-management/browser-profiles) before closing.
</Warning>
---
## When to use sessions vs workflows
If your steps run back-to-back with no pauses between them, a [workflow](/cloud/building-workflows/build-a-workflow) is more efficient. Workflow blocks share one browser instance with no inter-step startup cost.
Sessions are the right choice when you need:
- **Pauses between steps** — review results, get approval, or wait for external input before continuing
- **Human-in-the-loop** — interact with the browser yourself between automated steps
- **Ad-hoc sequences** — run different tasks one after another without pre-building a workflow
---
## Sessions vs profiles
| | Browser Session | Browser Profile |
|---|----------------|-----------------|
| **What it is** | Live browser instance | Saved snapshot of browser state |
| **Lifetime** | Minutes to hours | Days to months |
| **State** | Current page, cookies, open connections | Cookies, localStorage, session files |
| **Billing** | Charged while open | No cost when not in use |
| **Best for** | Back-to-back tasks, human-in-the-loop | Repeated logins, scheduled workflows |
<Tip>
Use a session when you need a live browser right now. Use a [profile](/cloud/browser-management/browser-profiles) when you want to save login state and reuse it later.
</Tip>
---
## What's next
<CardGroup cols={2}>
<Card
title="Browser Profiles"
icon="floppy-disk"
href="/cloud/browser-management/browser-profiles"
>
Save login state and reuse it across sessions
</Card>
<Card
title="Browser Sessions (API)"
icon="code"
href="/optimization/browser-sessions"
>
Create and manage sessions programmatically
</Card>
</CardGroup>

View file

@ -269,6 +269,7 @@ The **Run** button also saves before starting execution.
| **Save** (disk icon) | Save the current workflow definition |
| **Template** (bookmark icon) | Toggle whether this workflow is saved as an organization template |
| **History** (clock icon) | View past runs of this workflow |
| **Schedule** (clock icon) | Create or manage recurring schedules for this workflow (see [Create a Schedule](/cloud/building-workflows/scheduling)) |
| **Parameters** | Open the parameters panel (see [Workflow Parameters](/cloud/building-workflows/add-parameters)) |
| **Run** (play icon) | Save and start a new execution (see [Running Workflows](/cloud/building-workflows/run-a-workflow)) |
@ -298,4 +299,11 @@ The **Run** button also saves before starting execution.
>
Execute workflows and track results
</Card>
<Card
title="Schedule This Workflow"
icon="calendar"
href="/cloud/building-workflows/scheduling"
>
Set up recurring cron-based execution
</Card>
</CardGroup>

View file

@ -6,6 +6,9 @@ slug: cloud/configure-blocks
Workflows are built from blocks. Each block performs one specific operation: navigating a page, extracting data, making an API call, or branching logic. This page covers every block type available in the [editor](/cloud/building-workflows/build-a-workflow), grouped by category.
<Frame caption="To add a block in your workflow, click the **+** button —> click on Add block —> select the block type from the menu.">
<img src="/images/cloud/add-block-ui.png" alt="how to add a block to a workflow in Skyvern" />
</Frame>
## Quick reference
@ -79,6 +82,10 @@ Additional fields in **Advanced Settings** (Skyvern 1.0 only):
*Plus [common browser fields](#common-fields).*
<Frame>
<img src="/images/cloud/br_task_block.png" alt="Browser Task block configuration with URL and prompt fields" />
</Frame>
<Tip>
Browser Task is the recommended block for most browser automation. Use it for anything from form filling to multi-page navigation. Include your success criteria directly in the prompt so the AI knows when it's done.
</Tip>
@ -112,6 +119,10 @@ Extract structured data from the current page using AI.
*Plus [common browser fields](#common-fields).*
<Frame>
<img src="/images/cloud/extract_block.png" alt="Extraction block configuration with Data Extraction Goal and Data Schema fields" />
</Frame>
### Login
Authenticate to a website using stored credentials. Pre-filled with a default login goal that handles common login flows including 2FA.
@ -130,6 +141,10 @@ Additional fields in **Advanced Settings:**
*Plus [common browser fields](#common-fields).*
<Frame>
<img src="/images/cloud/login_block.png" alt="Login block configuration with URL, Login Goal, and Credential fields" />
</Frame>
### Go to URL
Navigate the browser directly to a specific URL without AI interaction.
@ -138,6 +153,10 @@ Navigate the browser directly to a specific URL without AI interaction.
|-------|------|-------------|
| **URL** | text | **(Required)** The URL to navigate to. Supports [parameter references](/cloud/building-workflows/add-parameters#referencing-parameters-in-blocks). |
<Frame>
<img src="/images/cloud/url_block.png" alt="Go to URL block configuration with URL field" />
</Frame>
### Print Page
Print the current browser page to a PDF file. The PDF is saved as a downloadable artifact.
@ -155,6 +174,10 @@ Additional fields in **Advanced Settings:**
| **Custom Filename** | text | Custom name for the generated PDF |
| **Landscape** | boolean | Use landscape orientation |
<Frame>
<img src="/images/cloud/print_block.png" alt="Print Page block configuration with page format, print background, and headers & footers options" />
</Frame>
---
## Data and Extraction
@ -169,6 +192,10 @@ Send text to the LLM for processing without browser interaction. Useful for summ
| **Model** | selector | Which LLM model to use |
| **Data Schema** | JSON | Expected output structure. Enable the checkbox to reveal the editor. Click **Generate with AI** to auto-suggest a schema. |
<Frame>
<img src="/images/cloud/text_block.png" alt="Text Prompt block configuration with Prompt, Model, and Data Schema fields" />
</Frame>
### File Parser
Parse PDFs, CSVs, Excel files, and images to extract structured data.
@ -179,6 +206,10 @@ Parse PDFs, CSVs, Excel files, and images to extract structured data.
| **Data Schema** | JSON | Schema for the extracted output. Enable the checkbox to reveal the editor. |
| **Model** | selector | Which LLM model to use |
<Frame>
<img src="/images/cloud/file_parse_block.png" alt="File Parser block configuration with File URL, Data Schema, and Model fields" />
</Frame>
---
## Control Flow
@ -196,7 +227,9 @@ Inside loop blocks, use these [reserved variables](/cloud/building-workflows/add
- `{{ current_value }}`: the current item
- `{{ current_index }}`: the iteration number (0-based)
<Frame>
<img src="/images/cloud/block-loop-config.png" alt="Loop block configuration" />
</Frame>
### Conditional
@ -212,7 +245,9 @@ Branch the workflow based on conditions. The UI shows branches as tabs (A, B, C,
{{ extraction_output.price > 100 }}
```
<img src="/images/cloud/block-conditional-config.png" alt="Conditional block configuration" />
<Frame>
<img src="/images/cloud/conditional_block.png" alt="Conditional block configuration with branch expressions" />
</Frame>
### AI Validation
@ -230,6 +265,10 @@ Additional fields in **Advanced Settings:**
| **Error Code Mapping** | JSON | Custom error codes for failure reasons |
| **Input Parameters** | select | Parameters to evaluate |
<Frame>
<img src="/images/cloud/ai_val_block.png" alt="AI Validation block configuration with Complete if and Terminate if fields" />
</Frame>
### Code
Execute custom Python code. Input parameters are available as global variables. Top-level variables in your code become the block's output.
@ -239,6 +278,10 @@ Execute custom Python code. Input parameters are available as global variables.
| **Code** | code editor | Python code to execute |
| **Input Parameters** | select | Parameters available as globals in the code |
<Frame>
<img src="/images/cloud/code_block.png" alt="Code block configuration with Python code editor and Input Parameters field" />
</Frame>
### Wait
Pause workflow execution for a specified duration.
@ -247,6 +290,10 @@ Pause workflow execution for a specified duration.
|-------|------|-------------|
| **Wait in Seconds** | number | Seconds to wait (0300) |
<Frame>
<img src="/images/cloud/wait_block.png" alt="Wait block configuration with Wait in Seconds field" />
</Frame>
---
## Files
@ -269,6 +316,10 @@ Additional fields in **Advanced Settings:**
*Plus [common browser fields](#common-fields).*
<Frame>
<img src="/images/cloud/download_block.png" alt="File Download block configuration with URL, Download Goal, and Download Timeout fields" />
</Frame>
### Cloud Storage Upload
Upload downloaded files to S3 or Azure Blob Storage.
@ -299,6 +350,10 @@ Upload downloaded files to S3 or Azure Blob Storage.
</Accordion>
<Frame>
<img src="/images/cloud/cloud_block.png" alt="Cloud Storage Upload block configuration with Storage Type and Folder Path fields" />
</Frame>
---
## Communication
@ -314,6 +369,10 @@ Send an email notification, optionally with file attachments from previous block
| **Body** | text | Email body. Supports [parameter references](/cloud/building-workflows/add-parameters#referencing-parameters-in-blocks). |
| **File Attachments** | text | Path to files to attach |
<Frame>
<img src="/images/cloud/email_block.png" alt="Send Email block configuration with Recipients, Subject, Body, and File Attachments fields" />
</Frame>
### HTTP Request
Make an API call to an external service. Click **Import cURL** in the block header to populate fields from a cURL command.
@ -336,6 +395,10 @@ Additional fields in **Advanced Settings:**
| **Save Response as File** | boolean | Save the response body as a downloadable file |
| **Download Filename** | text | Filename for the saved response (shown when Save Response as File is enabled) |
<Frame>
<img src="/images/cloud/http_block.png" alt="HTTP Request block configuration with Method, URL, Headers, and Body fields" />
</Frame>
### Human Interaction
Pause the workflow and request human input. Optionally sends an email notification to reviewers.
@ -359,3 +422,7 @@ Additional fields in **Advanced Settings:**
|-------|------|-------------|
| **Positive Button Label** | text | Label for the approval action (default: "Approve") |
| **Negative Button Label** | text | Label for the rejection action (default: "Reject") |
<Frame>
<img src="/images/cloud/human_block.png" alt="Human Interaction block configuration with Instructions For Human and Timeout fields" />
</Frame>

View file

@ -11,8 +11,13 @@ Once you've built and saved a workflow, you can run it from the editor or the wo
Two ways to start:
1. **From the editor**: Click the **Run** button (play icon) in the header bar. The editor saves your workflow first.
<img src="/images/cloud/run-wf-1.png" alt="Workflow editor with the Run button highlighted in the header bar" />
2. **From the workflows list**: Click the **play icon** on the workflow's row.
<img src="/images/cloud/run-wf-2.png" alt="Workflows list page with play icon to start a run from the list" />
Both take you to the parameters page where you fill in runtime values before the run starts.
@ -20,6 +25,7 @@ Both take you to the parameters page where you fill in runtime values before the
The parameters page shows all [workflow parameters](/cloud/building-workflows/add-parameters) defined in the editor. Fill in a value for each one, or leave them at their defaults. Parameters without defaults must be filled in before running.
<img src="/images/cloud/run-wf-3.png" alt="Parameters page showing input fields and settings before starting a workflow run" />
## Run settings
@ -33,6 +39,13 @@ Below the parameters, you can configure settings for this specific run:
| **Extra HTTP Headers** | Custom headers included in all browser requests (JSON) |
| **CDP Address** | Connect to your own browser for local development |
<Frame>
<img src="/images/cloud/run-wf-6.png" alt="Run settings showing webhook callback URL, proxy location, and AI fallback options" />
</Frame>
<br/>
<Frame>
<img src="/images/cloud/run-wf-7.png" alt="Advanced run settings showing browser session ID, browser address, extra HTTP headers, and max screenshot scrolls" />
</Frame>
---
## Monitoring a run
@ -68,6 +81,8 @@ After a run completes, open it from **Runs** in the sidebar. The run detail page
- **Recording**: Full video replay of the browser session
- **Parameters**: The values you submitted for this run
<img src="/images/cloud/run-wf-8.png" alt="Completed workflow run showing extracted data, output tabs, and block timeline" />
See [Reviewing results](/cloud/getting-started/monitor-a-run#reviewing-results) for details on the Actions, Recording, Parameters, and Diagnostics tabs.
---
@ -83,7 +98,10 @@ Click the **Cancel** button during execution. A confirmation dialog appears. The
Access past runs from two places:
1. **Runs** in the left sidebar: shows all runs across all workflows
2. **History** button (clock icon) in the workflow editor: shows runs for this specific workflow
<img src="/images/cloud/run-wf-4.png" alt="Run History page in the sidebar showing all past runs with status and duration" />
2. Go to the **History** button (clock icon) in the workflow editor: shows runs for this specific workflow
<img src="/images/cloud/run-wf-5.png" alt="Past Runs view for a specific workflow showing run statuses and timestamps" />
---

View file

@ -0,0 +1,134 @@
---
title: Schedule a Workflow
subtitle: Set up recurring cron-based execution from the UI
slug: cloud/scheduling
---
You can schedule any saved workflow to run automatically on a recurring basis. Schedules are configured with a cron expression and timezone, and Skyvern triggers the workflow at each interval.
Schedules are created as **active** by default: they start triggering runs immediately after creation.
When creating a schedule you'll fill in these fields:
- **Workflow** (required): The workflow to run. When creating from the workflow editor this is pre-selected.
- **Cron Expression** (required): A standard 5-field cron expression that defines how often the workflow runs (e.g., `0 9 * * 1-5` for every weekday at 9 AM). The UI validates the expression in real time and shows a human-readable translation. Quick presets are available for common patterns like hourly, daily, and weekly.
- **Timezone** (required): The IANA timezone the cron expression is evaluated in (e.g., `America/New_York`). Defaults to your browser's local timezone. The dropdown is searchable by city name.
- **Name**: A human-readable label to identify this schedule in the table and run history.
- **Description**: Optional notes about what this schedule does or why it exists.
<Warning>
The minimum interval between runs is **5 minutes**. Expressions that resolve to shorter intervals are rejected.
</Warning>
---
## Creating a schedule
<Tabs>
<Tab title="From the Schedules page">
<Steps>
<Step title="Open the Schedules page">
Click [**Schedules**](https://app.skyvern.com/schedules/) (calendar icon) in the left sidebar.
</Step>
<Step title="Click Create Schedule">
Click <kbd>Create Schedule</kbd> in the top right corner.
<img src="/images/cloud/create-schedule-button.png" alt="Schedules page with Create Schedule button in the top right corner" />
</Step>
<Step title="Select a workflow and configure the schedule">
Choose the workflow you want to schedule from the dropdown. Only saved workflows appear in this list. Then fill in the cron expression, timezone, and optionally a name and description. Check the **Next Scheduled Runs** preview to verify the upcoming execution times match your expectations.
<Frame>
<img src="/images/cloud/create-sched-form.png" alt="Schedule creation form with workflow dropdown, cron expression, timezone, name, and description fields" width="300" />
</Frame>
</Step>
<Step title="Create">
Click **Create Schedule**. The schedule starts in an **Active** state and triggers at the next scheduled time.
</Step>
</Steps>
</Tab>
<Tab title="From the workflow editor">
<Steps>
<Step title="Open the workflow">
Navigate to [**Workflows**](https://app.skyvern.com/workflows/) in the sidebar and click the workflow you want to schedule.
</Step>
<Step title="Click the Schedule button">
In the editor header bar, click the <kbd>Schedule</kbd> button (clock icon).
<img src="/images/cloud/wf-page-sched.png" alt="Workflow editor page with Schedule button (clock icon) in the header bar" />
</Step>
<Step title="Click Add and configure the schedule">
A panel opens showing any existing schedules for this workflow. Click <kbd>Add</kbd> to create a new one. Fill in the cron expression, timezone, and optionally a name and description. The workflow is already selected.
<Frame>
<img src="/images/cloud/create-sched-form.png" alt="Schedule creation form with cron expression, timezone, name, and description fields" width="300" />
</Frame>
</Step>
<Step title="Create">
Click **Create Schedule**. The schedule appears in the panel and starts immediately.
</Step>
</Steps>
</Tab>
</Tabs>
---
## The Schedules page
Click **Schedules** in the left sidebar to see every schedule across all workflows.
Each row shows:
| Column | What it shows |
|--------|--------------|
| **Workflow** | The workflow this schedule triggers |
| **Name** | Schedule name |
| **Schedule** | Human-readable cron description (e.g., "Every weekday at 9:00 AM") |
| **Next Run** | Next execution time in your local timezone |
| **Status** | **Active** (green) or **Paused** (gray) |
Use the search bar to filter by schedule name or workflow title. The status filter narrows to Active or Paused schedules.
---
## Editing a schedule
Click a schedule row to open its detail page.
<img src="/images/cloud/sched-page.png" alt="Schedule detail page showing configuration, details, and upcoming runs sections" />
On this page, you can:
- **Edit the schedule configuration**: Click the **Edit** button to change the frequency, timezone, cron expression, name, and description. Changes take effect immediately and the **Upcoming Runs** preview updates to reflect the new configuration.
- **View schedule details**: See when the schedule was created and last modified.
- **Preview upcoming runs**: Check the next 5 scheduled execution times in the **Upcoming Runs** section.
- **Pause or resume**: Toggle the **Status** switch to pause the schedule. Paused schedules keep their configuration and can be resumed at any time: no runs are triggered while paused.
- **Delete the schedule**: Use the delete button (trash icon) in the header. Deletion is permanent. Runs already triggered by the schedule are not affected.
<Info>
To change which workflow a schedule runs, delete the schedule and create a new one for the target workflow.
</Info>
---
## What's next
<CardGroup cols={3}>
<Card
title="Scheduling API"
icon="code"
href="/multi-step-automations/scheduling-workflows"
>
Create and manage schedules programmatically
</Card>
<Card
title="Run History"
icon="clock-rotate-left"
href="/cloud/viewing-results/run-history"
>
View and filter results from scheduled runs
</Card>
<Card
title="Cost Control"
icon="gauge"
href="/optimization/cost-control"
>
Manage costs for recurring automations
</Card>
</CardGroup>

View file

@ -0,0 +1,9 @@
---
title: Core Concepts
subtitle: The building blocks of Skyvern automations
slug: cloud/getting-started/core-concepts
---
import CoreConceptsContent from "/snippets/core-concepts-content.mdx";
<CoreConceptsContent />

View file

@ -0,0 +1,9 @@
---
title: What is Skyvern?
subtitle: AI-powered browser automation for any website
slug: cloud/getting-started/introduction
---
import IntroductionContent from "/snippets/introduction-content.mdx";
<IntroductionContent />

View file

@ -43,17 +43,30 @@ Once a task reaches a final state, the live stream closes. Open the run from **R
## Taking control of the browser
The **Take Control** button lets you interact directly with the browser. This is useful when:
- A CAPTCHA appears that the AI can't solve
- The site has an unusual login flow
- You need to navigate past an unexpected popup
While a task is running, you can take over the browser and interact with the page directly — just like you would in your own Chrome window.
Click **Take Control** to start interacting. Your mouse and keyboard input goes directly to the browser. Click **Stop Controlling** to hand control back to the AI.
### How it works
1. Click the **Take Control** button in the live browser panel.
2. The AI agent pauses and you get full mouse and keyboard control of the browser.
3. Interact with the page — type into fields, click buttons, solve CAPTCHAs, dismiss popups.
4. Click **Release Control** to hand the browser back to the AI. It picks up from the current page state and continues the task.
### When to use it
- **CAPTCHAs** — solve a CAPTCHA the AI can't get past, then release control
- **Unusual login flows** — complete a multi-factor or SSO login that the agent doesn't handle
- **Unexpected popups or modals** — dismiss cookie banners, age gates, or promotional overlays
- **Mid-task corrections** — navigate to the right page or fix a form field before letting the agent continue
<Warning>
Taking control pauses the AI agent. Remember to release control so the agent can resume.
Taking control pauses the AI agent. The task timer keeps running, so release control promptly to avoid timeouts.
</Warning>
<Tip>
You can take and release control multiple times during a single run. Each time, the agent resumes from whatever state you left the browser in.
</Tip>
---
## Stopping a running task

View file

@ -6,6 +6,10 @@ subtitle: Navigate the Skyvern Cloud dashboard
Skyvern Cloud ([app.skyvern.com](https://app.skyvern.com)) lets you automate any website from your browser. Describe what you want in plain English, watch an AI-powered browser do it live, and get structured results back. No code required.
<Tip>
New to Skyvern? The [homepage](/) covers how Skyvern works under the hood, built-in capabilities, and the eight core concepts behind every automation.
</Tip>
<Note>
Looking to integrate Skyvern into your own app? See the [API Quickstart](/getting-started/quickstart) instead.
</Note>
@ -27,7 +31,7 @@ Where you create and monitor automations.
| **Discover** | Run one-off tasks. Type your instructions and target URL into a single prompt, pick an engine, and hit send. |
| **Workflows** | Build multi-step automations with the visual workflow editor. Add loops, conditionals, and data passing between steps. |
| **Runs** | Execution history for every task and workflow. Filter by status, drill into any run to see actions, recordings, and extracted data. |
| **Browsers** | Active browser sessions. Useful for persistent sessions that keep login state across tasks. |
| **Browsers** | Active [browser sessions](/cloud/browser-management/browser-sessions). Keep a browser open across multiple tasks to preserve login state, cookies, and page context. Load a [browser profile](/cloud/browser-management/browser-profiles) to restore previously saved state. |
### Agents

View file

@ -22,6 +22,7 @@ The preferred method. Skyvern generates valid 6-digit codes on demand during log
<Steps>
<Step title="Create a password credential">
Go to the Credentials page and create a new password credential.
<img src="/images/cloud/totp-setup-1.png" alt="Credentials page showing password credentials with the Add button highlighted" />
</Step>
<Step title="Expand Two-Factor Authentication">
Below the password fields, click the **Two-Factor Authentication** accordion.
@ -31,6 +32,7 @@ The preferred method. Skyvern generates valid 6-digit codes on demand during log
</Step>
<Step title="Paste your TOTP secret key">
Enter the secret key into the **Authenticator Key** field and click **Save**.
<img src="/images/cloud/totp-setup-2.png" alt="Add Credential dialog with Authenticator App selected and the Authenticator Key field" />
</Step>
</Steps>
@ -63,6 +65,7 @@ When a site sends 2FA codes via email or SMS, someone (or something) needs to de
<Steps>
<Step title="Create a password credential">
Go to the Credentials page and create a new password credential.
<img src="/images/cloud/totp-setup-1.png" alt="Credentials page showing password credentials with the Add button highlighted" />
</Step>
<Step title="Expand Two-Factor Authentication">
Below the password fields, click the **Two-Factor Authentication** accordion.
@ -72,6 +75,7 @@ When a site sends 2FA codes via email or SMS, someone (or something) needs to de
</Step>
<Step title="Enter the identifier">
Provide the **email address** or **phone number** that receives the codes. For Email, this auto-fills from the Username field.
<img src="/images/cloud/totp-setup-3.png" alt="Add Credential dialog with Email selected and the TOTP Identifier field" />
</Step>
</Steps>

View file

@ -27,10 +27,29 @@ Each row is one run. The columns:
| **Run ID** | Workflow run (`wr_...`) or task (`tsk_...`) identifier. Hover for the full ID. |
| **Detail** | Workflow title or task description. A lightning bolt icon means code generation was used. |
| **Status** | Color-coded badge — green (completed), yellow (running/queued/created), red (failed/timed_out), orange (terminated/canceled). |
| **Trigger** | How the run was started: **Manual**, **API**, or **Scheduled** (calendar icon). |
| **Created At** | Start time in your local timezone. Hover for UTC. |
Workflow runs have a **settings icon** in the last column. Click it to expand the row and see the parameters passed to that run. If your search matches something inside the parameters, the row auto-expands so you can see the match in context.
## Trigger type
Every run has a trigger type that indicates how it was started:
| Trigger type | Description |
|-------------|-------------|
| **Manual** | Started by clicking **Run** in the Cloud UI |
| **API** | Started via the REST API or SDK |
| **Scheduled** | Started automatically by a [schedule](/cloud/building-workflows/scheduling) |
Scheduled runs display a **calendar icon badge** next to the run ID. Click the run to see the schedule name and cron expression in the run details.
### Filtering by trigger type
Use the **Trigger Type** dropdown filter to show only runs of a specific type. For example, select **Scheduled** to see all runs triggered by cron schedules. This filter can be combined with the status filter and search.
---
## Opening a run
Click any row to jump to its [detail page](/cloud/viewing-results/run-details). Hold **Ctrl** (or **Cmd** on Mac) to open in a new tab.

View file

@ -100,13 +100,12 @@ The AI will automatically fill the credential fields when it encounters a login
</Accordion>
<Accordion title="Can I run workflows on a schedule?">
Skyvern doesn't have built-in scheduling yet. Use external schedulers like:
Yes. Skyvern has built-in cron-based scheduling for workflows. You define a cron expression and timezone, and Skyvern triggers the workflow automatically at each interval.
- **Cron jobs** — Call the Skyvern API on a schedule
- **Zapier/Make** — Trigger workflows from automation platforms
- **Your backend** — Integrate scheduling into your application
- **Cloud UI** — Create and manage schedules from the [Schedules page](/cloud/building-workflows/scheduling)
- **API** — Use the [scheduling API](/multi-step-automations/scheduling-workflows) to create, update, enable/disable, and delete schedules programmatically
Set up a [webhook](/going-to-production/webhooks) to get notified when scheduled runs complete.
Scheduled runs appear in [Run History](/cloud/viewing-results/run-history) with a calendar icon badge. Set up a [webhook](/going-to-production/webhooks) to get notified when scheduled runs complete.
</Accordion>
<Accordion title="Why isn't my workflow using the parameter values I passed?">

View file

@ -42,16 +42,22 @@
"pages": ["index"]
},
{
"tab": "Documentation",
"tab": "Getting Started",
"groups": [
{
"group": "Getting Started",
"pages": [
"getting-started/introduction",
"getting-started/core-concepts",
"getting-started/quickstart",
"getting-started/core-concepts"
"getting-started/ai-agents-quickstart"
]
},
}
]
},
{
"tab": "Developers",
"groups": [
{
"group": "Running Automations",
"pages": [
@ -66,7 +72,8 @@
"multi-step-automations/build-a-workflow",
"multi-step-automations/workflow-blocks-reference",
"multi-step-automations/file-operations",
"multi-step-automations/workflow-parameters"
"multi-step-automations/workflow-parameters",
"multi-step-automations/scheduling-workflows"
]
},
{
@ -132,7 +139,15 @@
"cloud/building-workflows/manage-workflows",
"cloud/building-workflows/add-parameters",
"cloud/building-workflows/configure-blocks",
"cloud/building-workflows/run-a-workflow"
"cloud/building-workflows/run-a-workflow",
"cloud/building-workflows/scheduling"
]
},
{
"group": "Re-using Browsers",
"pages": [
"cloud/browser-management/browser-sessions",
"cloud/browser-management/browser-profiles"
]
},
{
@ -173,40 +188,6 @@
}
]
},
{
"tab": "SDKs",
"groups": [
{
"group": "Python SDK",
"pages": [
"sdk-reference/overview",
"sdk-reference/tasks",
"sdk-reference/workflows",
"sdk-reference/browser-sessions",
"sdk-reference/browser-profiles",
"sdk-reference/credentials",
"sdk-reference/helpers",
"sdk-reference/error-handling",
"sdk-reference/complete-reference"
]
},
{
"group": "TypeScript SDK",
"pages": [
"ts-sdk-reference/overview",
"ts-sdk-reference/tasks",
"ts-sdk-reference/workflows",
"ts-sdk-reference/browser-sessions",
"ts-sdk-reference/browser-profiles",
"ts-sdk-reference/credentials",
"ts-sdk-reference/helpers",
"ts-sdk-reference/browser-automation",
"ts-sdk-reference/error-handling",
"ts-sdk-reference/complete-reference"
]
}
]
},
{
"tab": "Cookbooks",
"pages": [
@ -223,6 +204,50 @@
{
"tab": "API Reference",
"openapi": "api-reference/openapi.json"
},
{
"tab": "SDK Reference",
"versions": [
{
"version": "Python",
"groups": [
{
"group": "Python SDK",
"pages": [
"sdk-reference/overview",
"sdk-reference/tasks",
"sdk-reference/workflows",
"sdk-reference/browser-sessions",
"sdk-reference/browser-profiles",
"sdk-reference/credentials",
"sdk-reference/helpers",
"sdk-reference/error-handling",
"sdk-reference/complete-reference"
]
}
]
},
{
"version": "TypeScript",
"groups": [
{
"group": "TypeScript SDK",
"pages": [
"ts-sdk-reference/overview",
"ts-sdk-reference/tasks",
"ts-sdk-reference/workflows",
"ts-sdk-reference/browser-sessions",
"ts-sdk-reference/browser-profiles",
"ts-sdk-reference/credentials",
"ts-sdk-reference/helpers",
"ts-sdk-reference/browser-automation",
"ts-sdk-reference/error-handling",
"ts-sdk-reference/complete-reference"
]
}
]
}
]
}
]
},

View file

@ -0,0 +1,212 @@
---
title: AI Agents Quickstart
subtitle: Give your AI coding assistant full browser automation in one command
slug: getting-started/ai-agents-quickstart
---
Skyvern's MCP server and Skills let AI assistants (Claude Code, Cursor, Windsurf, Codex) control browsers, extract data, and run workflows through natural language. This page gets you set up.
## Choose your path
<CardGroup cols={2}>
<Card title="Cloud (30 seconds)" icon="cloud">
Already have a Skyvern Cloud account? Jump to the [MCP config](#cloud-setup) for your client below.
</Card>
<Card title="Local (2 minutes)" icon="terminal">
Want to self-host? Run `skyvern quickstart` and it handles everything. See [local setup](#local-setup).
</Card>
</CardGroup>
## Cloud setup
Get your API key from [Settings](https://app.skyvern.com/settings) in the Skyvern dashboard, then pick your client.
<Tabs>
<Tab title="Claude Code">
```bash
claude mcp add-json skyvern '{"type":"http","url":"https://api.skyvern.com/mcp/","headers":{"x-api-key":"YOUR_SKYVERN_API_KEY"}}' --scope user
```
</Tab>
<Tab title="Cursor">
Add to `~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"Skyvern": {
"type": "streamable-http",
"url": "https://api.skyvern.com/mcp/",
"headers": {
"x-api-key": "YOUR_SKYVERN_API_KEY"
}
}
}
}
```
</Tab>
<Tab title="Windsurf">
Add to `~/.codeium/windsurf/mcp_config.json`:
```json
{
"mcpServers": {
"Skyvern": {
"type": "streamable-http",
"url": "https://api.skyvern.com/mcp/",
"headers": {
"x-api-key": "YOUR_SKYVERN_API_KEY"
}
}
}
}
```
</Tab>
<Tab title="Claude Desktop">
Add to your Claude Desktop config file:
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"Skyvern": {
"command": "npx",
"args": [
"mcp-remote",
"https://api.skyvern.com/mcp/",
"--header",
"x-api-key:YOUR_SKYVERN_API_KEY"
]
}
}
}
```
Requires Node.js >= 20 for [mcp-remote](https://www.npmjs.com/package/mcp-remote).
</Tab>
<Tab title="Codex">
Add to `~/.codex/config.toml`:
```toml
[mcp_servers.skyvern]
url = "https://api.skyvern.com/mcp/"
[mcp_servers.skyvern.http_headers]
x-api-key = "YOUR_SKYVERN_API_KEY"
```
</Tab>
</Tabs>
That's it. Ask your assistant to open a browser and navigate to any site.
<Tip>
Verify it works — paste this into your AI assistant: "Use Skyvern to create a browser session, go to news.ycombinator.com, and extract the title of the top post."
</Tip>
## Local setup
`skyvern quickstart` is the one-command path for self-hosting. Run it from your project or repo root:
```bash
pip install skyvern
skyvern quickstart
```
The interactive wizard will:
- Set up a PostgreSQL database (detects local install or uses Docker)
- Configure your LLM provider (OpenAI, Anthropic, Gemini, Ollama, etc.)
- Write MCP config for your AI client (Claude Code, Cursor, Windsurf)
- Install bundled Skills into `.claude/skills/` (Claude Code only)
If you only want to configure without starting services, use `skyvern init` instead, then start the server manually:
```bash
skyvern run server
```
For the full local setup guide including browser modes and environment configuration, see [Run with a local browser](/getting-started/quickstart#run-with-a-local-browser).
## What gets installed
### MCP Server
33 tools across 6 categories that your AI assistant calls automatically:
| Category | What it does |
|----------|-------------|
| **Browser sessions** | Open, manage, and reuse browser instances |
| **Browser actions** | Navigate, click, type, scroll — via natural language or selectors |
| **Data extraction** | Pull structured data from pages, take screenshots, run JavaScript |
| **Validation** | AI-powered checks on page state (returns true/false) |
| **Credentials** | Look up stored credentials for login flows |
| **Workflows** | Build and execute multi-step automations |
Full reference: [MCP Server](/integrations/mcp)
### Skills
Markdown files that teach your AI assistant Skyvern patterns. Skills are **not** MCP tools — they're documentation your agent reads for context on how to use the CLI and API effectively.
```bash
skyvern skill list # List available skills
skyvern skill show skyvern # Render a skill in the terminal
```
Full reference: [CLI & Skills](/integrations/cli#skills)
## Try it
Paste any of these into your AI assistant:
| Prompt | What happens |
|--------|-------------|
| "Use Skyvern to go to news.ycombinator.com and extract the top 5 post titles as JSON" | Creates a session, navigates, extracts structured data, closes |
| "Create a Skyvern browser session and take a screenshot of example.com" | Session creation + screenshot capture |
| "Run a Skyvern workflow that navigates to [url] and fills out the contact form with name 'Jane Doe' and email 'jane@example.com'" | Multi-step workflow with form filling |
| "List my Skyvern credentials and use one to log into [site]" | Credential lookup + authenticated login |
## Tips for better results
- Tell your agent to **use Skyvern MCP** explicitly when you want browser automation
- Be specific: "extract the price from the product page" beats "get info from the site"
- For multi-step flows, ask your agent to use **workflows** or **browser sessions** with persistent state
- Don't hardcode passwords — use `skyvern credential` to store them securely
- Use `--json` output when composing CLI commands in scripts
## Next steps
<CardGroup cols={3}>
<Card
title="Full MCP Reference"
icon="server"
href="/integrations/mcp"
>
All 33 tools, config options, local mode, and troubleshooting
</Card>
<Card
title="CLI & Skills"
icon="terminal"
href="/integrations/cli"
>
Browser commands, workflow management, and skill system
</Card>
<Card
title="API Quickstart"
icon="code"
href="/getting-started/quickstart"
>
Use the Python or TypeScript SDK directly
</Card>
</CardGroup>

View file

@ -4,390 +4,6 @@ subtitle: The building blocks of Skyvern automations
slug: getting-started/core-concepts
---
Skyvern has eight core concepts. Master these and you can build any browser automation.
import CoreConceptsContent from "/snippets/core-concepts-content.mdx";
---
## Tasks
A **Task** is a single automation job—think of it as asking Skyvern to do one thing. You provide a prompt describing the goal, and Skyvern navigates the browser to complete it.
```python
from skyvern import Skyvern
skyvern = Skyvern(api_key="YOUR_API_KEY")
result = await skyvern.run_task(
prompt="Find the top 3 posts on the front page",
url="https://news.ycombinator.com",
data_extraction_schema={
"type": "object",
"properties": {
"posts": {
"type": "array",
"items": {"type": "string"}
}
}
}
)
print(result.output) # {"posts": ["Post 1", "Post 2", "Post 3"]}
```
**Key properties:**
| Property | Description |
|----------|-------------|
| `prompt` | Natural language description of the goal (required) |
| `url` | Starting URL for the task |
| `data_extraction_schema` | JSON Schema defining expected output structure |
| `max_steps` | Maximum steps allowed (controls cost) |
| `engine` | AI model to use (see [Engines](#engines) below) |
| `browser_session_id` | Run in an existing browser session |
| `webhook_url` | Callback URL for async completion |
**Use Tasks for:** one-off automations, quick data extraction, prototyping before building a workflow.
When you need reusable, multi-step automations, use **Workflows** instead.
---
## Workflows
A **Workflow** is a reusable template composed of blocks. Unlike tasks, workflows can be versioned, shared across your team, and executed repeatedly with different parameters.
```mermaid
flowchart LR
A["Navigate<br/>Block"] --> B["Login<br/>Block"]
B --> C["Extract<br/>Block"]
C --> D["Download<br/>Block"]
subgraph WORKFLOW
direction LR
A
B
C
D
end
```
> **Parameters:** `{{search_query}}`, `{{max_price}}`
> Each block can reference outputs from previous blocks.
```python
result = await skyvern.run_workflow(
workflow_id="wpid_abc123",
parameters={
"search_query": "wireless headphones",
"max_price": 100
}
)
print(result.output)
```
Workflows support Jinja templating:
- Reference parameters: `{{search_query}}`
- Reference previous block outputs: `{{extract_block.product_name}}`
**Use Workflows for:** repeatable automations, multi-step processes, team-shared templates, complex logic with loops or conditionals.
See [Workflow Blocks](/multi-step-automations/workflow-blocks-reference) for the full block reference.
---
## Blocks
Blocks are the building units of workflows. Each block performs one specific task, and blocks execute sequentially—each can reference outputs from previous blocks.
### Navigation & Interaction
| Block | Purpose |
|-------|---------|
| **Navigation** | AI-guided navigation toward a goal—Skyvern figures out the clicks and inputs |
| **Action** | Perform a single specific action (click, type, select, upload) |
| **Go to URL** | Navigate directly to a specific URL |
| **Login** | Authenticate using stored credentials |
| **Wait** | Pause execution for a specified duration |
| **Human Interaction** | Pause for manual intervention (CAPTCHA, approval, etc.) |
### Data & Files
| Block | Purpose |
|-------|---------|
| **Extract** | Pull structured data from a page into JSON |
| **File Download** | Download files from websites |
| **File Upload** | Upload files to form fields |
| **File Parser** | Process PDFs, CSVs, and Excel files |
| **PDF Parser** | Specialized PDF text extraction |
### Logic & Control Flow
| Block | Purpose |
|-------|---------|
| **Conditional** | Branch workflow based on conditions (if/else) |
| **For Loop** | Repeat a sequence of blocks over a list |
| **Validation** | Assert conditions; halt workflow on failure |
| **Code** | Execute custom Python/Playwright scripts |
### Communication & Integration
| Block | Purpose |
|-------|---------|
| **HTTP Request** | Make API calls to external services |
| **Text Prompt** | Text-only LLM prompt (no browser interaction) |
| **Send Email** | Send email messages |
For detailed block configuration, see [Workflow Blocks](/workflows/workflow-blocks-details).
---
## Runs
Every time you execute a task or workflow, Skyvern creates a **Run** to track progress and store outputs.
```mermaid
flowchart LR
created --> queued --> running
running --> completed
running --> failed
running --> timed_out
running --> terminated
running --> canceled
```
```python
result = await skyvern.run_task(
prompt="Extract the main heading",
url="https://example.com"
)
print(result.run_id) # "tsk_abc123"
print(result.status) # "completed"
print(result.output) # {"heading": "Welcome"}
```
**Run identifiers:**
- Task runs: `tsk_*` prefix
- Workflow runs: `wr_*` prefix
**Run response fields:**
| Field | Description |
|-------|-------------|
| `run_id` | Unique identifier |
| `status` | Current lifecycle state |
| `output` | Extracted data (matches your schema) |
| `recording_url` | Video of the execution |
| `screenshot_urls` | Screenshots captured during execution |
| `downloaded_files` | Files retrieved (with URLs and checksums) |
| `failure_reason` | Error details if failed |
| `step_count` | Number of steps taken |
<Warning>
**Billing:** You're billed per step. A step is one AI decision + action cycle. Use `max_steps` to cap costs during development.
</Warning>
---
## Credentials
Credentials provide secure storage for authentication data. Skyvern encrypts credentials at rest and in transit, and injects them directly into the browser—**credentials are never sent to or seen by the LLM**.
```mermaid
flowchart TD
A["You store credentials"] --> B["Skyvern UI<br/>or API"]
B --> C["Encrypted Vault<br/>(or Bitwarden, 1Password)"]
C --> D["Login Block<br/>injects into<br/>browser fields"]
D --> E["LLM sees: 'login happened'<br/>LLM never sees: actual credentials"]
```
**Supported credential types:**
- Usernames and passwords
- TOTP codes (authenticator apps)
- Credit cards
**Credential sources:**
- **Skyvern** — Native encrypted storage
- **Bitwarden** — Sync from your Bitwarden vault
- **1Password** — Sync from your 1Password vault
- **Azure Key Vault** — Enterprise secret management
See [Credentials](/sdk-reference/credentials) for setup instructions.
---
## Browser Sessions & Profiles
Skyvern offers two ways to manage browser state across runs:
```mermaid
flowchart LR
subgraph SESSION ["Browser Session (live, temporary)"]
A["Active Browser<br/>Instance"]
end
subgraph PROFILE ["Browser Profile (saved, reusable)"]
B["Snapshot of state<br/>(cookies, storage)"]
end
A -- "save" --> B
B -- "restore" --> A
```
| | Browser Session | Browser Profile |
|---|---|---|
| **Lifetime** | Expires after timeout (max 24h) | Persists indefinitely |
| **Use case** | Real-time task chaining | Skip login on repeated runs |
| **Sharing** | Single use | Shared across team |
### Browser Sessions
A live browser instance that maintains state across multiple tasks. Use sessions when you need to chain tasks in real-time or allow human intervention.
```python
# Create a session (default: 60 minutes, max: 24 hours)
session = await skyvern.create_browser_session(timeout=120)
# Run tasks in the same session
await skyvern.run_task(
prompt="Log in with the test account",
url="https://example.com/login",
browser_session_id=session.browser_session_id
)
# Second task reuses the authenticated state
await skyvern.run_task(
prompt="Extract the account balance",
url="https://example.com/dashboard",
browser_session_id=session.browser_session_id
)
# Clean up
await skyvern.close_browser_session(
browser_session_id=session.browser_session_id
)
```
**Session limits:** 5 to 1,440 minutes (24 hours max). Default: 60 minutes.
### Browser Profiles
A saved snapshot of browser state. Unlike sessions, profiles persist indefinitely and can be reused across days or weeks—perfect for skipping login on repeated runs.
```python
# Create a profile from a completed run
profile = await skyvern.create_browser_profile(
name="my-authenticated-profile",
workflow_run_id=run.run_id
)
# Future runs restore the authenticated state
await skyvern.run_workflow(
workflow_id="wpid_extract_data",
browser_profile_id=profile.browser_profile_id
)
```
**What's saved:** Cookies, authentication tokens, local storage, session storage.
See [Browser Sessions](/optimization/browser-sessions) for details.
---
## Artifacts
Every run generates artifacts for observability, debugging, and audit trails.
```python
result = await skyvern.run_task(
prompt="Download the quarterly report",
url="https://example.com"
)
print(result.recording_url) # Full video of execution
print(result.screenshot_urls) # List of screenshot URLs
print(result.downloaded_files) # [{"url": "...", "checksum": "..."}]
```
| Artifact | Description |
|----------|-------------|
| **Recordings** | End-to-end video of the entire run |
| **Screenshots** | Captured after each action |
| **Downloaded files** | Files retrieved during execution |
| **Logs** | JSON-structured logs at step, task, and workflow levels |
| **HAR files** | HTTP Archive data for network debugging |
In the Skyvern UI, go to **Runs** → click a run → view the **Actions** and **Recording** tabs.
---
## Engines
Skyvern supports multiple AI engines for task execution:
| Engine | Description |
|--------|-------------|
| `skyvern-2.0` | Latest Skyvern model (default, recommended) |
| `skyvern-1.0` | Previous Skyvern model |
| `openai-cua` | OpenAI Computer Use Agent |
| `anthropic-cua` | Anthropic Computer Use Agent |
| `ui-tars` | UI-TARS model |
Specify the engine when running a task:
```python
from skyvern.schemas.runs import RunEngine
result = await skyvern.run_task(
prompt="Extract pricing data",
url="https://example.com",
engine=RunEngine.skyvern_v2
)
```
---
## Quick Reference
| I want to... | Use |
|--------------|-----|
| Run a one-off automation | [Task](#tasks) |
| Build a reusable multi-step process | [Workflow](#workflows) |
| Keep a browser open between tasks | [Browser Session](#browser-sessions) |
| Skip login on repeated runs | [Browser Profile](#browser-profiles) |
| Store secrets securely | [Credentials](#credentials) |
| Debug a failed run | [Artifacts](#artifacts) |
---
## Next Steps
<CardGroup cols={2}>
<Card
title="API Quickstart"
icon="rocket"
href="/getting-started/quickstart"
>
Run your first task in 5 minutes
</Card>
<Card
title="Build Workflows"
icon="diagram-project"
href="/multi-step-automations/build-a-workflow"
>
Create multi-step automations
</Card>
<Card
title="Workflow Blocks"
icon="cube"
href="/multi-step-automations/workflow-blocks-reference"
>
Full reference for all block types
</Card>
<Card
title="API Reference"
icon="code"
href="/api-reference"
>
Complete API documentation
</Card>
</CardGroup>
<CoreConceptsContent />

View file

@ -4,73 +4,6 @@ subtitle: AI-powered browser automation for any website
slug: getting-started/introduction
---
Skyvern automates anything you can do in a browser. You describe what you want in plain English, and Skyvern opens a real Chromium browser, looks at the page, and completes the task. No CSS selectors, no scripts, no maintenance when the site changes.
import IntroductionContent from "/snippets/introduction-content.mdx";
The reason this works is that Skyvern doesn't rely on hardcoded element paths. It screenshots the page, reads the DOM, and uses an LLM to decide what to do next, the same way a person would look at a screen and figure out where to click. That means it works on sites it's never seen before, and it keeps working when those sites redesign.
---
## The agent loop
Every Skyvern automation, whether you trigger it from the dashboard, the API, or a Zapier workflow, runs the same loop:
<img src="/images/skyvern-agent-loop.png" alt="Skyvern agent loop: Screenshot → Extract DOM → LLM reasons → Execute action → Goal check → Repeat" />
Each cycle through this loop is called a **step**. Understanding what happens in each step helps you write better prompts and debug runs when something goes wrong.
1. **Screenshot the viewport.** Skyvern captures what's currently on screen, giving the LLM visual context: where buttons are, what forms look like, whether a modal is blocking the page.
2. **Extract the DOM.** The visible page is scraped into a simplified tree of interactive elements (inputs, buttons, links, dropdowns) with their labels and positions. The LLM uses both the screenshot and the DOM together: the image shows layout and visual context, the DOM provides precise element identifiers that Playwright can target.
3. **LLM decides the next action.** The screenshot, DOM tree, and your original prompt go to the LLM. It picks which element to interact with and what to do: click, type, select, scroll, upload. If there's data to extract, it pulls that too.
4. **Playwright executes.** The action runs in a real Chromium browser. If credentials are needed, they're injected directly into the browser at this point. The LLM never sees passwords, TOTP codes, or credit card numbers.
5. **Check if the goal is met.** If not, loop back to step 1 and screenshot the now-changed page. If yes, return the results.
A typical task takes 2 to 10 steps. You can set `max_steps` to cap cost during development.
### The Planner-Agent-Validator system
For anything beyond simple single-page tasks, Skyvern 2.0 wraps the agent loop in a higher-level system with three components:
<img src="/images/skyvern-system-architecture.png" alt="Skyvern 2.0 architecture: Planner breaks prompt into sub-tasks, Task Agent executes them, Validator confirms completion" />
The **Planner** takes your prompt and breaks it into an ordered sequence of sub-tasks. If you say "log into the vendor portal, download all invoices from January, and save them as PDFs," the Planner sequences that into: navigate to login → authenticate → find the invoice section → loop through January invoices → download each one.
The **Task Agent** picks up each sub-task and runs the agent loop described above. It's focused on one thing at a time, which makes it more reliable than trying to handle a complex multi-page flow in a single pass.
The **Validator** checks whether the overall goal was actually met after the Task Agent finishes. If the Validator determines something was missed or went wrong, it sends feedback back to the Planner, which can re-sequence or retry. This closed-loop feedback is what makes Skyvern reliable on complex workflows where a single missed step would break the whole thing.
---
## What Skyvern handles for you
Most browser automation breaks down at authentication, CAPTCHAs, or dynamic content. Skyvern handles these natively so you don't have to build workarounds.
**Authentication and credentials.** You store passwords, TOTP secrets, and credit card numbers through the Credentials API or the dashboard. They're encrypted at rest and injected directly into browser fields during execution. The LLM orchestrates the login flow (finds the form, clicks submit, handles 2FA prompts) but never sees the actual credential values. Supports Bitwarden, 1Password, and Azure Key Vault as external sources.
**CAPTCHAs.** Automatically detected and solved during execution. You don't configure anything.
**Structured data extraction.** You can pass a JSON Schema defining exactly what fields you want, and Skyvern extracts typed data that conforms to it. Or you can skip the schema and let Skyvern infer structure from your prompt.
**File operations.** Download files from websites, upload documents to form fields, parse PDFs, CSVs, and Excel files. Downloaded files come back as signed URLs with checksums.
**Proxy and geolocation.** Residential proxies route through real IPs in 30+ countries. Set `proxy_location` per task when you need to appear from a specific region.
**Browser state persistence.** Sessions keep a live browser open across multiple tasks for up to 24 hours, useful when you need to chain tasks that share login state. Profiles snapshot cookies, auth tokens, and local storage into a reusable package you can restore on future runs, so you don't re-authenticate every time.
**Multi-step workflows.** When a single task isn't enough, Workflows let you chain blocks together: navigate, login, loop through a list, extract data, branch on conditions, send emails. Workflows are parameterized and version-controlled. You can build them visually in the dashboard or define them through the API.
---
## Get started
<CardGroup cols={2}>
<Card title="Automate from the dashboard" icon="window-maximize" href="/cloud/getting-started/overview">
Type what you want, watch it happen live, get results back. Build multi-step workflows with the visual editor. Connect to Zapier, Make, or n8n. No code required.
</Card>
<Card title="Integrate via API or SDK" icon="code" href="/getting-started/quickstart">
Install the Python or TypeScript SDK, get an API key, and run your first automation in 5 minutes.
</Card>
</CardGroup>
<Card title="Deploy on your own infrastructure" icon="server" href="/self-hosted/overview">
Skyvern is open-source. Run it with Docker, connect your own LLM keys, and keep all data on your network.
</Card>
<IntroductionContent />

View file

@ -178,7 +178,7 @@ if (run.status === "completed") {
```bash cURL
# Poll workflow run until terminal state
while true; do
RESPONSE=$(curl -s -X GET "https://api.skyvern.com/v1/workflow_runs/$WORKFLOW_RUN_ID" \
RESPONSE=$(curl -s -X GET "https://api.skyvern.com/v1/runs/$WORKFLOW_RUN_ID" \
-H "x-api-key: $SKYVERN_API_KEY")
STATUS=$(echo $RESPONSE | jq -r '.status')
@ -289,7 +289,7 @@ if (["failed", "terminated"].includes(run.status)) {
```
```bash cURL
RESPONSE=$(curl -s -X GET "https://api.skyvern.com/v1/workflow_runs/$WORKFLOW_RUN_ID" \
RESPONSE=$(curl -s -X GET "https://api.skyvern.com/v1/runs/$WORKFLOW_RUN_ID" \
-H "x-api-key: $SKYVERN_API_KEY")
STATUS=$(echo $RESPONSE | jq -r '.status')

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,014 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 KiB

View file

@ -139,7 +139,7 @@ mode: custom
<div className="sk-footer-cta-inner">
<p className="sk-footer-cta-text">
<span className="sk-mono" style={{ color: "var(--sk-blue)", marginRight: "8px" }}>●</span>
Open source · 20.8k+ GitHub stars
Open source · <img src="https://img.shields.io/github/stars/Skyvern-AI/skyvern?style=social" alt="GitHub stars" style={{ display: "inline", height: "1.2em", verticalAlign: "middle" }} />
</p>
<div className="sk-footer-cta-links">
<a href="https://github.com/Skyvern-AI/skyvern" className="sk-footer-link">GitHub ↗</a>

View file

@ -1112,4 +1112,11 @@ curl -X GET "https://api.skyvern.com/v1/workflows" \
>
Download, parse, and upload files in workflows
</Card>
<Card
title="Schedule a Workflow"
icon="calendar"
href="/multi-step-automations/scheduling-workflows"
>
Run workflows automatically on a recurring cron schedule
</Card>
</CardGroup>

View file

@ -0,0 +1,431 @@
---
title: Scheduling Workflows
subtitle: Run workflows automatically on a recurring cron schedule
slug: multi-step-automations/scheduling-workflows
---
Schedules let you run any workflow automatically on a recurring basis.
Define a cron expression and timezone, and Skyvern triggers the workflow at each interval.
Scheduled runs appear in your run history with `trigger_type: "scheduled"` so you can distinguish them from manual or API-triggered runs.
---
## How schedules work
A schedule links a **cron expression** and a **timezone** to a workflow. At each scheduled time, Skyvern creates a new workflow run with the parameters you configured when creating the schedule.
```mermaid
flowchart LR
A["Cron trigger<br/>e.g. 0 9 * * 1"] --> B["Skyvern creates<br/>workflow run"]
B --> C["Workflow executes<br/>with saved parameters"]
C --> D["Results in<br/>Run History"]
```
Each scheduled run is identical to a manually triggered run — same blocks, same parameters, same outputs. The only difference is the `trigger_type` field, which is set to `"scheduled"` instead of `"manual"` or `"api"`.
---
## Cron expression format
Schedules use standard 5-field cron expressions:
```
┌───────── minute (0-59)
│ ┌─────── hour (0-23)
│ │ ┌───── day of month (1-31)
│ │ │ ┌─── month (1-12)
│ │ │ │ ┌─ day of week (0-6, Sun=0)
│ │ │ │ │
* * * * *
```
**Common patterns:**
| Cron expression | Description |
|----------------|-------------|
| `0 9 * * 1-5` | Every weekday at 9:00 AM |
| `0 */6 * * *` | Every 6 hours |
| `0 9 * * 1` | Every Monday at 9:00 AM |
| `0 0 1 * *` | First day of every month at midnight |
| `*/30 * * * *` | Every 30 minutes |
| `0 8,17 * * *` | Twice daily at 8:00 AM and 5:00 PM |
<Warning>
The minimum interval is **5 minutes**. Cron expressions that resolve to intervals shorter than 5 minutes return a validation error:
```json
{
"detail": "Cron interval must be at least 5 minutes"
}
```
Invalid cron syntax returns `"Invalid cron expression"`.
</Warning>
### Timezone handling
Schedules use [IANA timezone identifiers](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (e.g., `America/New_York`, `Europe/London`, `Asia/Tokyo`). If you don't specify a timezone, it defaults to **UTC**.
---
## Create a schedule
Send a `POST` request to `/api/v1/workflows/{workflow_permanent_id}/schedules` with your cron expression and timezone.
<CodeGroup>
```python Python
import os
import asyncio
from skyvern import Skyvern
async def main():
client = Skyvern(api_key=os.getenv("SKYVERN_API_KEY"))
result = await client.agent.create_workflow_schedule(
workflow_permanent_id="wpid_123456789",
cron_expression="0 9 * * 1-5",
timezone="America/New_York",
name="Weekday morning report",
description="Runs the data extraction workflow every weekday at 9 AM ET",
parameters={
"url": "https://example.com/dashboard",
"output_format": "csv"
}
)
print(f"Schedule ID: {result.schedule.workflow_schedule_id}")
asyncio.run(main())
```
```bash cURL
curl -X POST "https://api.skyvern.com/api/v1/workflows/wpid_123456789/schedules" \
-H "x-api-key: $SKYVERN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"cron_expression": "0 9 * * 1-5",
"timezone": "America/New_York",
"name": "Weekday morning report",
"description": "Runs the data extraction workflow every weekday at 9 AM ET",
"parameters": {
"url": "https://example.com/dashboard",
"output_format": "csv"
}
}'
```
</CodeGroup>
<Info>
All Python examples on this page assume you've initialized the client as shown above:
```python
client = Skyvern(api_key=os.getenv("SKYVERN_API_KEY"))
```
</Info>
### Request body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `cron_expression` | string | Yes | 5-field cron expression (minimum 5-minute interval) |
| `timezone` | string | Yes | IANA timezone identifier (e.g., `America/New_York`) |
| `name` | string | No | Human-readable name for the schedule |
| `description` | string | No | Description of what this schedule does |
| `parameters` | object | No | Workflow parameters to pass on each run |
| `enabled` | boolean | No | Whether the schedule is active. Defaults to `true` |
### Example response
```json
{
"schedule": {
"workflow_schedule_id": "wfs_510037469402471882",
"organization_id": "o_510009610576515030",
"workflow_permanent_id": "wpid_510013188284271440",
"cron_expression": "0 9 * * 1-5",
"timezone": "America/New_York",
"enabled": true,
"parameters": null,
"temporal_schedule_id": "wf-sched-wfs_510037469402471882",
"name": "Weekday morning report",
"description": "Runs the data extraction workflow every weekday at 9 AM ET",
"created_at": "2026-03-25T10:45:52.193974",
"modified_at": "2026-03-25T10:45:52.221999",
"deleted_at": null
},
"next_runs": [
"2026-03-25T13:00:00Z",
"2026-03-26T13:00:00Z",
"2026-03-27T13:00:00Z",
"2026-03-30T13:00:00Z",
"2026-03-31T13:00:00Z"
]
}
```
**Response fields:**
| Field | Type | Description |
|-------|------|-------------|
| `schedule.workflow_schedule_id` | string | Unique identifier for this schedule (prefixed `wfs_`) |
| `schedule.organization_id` | string | The organization that owns this schedule |
| `schedule.workflow_permanent_id` | string | The workflow this schedule triggers |
| `schedule.cron_expression` | string | The cron expression defining the recurrence |
| `schedule.timezone` | string | IANA timezone for the cron expression |
| `schedule.enabled` | boolean | Whether the schedule is active (`true`) or paused (`false`). Defaults to `true` on creation |
| `schedule.parameters` | object \| null | Workflow parameters passed to each run |
| `schedule.temporal_schedule_id` | string | Internal identifier used by the scheduling engine |
| `schedule.name` | string \| null | Human-readable name |
| `schedule.description` | string \| null | Description of the schedule |
| `schedule.created_at` | string | When the schedule was created |
| `schedule.modified_at` | string | When the schedule was last updated |
| `schedule.deleted_at` | string \| null | When the schedule was deleted, if applicable |
| `next_runs` | array | Upcoming execution times in UTC based on the cron expression and timezone |
Workflow runs triggered by this schedule will include `trigger_type: "scheduled"` and `workflow_schedule_id` pointing back to the schedule, so you can distinguish them from manual or API-triggered runs.
---
## List schedules
To list all schedules in your organization:
<CodeGroup>
```python Python
result = await client.agent.list_organization_schedules()
for schedule in result.schedules:
status = "active" if schedule.enabled else "paused"
print(f"{schedule.workflow_schedule_id}: {schedule.name} ({status})")
```
```bash cURL
curl -X GET "https://api.skyvern.com/api/v1/schedules" \
-H "x-api-key: $SKYVERN_API_KEY"
```
</CodeGroup>
### Example response
```json
{
"schedules": [
{
"workflow_schedule_id": "wfs_510037469402471882",
"organization_id": "o_510009610576515030",
"workflow_permanent_id": "wpid_510013188284271440",
"workflow_title": "New Workflow",
"cron_expression": "0 9 * * 1-5",
"timezone": "America/New_York",
"enabled": true,
"parameters": null,
"name": "Weekday morning report",
"description": "Runs the data extraction workflow every weekday at 9 AM ET",
"next_run": "2026-03-25T13:00:00Z",
"created_at": "2026-03-25T10:45:52.193974",
"modified_at": "2026-03-25T10:45:52.221999"
}
],
"total_count": 1,
"page": 1,
"page_size": 10
}
```
**Response fields:**
| Field | Type | Description |
|-------|------|-------------|
| `schedules` | array | List of schedule objects |
| `schedules[].workflow_schedule_id` | string | Schedule identifier |
| `schedules[].workflow_permanent_id` | string | The workflow this schedule triggers |
| `schedules[].workflow_title` | string | Title of the associated workflow |
| `schedules[].enabled` | boolean | Whether the schedule is active |
| `schedules[].next_run` | string | Next scheduled execution time in UTC |
| `total_count` | integer | Total number of schedules matching the query |
| `page` | integer | Current page number |
| `page_size` | integer | Number of results per page |
To list schedules for a specific workflow:
<CodeGroup>
```python Python
result = await client.agent.list_workflow_schedules(
workflow_permanent_id="wpid_123456789"
)
```
```bash cURL
curl -X GET "https://api.skyvern.com/api/v1/workflows/wpid_123456789/schedules" \
-H "x-api-key: $SKYVERN_API_KEY"
```
</CodeGroup>
---
## Get schedule details
Retrieve a single schedule, including upcoming run times in `next_runs`.
<CodeGroup>
```python Python
result = await client.agent.get_workflow_schedule(
workflow_permanent_id="wpid_123456789",
workflow_schedule_id="wfs_abc123"
)
print(f"Next runs: {result.next_runs}")
```
```bash cURL
curl -X GET "https://api.skyvern.com/api/v1/workflows/wpid_123456789/schedules/wfs_abc123" \
-H "x-api-key: $SKYVERN_API_KEY"
```
</CodeGroup>
The response has the same shape as the [create response](#example-response) — a `schedule` object with a `next_runs` array.
---
## Update a schedule
Change the cron expression, timezone, name, description, or parameters.
<CodeGroup>
```python Python
result = await client.agent.update_workflow_schedule(
workflow_permanent_id="wpid_123456789",
workflow_schedule_id="wfs_abc123",
cron_expression="0 8 * * 1-5",
timezone="America/Chicago",
name="Updated morning report",
description="Updated description"
)
```
```bash cURL
curl -X PUT "https://api.skyvern.com/api/v1/workflows/wpid_123456789/schedules/wfs_abc123" \
-H "x-api-key: $SKYVERN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"cron_expression": "0 8 * * 1-5",
"timezone": "America/Chicago",
"name": "Updated morning report",
"description": "Updated description"
}'
```
</CodeGroup>
<Warning>
Updates **replace** all fields — any optional field you omit (like `description` or `parameters`) will be set to `null`. Re-send all fields you want to keep.
</Warning>
The response has the same shape as the [create response](#example-response).
---
## Enable and disable a schedule
You can pause a schedule by disabling it. Compared to deleting a schedule, this keeps its configuration saved to be re-enabled at any time.
<CodeGroup>
```python Python
# Disable
await client.agent.disable_workflow_schedule(
workflow_permanent_id="wpid_123456789",
workflow_schedule_id="wfs_abc123"
)
# Re-enable
await client.agent.enable_workflow_schedule(
workflow_permanent_id="wpid_123456789",
workflow_schedule_id="wfs_abc123"
)
```
```bash cURL
# Disable
curl -X POST "https://api.skyvern.com/api/v1/workflows/wpid_123456789/schedules/wfs_abc123/disable" \
-H "x-api-key: $SKYVERN_API_KEY"
# Re-enable
curl -X POST "https://api.skyvern.com/api/v1/workflows/wpid_123456789/schedules/wfs_abc123/enable" \
-H "x-api-key: $SKYVERN_API_KEY"
```
</CodeGroup>
Both endpoints return the same shape as the [create response](#example-response), with `enabled` set to `false` or `true`.
---
## Delete a schedule
Permanently remove a schedule. This cannot be undone. Runs that were already triggered by this schedule are not affected.
<CodeGroup>
```python Python
await client.agent.delete_workflow_schedule_route(
workflow_permanent_id="wpid_123456789",
workflow_schedule_id="wfs_abc123"
)
```
```bash cURL
curl -X DELETE "https://api.skyvern.com/api/v1/workflows/wpid_123456789/schedules/wfs_abc123" \
-H "x-api-key: $SKYVERN_API_KEY"
```
</CodeGroup>
### Example response
```json
{
"ok": true
}
```
---
## Limits
| | Self-hosted (OSS) | Skyvern Cloud |
|---|---|---|
| **Schedules per workflow** | Unlimited | Configurable per plan |
| **Total schedules per org** | Unlimited | Configurable per plan |
| **Minimum interval** | 5 minutes | 5 minutes |
---
## What's next
<CardGroup cols={2}>
<Card
title="Cloud UI: Scheduling"
icon="calendar"
href="/cloud/building-workflows/scheduling"
>
Create and manage schedules from the Skyvern dashboard
</Card>
<Card
title="Cost Control"
icon="gauge"
href="/optimization/cost-control"
>
Set step limits and optimize scheduled workflow costs
</Card>
<Card
title="Webhooks"
icon="bell"
href="/going-to-production/webhooks"
>
Get notified when scheduled runs complete
</Card>
<Card
title="Run History"
icon="clock-rotate-left"
href="/cloud/viewing-results/run-history"
>
View and filter scheduled run results
</Card>
</CardGroup>

View file

@ -194,7 +194,7 @@ curl -X POST "https://api.skyvern.com/v1/run/tasks" \
</CodeGroup>
<Tip>
Set `publish_workflow: true` to save the generated workflow so you can re-trigger it later or schedule it on a cron.
Set `publish_workflow: true` to save the generated workflow so you can re-trigger it later or [schedule it on a recurring cron](/multi-step-automations/scheduling-workflows).
</Tip>
---

View file

@ -10,6 +10,11 @@ Skyvern uses Playwright with Chromium to execute browser automations.
The `BROWSER_TYPE` setting controls how Skyvern runs the browser.
- **[Headful](#headful-default)** — visible browser window (default)
- **[Headless](#headless)** — no display, runs in the background
- **[Use Your Own Chrome](#use-your-own-chrome)** — runs the installed Chrome app with your logged-in profile
- **[CDP Connect](#cdp-connect-external-chrome)** — connects to an already-running Chrome instance
### Headful (default)
```bash .env
@ -30,6 +35,20 @@ The browser runs without any display.
Some websites detect and block headless browsers. If you encounter issues with bot detection, try headful mode with a virtual display.
</Note>
### Use Your Own Chrome
```bash .env
BROWSER_TYPE=cdp-connect
CHROME_EXECUTABLE_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
```
Skyvern launches the Chrome app installed on your machine and connects to it automatically. Your existing Chrome profile (cookies, saved logins, extensions) is copied over so automations run in a browser environment that matches your real one.
Useful for:
- Running automations with your saved logins and cookies
- Using your installed Chrome extensions
- Avoiding bot detection by using a real Chrome installation
### CDP Connect (External Chrome)
```bash .env
@ -44,6 +63,63 @@ Connect to an existing Chrome instance running with remote debugging enabled. Us
---
## Setting up Use Your Own Chrome
Set `CHROME_EXECUTABLE_PATH` to your Chrome installation path and Skyvern handles the rest — no need to manually start Chrome with debugging flags.
### Step 1: Find your Chrome path
<CodeGroup>
```bash macOS
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
```
```powershell Windows
"C:\Program Files\Google\Chrome\Application\chrome.exe"
```
```bash Linux
# Usually one of:
/usr/bin/google-chrome
/usr/bin/chromium
/usr/bin/chromium-browser
```
</CodeGroup>
### Step 2: Configure Skyvern
<CodeGroup>
```bash macOS .env
BROWSER_TYPE=cdp-connect
CHROME_EXECUTABLE_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
```
```bash Windows .env
BROWSER_TYPE=cdp-connect
CHROME_EXECUTABLE_PATH="C:\Program Files\Google\Chrome\Application\chrome.exe"
```
```bash Linux .env
BROWSER_TYPE=cdp-connect
CHROME_EXECUTABLE_PATH=/usr/bin/google-chrome
```
</CodeGroup>
When Skyvern starts, it will:
1. Copy your Chrome profile to a working directory (`./tmp/user_data_dir`)
2. Launch Chrome with remote debugging on port 9222
3. Connect via CDP automatically
<Warning>
All existing Chrome windows must be closed before Skyvern can launch Chrome with remote debugging. If Chrome is already running, Skyvern will return an error.
</Warning>
<Note>
If port 9222 is already in use (e.g., from a previous session), Skyvern will connect to the existing instance instead of launching a new one.
</Note>
---
## Setting up CDP Connect
CDP (Chrome DevTools Protocol) lets Skyvern control an external Chrome browser instead of launching its own.

View file

@ -19,6 +19,23 @@ For most deployments, configure a single provider using `LLM_KEY`. Skyvern also
---
## Quick Start Recommendations
**Best models for production (2025):**
| Provider | Primary Model | Secondary Model | Notes |
|----------|--------------|-----------------|-------|
| **Anthropic** | `ANTHROPIC_CLAUDE4.5_OPUS` | `ANTHROPIC_CLAUDE4.5_SONNET` | Most capable |
| **OpenAI** | `OPENAI_GPT5` | `OPENAI_GPT5_MINI` | Latest |
| **Google** | `GEMINI_3_PRO` | `GEMINI_3.0_FLASH` | Latest |
| **AWS Bedrock** | `BEDROCK_ANTHROPIC_CLAUDE4.5_OPUS_INFERENCE_PROFILE` | `BEDROCK_ANTHROPIC_CLAUDE4.5_SONNET_INFERENCE_PROFILE` | Latest Claude |
<Tip>
**New in 2025:** GPT-5 series, Claude 4.6 Opus, Gemini 3, Amazon Nova, and many new open-source models via Novita and VolcEngine.
</Tip>
---
## OpenAI
The most common choice. Requires an API key from [platform.openai.com](https://platform.openai.com/).
@ -31,16 +48,29 @@ LLM_KEY=OPENAI_GPT4O
### Available models
| LLM_KEY | Model | Notes |
|---------|-------|-------|
| `OPENAI_GPT4O` | gpt-4o | Recommended for most use cases |
| `OPENAI_GPT4O_MINI` | gpt-4o-mini | Cheaper, less capable |
| `OPENAI_GPT4_1` | gpt-4.1 | Latest GPT-4 family |
| `OPENAI_GPT4_1_MINI` | gpt-4.1-mini | Cheaper GPT-4.1 variant |
| `OPENAI_O3` | o3 | Reasoning model |
| `OPENAI_O3_MINI` | o3-mini | Cheaper reasoning model |
| `OPENAI_GPT4_TURBO` | gpt-4-turbo | Previous generation |
| `OPENAI_GPT4V` | gpt-4-turbo | Legacy alias for gpt-4-turbo |
| LLM_KEY | Notes |
|---------|-------|
| **GPT-5 Series** | |
| `OPENAI_GPT5` | Recommended for most complex tasks |
| `OPENAI_GPT5_MINI` | |
| `OPENAI_GPT5_MINI_FLEX` | Flex service tier, 15min timeout |
| `OPENAI_GPT5_NANO` | |
| `OPENAI_GPT5_1` | |
| `OPENAI_GPT5_2` | |
| `OPENAI_GPT5_4` | |
| **GPT-4 Series** | |
| `OPENAI_GPT4O` | |
| `OPENAI_GPT4O_MINI` | |
| `OPENAI_GPT4_1` | |
| `OPENAI_GPT4_1_MINI` | |
| `OPENAI_GPT4_1_NANO` | |
| `OPENAI_GPT4_5` | |
| `OPENAI_GPT4_TURBO` | Legacy |
| `OPENAI_GPT4V` | Legacy alias |
| **O-Series (Reasoning)** | |
| `OPENAI_O4_MINI` | Vision support |
| `OPENAI_O3` | Vision support |
| `OPENAI_O3_MINI` | No vision |
### Optional settings
@ -66,15 +96,26 @@ LLM_KEY=ANTHROPIC_CLAUDE3.5_SONNET
### Available models
| LLM_KEY | Model | Notes |
|---------|-------|-------|
| `ANTHROPIC_CLAUDE4.5_SONNET` | claude-4.5-sonnet | Latest Sonnet |
| `ANTHROPIC_CLAUDE4.5_OPUS` | claude-4.5-opus | Most capable |
| `ANTHROPIC_CLAUDE4_SONNET` | claude-4-sonnet | Claude 4 |
| `ANTHROPIC_CLAUDE4_OPUS` | claude-4-opus | Claude 4 Opus |
| `ANTHROPIC_CLAUDE3.7_SONNET` | claude-3-7-sonnet | Previous generation |
| `ANTHROPIC_CLAUDE3.5_SONNET` | claude-3-5-sonnet | Previous generation |
| `ANTHROPIC_CLAUDE3.5_HAIKU` | claude-3-5-haiku | Cheap and fast |
| LLM_KEY | Notes |
|---------|-------|
| **Claude 4.6** | |
| `ANTHROPIC_CLAUDE4.6_OPUS` | Newest |
| **Claude 4.5** | |
| `ANTHROPIC_CLAUDE4.5_OPUS` | Recommended for primary use |
| `ANTHROPIC_CLAUDE4.5_SONNET` | Recommended for secondary use |
| `ANTHROPIC_CLAUDE4.5_HAIKU` | Fastest |
| **Claude 4** | |
| `ANTHROPIC_CLAUDE4_OPUS` | |
| `ANTHROPIC_CLAUDE4_SONNET` | |
| **Claude 3.7** | |
| `ANTHROPIC_CLAUDE3.7_SONNET` | |
| **Claude 3.5** | |
| `ANTHROPIC_CLAUDE3.5_SONNET` | |
| `ANTHROPIC_CLAUDE3.5_HAIKU` | |
| **Claude 3 (Legacy)** | |
| `ANTHROPIC_CLAUDE3_OPUS` | |
| `ANTHROPIC_CLAUDE3_SONNET` | |
| `ANTHROPIC_CLAUDE3_HAIKU` | |
---
@ -117,21 +158,45 @@ The quickest way to use Gemini. Get an API key from [Google AI Studio](https://a
```bash .env
ENABLE_GEMINI=true
GEMINI_API_KEY=your-gemini-api-key
LLM_KEY=VERTEX_GEMINI_2.5_FLASH
LLM_KEY=GEMINI_2.5_PRO
```
#### Available Gemini API models
| LLM_KEY | Notes |
|---------|-------|
| **Gemini 3** | |
| `GEMINI_3_PRO` | Recommended for primary use |
| `GEMINI_3.0_FLASH` | Recommended for secondary use |
| **Gemini 2.5** | |
| `GEMINI_2.5_PRO` | |
| `GEMINI_2.5_PRO_PREVIEW` | |
| `GEMINI_2.5_PRO_EXP_03_25` | Experimental |
| `GEMINI_2.5_FLASH` | |
| `GEMINI_2.5_FLASH_PREVIEW` | |
| **Gemini 2.0** | |
| `GEMINI_FLASH_2_0` | |
| `GEMINI_FLASH_2_0_LITE` | |
| **Gemini 1.5 Legacy** | |
| `GEMINI_PRO` | |
| `GEMINI_FLASH` | |
### Vertex AI
For enterprise deployments through [Vertex AI](https://cloud.google.com/vertex-ai). Requires a GCP project with Vertex AI enabled.
```bash .env
ENABLE_VERTEX_AI=true
LLM_KEY=VERTEX_GEMINI_3.0_FLASH
LLM_KEY=VERTEX_GEMINI_3_PRO
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
GCP_PROJECT_ID=your-gcp-project-id
GCP_REGION=us-central1
VERTEX_LOCATION=us-central1
```
<Note>
If you're migrating from an older Skyvern version, `VERTEX_LOCATION` replaces the previous `GCP_REGION` variable. Update your `.env` accordingly.
</Note>
**Vertex AI setup steps:**
1. Create a [GCP project](https://console.cloud.google.com/) with billing enabled
@ -140,13 +205,28 @@ GCP_REGION=us-central1
4. Download the service account JSON key file
5. Set `GOOGLE_APPLICATION_CREDENTIALS` to the path of that file
### Available models
<Note>
For global endpoint access, set `VERTEX_LOCATION=global` and ensure `VERTEX_PROJECT_ID` is set. Not all models support the global endpoint.
</Note>
| LLM_KEY | Model | Notes |
|---------|-------|-------|
| `VERTEX_GEMINI_3.0_FLASH` | gemini-3-flash-preview | Recommended |
| `VERTEX_GEMINI_2.5_PRO` | gemini-2.5-pro | Stable |
| `VERTEX_GEMINI_2.5_FLASH` | gemini-2.5-flash | Cheaper, faster |
#### Available Vertex AI models
| LLM_KEY | Notes |
|---------|-------|
| **Gemini 3** | |
| `VERTEX_GEMINI_3_PRO` | Recommended for primary use |
| `VERTEX_GEMINI_3.0_FLASH` | Recommended for secondary use |
| **Gemini 2.5** | |
| `VERTEX_GEMINI_2.5_PRO` | |
| `VERTEX_GEMINI_2.5_PRO_PREVIEW` | |
| `VERTEX_GEMINI_2.5_FLASH` | |
| `VERTEX_GEMINI_2.5_FLASH_LITE` | |
| `VERTEX_GEMINI_2.5_FLASH_PREVIEW` | |
| **Gemini 2.0** | |
| `VERTEX_GEMINI_FLASH_2_0` | |
| **Gemini 1.5 Legacy** | |
| `VERTEX_GEMINI_PRO` | |
| `VERTEX_GEMINI_FLASH` | |
---
@ -171,13 +251,30 @@ AWS_SECRET_ACCESS_KEY=...
### Available models
| LLM_KEY | Model |
| LLM_KEY | Notes |
|---------|-------|
| `BEDROCK_ANTHROPIC_CLAUDE3.5_SONNET` | Claude 3.5 Sonnet v2 |
| `BEDROCK_ANTHROPIC_CLAUDE3.5_SONNET_V1` | Claude 3.5 Sonnet v1 |
| `BEDROCK_ANTHROPIC_CLAUDE3.7_SONNET_INFERENCE_PROFILE` | Claude 3.7 Sonnet (cross-region) |
| `BEDROCK_ANTHROPIC_CLAUDE4_SONNET_INFERENCE_PROFILE` | Claude 4 Sonnet (cross-region) |
| `BEDROCK_ANTHROPIC_CLAUDE4.5_SONNET_INFERENCE_PROFILE` | Claude 4.5 Sonnet (cross-region) |
| **Amazon Nova (AWS Native)** | |
| `BEDROCK_AMAZON_NOVA_PRO` | |
| `BEDROCK_AMAZON_NOVA_LITE` | |
| **Claude 4.6** | |
| `BEDROCK_ANTHROPIC_CLAUDE4.6_OPUS_INFERENCE_PROFILE` | Cross-region |
| **Claude 4.5** | |
| `BEDROCK_ANTHROPIC_CLAUDE4.5_OPUS_INFERENCE_PROFILE` | Cross-region |
| `BEDROCK_ANTHROPIC_CLAUDE4.5_SONNET_INFERENCE_PROFILE` | Cross-region |
| **Claude 4** | |
| `BEDROCK_ANTHROPIC_CLAUDE4_OPUS_INFERENCE_PROFILE` | Cross-region |
| `BEDROCK_ANTHROPIC_CLAUDE4_SONNET_INFERENCE_PROFILE` | Cross-region |
| **Claude 3.7** | |
| `BEDROCK_ANTHROPIC_CLAUDE3.7_SONNET_INFERENCE_PROFILE` | Cross-region |
| **Claude 3.5** | |
| `BEDROCK_ANTHROPIC_CLAUDE3.5_SONNET` | v2 |
| `BEDROCK_ANTHROPIC_CLAUDE3.5_SONNET_V1` | |
| `BEDROCK_ANTHROPIC_CLAUDE3.5_SONNET_INFERENCE_PROFILE` | Cross-region |
| `BEDROCK_ANTHROPIC_CLAUDE3.5_HAIKU` | |
| **Claude 3 (Legacy)** | |
| `BEDROCK_ANTHROPIC_CLAUDE3_OPUS` | |
| `BEDROCK_ANTHROPIC_CLAUDE3_SONNET` | |
| `BEDROCK_ANTHROPIC_CLAUDE3_HAIKU` | |
<Note>
Bedrock inference profile keys (`*_INFERENCE_PROFILE`) use cross-region inference and require `AWS_REGION` only. No access keys needed if running on an IAM-authenticated instance.
@ -185,6 +282,142 @@ Bedrock inference profile keys (`*_INFERENCE_PROFILE`) use cross-region inferenc
---
## MiniMax
[MiniMax](https://www.minimax.io/) models with vision support.
```bash .env
ENABLE_MINIMAX=true
MINIMAX_API_KEY=your-minimax-api-key
LLM_KEY=MINIMAX_M2_5
```
### Available models
| LLM_KEY | Notes |
|---------|-------|
| `MINIMAX_M2_5` | |
| `MINIMAX_M2_5_HIGHSPEED` | Faster variant |
### Optional settings
```bash .env
# Use a custom API endpoint
MINIMAX_API_BASE=https://api.minimax.io/v1
```
---
## VolcEngine (ByteDance Doubao)
[VolcEngine](https://www.volcengine.com/) provides access to ByteDance's Doubao models with vision support.
```bash .env
ENABLE_VOLCENGINE=true
VOLCENGINE_API_KEY=your-volcengine-api-key
LLM_KEY=VOLCENGINE_DOUBAO_SEED_1_6
```
### Available models
| LLM_KEY | Notes |
|---------|-------|
| `VOLCENGINE_DOUBAO_SEED_1_6` | Recommended for general use |
| `VOLCENGINE_DOUBAO_SEED_1_6_FLASH` | Faster variant |
| `VOLCENGINE_DOUBAO_1_5_THINKING_VISION_PRO` | Reasoning model |
### Optional settings
```bash .env
# Use a custom API endpoint
VOLCENGINE_API_BASE=https://ark.cn-beijing.volces.com/api/v3
```
---
## Novita
[Novita AI](https://novita.ai/) provides access to DeepSeek, Llama, and other open-source models.
```bash .env
ENABLE_NOVITA=true
NOVITA_API_KEY=your-novita-api-key
LLM_KEY=NOVITA_LLAMA_3_2_11B_VISION
```
### Available models
| LLM_KEY | Notes |
|---------|-------|
| **DeepSeek** | |
| `NOVITA_DEEPSEEK_R1` | Reasoning model |
| `NOVITA_DEEPSEEK_V3` | |
| **Llama 3.3** | |
| `NOVITA_LLAMA_3_3_70B` | |
| **Llama 3.2** | |
| `NOVITA_LLAMA_3_2_11B_VISION` | Vision support |
| `NOVITA_LLAMA_3_2_3B` | |
| `NOVITA_LLAMA_3_2_1B` | |
| **Llama 3.1** | |
| `NOVITA_LLAMA_3_1_405B` | |
| `NOVITA_LLAMA_3_1_70B` | |
| `NOVITA_LLAMA_3_1_8B` | |
| **Llama 3** | |
| `NOVITA_LLAMA_3_70B` | |
| `NOVITA_LLAMA_3_8B` | |
---
## Moonshot
[Moonshot AI](https://www.moonshot.cn/) provides the Kimi series models with long context support.
```bash .env
ENABLE_MOONSHOT=true
MOONSHOT_API_KEY=your-moonshot-api-key
LLM_KEY=MOONSHOT_KIMI_K2
```
### Available models
| LLM_KEY | Notes |
|---------|-------|
| `MOONSHOT_KIMI_K2` | |
### Optional settings
```bash .env
# Use a custom API endpoint
MOONSHOT_API_BASE=https://api.moonshot.cn/v1
```
---
## Inception
[Inception AI](https://inception.ai/) provides the Mercury series models.
```bash .env
ENABLE_INCEPTION=true
INCEPTION_API_KEY=your-inception-api-key
LLM_KEY=INCEPTION_MERCURY_2
```
### Available models
| LLM_KEY | Notes |
|---------|-------|
| `INCEPTION_MERCURY_2` | |
### Optional settings
```bash .env
# Use a custom API endpoint
INCEPTION_API_BASE=https://api.inception.ai/v1
```
---
## Ollama (Local Models)
Run open-source models locally with [Ollama](https://ollama.ai/). No API costs, but requires sufficient local compute.
@ -276,22 +509,38 @@ Configure a cheaper model for lightweight operations:
```bash .env
# Main model for complex decisions
LLM_KEY=OPENAI_GPT4O
LLM_KEY=ANTHROPIC_CLAUDE4.5_OPUS
# or: OPENAI_GPT5
# or: GEMINI_3_PRO
# Cheaper model for simple tasks like dropdown selection
SECONDARY_LLM_KEY=OPENAI_GPT4O_MINI
# Faster model for simple tasks like dropdown selection
SECONDARY_LLM_KEY=ANTHROPIC_CLAUDE4.5_SONNET
# or: OPENAI_GPT5_MINI
# or: GEMINI_3.0_FLASH
```
<Tip>
**Recommended primary models (latest):**
- **Anthropic Claude 4.5 Opus** (`ANTHROPIC_CLAUDE4.5_OPUS`) - Most capable
- **OpenAI GPT-5** (`OPENAI_GPT5`) - Latest
- **Google Gemini 3 Pro** (`GEMINI_3_PRO`) - Latest
**Recommended secondary models (latest):**
- **Claude 4.5 Sonnet** (`ANTHROPIC_CLAUDE4.5_SONNET`) - Balanced
- **GPT-5 Mini** (`OPENAI_GPT5_MINI`) - Faster GPT-5
- **Gemini 3.0 Flash** (`GEMINI_3.0_FLASH`) - Faster Gemini 3
</Tip>
### Task-specific models
For fine-grained control, you can override models for specific operations:
```bash .env
# Model for data extraction from pages (defaults to LLM_KEY if not set)
EXTRACTION_LLM_KEY=ANTHROPIC_CLAUDE3.5_SONNET
EXTRACTION_LLM_KEY=ANTHROPIC_CLAUDE4.5_SONNET
# Model for generating code/scripts in code blocks (defaults to LLM_KEY if not set)
SCRIPT_GENERATION_LLM_KEY=OPENAI_GPT4O
SCRIPT_GENERATION_LLM_KEY=OPENAI_GPT5
```
Most deployments don't need task-specific models. Start with `LLM_KEY` and `SECONDARY_LLM_KEY`.
@ -311,7 +560,7 @@ SECONDARY_LLM_KEY=OPENAI_GPT4O_MINI
### "Context window exceeded"
The page content is too large for the model's context window. Options:
- Use a model with a larger context (GPT-4o supports 128k tokens)
- Use a model with larger context support (GPT-5, Gemini 2.5 Pro, or Claude 4.5 Sonnet)
- Simplify your prompt to require less page analysis
- Start from a more specific URL with less content

View file

@ -0,0 +1,430 @@
Skyvern has nine core concepts. Master these and you can build any browser automation.
---
## Tasks
A **Task** is a single automation job—think of it as asking Skyvern to do one thing. You provide a prompt describing the goal, and Skyvern navigates the browser to complete it.
```python
from skyvern import Skyvern
skyvern = Skyvern(api_key="YOUR_API_KEY")
result = await skyvern.run_task(
prompt="Find the top 3 posts on the front page",
url="https://news.ycombinator.com",
data_extraction_schema={
"type": "object",
"properties": {
"posts": {
"type": "array",
"items": {"type": "string"}
}
}
}
)
print(result.output) # {"posts": ["Post 1", "Post 2", "Post 3"]}
```
**Key properties:**
| Property | Description |
|----------|-------------|
| `prompt` | Natural language description of the goal (required) |
| `url` | Starting URL for the task |
| `data_extraction_schema` | JSON Schema defining expected output structure |
| `max_steps` | Maximum steps allowed (controls cost) |
| `engine` | AI model to use (see [Engines](#engines) below) |
| `browser_session_id` | Run in an existing browser session |
| `webhook_url` | Callback URL for async completion |
**Use Tasks for:** one-off automations, quick data extraction, prototyping before building a workflow.
When you need reusable, multi-step automations, use **Workflows** instead.
---
## Workflows
A **Workflow** is a reusable template composed of blocks. Unlike tasks, workflows can be versioned, shared across your team, and executed repeatedly with different parameters.
```mermaid
flowchart LR
A["Navigate<br/>Block"] --> B["Login<br/>Block"]
B --> C["Extract<br/>Block"]
C --> D["Download<br/>Block"]
subgraph WORKFLOW
direction LR
A
B
C
D
end
```
> **Parameters:** `{{search_query}}`, `{{max_price}}`
> Each block can reference outputs from previous blocks.
```python
result = await skyvern.run_workflow(
workflow_id="wpid_abc123",
parameters={
"search_query": "wireless headphones",
"max_price": 100
}
)
print(result.output)
```
Workflows support Jinja templating:
- Reference parameters: `{{search_query}}`
- Reference previous block outputs: `{{extract_block.product_name}}`
**Use Workflows for:** repeatable automations, multi-step processes, team-shared templates, complex logic with loops or conditionals.
See [Workflow Blocks](/multi-step-automations/workflow-blocks-reference) for the full block reference.
---
## Blocks
Blocks are the building units of workflows. Each block performs one specific task, and blocks execute sequentially—each can reference outputs from previous blocks.
### Navigation & Interaction
| Block | Purpose |
|-------|---------|
| **Navigation** | AI-guided navigation toward a goal—Skyvern figures out the clicks and inputs |
| **Action** | Perform a single specific action (click, type, select, upload) |
| **Go to URL** | Navigate directly to a specific URL |
| **Login** | Authenticate using stored credentials |
| **Wait** | Pause execution for a specified duration |
| **Human Interaction** | Pause for manual intervention (CAPTCHA, approval, etc.) |
### Data & Files
| Block | Purpose |
|-------|---------|
| **Extract** | Pull structured data from a page into JSON |
| **File Download** | Download files from websites |
| **File Upload** | Upload files to form fields |
| **File Parser** | Process PDFs, CSVs, and Excel files |
| **PDF Parser** | Specialized PDF text extraction |
### Logic & Control Flow
| Block | Purpose |
|-------|---------|
| **Conditional** | Branch workflow based on conditions (if/else) |
| **For Loop** | Repeat a sequence of blocks over a list |
| **Validation** | Assert conditions; halt workflow on failure |
| **Code** | Execute custom Python/Playwright scripts |
### Communication & Integration
| Block | Purpose |
|-------|---------|
| **HTTP Request** | Make API calls to external services |
| **Text Prompt** | Text-only LLM prompt (no browser interaction) |
| **Send Email** | Send email messages |
For detailed block configuration, see [Workflow Blocks](/workflows/workflow-blocks-details).
---
## Runs
Every time you execute a task or workflow, Skyvern creates a **Run** to track progress and store outputs.
```mermaid
flowchart LR
created --> queued --> running
running --> completed
running --> failed
running --> timed_out
running --> terminated
running --> canceled
```
```python
result = await skyvern.run_task(
prompt="Extract the main heading",
url="https://example.com"
)
print(result.run_id) # "tsk_abc123"
print(result.status) # "completed"
print(result.output) # {"heading": "Welcome"}
```
**Run identifiers:**
- Task runs: `tsk_*` prefix
- Workflow runs: `wr_*` prefix
**Run response fields:**
| Field | Description |
|-------|-------------|
| `run_id` | Unique identifier |
| `status` | Current lifecycle state |
| `output` | Extracted data (matches your schema) |
| `recording_url` | Video of the execution |
| `screenshot_urls` | Screenshots captured during execution |
| `downloaded_files` | Files retrieved (with URLs and checksums) |
| `failure_reason` | Error details if failed |
| `step_count` | Number of steps taken |
<Warning>
**Billing:** You're billed per step. A step is one AI decision + action cycle. Use `max_steps` to cap costs during development.
</Warning>
---
## Schedules
A **Schedule** runs a workflow automatically on a recurring basis. You define a cron expression and timezone, and Skyvern triggers the workflow at each interval.
<CodeGroup>
```python Python
result = await client.agent.create_workflow_schedule(
workflow_permanent_id="wpid_abc123",
cron_expression="0 9 * * 1-5", # Weekdays at 9 AM
timezone="America/New_York",
parameters={
"url": "https://example.com/dashboard"
}
)
```
```typescript TypeScript
const result = await skyvern.agent.createWorkflowSchedule("wpid_abc123", {
cron_expression: "0 9 * * 1-5", // Weekdays at 9 AM
timezone: "America/New_York",
parameters: {
url: "https://example.com/dashboard",
},
});
```
</CodeGroup>
**Key properties:**
| Property | Description |
|----------|-------------|
| `cron_expression` | 5-field cron expression defining the recurrence (minimum 5-minute interval) |
| `timezone` | IANA timezone identifier (e.g., `America/New_York`) |
| `parameters` | Workflow parameters passed to each scheduled run |
| `enabled` | `true` (active) or `false` (paused) |
**Use Schedules for:** recurring data extraction, periodic report generation, daily monitoring tasks, any workflow you want to run without manual intervention.
---
## Credentials
Credentials provide secure storage for authentication data. Skyvern encrypts credentials at rest and in transit, and injects them directly into the browser—**credentials are never sent to or seen by the LLM**.
```mermaid
flowchart TD
A["You store credentials"] --> B["Skyvern UI<br/>or API"]
B --> C["Encrypted Vault<br/>(or Bitwarden, 1Password)"]
C --> D["Login Block<br/>injects into<br/>browser fields"]
D --> E["LLM sees: 'login happened'<br/>LLM never sees: actual credentials"]
```
**Supported credential types:**
- Usernames and passwords
- TOTP codes (authenticator apps)
- Credit cards
**Credential sources:**
- **Skyvern** — Native encrypted storage
- **Bitwarden** — Sync from your Bitwarden vault
- **1Password** — Sync from your 1Password vault
- **Azure Key Vault** — Enterprise secret management
See [Credentials](/sdk-reference/credentials) for setup instructions.
---
## Browser Sessions & Profiles
Skyvern offers two ways to manage browser state across runs:
```mermaid
flowchart LR
subgraph SESSION ["Browser Session (live, temporary)"]
A["Active Browser<br/>Instance"]
end
subgraph PROFILE ["Browser Profile (saved, reusable)"]
B["Snapshot of state<br/>(cookies, storage)"]
end
A -- "save" --> B
B -- "restore" --> A
```
| | Browser Session | Browser Profile |
|---|---|---|
| **Lifetime** | Expires after timeout (max 24h) | Persists indefinitely |
| **Use case** | Real-time task chaining | Skip login on repeated runs |
| **Sharing** | Single use | Shared across team |
### Browser Sessions
A live browser instance that maintains state across multiple tasks. Use sessions when you need to chain tasks in real-time or allow human intervention.
```python
# Create a session (default: 60 minutes, max: 24 hours)
session = await skyvern.create_browser_session(timeout=120)
# Run tasks in the same session
await skyvern.run_task(
prompt="Log in with the test account",
url="https://example.com/login",
browser_session_id=session.browser_session_id
)
# Second task reuses the authenticated state
await skyvern.run_task(
prompt="Extract the account balance",
url="https://example.com/dashboard",
browser_session_id=session.browser_session_id
)
# Clean up
await skyvern.close_browser_session(
browser_session_id=session.browser_session_id
)
```
**Session limits:** 5 to 1,440 minutes (24 hours max). Default: 60 minutes.
### Browser Profiles
A saved snapshot of browser state. Unlike sessions, profiles persist indefinitely and can be reused across days or weeks—perfect for skipping login on repeated runs.
```python
# Create a profile from a completed run
profile = await skyvern.create_browser_profile(
name="my-authenticated-profile",
workflow_run_id=run.run_id
)
# Future runs restore the authenticated state
await skyvern.run_workflow(
workflow_id="wpid_extract_data",
browser_profile_id=profile.browser_profile_id
)
```
**What's saved:** Cookies, authentication tokens, local storage, session storage.
See [Browser Sessions](/optimization/browser-sessions) for details.
---
## Artifacts
Every run generates artifacts for observability, debugging, and audit trails.
```python
result = await skyvern.run_task(
prompt="Download the quarterly report",
url="https://example.com"
)
print(result.recording_url) # Full video of execution
print(result.screenshot_urls) # List of screenshot URLs
print(result.downloaded_files) # [{"url": "...", "checksum": "..."}]
```
| Artifact | Description |
|----------|-------------|
| **Recordings** | End-to-end video of the entire run |
| **Screenshots** | Captured after each action |
| **Downloaded files** | Files retrieved during execution |
| **Logs** | JSON-structured logs at step, task, and workflow levels |
| **HAR files** | HTTP Archive data for network debugging |
In the Skyvern UI, go to **Runs** → click a run → view the **Actions** and **Recording** tabs.
---
## Engines
Skyvern supports multiple AI engines for task execution:
| Engine | Description |
|--------|-------------|
| `skyvern-2.0` | Latest Skyvern model (default, recommended) |
| `skyvern-1.0` | Previous Skyvern model |
| `openai-cua` | OpenAI Computer Use Agent |
| `anthropic-cua` | Anthropic Computer Use Agent |
| `ui-tars` | UI-TARS model |
Specify the engine when running a task:
```python
from skyvern.schemas.runs import RunEngine
result = await skyvern.run_task(
prompt="Extract pricing data",
url="https://example.com",
engine=RunEngine.skyvern_v2
)
```
---
## Quick Reference
| I want to... | Use |
|--------------|-----|
| Run a one-off automation | [Task](#tasks) |
| Build a reusable multi-step process | [Workflow](#workflows) |
| Keep a browser open between tasks | [Browser Session](#browser-sessions) |
| Skip login on repeated runs | [Browser Profile](#browser-profiles) |
| Store secrets securely | [Credentials](#credentials) |
| Debug a failed run | [Artifacts](#artifacts) |
---
## Choose your path
<CardGroup cols={2}>
<Card
title="Use the dashboard"
icon="window-maximize"
href="/cloud/getting-started/overview"
>
Run tasks, build workflows visually, and monitor runs — no code required.
</Card>
<Card
title="Build with the API"
icon="code"
href="/getting-started/quickstart"
>
Integrate Skyvern into your product with Python, TypeScript, or REST.
</Card>
</CardGroup>
<CardGroup cols={2}>
<Card
title="AI Agents Quickstart"
icon="robot"
href="/getting-started/ai-agents-quickstart"
>
Give Claude Code, Cursor, or Windsurf full browser automation via MCP.
</Card>
<Card
title="Self-host Skyvern"
icon="server"
href="/self-hosted/overview"
>
Deploy on your own infrastructure with your own LLM keys.
</Card>
</CardGroup>

View file

@ -0,0 +1,63 @@
Skyvern automates anything you can do in a browser. You describe what you want in plain English, and Skyvern opens a real Chromium browser, looks at the page, and completes the task. No CSS selectors, no scripts, no maintenance when the site changes.
The reason this works is that Skyvern doesn't rely on hardcoded element paths. It screenshots the page, reads the DOM, and uses an LLM to decide what to do next, the same way a person would look at a screen and figure out where to click. That means it works on sites it's never seen before, and it keeps working when those sites redesign.
---
## The agent loop
Every Skyvern automation, whether you trigger it from the dashboard, the API, or a Zapier workflow, runs the same loop:
<img src="/images/skyvern-agent-loop.png" alt="Skyvern agent loop: Screenshot → Extract DOM → LLM reasons → Execute action → Goal check → Repeat" />
Each cycle through this loop is called a **step**. Understanding what happens in each step helps you write better prompts and debug runs when something goes wrong.
1. **Screenshot the viewport.** Skyvern captures what's currently on screen, giving the LLM visual context: where buttons are, what forms look like, whether a modal is blocking the page.
2. **Extract the DOM.** The visible page is scraped into a simplified tree of interactive elements (inputs, buttons, links, dropdowns) with their labels and positions. The LLM uses both the screenshot and the DOM together: the image shows layout and visual context, the DOM provides precise element identifiers that Playwright can target.
3. **LLM decides the next action.** The screenshot, DOM tree, and your original prompt go to the LLM. It picks which element to interact with and what to do: click, type, select, scroll, upload. If there's data to extract, it pulls that too.
4. **Playwright executes.** The action runs in a real Chromium browser. If credentials are needed, they're injected directly into the browser at this point. The LLM never sees passwords, TOTP codes, or credit card numbers.
5. **Check if the goal is met.** If not, loop back to step 1 and screenshot the now-changed page. If yes, return the results.
A typical task takes 2 to 10 steps. You can set `max_steps` to cap cost during development.
### The Planner-Agent-Validator system
For anything beyond simple single-page tasks, Skyvern 2.0 wraps the agent loop in a higher-level system with three components:
<img src="/images/skyvern-system-architecture.png" alt="Skyvern 2.0 architecture: Planner breaks prompt into sub-tasks, Task Agent executes them, Validator confirms completion" />
The **Planner** takes your prompt and breaks it into an ordered sequence of sub-tasks. If you say "log into the vendor portal, download all invoices from January, and save them as PDFs," the Planner sequences that into: navigate to login → authenticate → find the invoice section → loop through January invoices → download each one.
The **Task Agent** picks up each sub-task and runs the agent loop described above. It's focused on one thing at a time, which makes it more reliable than trying to handle a complex multi-page flow in a single pass.
The **Validator** checks whether the overall goal was actually met after the Task Agent finishes. If the Validator determines something was missed or went wrong, it sends feedback back to the Planner, which can re-sequence or retry. This closed-loop feedback is what makes Skyvern reliable on complex workflows where a single missed step would break the whole thing.
---
## What Skyvern handles for you
Most browser automation breaks down at authentication, CAPTCHAs, or dynamic content. Skyvern handles these natively so you don't have to build workarounds.
**Authentication and credentials.** You store passwords, TOTP secrets, and credit card numbers through the Credentials API or the dashboard. They're encrypted at rest and injected directly into browser fields during execution. The LLM orchestrates the login flow (finds the form, clicks submit, handles 2FA prompts) but never sees the actual credential values. Supports Bitwarden, 1Password, and Azure Key Vault as external sources.
**CAPTCHAs.** Automatically detected and solved during execution. You don't configure anything.
**Structured data extraction.** You can pass a JSON Schema defining exactly what fields you want, and Skyvern extracts typed data that conforms to it. Or you can skip the schema and let Skyvern infer structure from your prompt.
**File operations.** Download files from websites, upload documents to form fields, parse PDFs, CSVs, and Excel files. Downloaded files come back as signed URLs with checksums.
**Proxy and geolocation.** Residential proxies route through real IPs in 30+ countries. Set `proxy_location` per task when you need to appear from a specific region.
**Browser state persistence.** Sessions keep a live browser open across multiple tasks for up to 24 hours, useful when you need to chain tasks that share login state. Profiles snapshot cookies, auth tokens, and local storage into a reusable package you can restore on future runs, so you don't re-authenticate every time.
**Multi-step workflows.** When a single task isn't enough, Workflows let you chain blocks together: navigate, login, loop through a list, extract data, branch on conditions, send emails. Workflows are parameterized and version-controlled. You can build them visually in the dashboard or define them through the API.
---
## Next: Core Concepts
Now that you understand how Skyvern works under the hood, learn the eight building blocks you'll use to create any automation.
<Card title="Core Concepts →" icon="book-open" href="/getting-started/core-concepts">
Tasks, workflows, blocks, runs, credentials, browser sessions, engines — everything you need to know before you start building.
</Card>

View file

@ -611,6 +611,145 @@ header[class*="header"] {
}
/* =============================================
CHANGELOG Custom Layout
============================================= */
.cl-page {
position: relative;
max-width: 860px;
margin: 0 auto;
padding: 48px 40px 80px;
}
/* --- Sidebar nav --- */
.cl-sidebar {
display: block !important;
position: fixed;
top: 140px;
left: max(20px, calc(50% - 430px - 200px));
width: 180px;
}
.cl-sidebar-title {
font-family: var(--sk-mono) !important;
font-size: 11px !important;
font-weight: 600 !important;
letter-spacing: 0.06em !important;
text-transform: uppercase !important;
color: var(--sk-text-muted) !important;
margin: 0 0 12px 0 !important;
}
.cl-sidebar-link {
display: block;
font-size: 13px;
line-height: 1.4;
color: var(--sk-text-muted) !important;
text-decoration: none !important;
padding: 6px 0;
border-bottom: none !important;
transition: color 0.15s ease;
}
.cl-sidebar-link:hover {
color: var(--sk-blue) !important;
}
/* --- Content area --- */
.cl-content {
min-width: 0;
}
.cl-title {
font-size: 32px;
font-weight: 700;
letter-spacing: -0.02em;
color: #0f172a;
margin: 0 0 8px 0;
}
.cl-subtitle {
font-size: 16px;
color: #64748b;
margin: 0 0 40px 0;
}
/* --- Text colour inside Update blocks --- */
.cl-content h2 {
color: #0f172a !important;
}
.cl-content li,
.cl-content p {
color: #334155 !important;
}
.cl-content li strong {
color: #0f172a !important;
}
/* --- Bullets and spacing inside Update blocks --- */
.cl-content ul {
padding-left: 24px !important;
list-style-type: disc !important;
}
.cl-content li {
margin-bottom: 8px;
line-height: 1.7;
display: list-item !important;
list-style-type: disc !important;
}
/* --- Dark mode overrides for changelog text --- */
.dark .cl-title,
[data-theme="dark"] .cl-title {
color: #f1f5f9;
}
.dark .cl-subtitle,
[data-theme="dark"] .cl-subtitle {
color: #94a3b8;
}
.dark .cl-content h2,
[data-theme="dark"] .cl-content h2 {
color: #f1f5f9 !important;
}
.dark .cl-content li,
.dark .cl-content p,
[data-theme="dark"] .cl-content li,
[data-theme="dark"] .cl-content p {
color: #cbd5e1 !important;
}
.dark .cl-content li strong,
[data-theme="dark"] .cl-content li strong {
color: #f1f5f9 !important;
}
/* --- Responsive --- */
@media (max-width: 1100px) {
.cl-sidebar {
display: none !important;
}
.cl-page {
padding: 48px 20px 60px;
}
.cl-title {
font-size: 26px;
}
.cl-subtitle {
margin-bottom: 24px;
}
}
/* =============================================
DARK MODE
============================================= */
@ -649,3 +788,810 @@ header[class*="header"] {
[data-theme="dark"] ::-webkit-scrollbar-track {
background: #1C1917;
}
/* =============================================
HOMEPAGE Full-width editorial layout
Inspired by farming-labs: typography-first,
sharp corners, hairline dividers, full bleed
============================================= */
/* --- Page wrapper — full width with edge padding --- */
.fm-page {
max-width: 1200px;
margin: 0 auto;
padding: 0 48px 80px;
}
/* ---- SIDE TABLE OF CONTENTS ---- */
.fm-toc {
position: fixed;
top: 50%;
left: max(16px, calc(50% - 600px - 200px));
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: 4px;
width: 150px;
z-index: 10;
}
.fm-toc-title {
font-family: var(--sk-mono);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.1em;
color: var(--sk-text-muted);
opacity: 0.5;
margin-bottom: 8px;
}
.fm-toc-link {
font-family: var(--sk-mono) !important;
font-size: 12px !important;
color: var(--sk-text-muted) !important;
text-decoration: none !important;
padding: 5px 0 5px 14px;
border-left: 1.5px solid transparent;
transition: color 0.15s ease, border-color 0.15s ease;
display: block;
}
.fm-toc-link:hover {
color: var(--sk-charcoal) !important;
border-left-color: var(--sk-charcoal);
}
.fm-toc-divider {
height: 1px;
background: rgba(0, 0, 0, 0.06);
margin: 8px 0 8px 14px;
width: 40px;
}
.fm-toc-ext {
font-size: 11px !important;
opacity: 0.7;
}
@media (max-width: 1350px) {
.fm-toc {
display: none;
}
}
.dark .fm-toc-divider,
[data-theme="dark"] .fm-toc-divider {
background: rgba(255, 255, 255, 0.08);
}
.dark .fm-toc-link:hover,
[data-theme="dark"] .fm-toc-link:hover {
color: #f1f5f9 !important;
border-left-color: #f1f5f9;
}
.dark .fm-hero-nav-link:hover,
[data-theme="dark"] .fm-hero-nav-link:hover {
color: #f1f5f9 !important;
}
.dark .fm-hero-toc,
[data-theme="dark"] .fm-hero-toc {
border-color: rgba(255, 255, 255, 0.1);
}
.dark .fm-hero-toc-item,
[data-theme="dark"] .fm-hero-toc-item {
color: #f1f5f9 !important;
}
.dark .fm-btn-outline,
[data-theme="dark"] .fm-btn-outline {
color: #f1f5f9 !important;
border-color: rgba(255, 255, 255, 0.2);
}
.dark .fm-btn-outline:hover,
[data-theme="dark"] .fm-btn-outline:hover {
border-color: rgba(255, 255, 255, 0.5);
}
/* ---- THEME TOGGLE (injected by theme-toggle.js) ---- */
.fm-theme-toggle {
font-family: var(--sk-mono);
font-size: 14px;
cursor: pointer;
color: var(--sk-text-muted);
transition: color 0.15s ease;
line-height: 1;
user-select: none;
background: none;
border: none;
padding: 0;
margin: 0;
}
.fm-theme-toggle:hover {
color: var(--sk-charcoal);
}
.dark .fm-theme-toggle:hover,
[data-theme="dark"] .fm-theme-toggle:hover {
color: #f1f5f9;
}
/* --- Shared mono link style --- */
.fm-mono-link {
font-family: var(--sk-mono) !important;
font-size: 12px !important;
letter-spacing: 0.03em;
color: var(--sk-text-muted) !important;
text-decoration: none !important;
transition: color 0.15s ease;
}
.fm-mono-link:hover {
color: var(--sk-charcoal) !important;
}
/* ---- HERO ---- */
.fm-hero {
padding: 80px 0 72px;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
text-align: center;
}
.fm-hero-meta {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-bottom: 40px;
}
.fm-meta-sep {
font-family: var(--sk-mono);
font-size: 12px;
color: rgba(0, 0, 0, 0.15);
}
.fm-hero-title {
font-size: 56px;
font-weight: 700;
line-height: 0.97;
letter-spacing: -0.04em;
color: var(--sk-charcoal);
margin: 0 0 28px;
}
.fm-hero-sub {
font-size: 18px;
line-height: 1.7;
color: var(--sk-text-muted);
max-width: 620px;
margin: 0 auto 20px;
}
/* --- Hero TOC block --- */
.fm-hero-toc {
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 2px;
padding: 16px 24px;
margin: 0 auto 28px;
display: inline-block;
}
.fm-hero-toc-label {
display: block;
font-family: var(--sk-mono);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.12em;
color: var(--sk-text-muted);
opacity: 0.5;
margin-bottom: 10px;
text-align: center;
}
.fm-hero-toc-items {
display: flex;
justify-content: center;
gap: 20px;
}
.fm-hero-toc-item {
font-family: var(--sk-mono) !important;
font-size: 12px !important;
letter-spacing: 0.02em;
color: var(--sk-charcoal) !important;
text-decoration: none !important;
transition: opacity 0.15s ease;
white-space: nowrap;
}
.fm-hero-toc-item:hover {
opacity: 0.6;
}
/* --- Hero nav (secondary links below CTAs) --- */
.fm-hero-nav {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin-top: 16px;
}
.fm-hero-nav-link {
font-family: var(--sk-mono) !important;
font-size: 12px !important;
letter-spacing: 0.03em;
color: var(--sk-text-muted) !important;
text-decoration: none !important;
transition: color 0.15s ease;
}
.fm-hero-nav-link:hover {
color: var(--sk-charcoal) !important;
}
.fm-hero-ctas {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
}
.fm-btn-primary {
display: inline-block;
font-family: var(--sk-mono) !important;
font-size: 12px !important;
font-weight: 600;
letter-spacing: 0.1em;
padding: 14px 32px;
background: var(--sk-charcoal);
color: #fff !important;
text-decoration: none !important;
transition: opacity 0.15s ease;
}
.fm-btn-primary:hover {
opacity: 0.8;
}
.fm-btn-outline {
display: inline-block;
font-family: var(--sk-mono) !important;
font-size: 12px !important;
font-weight: 600;
letter-spacing: 0.1em;
padding: 14px 32px;
background: transparent;
color: var(--sk-charcoal) !important;
border: 1px solid rgba(0, 0, 0, 0.2);
text-decoration: none !important;
transition: border-color 0.15s ease;
}
.fm-btn-outline:hover {
border-color: rgba(0, 0, 0, 0.5);
}
.fm-btn-ghost {
font-family: var(--sk-mono) !important;
font-size: 12px !important;
font-weight: 500;
letter-spacing: 0.06em;
color: var(--sk-text-muted) !important;
text-decoration: none !important;
transition: color 0.15s ease;
}
.fm-btn-ghost:hover {
color: var(--sk-charcoal) !important;
}
/* ---- SECTIONS ---- */
.fm-section {
padding: 72px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
.fm-section-last {
border-bottom: none;
padding-bottom: 48px;
}
.fm-section-head {
margin-bottom: 48px;
}
.fm-section-num {
display: block;
font-family: var(--sk-mono);
font-size: 12px;
font-weight: 500;
letter-spacing: 0.08em;
color: var(--sk-text-muted);
margin-bottom: 12px;
}
.fm-section-title {
font-size: 40px;
font-weight: 700;
letter-spacing: -0.035em;
line-height: 1.05;
color: var(--sk-charcoal);
margin: 0;
}
.fm-section-sub {
font-size: 17px;
color: var(--sk-text-muted);
margin: 12px 0 0;
}
/* ---- TYPOGRAPHY ---- */
.fm-prose {
font-size: 17px;
line-height: 1.75;
color: var(--sk-text-muted);
margin: 0 0 24px;
}
.fm-prose strong {
color: var(--sk-charcoal);
font-weight: 600;
}
.fm-h3 {
font-size: 24px;
font-weight: 700;
letter-spacing: -0.02em;
color: var(--sk-charcoal);
margin: 0 0 16px;
}
/* ---- TWO-COLUMN LAYOUT ---- */
.fm-two-col {
display: grid;
grid-template-columns: 1fr 280px;
gap: 48px;
align-items: start;
margin-bottom: 8px;
}
.fm-aside-note {
font-family: var(--sk-mono);
font-size: 13px;
line-height: 1.6;
color: var(--sk-text-muted);
border-left: 2px solid rgba(0, 0, 0, 0.08);
padding-left: 16px;
margin: 0;
}
/* ---- IMAGES ---- */
.fm-img {
display: block;
width: 100%;
margin: 32px 0;
}
/* ---- RULE ---- */
.fm-rule {
height: 1px;
background: rgba(0, 0, 0, 0.08);
margin: 56px 0;
}
/* (steps grid removed — replaced by diagrams) */
/* ---- CAPABILITIES GRID ---- */
.fm-cap-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0;
border-top: 1px solid rgba(0, 0, 0, 0.08);
margin-top: 8px;
}
.fm-cap-cell {
padding: 28px 24px;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
border-right: 1px solid rgba(0, 0, 0, 0.08);
}
.fm-cap-cell:nth-child(3n) {
border-right: none;
}
.fm-cap-wide {
grid-column: 1 / -1;
border-right: none;
}
.fm-cap-name {
font-size: 14px;
font-weight: 600;
color: var(--sk-charcoal);
margin: 0 0 6px;
}
.fm-cap-desc {
font-size: 14px;
line-height: 1.6;
color: var(--sk-text-muted);
margin: 0;
}
.fm-cap-desc code {
font-family: var(--sk-mono);
font-size: 12px;
color: var(--sk-text-muted);
}
/* ---- PATH GRID ---- */
.fm-path-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0;
border: 1px solid rgba(0, 0, 0, 0.08);
margin-top: 8px;
}
.fm-path-card {
display: block;
padding: 32px 28px;
text-decoration: none !important;
color: inherit !important;
border-right: 1px solid rgba(0, 0, 0, 0.08);
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
transition: background-color 0.15s ease;
}
.fm-path-card:nth-child(2n) {
border-right: none;
}
.fm-path-card:nth-child(n+3) {
border-bottom: none;
}
.fm-path-card:hover {
background-color: rgba(0, 0, 0, 0.02);
}
.fm-path-label {
display: block;
font-family: var(--sk-mono);
font-size: 11px;
font-weight: 500;
letter-spacing: 0.1em;
color: var(--sk-text-muted);
margin-bottom: 10px;
}
.fm-path-name {
display: block;
font-size: 17px;
font-weight: 600;
color: var(--sk-charcoal);
margin-bottom: 8px;
}
.fm-path-desc {
font-size: 14px;
line-height: 1.6;
color: var(--sk-text-muted);
margin: 0;
}
/* ---- FOOTER ---- */
.fm-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28px 0 0;
margin-top: 8px;
border-top: 1px solid rgba(0, 0, 0, 0.08);
}
.fm-footer-right {
display: flex;
gap: 20px;
}
/* ---- DIAGRAMS ---- */
.fm-diagram {
margin: 40px 0;
}
.fm-dia-row {
display: flex;
align-items: stretch;
}
.fm-dia-row-3 .fm-dia-card {
flex: 1;
}
.fm-dia-card {
flex: 1;
padding: 24px 20px;
background: #fff;
border: 1px solid rgba(0, 0, 0, 0.08);
}
.fm-dia-card-lg {
padding: 28px 24px;
}
.fm-dia-arrow {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
flex-shrink: 0;
font-size: 18px;
color: rgba(0, 0, 0, 0.18);
font-family: var(--sk-mono);
}
.fm-dia-num {
display: block;
font-family: var(--sk-mono);
font-size: 28px;
font-weight: 700;
color: rgba(0, 0, 0, 0.35);
line-height: 1;
margin-bottom: 14px;
}
.fm-dia-label {
display: block;
font-family: var(--sk-mono);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.08em;
color: var(--sk-charcoal);
margin-bottom: 8px;
}
.fm-dia-desc {
font-size: 14px;
line-height: 1.6;
color: var(--sk-text-muted);
margin: 0;
}
.fm-dia-return {
margin: 0 20px;
border-left: 1px dashed rgba(0, 0, 0, 0.1);
border-bottom: 1px dashed rgba(0, 0, 0, 0.1);
border-right: 1px dashed rgba(0, 0, 0, 0.1);
border-radius: 0 0 4px 4px;
padding: 14px 0;
text-align: center;
}
.fm-dia-return span {
font-family: var(--sk-mono);
font-size: 12px;
letter-spacing: 0.04em;
color: var(--sk-text-muted);
}
/* ---- DARK MODE ---- */
.dark .fm-hero,
[data-theme="dark"] .fm-hero,
.dark .fm-section,
[data-theme="dark"] .fm-section,
.dark .fm-rule,
[data-theme="dark"] .fm-rule,
.dark .fm-footer,
[data-theme="dark"] .fm-footer {
border-color: rgba(255, 255, 255, 0.08);
}
.dark .fm-meta-sep,
[data-theme="dark"] .fm-meta-sep {
color: rgba(255, 255, 255, 0.15);
}
.dark .fm-btn-primary,
[data-theme="dark"] .fm-btn-primary {
background: #f5f0eb;
color: #1C1917 !important;
}
.dark .fm-cap-cell,
[data-theme="dark"] .fm-cap-cell {
border-color: rgba(255, 255, 255, 0.08);
}
.dark .fm-cap-grid,
[data-theme="dark"] .fm-cap-grid {
border-color: rgba(255, 255, 255, 0.08);
}
.dark .fm-path-grid,
[data-theme="dark"] .fm-path-grid {
border-color: rgba(255, 255, 255, 0.08);
}
.dark .fm-path-card,
[data-theme="dark"] .fm-path-card {
border-color: rgba(255, 255, 255, 0.08);
}
.dark .fm-path-card:hover,
[data-theme="dark"] .fm-path-card:hover {
background-color: rgba(255, 255, 255, 0.03);
}
.dark .fm-aside-note,
[data-theme="dark"] .fm-aside-note {
border-color: rgba(255, 255, 255, 0.08);
}
.dark .fm-dia-card,
[data-theme="dark"] .fm-dia-card {
background: rgba(255, 255, 255, 0.04);
border-color: rgba(255, 255, 255, 0.08);
}
.dark .fm-dia-num,
[data-theme="dark"] .fm-dia-num {
color: rgba(255, 255, 255, 0.4);
}
.dark .fm-dia-label,
[data-theme="dark"] .fm-dia-label {
color: #e2e8f0;
}
.dark .fm-dia-desc,
[data-theme="dark"] .fm-dia-desc {
color: rgba(255, 255, 255, 0.5);
}
.dark .fm-dia-arrow,
[data-theme="dark"] .fm-dia-arrow {
color: rgba(255, 255, 255, 0.18);
}
.dark .fm-dia-return,
[data-theme="dark"] .fm-dia-return {
border-color: rgba(255, 255, 255, 0.1);
}
.dark .fm-prose strong,
[data-theme="dark"] .fm-prose strong {
color: #f1f5f9;
}
.dark .fm-mono-link:hover,
[data-theme="dark"] .fm-mono-link:hover {
color: #f1f5f9 !important;
}
/* ---- RESPONSIVE ---- */
@media (max-width: 900px) {
.fm-page {
padding: 0 28px 60px;
}
.fm-hero-title {
font-size: 40px;
}
.fm-section-title {
font-size: 32px;
}
.fm-two-col {
grid-template-columns: 1fr;
gap: 20px;
}
.fm-dia-row {
flex-direction: column;
}
.fm-dia-arrow {
width: auto;
height: 28px;
font-size: 16px;
transform: rotate(90deg);
}
.fm-dia-return {
margin: 0 12px;
}
.fm-cap-grid {
grid-template-columns: 1fr;
}
.fm-cap-cell {
border-right: none;
}
.fm-path-grid {
grid-template-columns: 1fr;
}
.fm-path-card {
border-right: none;
}
.fm-path-card:nth-child(n+3) {
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
.fm-path-card:last-child {
border-bottom: none;
}
}
@media (max-width: 600px) {
.fm-page {
padding: 0 20px 48px;
}
.fm-hero {
padding: 48px 0 40px;
}
.fm-hero-title {
font-size: 32px;
}
.fm-hero-ctas {
flex-direction: column;
align-items: center;
gap: 12px;
}
.fm-hero-nav {
flex-wrap: wrap;
gap: 8px;
}
.fm-section {
padding: 48px 0;
}
.fm-section-title {
font-size: 26px;
}
.fm-footer {
flex-direction: column;
gap: 16px;
text-align: center;
}
.fm-footer-right {
justify-content: center;
}
}