Skyvern/docs/sdk-reference/error-handling.mdx

346 lines
9.2 KiB
Text

---
title: Error Handling
subtitle: Handle API errors, timeouts, and configure retries
description: Handle API errors, timeouts, and configure retries in the Skyvern Python SDK. Covers error types, exception handling, HTTP and completion timeouts, retry configuration, and run failure vs API error patterns.
slug: sdk-reference/error-handling
keywords:
- BadRequestError
- ForbiddenError
- NotFoundError
- ConflictError
- UnprocessableEntityError
- timeout
- retry
- polling
- run status
- failure
---
The SDK raises typed exceptions for API errors. In Python, all errors extend `ApiError`. In TypeScript, all errors extend `SkyvernError`. Both include the HTTP status code, response body, and headers.
---
## 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` (Python) / `SkyvernError` (TS) | Any | Base class for all API errors. Catch this as a fallback. |
| `SkyvernTimeoutError` (TS only) | - | HTTP request timed out. |
Import errors from the package:
<CodeGroup>
```python Python
from skyvern.client.core import ApiError
from skyvern.client.errors import (
BadRequestError,
ForbiddenError,
NotFoundError,
ConflictError,
UnprocessableEntityError,
)
```
```typescript TypeScript
import { SkyvernError, SkyvernTimeoutError, SkyvernApi } from "@skyvern/client";
// Base errors are top-level exports:
// SkyvernError - base class for all API errors
// SkyvernTimeoutError - HTTP request timed out
// HTTP status error subclasses are accessed via the SkyvernApi namespace:
// SkyvernApi.BadRequestError - 400
// SkyvernApi.ForbiddenError - 403
// SkyvernApi.NotFoundError - 404
// SkyvernApi.ConflictError - 409
// SkyvernApi.UnprocessableEntityError - 422
```
</CodeGroup>
The specific Python error classes live in `skyvern.client.errors`. The base `ApiError` class lives in `skyvern.client.core`.
<Warning>
**TypeScript:** `SkyvernError` and `SkyvernTimeoutError` are top-level exports. The HTTP-specific errors (`BadRequestError`, etc.) extend `SkyvernError` and are accessed via the `SkyvernApi` namespace.
</Warning>
---
## Catching errors
<CodeGroup>
```python 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}")
```
```typescript TypeScript
import { Skyvern, SkyvernError, SkyvernApi } from "@skyvern/client";
const skyvern = new Skyvern({ apiKey: "YOUR_API_KEY" });
try {
const run = await skyvern.getRun("tsk_nonexistent");
} catch (e) {
if (e instanceof SkyvernApi.NotFoundError) {
console.log(`Run not found: ${e.body}`);
} else if (e instanceof SkyvernError) {
console.log(`API error ${e.statusCode}: ${e.body}`);
}
}
```
</CodeGroup>
### Error properties
Every error has these attributes:
| Property (Python) | Property (TS) | Type | Description |
|-------------------|---------------|------|-------------|
| `status_code` | `statusCode` | `int \| None` | HTTP status code. |
| `body` | `body` | `Any` | Response body (usually a dict with error details). |
| `headers` | - | `dict[str, str] \| None` | Response headers. |
| - | `rawResponse` | `RawResponse \| undefined` | The raw HTTP response (TS only). |
| - | `message` | `string` | Human-readable error message (TS only). |
---
## 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:
<CodeGroup>
```python 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),
)
```
```typescript TypeScript
// Global timeout (applies to all requests)
const skyvern = new Skyvern({
apiKey: "YOUR_API_KEY",
timeoutInSeconds: 30,
});
// Per-request timeout
const result = await skyvern.getRun("tsk_abc123", {
timeoutInSeconds: 10,
});
```
</CodeGroup>
When an HTTP request times out in TypeScript, a `SkyvernTimeoutError` is thrown.
### Completion timeout
Controls how long `wait_for_completion` / `waitForCompletion` polls before giving up. This is separate from the HTTP timeout:
<CodeGroup>
```python 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")
```
```typescript TypeScript
try {
const result = await skyvern.runTask({
body: {
prompt: "Extract data",
url: "https://example.com",
},
waitForCompletion: true,
timeout: 300, // Give up after 5 minutes
});
} catch (e) {
if (e instanceof Error && e.message.includes("Timeout")) {
console.log("Task didn't complete in time");
}
}
```
</CodeGroup>
The completion timeout raises Python's built-in `TimeoutError` (via `asyncio.timeout`), not `ApiError`. In TypeScript, it throws a standard `Error` with a timeout message.
---
## Retries
Configure automatic retries for transient failures. Set it in the constructor or per-request:
<CodeGroup>
```python 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),
)
```
```typescript TypeScript
// Global retries (default: 2)
const skyvern = new Skyvern({
apiKey: "YOUR_API_KEY",
maxRetries: 3,
});
// Per-request retries
const result = await skyvern.runTask(
{
body: {
prompt: "Extract product data",
url: "https://example.com/products",
},
},
{ maxRetries: 5 },
);
```
</CodeGroup>
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` / `getRun` to check the status and re-run if needed.
---
## Abort requests (TypeScript only)
Cancel in-flight requests using `AbortSignal`:
```typescript
const controller = new AbortController();
// Cancel after 10 seconds
setTimeout(() => controller.abort(), 10000);
try {
const result = await skyvern.runTask(
{
body: {
prompt: "Extract data",
url: "https://example.com",
},
},
{ abortSignal: controller.signal },
);
} catch (e) {
if (e instanceof Error && e.name === "AbortError") {
console.log("Request was aborted");
}
}
```
---
## Run failure vs API errors
There are two distinct failure modes:
**API error** - The HTTP request itself failed. The SDK raises an exception.
<CodeGroup>
```python 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}")
```
```typescript TypeScript
import { SkyvernError } from "@skyvern/client";
try {
const result = await skyvern.runTask({
body: { prompt: "..." },
});
} catch (e) {
if (e instanceof SkyvernError) {
console.log(`API call failed: ${e.statusCode}`);
}
}
```
</CodeGroup>
**Run failure** - The API call succeeded, but the task/workflow failed during execution. No exception is raised. Check the `status` field:
<CodeGroup>
```python 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}")
```
```typescript TypeScript
const result = await skyvern.runTask({
body: {
prompt: "Fill out the form",
url: "https://example.com",
},
waitForCompletion: true,
});
if (result.status === "failed") {
console.log(`Task failed: ${result.failure_reason}`);
} else if (result.status === "timed_out") {
console.log(`Task exceeded step limit after ${result.step_count} steps`);
} else if (result.status === "completed") {
console.log(`Success: ${JSON.stringify(result.output)}`);
}
```
</CodeGroup>
### 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. |