From d1447b8ee13ab2be3017908265774f79b5acef7b Mon Sep 17 00:00:00 2001 From: Naman Date: Thu, 12 Feb 2026 18:52:50 +0530 Subject: [PATCH] Add Python SDK reference docs with LLM-optimized complete reference (#4713) --- docs/docs.json | 19 + docs/getting-started/core-concepts.mdx | 6 +- docs/sdk-reference/browser-profiles.mdx | 128 +++++ docs/sdk-reference/browser-sessions.mdx | 119 +++++ docs/sdk-reference/complete-reference.mdx | 593 ++++++++++++++++++++++ docs/sdk-reference/credentials.mdx | 119 +++++ docs/sdk-reference/error-handling.mdx | 168 ++++++ docs/sdk-reference/helpers.mdx | 159 ++++++ docs/sdk-reference/overview.mdx | 189 +++++++ docs/sdk-reference/tasks.mdx | 297 +++++++++++ docs/sdk-reference/workflows.mdx | 274 ++++++++++ 11 files changed, 2069 insertions(+), 2 deletions(-) create mode 100644 docs/sdk-reference/browser-profiles.mdx create mode 100644 docs/sdk-reference/browser-sessions.mdx create mode 100644 docs/sdk-reference/complete-reference.mdx create mode 100644 docs/sdk-reference/credentials.mdx create mode 100644 docs/sdk-reference/error-handling.mdx create mode 100644 docs/sdk-reference/helpers.mdx create mode 100644 docs/sdk-reference/overview.mdx create mode 100644 docs/sdk-reference/tasks.mdx create mode 100644 docs/sdk-reference/workflows.mdx diff --git a/docs/docs.json b/docs/docs.json index c41e6f7f..bbb0664b 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -117,6 +117,25 @@ } ] }, + { + "tab": "Python SDK", + "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" + ] + } + ] + }, { "tab": "API Reference", "openapi": "api-reference/openapi.json" diff --git a/docs/getting-started/core-concepts.mdx b/docs/getting-started/core-concepts.mdx index afd194b4..1caeb50a 100644 --- a/docs/getting-started/core-concepts.mdx +++ b/docs/getting-started/core-concepts.mdx @@ -290,7 +290,7 @@ A saved snapshot of browser state. Unlike sessions, profiles persist indefinitel ```python # Create a profile from a completed run -profile = await skyvern.browser_profiles.create_browser_profile( +profile = await skyvern.create_browser_profile( name="my-authenticated-profile", workflow_run_id=run.run_id ) @@ -350,10 +350,12 @@ Skyvern supports multiple AI engines for task execution: 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="skyvern-2.0" + engine=RunEngine.skyvern_v2 ) ``` diff --git a/docs/sdk-reference/browser-profiles.mdx b/docs/sdk-reference/browser-profiles.mdx new file mode 100644 index 00000000..446dd507 --- /dev/null +++ b/docs/sdk-reference/browser-profiles.mdx @@ -0,0 +1,128 @@ +--- +title: Browser Profiles +subtitle: Save and reuse browser state across runs +slug: sdk-reference/browser-profiles +--- + +A browser profile is a snapshot of browser state — cookies, local storage, session data. Create a profile from a completed run, then load it into future workflow runs to skip login and setup steps. + +For conceptual background, see [Browser Profiles](/optimization/browser-profiles). + +--- + +## `create_browser_profile` + +Create a profile from a completed workflow run. + +```python +profile = await client.create_browser_profile( + name="production-login", + workflow_run_id="wr_abc123", +) +print(profile.browser_profile_id) # bpf_abc123 +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `name` | `str` | Yes | Display name for the profile. | +| `description` | `str` | No | Optional description. | +| `workflow_run_id` | `str` | No | The workflow run ID to snapshot. The run must have used `persist_browser_session=True`. | +| `browser_session_id` | `str` | No | The browser session ID to snapshot. | + +### Returns `BrowserProfile` + +| Field | Type | Description | +|-------|------|-------------| +| `browser_profile_id` | `str` | Unique ID. Starts with `bpf_`. | +| `name` | `str` | Profile name. | +| `description` | `str \| None` | Profile description. | +| `created_at` | `datetime` | When the profile was created. | + +### Example: Create a profile from a login workflow + +```python +# Step 1: Run a workflow with persist_browser_session +run = await client.run_workflow( + workflow_id="wpid_login_flow", + parameters={"username": "demo@example.com"}, + wait_for_completion=True, +) + +# Step 2: Create a profile from the run +profile = await client.create_browser_profile( + name="demo-account-login", + workflow_run_id=run.run_id, +) + +# Step 3: Use the profile in future runs (skip login) +result = await client.run_workflow( + workflow_id="wpid_extract_invoices", + browser_profile_id=profile.browser_profile_id, + wait_for_completion=True, +) +``` + + +Session archiving is asynchronous. If `create_browser_profile` fails immediately after a workflow completes, wait a few seconds and retry. + + +--- + +## `list_browser_profiles` + +List all browser profiles. + +```python +profiles = await client.list_browser_profiles() +for p in profiles: + print(f"{p.name} ({p.browser_profile_id})") +``` + +### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `include_deleted` | `bool` | No | `None` | Include soft-deleted profiles in the results. | + +### Returns `list[BrowserProfile]` + +--- + +## `get_browser_profile` + +Get a single profile by ID. + +```python +profile = await client.get_browser_profile("bpf_abc123") +print(profile.name) +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `profile_id` | `str` | Yes | The browser profile ID. | + +### Returns `BrowserProfile` + +--- + +## `delete_browser_profile` + +Delete a browser profile. + +```python +await client.delete_browser_profile("bpf_abc123") +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `profile_id` | `str` | Yes | The browser profile ID to delete. | + + +`browser_profile_id` only works with `run_workflow`, not `run_task`. If you pass it to `run_task`, it will be silently ignored. + diff --git a/docs/sdk-reference/browser-sessions.mdx b/docs/sdk-reference/browser-sessions.mdx new file mode 100644 index 00000000..503e4166 --- /dev/null +++ b/docs/sdk-reference/browser-sessions.mdx @@ -0,0 +1,119 @@ +--- +title: Browser Sessions +subtitle: Maintain live browser state between API calls +slug: sdk-reference/browser-sessions +--- + +A browser session is a persistent browser instance that stays alive between API calls. Use sessions to chain multiple tasks in the same browser without losing cookies, local storage, or login state. + +For conceptual background, see [Browser Sessions](/optimization/browser-sessions). + +--- + +## `create_browser_session` + +Spin up a new cloud browser session. + +```python +session = await client.create_browser_session(timeout=60) +print(session.browser_session_id) # pbs_abc123 +``` + +### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `timeout` | `int` | No | `60` | Session timeout in minutes (5–1440). Timer starts after the session is ready. | +| `proxy_location` | `ProxyLocation` | No | `None` | Route browser traffic through a geographic proxy. | +| `extensions` | `list[Extensions]` | No | `None` | Browser extensions to install. Options: `"ad-blocker"`, `"captcha-solver"`. | +| `browser_type` | `PersistentBrowserType` | No | `None` | Browser type. Options: `"chrome"`, `"msedge"`. | + +### Returns `BrowserSessionResponse` + +| Field | Type | Description | +|-------|------|-------------| +| `browser_session_id` | `str` | Unique ID. Starts with `pbs_`. | +| `status` | `str \| None` | Current session status. | +| `browser_address` | `str \| None` | CDP address for connecting to the browser. | +| `app_url` | `str \| None` | Link to the live browser view in the Cloud UI. | +| `timeout` | `int \| None` | Configured timeout in minutes. | +| `started_at` | `datetime \| None` | When the session became ready. | +| `created_at` | `datetime` | When the session was requested. | + +### Example: Chain multiple tasks in one session + +```python +session = await client.create_browser_session() + +# Step 1: Log in +await client.run_task( + prompt="Log in with username demo@example.com", + url="https://app.example.com/login", + browser_session_id=session.browser_session_id, + wait_for_completion=True, +) + +# Step 2: Extract data (same browser, already logged in) +result = await client.run_task( + prompt="Go to the invoices page and extract all invoice numbers", + browser_session_id=session.browser_session_id, + wait_for_completion=True, +) +print(result.output) + +# Clean up +await client.close_browser_session(session.browser_session_id) +``` + +--- + +## `get_browser_session` + +Get the status and details of a session. + +```python +session = await client.get_browser_session("pbs_abc123") +print(session.status, session.browser_address) +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `browser_session_id` | `str` | Yes | The session ID. | + +### Returns `BrowserSessionResponse` + +--- + +## `get_browser_sessions` + +List all active browser sessions. + +```python +sessions = await client.get_browser_sessions() +for s in sessions: + print(f"{s.browser_session_id} — {s.status}") +``` + +### Returns `list[BrowserSessionResponse]` + +--- + +## `close_browser_session` + +Close a browser session and release its resources. + +```python +await client.close_browser_session("pbs_abc123") +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `browser_session_id` | `str` | Yes | The session ID to close. | + + +Closing a session is irreversible. Any unsaved state (cookies, local storage) is lost unless you created a [browser profile](/sdk-reference/browser-profiles) from it. + diff --git a/docs/sdk-reference/complete-reference.mdx b/docs/sdk-reference/complete-reference.mdx new file mode 100644 index 00000000..67309cb2 --- /dev/null +++ b/docs/sdk-reference/complete-reference.mdx @@ -0,0 +1,593 @@ +--- +title: Complete Reference (For LLMs) +subtitle: Every method, parameter, and type in one page +slug: sdk-reference/complete-reference +--- + +## Install and initialize + +```bash +pip install skyvern +``` + +```python +import asyncio +from skyvern import Skyvern + +async def main(): + client = Skyvern(api_key="YOUR_API_KEY") + result = await client.run_task( + prompt="Get the title of the top post on Hacker News", + url="https://news.ycombinator.com", + wait_for_completion=True, + ) + print(result.output) + +asyncio.run(main()) +``` + +**Constructor:** + +```python +Skyvern( + api_key: str, # Required + base_url: str | None = None, # Override for self-hosted deployments + environment: SkyvernEnvironment = CLOUD,# CLOUD or LOCAL + timeout: float | None = None, # HTTP request timeout (seconds) +) +``` + +**Local mode** (runs entirely on your machine, reads `.env`): + +```python +client = Skyvern.local() +``` + +All methods are async. Use `await` inside `async def`, with `asyncio.run()` as entry point. + +--- + +## Imports + +```python +from skyvern import Skyvern +from skyvern.client import SkyvernEnvironment # CLOUD, LOCAL +from skyvern.client.core import ApiError, RequestOptions # Base error, per-request config +from skyvern.client.errors import ( # HTTP error subclasses + BadRequestError, # 400 + ForbiddenError, # 403 + NotFoundError, # 404 + ConflictError, # 409 + UnprocessableEntityError, # 422 +) +from skyvern.schemas.runs import RunEngine # skyvern_v1, skyvern_v2, openai_cua, anthropic_cua, ui_tars +from skyvern.schemas.run_blocks import CredentialType # skyvern, bitwarden, onepassword, azure_vault +``` + +**Important:** `ApiError` lives in `skyvern.client.core`, not `skyvern.client.errors`. The subclasses (`NotFoundError`, etc.) live in `skyvern.client.errors`. + +--- + +## Tasks + +### `run_task` + +```python +result = await client.run_task( + prompt: str, # Required. Natural language instructions. + url: str | None = None, # Starting page URL. + engine: RunEngine = RunEngine.skyvern_v2, # AI engine. + wait_for_completion: bool = False, # Block until finished. + timeout: float = 1800, # Max wait (seconds). Only with wait_for_completion. + max_steps: int | None = None, # Cap AI steps to limit cost. + data_extraction_schema: dict | str | None = None, # JSON Schema constraining output shape. + browser_session_id: str | None = None, # Run in existing session. + publish_workflow: bool = False, # Save generated code as reusable workflow. + proxy_location: ProxyLocation | None = None, + webhook_url: str | None = None, + error_code_mapping: dict[str, str] | None = None, + totp_identifier: str | None = None, + totp_url: str | None = None, + title: str | None = None, + user_agent: str | None = None, + extra_http_headers: dict[str, str] | None = None, + browser_address: str | None = None, +) -> TaskRunResponse +``` + +**`TaskRunResponse` fields:** + +| Field | Type | Description | +|-------|------|-------------| +| `run_id` | `str` | Unique ID (`tsk_...`). | +| `status` | `str` | `created` \| `queued` \| `running` \| `completed` \| `failed` \| `terminated` \| `timed_out` \| `canceled` | +| `output` | `dict \| list \| None` | Extracted data. `None` until completed. | +| `failure_reason` | `str \| None` | Error description if failed. | +| `downloaded_files` | `list[FileInfo] \| None` | Files downloaded during the run. | +| `recording_url` | `str \| None` | Session recording video URL. | +| `screenshot_urls` | `list[str] \| None` | Final screenshots. | +| `app_url` | `str \| None` | Link to run in Skyvern UI. | +| `step_count` | `int \| None` | Number of AI steps taken. | +| `created_at` | `datetime` | When the run was created. | +| `finished_at` | `datetime \| None` | When the run finished. | + +### `get_run` + +```python +run = await client.get_run(run_id: str) -> GetRunResponse +``` + +Returns status and results for any run (task or workflow). + +### `cancel_run` + +```python +await client.cancel_run(run_id: str) +``` + +### `get_run_timeline` + +```python +timeline = await client.get_run_timeline(run_id: str) -> list[WorkflowRunTimeline] +``` + +### `get_run_artifacts` + +```python +artifacts = await client.get_run_artifacts( + run_id: str, + artifact_type: ArtifactType | list[ArtifactType] | None = None, +) -> list[Artifact] +``` + +### `get_artifact` + +```python +artifact = await client.get_artifact(artifact_id: str) -> Artifact +``` + +### `retry_run_webhook` + +```python +await client.retry_run_webhook(run_id: str) +``` + +--- + +## Workflows + +### `run_workflow` + +```python +result = await client.run_workflow( + workflow_id: str, # Required. Permanent ID (wpid_...). + parameters: dict | None = None, # Input params matching workflow definition. + wait_for_completion: bool = False, + timeout: float = 1800, + run_with: str | None = None, # "code" (cached Playwright) or "agent" (AI). + ai_fallback: bool | None = None, # Fall back to AI if code fails. + browser_session_id: str | None = None, + browser_profile_id: str | None = None, # Load saved browser state. + proxy_location: ProxyLocation | None = None, + max_steps_override: int | None = None, + webhook_url: str | None = None, + title: str | None = None, + template: bool | None = None, + totp_identifier: str | None = None, + totp_url: str | None = None, + user_agent: str | None = None, + extra_http_headers: dict[str, str] | None = None, + browser_address: str | None = None, +) -> WorkflowRunResponse +``` + +**`WorkflowRunResponse` fields:** Same as `TaskRunResponse` plus `run_with`, `ai_fallback`, `script_run`. + +### `create_workflow` + +```python +workflow = await client.create_workflow( + json_definition: dict | None = None, + yaml_definition: str | None = None, + folder_id: str | None = None, +) -> Workflow +``` + +Provide either `json_definition` or `yaml_definition`. Example: + +```python +workflow = await client.create_workflow( + json_definition={ + "title": "Extract Products", + "workflow_definition": { + "parameters": [ + { + "key": "target_url", + "parameter_type": "workflow", + "workflow_parameter_type": "string", + "description": "URL to scrape", + } + ], + "blocks": [ + { + "block_type": "task", + "label": "extract", + "prompt": "Extract the top 3 products with name and price", + "url": "{{ target_url }}", + } + ], + }, + }, +) +print(workflow.workflow_permanent_id) # wpid_... — use this to run it +``` + +**`Workflow` fields:** `workflow_id`, `workflow_permanent_id`, `version`, `title`, `workflow_definition`, `status`, `created_at` + +### `get_workflows` + +```python +workflows = await client.get_workflows( + page: int | None = None, + page_size: int | None = None, + only_saved_tasks: bool | None = None, + only_workflows: bool | None = None, + title: str | None = None, + search_key: str | None = None, + folder_id: str | None = None, + status: WorkflowStatus | list[WorkflowStatus] | None = None, +) -> list[Workflow] +``` + +### `get_workflow` + +```python +workflow = await client.get_workflow( + workflow_permanent_id: str, + version: int | None = None, + template: bool | None = None, +) -> Workflow +``` + +### `get_workflow_versions` + +```python +versions = await client.get_workflow_versions( + workflow_permanent_id: str, + template: bool | None = None, +) -> list[Workflow] +``` + +### `update_workflow` + +```python +updated = await client.update_workflow( + workflow_id: str, # The version ID (not permanent ID). + json_definition: dict | None = None, + yaml_definition: str | None = None, +) -> Workflow +``` + +### `delete_workflow` + +```python +await client.delete_workflow(workflow_id: str) +``` + +--- + +## Browser sessions + +A persistent browser instance that stays alive between API calls. Use to chain tasks without losing cookies or login state. + +### `create_browser_session` + +```python +session = await client.create_browser_session( + timeout: int | None = 60, # Minutes (5-1440). + proxy_location: ProxyLocation | None = None, + extensions: list[Extensions] | None = None, # "ad-blocker", "captcha-solver" + browser_type: PersistentBrowserType | None = None, # "chrome", "msedge" +) -> BrowserSessionResponse +``` + +**`BrowserSessionResponse` fields:** `browser_session_id`, `status`, `browser_address`, `app_url`, `timeout`, `started_at`, `created_at` + +### `get_browser_session` + +```python +session = await client.get_browser_session(browser_session_id: str) -> BrowserSessionResponse +``` + +### `get_browser_sessions` + +```python +sessions = await client.get_browser_sessions() -> list[BrowserSessionResponse] +``` + +### `close_browser_session` + +```python +await client.close_browser_session(browser_session_id: str) +``` + +### Session chaining example + +```python +session = await client.create_browser_session() + +# Step 1: Log in +await client.run_task( + prompt="Log in with username demo@example.com", + url="https://app.example.com/login", + browser_session_id=session.browser_session_id, + wait_for_completion=True, +) + +# Step 2: Extract data (same browser, already logged in) +result = await client.run_task( + prompt="Go to the invoices page and extract all invoice numbers", + browser_session_id=session.browser_session_id, + wait_for_completion=True, +) +print(result.output) + +# Clean up +await client.close_browser_session(session.browser_session_id) +``` + +--- + +## Browser profiles + +A saved snapshot of browser state (cookies, local storage). Persists indefinitely. Create from a completed workflow run, then reuse to skip login. + +### `create_browser_profile` + +```python +profile = await client.create_browser_profile( + name: str, + description: str | None = None, + workflow_run_id: str | None = None, # Run must have used persist_browser_session=True. + browser_session_id: str | None = None, +) -> BrowserProfile +``` + +**`BrowserProfile` fields:** `browser_profile_id`, `name`, `description`, `created_at` + +### `list_browser_profiles` + +```python +profiles = await client.list_browser_profiles( + include_deleted: bool | None = None, +) -> list[BrowserProfile] +``` + +### `get_browser_profile` + +```python +profile = await client.get_browser_profile(profile_id: str) -> BrowserProfile +``` + +### `delete_browser_profile` + +```python +await client.delete_browser_profile(profile_id: str) +``` + +### Profile workflow example + +```python +# Step 1: Run a login workflow +run = await client.run_workflow( + workflow_id="wpid_login_flow", + parameters={"username": "demo@example.com"}, + wait_for_completion=True, +) + +# Step 2: Create a profile from the run +profile = await client.create_browser_profile( + name="demo-account-login", + workflow_run_id=run.run_id, +) + +# Step 3: Use the profile in future runs (skip login) +result = await client.run_workflow( + workflow_id="wpid_extract_invoices", + browser_profile_id=profile.browser_profile_id, + wait_for_completion=True, +) +``` + +--- + +## Credentials + +Store login information securely. Reference by ID instead of passing secrets in code. + +### `create_credential` + +```python +credential = await client.create_credential( + name: str, + credential_type: CredentialType, # e.g. "password" + credential: dict, # Password: {"username": "...", "password": "..."} +) -> CredentialResponse +``` + +### `get_credentials` + +```python +creds = await client.get_credentials( + page: int | None = None, + page_size: int | None = None, +) -> list[CredentialResponse] +``` + +### `get_credential` + +```python +cred = await client.get_credential(credential_id: str) -> CredentialResponse +``` + +### `delete_credential` + +```python +await client.delete_credential(credential_id: str) +``` + +### `send_totp_code` + +Send a TOTP code to Skyvern during a run that requires 2FA. + +```python +await client.send_totp_code( + totp_identifier: str, + content: str, # The TOTP code value. + task_id: str | None = None, + workflow_id: str | None = None, + workflow_run_id: str | None = None, + source: str | None = None, + expired_at: datetime | None = None, +) -> TotpCode +``` + +--- + +## Helper methods + +### `login` + +Automate logging into a website using stored credentials. + +```python +from skyvern.schemas.run_blocks import CredentialType + +result = await client.login( + credential_type: CredentialType, # Required. skyvern, bitwarden, onepassword, azure_vault. + url: str | None = None, + credential_id: str | None = None, # When using CredentialType.skyvern. + prompt: str | None = None, + browser_session_id: str | None = None, + wait_for_completion: bool = False, + timeout: float = 1800, + # Bitwarden: bitwarden_collection_id, bitwarden_item_id + # 1Password: onepassword_vault_id, onepassword_item_id + # Azure: azure_vault_name, azure_vault_username_key, azure_vault_password_key, azure_vault_totp_secret_key +) -> WorkflowRunResponse +``` + +### `download_files` + +Does **not** support `wait_for_completion`. Returns immediately — poll with `get_run()`. + +```python +result = await client.download_files( + navigation_goal: str, # Required. What to download. + url: str | None = None, + browser_session_id: str | None = None, + browser_profile_id: str | None = None, + download_suffix: str | None = None, # Expected extension, e.g. ".pdf" + download_timeout: float | None = None, + max_steps_per_run: int | None = None, +) -> WorkflowRunResponse +``` + +### `upload_file` + +```python +with open("data.csv", "rb") as f: + upload = await client.upload_file(file=f) + print(upload.s3uri) # s3://skyvern-uploads/... + print(upload.presigned_url) # https://...signed download URL +``` + +Returns `UploadFileResponse` with fields: `s3uri`, `presigned_url`. + +--- + +## Error handling + +```python +from skyvern.client.core import ApiError +from skyvern.client.errors import NotFoundError + +try: + run = await client.get_run("tsk_nonexistent") +except NotFoundError as e: + print(e.status_code, e.body) # 404 +except ApiError as e: + print(e.status_code, e.body) # Any other HTTP error +``` + +**Error types:** `BadRequestError` (400), `ForbiddenError` (403), `NotFoundError` (404), `ConflictError` (409), `UnprocessableEntityError` (422). All inherit from `ApiError`. + +**Completion timeout** raises Python's built-in `TimeoutError`: + +```python +try: + result = await client.run_task( + prompt="...", url="...", wait_for_completion=True, timeout=300, + ) +except TimeoutError: + print("Task didn't complete in time") +``` + +**Run failure** is not an exception — check `result.status`: + +```python +if result.status == "failed": + print(result.failure_reason) +elif result.status == "completed": + print(result.output) +``` + +--- + +## Request options + +Override timeout, retries, or headers per-request: + +```python +from skyvern.client.core import RequestOptions + +result = await client.run_task( + prompt="...", + url="...", + request_options=RequestOptions( + timeout_in_seconds=120, + max_retries=3, + additional_headers={"x-custom-header": "value"}, + ), +) +``` + +--- + +## Polling pattern + +When not using `wait_for_completion`: + +```python +import asyncio + +task = await client.run_task(prompt="...", url="...") + +while True: + run = await client.get_run(task.run_id) + if run.status in ("completed", "failed", "terminated", "timed_out", "canceled"): + break + await asyncio.sleep(5) + +print(run.output) +``` + +--- + +## Key constraints + +- `browser_profile_id` works with `run_workflow` only — silently ignored by `run_task`. +- `download_files` does not support `wait_for_completion` — poll manually or use webhooks. +- Only workflow runs with `persist_browser_session=True` produce archives for profile creation. +- Session archiving is async — profile creation may need a short retry delay after a run completes. +- `engine` accepts `RunEngine` enum values: `skyvern_v1`, `skyvern_v2`, `openai_cua`, `anthropic_cua`, `ui_tars`. diff --git a/docs/sdk-reference/credentials.mdx b/docs/sdk-reference/credentials.mdx new file mode 100644 index 00000000..28a15eb5 --- /dev/null +++ b/docs/sdk-reference/credentials.mdx @@ -0,0 +1,119 @@ +--- +title: Credentials +subtitle: Store and manage authentication credentials securely +slug: sdk-reference/credentials +--- + +Credentials let you store login information (username/password, TOTP secrets) securely in Skyvern's vault. Reference them by ID in tasks and workflows instead of passing secrets in your code. + +--- + +## `create_credential` + +Store a new credential. + +```python +credential = await client.create_credential( + name="my-app-login", + credential_type="password", + credential={ + "username": "demo@example.com", + "password": "s3cur3-p4ss", + }, +) +print(credential.credential_id) +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `name` | `str` | Yes | Display name for the credential. | +| `credential_type` | `CredentialType` | Yes | Type of credential. | +| `credential` | `CreateCredentialRequestCredential` | Yes | The credential data. Shape depends on `credential_type`. | + +### Returns `CredentialResponse` + +--- + +## `get_credentials` + +List all credentials. Credential values are never returned — only metadata. + +```python +creds = await client.get_credentials() +for c in creds: + print(f"{c.name} ({c.credential_id})") +``` + +### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `page` | `int` | No | `None` | Page number. | +| `page_size` | `int` | No | `None` | Results per page. | + +### Returns `list[CredentialResponse]` + +--- + +## `get_credential` + +Get a single credential's metadata by ID. + +```python +cred = await client.get_credential("cred_abc123") +print(cred.name, cred.credential_type) +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `credential_id` | `str` | Yes | The credential ID. | + +### Returns `CredentialResponse` + +--- + +## `delete_credential` + +Delete a credential. + +```python +await client.delete_credential("cred_abc123") +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `credential_id` | `str` | Yes | The credential ID to delete. | + +--- + +## `send_totp_code` + +Send a TOTP (time-based one-time password) code to Skyvern during a run that requires 2FA. Call this when your webhook or polling detects that Skyvern is waiting for a TOTP code. + +```python +await client.send_totp_code( + totp_identifier="demo@example.com", + content="123456", +) +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `totp_identifier` | `str` | Yes | The identifier matching the `totp_identifier` used in the task/workflow. | +| `content` | `str` | Yes | The TOTP code value. | +| `task_id` | `str` | No | Associate with a specific task run. | +| `workflow_id` | `str` | No | Associate with a specific workflow. | +| `workflow_run_id` | `str` | No | Associate with a specific workflow run. | +| `source` | `str` | No | Source of the TOTP code. | +| `expired_at` | `datetime` | No | When this code expires. | +| `type` | `OtpType` | No | OTP type. | + +### Returns `TotpCode` diff --git a/docs/sdk-reference/error-handling.mdx b/docs/sdk-reference/error-handling.mdx new file mode 100644 index 00000000..446cfb68 --- /dev/null +++ b/docs/sdk-reference/error-handling.mdx @@ -0,0 +1,168 @@ +--- +title: Error Handling +subtitle: Handle API errors, timeouts, and configure retries +slug: sdk-reference/error-handling +--- + +The SDK raises typed exceptions for API errors. All errors extend `ApiError` and include the HTTP status code, response headers, and body. + +--- + +## Error types + +| Exception | Status Code | When it's raised | +|-----------|-------------|------------------| +| `BadRequestError` | 400 | Invalid request parameters. | +| `ForbiddenError` | 403 | Invalid or missing API key. | +| `NotFoundError` | 404 | Resource (run, workflow, session) not found. | +| `ConflictError` | 409 | Resource conflict (e.g., duplicate creation). | +| `UnprocessableEntityError` | 422 | Request validation failed. | +| `ApiError` | Any | Base class for all API errors. Catch this as a fallback. | + +The specific error classes live in `skyvern.client.errors`. The base `ApiError` class lives in `skyvern.client.core`: + +```python +from skyvern.client.core import ApiError +from skyvern.client.errors import ( + BadRequestError, + ForbiddenError, + NotFoundError, + ConflictError, + UnprocessableEntityError, +) +``` + +--- + +## Catching errors + +```python +from skyvern import Skyvern +from skyvern.client.core import ApiError +from skyvern.client.errors import NotFoundError + +client = Skyvern(api_key="YOUR_API_KEY") + +try: + run = await client.get_run("tsk_nonexistent") +except NotFoundError as e: + print(f"Run not found: {e.body}") +except ApiError as e: + print(f"API error {e.status_code}: {e.body}") +``` + +### Error properties + +Every error has these attributes: + +| Property | Type | Description | +|----------|------|-------------| +| `status_code` | `int \| None` | HTTP status code. | +| `body` | `Any` | Response body (usually a dict with error details). | +| `headers` | `dict[str, str] \| None` | Response headers. | + +--- + +## Timeouts + +Two different timeouts apply: + +### HTTP request timeout + +Controls how long the SDK waits for the HTTP response from the Skyvern API. Set it in the constructor or per-request: + +```python +# Global timeout (applies to all requests) +client = Skyvern(api_key="YOUR_API_KEY", timeout=30.0) + +# Per-request timeout +from skyvern.client.core import RequestOptions + +result = await client.get_run( + "tsk_abc123", + request_options=RequestOptions(timeout_in_seconds=10), +) +``` + +### Completion timeout + +Controls how long `wait_for_completion` polls before giving up. This is separate from the HTTP timeout: + +```python +try: + result = await client.run_task( + prompt="Extract data", + url="https://example.com", + wait_for_completion=True, + timeout=300, # Give up after 5 minutes + ) +except TimeoutError: + print("Task didn't complete in time") +``` + +The completion timeout raises Python's built-in `TimeoutError` (via `asyncio.timeout`), not `ApiError`. + +--- + +## Retries + +Configure automatic retries for transient failures using `RequestOptions`: + +```python +from skyvern.client.core import RequestOptions + +result = await client.run_task( + prompt="Extract product data", + url="https://example.com/products", + request_options=RequestOptions(max_retries=3), +) +``` + +Retries apply to the HTTP request level (network errors, 5xx responses). They do not retry the entire task if it fails at the AI level — use `get_run` to check the status and re-run if needed. + +--- + +## Run failure vs API errors + +There are two distinct failure modes: + +**API error** — The HTTP request itself failed. The SDK raises an exception. + +```python +from skyvern.client.core import ApiError + +try: + result = await client.run_task(prompt="...") +except ApiError as e: + print(f"API call failed: {e.status_code}") +``` + +**Run failure** — The API call succeeded, but the task/workflow failed during execution. No exception is raised. Check the `status` field: + +```python +result = await client.run_task( + prompt="Fill out the form", + url="https://example.com", + wait_for_completion=True, +) + +if result.status == "failed": + print(f"Task failed: {result.failure_reason}") +elif result.status == "timed_out": + print(f"Task exceeded step limit after {result.step_count} steps") +elif result.status == "completed": + print(f"Success: {result.output}") +``` + +### Run statuses + +| Status | Description | +|--------|-------------| +| `created` | Run initialized, not yet queued. | +| `queued` | Waiting for an available browser. | +| `running` | AI is executing. | +| `completed` | Finished successfully. | +| `failed` | Encountered an error during execution. | +| `terminated` | Manually stopped. | +| `timed_out` | Exceeded step limit (`max_steps`). | +| `canceled` | Canceled before starting. | diff --git a/docs/sdk-reference/helpers.mdx b/docs/sdk-reference/helpers.mdx new file mode 100644 index 00000000..f25dc4dd --- /dev/null +++ b/docs/sdk-reference/helpers.mdx @@ -0,0 +1,159 @@ +--- +title: Helper Methods +subtitle: High-level methods for common automation patterns +slug: sdk-reference/helpers +--- + +These methods wrap common multi-step patterns into single API calls. Under the hood, they create and run specialized workflows. + +--- + +## `login` + +Automate logging into a website using stored credentials. This creates a login workflow, executes it, and optionally waits for completion. + +```python +from skyvern.schemas.run_blocks import CredentialType + +result = await client.login( + credential_type=CredentialType.skyvern, + credential_id="cred_abc123", + url="https://app.example.com/login", + wait_for_completion=True, +) +print(result.status) +``` + +### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `credential_type` | `CredentialType` | Yes | — | How credentials are stored. Options: `skyvern`, `bitwarden`, `onepassword`, `azure_vault`. | +| `url` | `str` | No | `None` | The login page URL. | +| `credential_id` | `str` | No | `None` | The Skyvern credential ID (when using `CredentialType.skyvern`). | +| `prompt` | `str` | No | `None` | Additional instructions for the AI during login. | +| `browser_session_id` | `str` | No | `None` | Run login inside an existing browser session. | +| `browser_address` | `str` | No | `None` | Connect to a browser at this CDP address. | +| `proxy_location` | `ProxyLocation` | No | `None` | Route browser traffic through a geographic proxy. | +| `webhook_url` | `str` | No | `None` | URL to receive a POST when the login finishes. | +| `totp_identifier` | `str` | No | `None` | Identifier for TOTP verification. | +| `totp_url` | `str` | No | `None` | URL to receive TOTP codes. | +| `wait_for_completion` | `bool` | No | `False` | Block until the login finishes. | +| `timeout` | `float` | No | `1800` | Max wait time in seconds. | +| `extra_http_headers` | `dict[str, str]` | No | `None` | Additional HTTP headers. | +| `max_screenshot_scrolling_times` | `int` | No | `None` | Number of screenshot scrolls. | + +**Bitwarden-specific parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `bitwarden_collection_id` | `str` | Bitwarden collection ID. | +| `bitwarden_item_id` | `str` | Bitwarden item ID. | + +**1Password-specific parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `onepassword_vault_id` | `str` | 1Password vault ID. | +| `onepassword_item_id` | `str` | 1Password item ID. | + +**Azure Key Vault-specific parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `azure_vault_name` | `str` | Azure Key Vault name. | +| `azure_vault_username_key` | `str` | Secret name for the username. | +| `azure_vault_password_key` | `str` | Secret name for the password. | +| `azure_vault_totp_secret_key` | `str` | Secret name for the TOTP secret. | + +### Returns `WorkflowRunResponse` + +### Example: Login then extract data + +```python +from skyvern.schemas.run_blocks import CredentialType + +session = await client.create_browser_session() + +# Login +await client.login( + credential_type=CredentialType.skyvern, + credential_id="cred_abc123", + url="https://app.example.com/login", + browser_session_id=session.browser_session_id, + wait_for_completion=True, +) + +# Now extract data from the authenticated session +result = await client.run_task( + prompt="Go to the billing page and extract all invoices", + browser_session_id=session.browser_session_id, + wait_for_completion=True, +) +print(result.output) +``` + +--- + +## `download_files` + +Navigate to a page and download files. Unlike `run_task` and `run_workflow`, this method does **not** support `wait_for_completion` — it returns immediately with a `run_id`. Poll with `get_run()` or use a webhook to know when the download finishes. + +```python +result = await client.download_files( + navigation_goal="Download the latest monthly report PDF", + url="https://app.example.com/reports", +) + +# Poll for completion +import asyncio +while True: + run = await client.get_run(result.run_id) + if run.status in ("completed", "failed", "terminated", "timed_out", "canceled"): + break + await asyncio.sleep(5) + +for f in run.downloaded_files: + print(f.name) +``` + +### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `navigation_goal` | `str` | Yes | — | Natural language description of what to download. | +| `url` | `str` | No | `None` | Starting page URL. | +| `browser_session_id` | `str` | No | `None` | Run inside an existing browser session. | +| `browser_profile_id` | `str` | No | `None` | Load a browser profile. | +| `proxy_location` | `ProxyLocation` | No | `None` | Route through a geographic proxy. | +| `webhook_url` | `str` | No | `None` | URL to receive a POST when the download finishes. | +| `download_suffix` | `str` | No | `None` | Expected file extension to wait for (e.g., `".pdf"`). | +| `download_timeout` | `float` | No | `None` | Max time to wait for the download in seconds. | +| `max_steps_per_run` | `int` | No | `None` | Cap AI steps. | +| `extra_http_headers` | `dict[str, str]` | No | `None` | Additional HTTP headers. | + +### Returns `WorkflowRunResponse` + +The `downloaded_files` field contains the list of files that were downloaded. + +--- + +## `upload_file` + +Upload a file to Skyvern's storage. Returns a presigned URL and S3 URI you can reference in tasks and workflows. + +```python +with open("data.csv", "rb") as f: + upload = await client.upload_file(file=f) + print(upload.s3uri) # s3://skyvern-uploads/... + print(upload.presigned_url) # https://...signed download URL +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `file` | `File` | Yes | The file to upload. Accepts file objects, byte streams, or paths. | +| `file_storage_type` | `FileStorageType` | No | Storage backend type. | + +### Returns `UploadFileResponse` diff --git a/docs/sdk-reference/overview.mdx b/docs/sdk-reference/overview.mdx new file mode 100644 index 00000000..8c13ec39 --- /dev/null +++ b/docs/sdk-reference/overview.mdx @@ -0,0 +1,189 @@ +--- +title: Python SDK +subtitle: Install, authenticate, and configure the Skyvern Python client +slug: sdk-reference/overview +--- + +The `skyvern` package wraps the Skyvern REST API in a typed, async Python client. + +```bash +pip install skyvern +``` + + +Requires Python 3.11+. If you hit version errors, use `pipx install skyvern` to install in an isolated environment. + + +--- + +## Initialize the client + +The `Skyvern` class is async — all methods are coroutines. Wrap calls in an `async` function and use `asyncio.run()` as the entry point: + +```python +import asyncio +from skyvern import Skyvern + +async def main(): + client = Skyvern(api_key="YOUR_API_KEY") + + result = await client.run_task( + prompt="Get the title of the top post on Hacker News", + url="https://news.ycombinator.com", + wait_for_completion=True, + ) + print(result.output) + +asyncio.run(main()) +``` + +If you're inside a framework that already runs an event loop (FastAPI, Django ASGI), `await` directly without `asyncio.run()`. + +### Constructor parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `api_key` | `str` | — | **Required.** Your Skyvern API key. Get one at [app.skyvern.com/settings](https://app.skyvern.com/settings/api-keys). | +| `environment` | `SkyvernEnvironment` | `CLOUD` | Target environment. Options: `CLOUD`, `STAGING`, `LOCAL`. | +| `base_url` | `str \| None` | `None` | Override the API base URL. Use this for self-hosted deployments. | +| `timeout` | `float \| None` | `None` | HTTP request timeout in seconds. | +| `follow_redirects` | `bool` | `True` | Whether to follow HTTP redirects. | +| `httpx_client` | `AsyncClient \| None` | `None` | Provide your own httpx client for custom TLS, proxying, or connection pooling. | + +--- + +## Environments + +The SDK ships with two built-in environment URLs: + +```python +from skyvern.client import SkyvernEnvironment +``` + +| Environment | URL | When to use | +|-------------|-----|-------------| +| `SkyvernEnvironment.CLOUD` | `https://api.skyvern.com` | Skyvern Cloud (default) | +| `SkyvernEnvironment.LOCAL` | `http://localhost:8000` | Local server started with `skyvern run server` | + +For a self-hosted instance at a custom URL, pass `base_url` instead: + +```python +client = Skyvern( + api_key="YOUR_API_KEY", + base_url="https://skyvern.your-company.com", +) +``` + +--- + +## Local mode + +Run Skyvern entirely on your machine — no cloud, no network calls. `Skyvern.local()` reads your `.env` file, boots the engine in-process, and connects the client to it. + +**Prerequisite:** Run `skyvern quickstart` once to create the `.env` file with your database connection and LLM API keys. + +```python +from skyvern import Skyvern + +client = Skyvern.local() + +result = await client.run_task( + prompt="Get the title of the top post", + url="https://news.ycombinator.com", + wait_for_completion=True, +) +``` + +If you configured headful mode during `skyvern quickstart`, a Chromium window opens on your machine so you can watch the AI work. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `llm_config` | `LLMConfig \| LLMRouterConfig \| None` | `None` | Override the LLM. If omitted, uses `LLM_KEY` from `.env`. | +| `settings` | `dict \| None` | `None` | Override `.env` settings at runtime. Example: `{"MAX_STEPS_PER_RUN": 100}` | + +--- + +## `wait_for_completion` + +By default, `run_task` and `run_workflow` return immediately after the run is queued — you get a `run_id` and need to poll `get_run()` yourself. Pass `wait_for_completion=True` to have the SDK poll automatically until the run reaches a terminal state (`completed`, `failed`, `terminated`, `timed_out`, or `canceled`): + +```python +# Returns only after the task finishes (up to 30 min by default) +result = await client.run_task( + prompt="Fill out the contact form", + url="https://example.com/contact", + wait_for_completion=True, + timeout=600, # give up after 10 minutes +) + +# Without wait_for_completion — returns immediately +task = await client.run_task( + prompt="Fill out the contact form", + url="https://example.com/contact", +) +print(task.run_id) # poll with client.get_run(task.run_id) +``` + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `wait_for_completion` | `bool` | `False` | Poll until the run finishes. | +| `timeout` | `float` | `1800` | Maximum wait time in seconds. Raises Python's `TimeoutError` if exceeded. | + +Supported on `run_task`, `run_workflow`, and `login`. + +--- + +## Request options + +Every method accepts an optional `request_options` parameter for per-request overrides of timeout, retries, and headers: + +```python +from skyvern.client.core import RequestOptions + +result = await client.run_task( + prompt="Extract data", + url="https://example.com", + request_options=RequestOptions( + timeout_in_seconds=120, + max_retries=3, + additional_headers={"x-custom-header": "value"}, + ), +) +``` + +These override the client-level defaults for that single call only. + +--- + +## Next steps + + + + Run browser automations with `run_task` + + + Create and run multi-step automations + + + Maintain live browser state between calls + + + Handle errors and configure retries + + diff --git a/docs/sdk-reference/tasks.mdx b/docs/sdk-reference/tasks.mdx new file mode 100644 index 00000000..0c96492f --- /dev/null +++ b/docs/sdk-reference/tasks.mdx @@ -0,0 +1,297 @@ +--- +title: Tasks +subtitle: Run single browser automations with natural language +slug: sdk-reference/tasks +--- + +A task is a single browser automation. You describe what you want in natural language — Skyvern opens a browser, navigates to the URL, and executes the instructions with AI. + +For when to use tasks vs workflows, see [Run a Task](/running-automations/run-a-task). + +--- + +## `run_task` + +Start a browser automation. Skyvern opens a cloud browser, navigates to the URL, and executes your prompt with AI. + +```python +result = await client.run_task( + prompt="Get the title of the top post", + url="https://news.ycombinator.com", + wait_for_completion=True, +) +print(result.output) +``` + +### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `prompt` | `str` | Yes | — | Natural language instructions for what the AI should do. | +| `url` | `str` | No | `None` | Starting page URL. If omitted, the AI navigates from a blank page. | +| `engine` | `RunEngine` | No | `skyvern_v2` | AI engine. Options: `skyvern_v2`, `skyvern_v1`, `openai_cua`, `anthropic_cua`, `ui_tars`. | +| `wait_for_completion` | `bool` | No | `False` | Block until the run finishes. | +| `timeout` | `float` | No | `1800` | Max wait time in seconds when `wait_for_completion=True`. | +| `max_steps` | `int` | No | `None` | Cap the number of AI steps to limit cost. Run terminates with `timed_out` if hit. | +| `data_extraction_schema` | `dict \| str` | No | `None` | JSON schema or Pydantic model name constraining the output shape. | +| `proxy_location` | `ProxyLocation` | No | `None` | Route the browser through a geographic proxy. | +| `browser_session_id` | `str` | No | `None` | Run inside an existing [browser session](/optimization/browser-sessions). | +| `publish_workflow` | `bool` | No | `False` | Save the generated code as a reusable workflow. Only works with `skyvern_v2`. | +| `webhook_url` | `str` | No | `None` | URL to receive a POST when the run finishes. | +| `error_code_mapping` | `dict[str, str]` | No | `None` | Map custom error codes to failure reasons. | +| `totp_identifier` | `str` | No | `None` | Identifier for TOTP verification. | +| `totp_url` | `str` | No | `None` | URL to receive TOTP codes. | +| `title` | `str` | No | `None` | Display name for this run in the dashboard. | +| `model` | `dict` | No | `None` | Override the output model definition. | +| `user_agent` | `str` | No | `None` | Custom User-Agent header for the browser. | +| `extra_http_headers` | `dict[str, str]` | No | `None` | Additional HTTP headers injected into every browser request. | +| `include_action_history_in_verification` | `bool` | No | `None` | Include action history when verifying task completion. | +| `max_screenshot_scrolls` | `int` | No | `None` | Number of scrolls for post-action screenshots. Useful for lazy-loaded content. | +| `browser_address` | `str` | No | `None` | Connect to a browser at this CDP address instead of spinning up a new one. | + +### Returns `TaskRunResponse` + +| Field | Type | Description | +|-------|------|-------------| +| `run_id` | `str` | Unique identifier. Starts with `tsk_` for task runs. | +| `status` | `str` | `created`, `queued`, `running`, `completed`, `failed`, `terminated`, `timed_out`, or `canceled`. | +| `output` | `dict \| None` | Extracted data from the run. Shape depends on your prompt or `data_extraction_schema`. | +| `downloaded_files` | `list[FileInfo] \| None` | Files downloaded during the run. | +| `recording_url` | `str \| None` | URL to the session recording video. | +| `screenshot_urls` | `list[str] \| None` | Final screenshots (most recent first). | +| `failure_reason` | `str \| None` | Error description if the run failed. | +| `app_url` | `str \| None` | Link to view this run in the Cloud UI. | +| `step_count` | `int \| None` | Number of AI steps taken. | +| `script_run` | `ScriptRunResponse \| None` | Code execution result if the run used generated code. | +| `created_at` | `datetime` | When the run was created. | +| `finished_at` | `datetime \| None` | When the run finished. | + +### Examples + +**Extract structured data:** + +```python +result = await client.run_task( + prompt="Extract the name, price, and rating of the top 3 products", + url="https://example.com/products", + data_extraction_schema={ + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "price": {"type": "string"}, + "rating": {"type": "number"}, + }, + }, + }, + wait_for_completion=True, +) +print(result.output) +# [{"name": "Widget A", "price": "$29.99", "rating": 4.5}, ...] +``` + +**Run inside an existing browser session:** + +```python +session = await client.create_browser_session() + +result = await client.run_task( + prompt="Log in and download the latest invoice", + url="https://app.example.com/login", + browser_session_id=session.browser_session_id, + wait_for_completion=True, +) +``` + +**Limit cost with max_steps:** + +```python +result = await client.run_task( + prompt="Fill out the contact form", + url="https://example.com/contact", + max_steps=10, + wait_for_completion=True, +) +``` + +**Use a lighter engine:** + +```python +from skyvern.schemas.runs import RunEngine + +result = await client.run_task( + prompt="Get the page title", + url="https://example.com", + engine=RunEngine.skyvern_v1, + wait_for_completion=True, +) +``` + +**Publish as a reusable workflow:** + +```python +result = await client.run_task( + prompt="Fill out the contact form with the provided data", + url="https://example.com/contact", + publish_workflow=True, + wait_for_completion=True, +) +# The generated workflow is saved and can be re-triggered via run_workflow +``` + +--- + +## `get_run` + +Get the current status and results of any run (task or workflow). + +```python +run = await client.get_run("tsk_v2_486305187432193504") +print(run.status, run.output) +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `run_id` | `str` | Yes | The run ID returned by `run_task` or `run_workflow`. | + +### Returns `GetRunResponse` + +A discriminated union based on `run_type`. All variants share the same core fields as `TaskRunResponse` above, plus a `run_type` field (`task_v1`, `task_v2`, `openai_cua`, `anthropic_cua`, `ui_tars`, `workflow_run`). + +Workflow run responses additionally include `run_with` and `ai_fallback` fields. + +--- + +## `cancel_run` + +Cancel a running or queued run. + +```python +await client.cancel_run("tsk_v2_486305187432193504") +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `run_id` | `str` | Yes | The run ID to cancel. | + +The run transitions to `canceled` status. If the run has already finished, this is a no-op. + +--- + +## `get_run_timeline` + +Get the step-by-step timeline of a run. Each entry represents one AI action with screenshots and reasoning. + +```python +timeline = await client.get_run_timeline("tsk_v2_486305187432193504") +for step in timeline: + print(f"Step {step.order}: {step.type} — {step.status}") +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `run_id` | `str` | Yes | The run ID. | + +### Returns `list[WorkflowRunTimeline]` + +Each timeline entry contains step details including type, status, order, and associated artifacts. + +--- + +## `get_run_artifacts` + +Get all artifacts (screenshots, recordings, generated code, etc.) for a run. + +```python +artifacts = await client.get_run_artifacts("tsk_v2_486305187432193504") +for artifact in artifacts: + print(f"{artifact.artifact_type}: {artifact.uri}") +``` + +Filter by type to get specific artifacts: + +```python +# Get only the generated Playwright scripts +scripts = await client.get_run_artifacts( + "tsk_v2_486305187432193504", + artifact_type=["script_file"], +) +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `run_id` | `str` | Yes | The run ID. | +| `artifact_type` | `ArtifactType \| list[ArtifactType]` | No | Filter by artifact type. | + +### Returns `list[Artifact]` + +--- + +## `get_artifact` + +Get a single artifact by ID. + +```python +artifact = await client.get_artifact("art_486305187432193504") +print(artifact.uri) +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `artifact_id` | `str` | Yes | The artifact ID. | + +### Returns `Artifact` + +--- + +## `retry_run_webhook` + +Re-send the webhook notification for a completed run. Useful if your webhook endpoint was down when the run finished. + +```python +await client.retry_run_webhook("tsk_v2_486305187432193504") +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `run_id` | `str` | Yes | The run ID. | + +--- + +## Polling pattern + +If you don't use `wait_for_completion`, poll `get_run` manually: + +```python +import asyncio + +task = await client.run_task( + prompt="Extract product data", + url="https://example.com/products", +) + +while True: + run = await client.get_run(task.run_id) + if run.status in ("completed", "failed", "terminated", "timed_out", "canceled"): + break + await asyncio.sleep(5) + +print(run.output) +``` + + +For production, prefer `wait_for_completion=True` or [webhooks](/going-to-production/webhooks) over manual polling. + diff --git a/docs/sdk-reference/workflows.mdx b/docs/sdk-reference/workflows.mdx new file mode 100644 index 00000000..c0e452a0 --- /dev/null +++ b/docs/sdk-reference/workflows.mdx @@ -0,0 +1,274 @@ +--- +title: Workflows +subtitle: Create and run multi-step browser automations +slug: sdk-reference/workflows +--- + +A workflow chains multiple steps (blocks) into a single automation. Workflows support loops, conditionals, data passing between steps, and code-based re-execution. + +For conceptual background, see [Build a Workflow](/multi-step-automations/build-a-workflow). + +--- + +## `run_workflow` + +Execute a workflow by its permanent ID. Skyvern opens a cloud browser and runs each block in sequence. + +```python +result = await client.run_workflow( + workflow_id="wpid_abc123", + wait_for_completion=True, +) +print(result.output) +``` + +### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `workflow_id` | `str` | Yes | — | The workflow's permanent ID (`wpid_...`). | +| `parameters` | `dict` | No | `None` | Input parameters defined in the workflow. Keys must match parameter names. | +| `wait_for_completion` | `bool` | No | `False` | Block until the workflow finishes. | +| `timeout` | `float` | No | `1800` | Max wait time in seconds when `wait_for_completion=True`. | +| `run_with` | `str` | No | `None` | Force execution mode: `"code"` (use cached Playwright code) or `"agent"` (use AI). | +| `ai_fallback` | `bool` | No | `None` | Fall back to AI if the cached code fails. | +| `browser_session_id` | `str` | No | `None` | Run inside an existing [browser session](/optimization/browser-sessions). | +| `browser_profile_id` | `str` | No | `None` | Load a [browser profile](/optimization/browser-profiles) (cookies, storage) into the session. | +| `proxy_location` | `ProxyLocation` | No | `None` | Route the browser through a geographic proxy. | +| `max_steps_override` | `int` | No | `None` | Cap total AI steps across all blocks. | +| `webhook_url` | `str` | No | `None` | URL to receive a POST when the run finishes. | +| `title` | `str` | No | `None` | Display name for this run in the dashboard. | +| `totp_identifier` | `str` | No | `None` | Identifier for TOTP verification. | +| `totp_url` | `str` | No | `None` | URL to receive TOTP codes. | +| `template` | `bool` | No | `None` | Run a template workflow. | +| `user_agent` | `str` | No | `None` | Custom User-Agent header for the browser. | +| `extra_http_headers` | `dict[str, str]` | No | `None` | Additional HTTP headers injected into every browser request. | +| `max_screenshot_scrolls` | `int` | No | `None` | Number of scrolls for post-action screenshots. | +| `browser_address` | `str` | No | `None` | Connect to a browser at this CDP address. | + +### Returns `WorkflowRunResponse` + +| Field | Type | Description | +|-------|------|-------------| +| `run_id` | `str` | Unique identifier. Starts with `wr_` for workflow runs. | +| `status` | `str` | `created`, `queued`, `running`, `completed`, `failed`, `terminated`, `timed_out`, or `canceled`. | +| `output` | `dict \| None` | Extracted data from the workflow's output block. | +| `downloaded_files` | `list[FileInfo] \| None` | Files downloaded during the run. | +| `recording_url` | `str \| None` | URL to the session recording. | +| `failure_reason` | `str \| None` | Error description if the run failed. | +| `app_url` | `str \| None` | Link to view this run in the Cloud UI. | +| `step_count` | `int \| None` | Total AI steps taken across all blocks. | +| `run_with` | `str \| None` | Whether the run used `"code"` or `"agent"`. | +| `ai_fallback` | `bool \| None` | Whether AI fallback was configured. | +| `script_run` | `ScriptRunResponse \| None` | Code execution result. Contains `ai_fallback_triggered` if code was used. | + +### Examples + +**Pass parameters to a workflow:** + +```python +result = await client.run_workflow( + workflow_id="wpid_invoice_extraction", + parameters={ + "company_name": "Acme Corp", + "date_range": "2025-01-01 to 2025-12-31", + }, + wait_for_completion=True, +) +``` + +**Run with cached code (skip AI, use generated Playwright scripts):** + +```python +result = await client.run_workflow( + workflow_id="wpid_daily_report", + run_with="code", + ai_fallback=True, # Fall back to AI if code fails + wait_for_completion=True, +) +``` + +**Run with a browser profile (skip login):** + +```python +result = await client.run_workflow( + workflow_id="wpid_daily_report", + browser_profile_id="bpf_abc123", + wait_for_completion=True, +) +``` + +--- + +## `create_workflow` + +Create a new workflow from a JSON or YAML definition. + +```python +workflow = await client.create_workflow( + json_definition={ + "blocks": [ + { + "block_type": "task", + "label": "extract_data", + "prompt": "Extract the top 3 products", + "url": "https://example.com/products", + } + ], + "parameters": [], + }, +) +print(workflow.workflow_permanent_id) +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `json_definition` | `WorkflowCreateYamlRequest` | No | Workflow definition as a JSON object. | +| `yaml_definition` | `str` | No | Workflow definition as a YAML string. | +| `folder_id` | `str` | No | Folder to organize the workflow in. | + +You must provide either `json_definition` or `yaml_definition`. + +### Returns `Workflow` + +| Field | Type | Description | +|-------|------|-------------| +| `workflow_id` | `str` | Unique ID for this version. | +| `workflow_permanent_id` | `str` | Stable ID across all versions. Use this to run workflows. | +| `version` | `int` | Version number. | +| `title` | `str` | Workflow title. | +| `workflow_definition` | `WorkflowDefinition` | The full definition including blocks and parameters. | +| `status` | `str \| None` | Workflow status. | +| `created_at` | `datetime` | When the workflow was created. | + +--- + +## `get_workflows` + +List all workflows. Supports filtering and pagination. + +```python +workflows = await client.get_workflows() +for wf in workflows: + print(f"{wf.title} ({wf.workflow_permanent_id})") +``` + +### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `page` | `int` | No | `None` | Page number for pagination. | +| `page_size` | `int` | No | `None` | Number of results per page. | +| `only_saved_tasks` | `bool` | No | `None` | Only return saved tasks. | +| `only_workflows` | `bool` | No | `None` | Only return workflows (not saved tasks). | +| `only_templates` | `bool` | No | `None` | Only return templates. | +| `title` | `str` | No | `None` | Filter by exact title. | +| `search_key` | `str` | No | `None` | Search by title. | +| `folder_id` | `str` | No | `None` | Filter by folder. | +| `status` | `WorkflowStatus \| list[WorkflowStatus]` | No | `None` | Filter by status. | + +### Returns `list[Workflow]` + +--- + +## `get_workflow` + + +Requires `skyvern` version 1.1.0 or later. Run `pip install --upgrade skyvern` to update. + + +Get a specific workflow by its permanent ID. + +```python +workflow = await client.get_workflow("wpid_abc123") +print(workflow.title, f"v{workflow.version}") +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `workflow_permanent_id` | `str` | Yes | The workflow's permanent ID. | +| `version` | `int` | No | Specific version to retrieve. Defaults to latest. | +| `template` | `bool` | No | Whether to fetch a template workflow. | + +### Returns `Workflow` + +--- + +## `get_workflow_versions` + + +Requires `skyvern` version 1.1.0 or later. Run `pip install --upgrade skyvern` to update. + + +List all versions of a workflow. + +```python +versions = await client.get_workflow_versions("wpid_abc123") +for v in versions: + print(f"v{v.version} — {v.modified_at}") +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `workflow_permanent_id` | `str` | Yes | The workflow's permanent ID. | +| `template` | `bool` | No | Whether to fetch template versions. | + +### Returns `list[Workflow]` + +--- + +## `update_workflow` + +Update an existing workflow's definition. + +```python +updated = await client.update_workflow( + "wf_abc123", + json_definition={ + "blocks": [ + { + "block_type": "task", + "label": "extract_data", + "prompt": "Extract the top 5 products", + "url": "https://example.com/products", + } + ], + "parameters": [], + }, +) +print(f"Updated to v{updated.version}") +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `workflow_id` | `str` | Yes | The workflow version ID (not the permanent ID). | +| `json_definition` | `WorkflowCreateYamlRequest` | No | Updated workflow definition as JSON. | +| `yaml_definition` | `str` | No | Updated workflow definition as YAML. | + +### Returns `Workflow` + +Creates a new version of the workflow. + +--- + +## `delete_workflow` + +Delete a workflow. + +```python +await client.delete_workflow("wf_abc123") +``` + +### Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `workflow_id` | `str` | Yes | The workflow version ID to delete. |