> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pre.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Run a Task

> Run one or more browser-automation tasks. Supports sync, async, and SSE streaming modes. Up to 1000 tasks per request.

Run one or more browser-agent tasks in parallel. One request, one clean response — or a live SSE stream, or an async handle to poll.

<Note>
  The `tasks` field is always an array, even for a single task — so one endpoint covers both "run one task" and "run 1000 tasks in parallel". No separate batch vs. single-task API.
</Note>

## Overview

* **Method:** `POST`
* **Path:** `/browser-agent`
* **Cost:** billed per successful task. Failed tasks are free.
* **Max tasks per request:** `1000`
* **Max in-flight tasks per user:** `5000`
* **Per-task default timeout:** `240000` ms

## Headers

| Header          | Required | Description           |
| --------------- | -------- | --------------------- |
| `Authorization` | ✅        | `Bearer YOUR_API_KEY` |
| `Content-Type`  | ✅        | `application/json`    |

## Request Body

| Field         | Type      | Required | Description                                                                                                                                       |
| ------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tasks`       | `Task[]`  | ✅        | Array of tasks. `1` ≤ length ≤ `1000`.                                                                                                            |
| `concurrency` | `integer` | ❌        | Parallel workers within this run. `1`–`20`. Default `5`.                                                                                          |
| `async`       | `boolean` | ❌        | If `true`, returns `{ id, status: "processing" }` immediately. Poll [`GET /browser-agent/:id`](/browser-agents/api/task-status). Default `false`. |
| `stream`      | `boolean` | ❌        | If `true`, returns an SSE stream with per-step events + final results. Default `false`. Mutually exclusive with `async`.                          |

### `Task` object

| Field              | Type                     | Required | Description                                                                                      |
| ------------------ | ------------------------ | -------- | ------------------------------------------------------------------------------------------------ |
| `url`              | `string` (URI)           | ✅        | Starting page URL the agent navigates to.                                                        |
| `instruction`      | `string`                 | ❌        | Natural-language goal. What should the agent accomplish on this page?                            |
| `input`            | `Record<string, string>` | ❌        | String values the agent should use during the run — form values, credentials, search queries.    |
| `output`           | JSON Schema              | ❌        | Schema describing the shape of data to extract. If omitted, the agent returns unstructured text. |
| `successCondition` | `string`                 | ❌        | Natural-language assertion. Task is marked `SUCCESS` only if this holds at the end.              |
| `timeoutMs`        | `integer`                | ❌        | Per-task max runtime in milliseconds. Default `240000`.                                          |

## Response Modes

### Sync (default)

Default behavior. The request holds the HTTP connection until every task in the run completes, then returns the full `BatchResult`.

Best for: small runs (≤ 50 tasks), interactive scripts, jobs where you want one clean response.

```bash theme={null}
curl -X POST https://api.pre.dev/browser-agent \
  -H "Authorization: Bearer $PREDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tasks": [
      { "url": "https://example.com", "instruction": "Extract the page heading." }
    ]
  }'
```

**Response (200):**

```json theme={null}
{
  "id": "65f8a9d2c1e4b5a6f7e8d9c0",
  "total": 1,
  "completed": 1,
  "results": [
    {
      "url": "https://example.com",
      "instruction": "Extract the page heading.",
      "status": "SUCCESS",
      "data": { "heading": "Example Domain" },
      "creditsUsed": 0.11,
      "durationMs": 4820
    }
  ],
  "totalCreditsUsed": 0.11,
  "status": "completed",
  "createdAt": "2026-04-16T18:22:10.224Z",
  "completedAt": "2026-04-16T18:22:15.044Z"
}
```

### Async (`async: true`)

Returns immediately with a `batchId`. Poll [`GET /browser-agent/:id`](/browser-agents/api/task-status) for progress.

Best for: large runs, long-running tasks, fire-and-forget jobs.

```bash theme={null}
curl -X POST https://api.pre.dev/browser-agent \
  -H "Authorization: Bearer $PREDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tasks": [{ "url": "https://example.com", "instruction": "Extract the heading." }],
    "async": true
  }'
```

**Response (200):**

```json theme={null}
{
  "id": "65f8a9d2c1e4b5a6f7e8d9c0",
  "total": 1,
  "completed": 0,
  "results": [],
  "totalCreditsUsed": 0,
  "status": "processing"
}
```

### Stream (`stream: true`)

Returns `text/event-stream` with per-step events. Each frame has a `taskIndex` tying it back to a task in the run.

Best for: live UI progress, debugging, watching what the agent is doing in real time.

**SSE frames:**

| Event         | When                                                                     | Payload                        |
| ------------- | ------------------------------------------------------------------------ | ------------------------------ |
| `task_event`  | Agent performs a step (navigation, plan, action, screenshot, validation) | `{ taskIndex, type, data }`    |
| `task_result` | A single task finishes                                                   | `{ taskIndex, ...TaskResult }` |
| `done`        | Entire run finishes                                                      | Full `BatchResult`             |
| `error`       | Fatal error aborted the run                                              | `{ error }`                    |

Keepalive `:keepalive` comments are sent every 10s to stop intermediaries from closing the connection.

```bash theme={null}
curl -N -X POST https://api.pre.dev/browser-agent \
  -H "Authorization: Bearer $PREDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tasks": [{ "url": "https://example.com", "instruction": "Extract the heading." }],
    "stream": true
  }'
```

**Example frames:**

```
:ok

event: task_event
data: {"taskIndex":0,"type":"navigation","data":{"url":"https://example.com"}}

event: task_event
data: {"taskIndex":0,"type":"screenshot","data":{"url":"https://..."}}

event: task_result
data: {"taskIndex":0,"status":"SUCCESS","data":{"heading":"Example Domain"},"durationMs":4820}

event: done
data: {"id":"65f8...","total":1,"completed":1,"results":[...],"status":"completed"}
```

<Note>
  If a pod is already serving its SSE cap (`5000` concurrent streams), new stream requests get `503`. Fall back to `async: true` + polling.
</Note>

## Response Schemas

### `BatchResult`

| Field              | Type                                      | Description                                                                                                  |
| ------------------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `id`               | `string`                                  | Run id (Mongo ObjectId). Use with [`GET /browser-agent/:id`](/browser-agents/api/task-status).               |
| `total`            | `integer`                                 | Number of tasks in the run.                                                                                  |
| `completed`        | `integer`                                 | Number of tasks finished (any status).                                                                       |
| `results`          | `TaskResult[]`                            | Per-task results, aligned by `taskIndex`.                                                                    |
| `totalCreditsUsed` | `number`                                  | Sum of credits billed across the run. 1 credit = \$0.10, floor 0.1 per billed task.                          |
| `status`           | `"processing" \| "completed" \| "failed"` | Run state.                                                                                                   |
| `createdAt`        | ISO-8601                                  | Run creation time.                                                                                           |
| `completedAt`      | ISO-8601                                  | Run completion time. Omitted while processing.                                                               |
| `liveEvents`       | `RunnerEvent[][]`                         | Only when fetching with `includeEvents=true` — in-flight event streams for tasks that haven't yet completed. |
| `error`            | `string`                                  | Set only when `status === "failed"`.                                                                         |

### `TaskResult`

| Field         | Type                     | Description                                                                                                                        |
| ------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `url`         | `string`                 | Starting URL (echoed from the request).                                                                                            |
| `instruction` | `string`                 | Task instruction (echoed).                                                                                                         |
| `input`       | `Record<string, string>` | Task input (echoed).                                                                                                               |
| `status`      | `TaskStatus`             | See [task statuses](#task-statuses).                                                                                               |
| `data`        | `any`                    | Extracted data, validated against the task's `output` schema. `null` if no `output` was specified or the task failed.              |
| `creditsUsed` | `number`                 | Credits billed for this task. Floor 0.1 (= \$0.01) for `SUCCESS`; scales up with task complexity. Zero for non-`SUCCESS` statuses. |
| `durationMs`  | `integer`                | Wall-clock runtime.                                                                                                                |
| `error`       | `string`                 | Failure reason, when `status` is not `SUCCESS`.                                                                                    |
| `events`      | `RunnerEvent[]`          | Full step timeline. Only returned when fetching with `includeEvents=true`.                                                         |

### Task statuses

| Status           | Meaning                                                          | Billed? |
| ---------------- | ---------------------------------------------------------------- | ------- |
| `SUCCESS`        | Task completed; `output` schema (if any) validated.              | ✅       |
| `PENDING`        | Task queued, not yet started (async/polling response only).      | —       |
| `ERROR`          | Task errored during execution.                                   | ❌       |
| `TIMEOUT`        | Task hit `timeoutMs`.                                            | ❌       |
| `BLOCKED`        | Target site blocked the agent (bot protection, geo-block, etc.). | ❌       |
| `CAPTCHA_FAILED` | CAPTCHA challenge couldn't be solved.                            | ❌       |
| `LOOP`           | Agent got stuck in a redirect or action loop.                    | ❌       |
| `NO_TARGET`      | Required element wasn't found on the page.                       | ❌       |

## Status Codes

| Code  | Meaning                                                                                                                                                                                      |
| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200` | Sync result, async stub, or start of SSE stream.                                                                                                                                             |
| `400` | Missing `tasks`, task without `url`, `tasks.length > 1000`, or `code: BATCH_TOO_LARGE`.                                                                                                      |
| `401` | Missing or invalid bearer token.                                                                                                                                                             |
| `402` | Subscription required (`code: SUBSCRIPTION_REQUIRED`) or insufficient credits (`code: INSUFFICIENT_CREDITS`). Body includes `actionUrl` — see [Error response shape](#error-response-shape). |
| `429` | Rate-limited (`code: RATE_LIMITED`) or queue full (`code: QUEUE_FULL`). Retry after a back-off.                                                                                              |
| `503` | SSE capacity exceeded on this pod. Retry with `async: true` and poll.                                                                                                                        |

### Error response shape

Non-2xx responses return a structured JSON body (and the matching SSE
`error` event when streaming):

```json theme={null}
{
  "error": "Need ~0.5 credits, have 0.00. Buy more to continue.",
  "code": "INSUFFICIENT_CREDITS",
  "actionUrl": "https://pre.dev/projects/browser-agents?upgrade=credits"
}
```

`code` lets clients dispatch typed handlers without parsing strings.
`actionUrl`, when present, deep-links the user back to pre.dev with the
right modal pre-opened — paste it into `window.open` and they'll land
on the credit-purchase or subscription flow ready to go.

| `code`                  | When                                                  | `actionUrl`      |
| ----------------------- | ----------------------------------------------------- | ---------------- |
| `SUBSCRIPTION_REQUIRED` | Trial limit reached on this API key.                  | Subscribe modal. |
| `INSUFFICIENT_CREDITS`  | Subscribed but credit balance too low for this batch. | Credits modal.   |
| `RATE_LIMITED`          | Per-minute request cap hit.                           | —                |
| `QUEUE_FULL`            | Per-user in-flight task cap hit.                      | —                |
| `BATCH_TOO_LARGE`       | `tasks.length` exceeds the per-request maximum.       | —                |

The Node and Python SDKs map every `code` value to a typed exception
class with the `actionUrl` populated — see the
[Node SDK](/browser-agents/sdks/node#error-handling) and
[Python SDK](/browser-agents/sdks/python#error-handling) error-handling
sections.

## Code Examples

### Multiple tasks with concurrency

```bash theme={null}
curl -X POST https://api.pre.dev/browser-agent \
  -H "Authorization: Bearer $PREDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tasks": [
      { "url": "https://news.ycombinator.com", "instruction": "Extract the top 5 story titles." },
      { "url": "https://www.reddit.com/r/programming", "instruction": "Extract the top 5 post titles." }
    ],
    "concurrency": 2
  }'
```

### Structured extraction with `output` schema

```bash theme={null}
curl -X POST https://api.pre.dev/browser-agent \
  -H "Authorization: Bearer $PREDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tasks": [
      {
        "url": "https://news.ycombinator.com",
        "instruction": "Extract the top 5 stories.",
        "output": {
          "type": "object",
          "properties": {
            "stories": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "title": { "type": "string" },
                  "points": { "type": "number" }
                },
                "required": ["title", "points"]
              }
            }
          },
          "required": ["stories"]
        }
      }
    ]
  }'
```

### Python — sync + async + streaming

```python theme={null}
import json
import time
import requests

API_KEY = "YOUR_API_KEY"
BASE = "https://api.pre.dev/browser-agent"
HDR = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}


def run_sync(tasks, concurrency=5):
    r = requests.post(BASE, headers=HDR, json={"tasks": tasks, "concurrency": concurrency})
    r.raise_for_status()
    return r.json()


def run_async(tasks, concurrency=5, poll_every=5):
    r = requests.post(BASE, headers=HDR, json={"tasks": tasks, "concurrency": concurrency, "async": True})
    r.raise_for_status()
    batch_id = r.json()["id"]
    while True:
        poll = requests.get(f"{BASE}/{batch_id}", headers=HDR).json()
        if poll["status"] in ("completed", "failed"):
            return poll
        print(f"{poll['completed']}/{poll['total']} done")
        time.sleep(poll_every)


def run_stream(tasks):
    with requests.post(BASE, headers=HDR, json={"tasks": tasks, "stream": True}, stream=True) as r:
        r.raise_for_status()
        event = None
        for line in r.iter_lines(decode_unicode=True):
            if line is None or line.startswith(":"):
                continue
            if line.startswith("event: "):
                event = line[len("event: "):].strip()
            elif line.startswith("data: "):
                data = json.loads(line[len("data: "):])
                yield event, data


# sync
result = run_sync([{"url": "https://example.com", "instruction": "Extract the heading."}])
print(result["results"][0]["data"])

# stream
for event, data in run_stream([{"url": "https://example.com", "instruction": "Extract the heading."}]):
    if event == "task_event":
        print(f"[task {data['taskIndex']}] {data.get('type')}")
    elif event == "task_result":
        print(f"[task {data['taskIndex']}] {data['status']} → {data.get('data')}")
    elif event == "done":
        print("batch done:", data["id"])
        break
```

### Node.js — sync + streaming

```javascript theme={null}
const API_KEY = process.env.PREDEV_API_KEY;
const BASE = "https://api.pre.dev/browser-agent";
const HDR = {
  "Authorization": `Bearer ${API_KEY}`,
  "Content-Type": "application/json",
};

async function runSync(tasks, concurrency = 5) {
  const res = await fetch(BASE, {
    method: "POST",
    headers: HDR,
    body: JSON.stringify({ tasks, concurrency }),
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  return res.json();
}

async function* runStream(tasks) {
  const res = await fetch(BASE, {
    method: "POST",
    headers: HDR,
    body: JSON.stringify({ tasks, stream: true }),
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);

  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let event = null;

  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });

    let idx;
    while ((idx = buffer.indexOf("\n")) !== -1) {
      const line = buffer.slice(0, idx).trim();
      buffer = buffer.slice(idx + 1);
      if (!line || line.startsWith(":")) continue;
      if (line.startsWith("event: ")) event = line.slice(7).trim();
      else if (line.startsWith("data: ")) yield { event, data: JSON.parse(line.slice(6)) };
    }
  }
}

// sync
const result = await runSync([
  { url: "https://example.com", instruction: "Extract the heading." },
]);
console.log(result.results[0].data);

// stream
for await (const { event, data } of runStream([
  { url: "https://example.com", instruction: "Extract the heading." },
])) {
  if (event === "task_event") console.log(`[task ${data.taskIndex}]`, data.type);
  if (event === "task_result") console.log(`[task ${data.taskIndex}]`, data.status, data.data);
  if (event === "done") break;
}
```

### TypeScript — fully typed client

```typescript theme={null}
type TaskStatus =
  | "SUCCESS" | "PENDING" | "ERROR" | "TIMEOUT"
  | "BLOCKED" | "CAPTCHA_FAILED" | "LOOP" | "NO_TARGET";

interface Task {
  url: string;
  instruction?: string;
  input?: Record<string, string>;
  output?: Record<string, unknown>;
  successCondition?: string;
  timeoutMs?: number;
}

interface TaskResult {
  url: string;
  instruction?: string;
  input?: Record<string, string>;
  status: TaskStatus;
  data?: unknown;
  creditsUsed: number;
  durationMs: number;
  error?: string;
}

interface BatchResult {
  id: string;
  total: number;
  completed: number;
  results: TaskResult[];
  totalCreditsUsed: number;
  status: "processing" | "completed" | "failed";
  createdAt?: string;
  completedAt?: string;
  error?: string;
}

interface BatchRequest {
  tasks: Task[];
  concurrency?: number;
  async?: boolean;
  stream?: boolean;
}

export class BrowserAgents {
  constructor(private apiKey: string, private base = "https://api.pre.dev/browser-agent") {}

  private headers() {
    return {
      "Authorization": `Bearer ${this.apiKey}`,
      "Content-Type": "application/json",
    };
  }

  async run(req: BatchRequest): Promise<BatchResult> {
    const res = await fetch(this.base, { method: "POST", headers: this.headers(), body: JSON.stringify(req) });
    if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
    return res.json();
  }
}
```

## Error Handling

All non-2xx responses follow the [structured shape above](#error-response-shape). The two billing codes carry an `actionUrl` that auto-opens the right modal on pre.dev — surface it in your UI and the user is one click from resolving the gate.

### `402 SUBSCRIPTION_REQUIRED`

This API key's user is on the trial plan and has used their lifetime free task. Send them to `actionUrl` (subscribe modal) or have them upgrade at [pre.dev/billing](https://pre.dev/billing).

### `402 INSUFFICIENT_CREDITS`

Subscription is fine, but the credit balance is below the per-task estimate (0.1 credits/task by default; failed tasks aren't billed but you need the headroom to start). Send them to `actionUrl` (credits modal) — or top up at [pre.dev/billing](https://pre.dev/billing).

### `429 RATE_LIMITED` / `QUEUE_FULL`

Per-minute request cap hit, or your account has more than `5000` in-flight tasks. Wait for some to drain, or lower `concurrency`.

### `503 SSE capacity exceeded`

The serving pod is already at its SSE connection cap. Retry the request with `"async": true` and poll — the underlying work isn't affected.

### `400` / invalid task shape

Every task needs a `url`. If you get `400`, check you're not sending `tasks: { ... }` (a single object) — it must always be an array, even for one task.

<Card title="Next: Task Status" icon="arrow-right" href="/browser-agents/api/task-status">
  Fetch results for an async or historical task submission, with the full per-step event timeline.
</Card>


## OpenAPI

````yaml POST /browser-agent
openapi: 3.1.0
info:
  title: pre.dev Architect API
  description: >-
    Generate comprehensive software specifications for coding agents. The
    Architect API helps you create detailed project specifications that AI
    coding agents can understand and implement.
  version: 1.1.0
  contact:
    name: pre.dev Support
    url: https://pre.dev
    email: support@pre.dev
  license:
    name: Proprietary
    url: https://pre.dev/terms
servers:
  - url: https://api.pre.dev
    description: Production API Server
security:
  - apiKeyAuth: []
tags:
  - name: Spec Generation
    description: Generate comprehensive software specifications for AI coding agents
  - name: Status
    description: Check status of asynchronous specification processing
  - name: Spec Management
    description: List and search existing specifications
  - name: Account
    description: Manage user account and credits
  - name: Batches
    description: Submit and retrieve browser-agent task batches
paths:
  /browser-agent:
    post:
      tags:
        - Batches
      summary: Run one or more browser tasks
      description: >-
        Run one or more browser-agent tasks. Tasks run in parallel (configurable
        concurrency). Default behavior is synchronous — the request waits for
        all tasks to complete and returns the full batch result. Set `async:
        true` to return immediately with a `batchId` you can poll. Set `stream:
        true` to receive Server-Sent Events (per-step events + final results).


        Max 1000 tasks per request. Max 1000 in-flight tasks per user.
        Subscribed users get 100 requests/minute.
      operationId: runTask
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BatchRequest'
            examples:
              singleTask:
                summary: Single task — extract a heading
                value:
                  tasks:
                    - url: https://example.com
                      instruction: Extract the page heading.
                      output:
                        type: object
                        properties:
                          heading:
                            type: string
                        required:
                          - heading
              multipleTasks:
                summary: Multiple tasks with concurrency
                value:
                  tasks:
                    - url: https://news.ycombinator.com
                      instruction: Extract the top 5 story titles.
                    - url: https://www.reddit.com/r/programming
                      instruction: Extract the top 5 post titles.
                  concurrency: 2
              asyncMode:
                summary: Async — returns batchId immediately
                value:
                  tasks:
                    - url: https://example.com
                      instruction: Extract the heading.
                  async: true
      responses:
        '200':
          description: Batch result (sync mode) or batch stub (async mode).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchResult'
            text/event-stream:
              schema:
                type: string
                description: 'SSE frames: `event: task_event | task_result | done | error`'
        '400':
          description: >-
            Missing or invalid `tasks` array, `tasks.length > 1000`, or a task
            without a `url`.
        '401':
          description: Missing or invalid bearer token.
        '402':
          description: >-
            Insufficient credits. Top up at
            [pre.dev/billing](https://pre.dev/billing).
        '429':
          description: >-
            Per-user in-flight queue depth exceeded (max 5000). Retry later or
            reduce concurrency.
        '503':
          description: >-
            SSE capacity exceeded on this pod (5000 concurrent streams). Retry
            with `async: true` and poll.
components:
  schemas:
    BatchRequest:
      type: object
      required:
        - tasks
      properties:
        tasks:
          type: array
          items:
            $ref: '#/components/schemas/Task'
          minItems: 1
          maxItems: 1000
        concurrency:
          type: integer
          minimum: 1
          maximum: 20
          description: 'How many tasks to run in parallel. Default: 5.'
        async:
          type: boolean
          default: false
          description: >-
            If true, returns `{ id, status: 'processing' }` immediately — poll
            `GET /:id` for progress.
        stream:
          type: boolean
          default: false
          description: >-
            If true, returns an SSE stream with `task_event`, `task_result`,
            `done`, and `error` frames.
    BatchResult:
      type: object
      description: >-
        Run summary. The schema is named `BatchResult` for backwards
        compatibility with older clients.
      properties:
        id:
          type: string
          description: Run id (24-char Mongo ObjectId).
        total:
          type: integer
          description: Total tasks in the run.
        completed:
          type: integer
          description: Number of tasks that have finished (any status).
        results:
          type: array
          description: >-
            Per-task results aligned by `taskIndex`. In-progress runs return
            PENDING stubs for tasks that haven't started; the stub has only
            `url`, `instruction`, `input`, and `status: "PENDING"`.
          items:
            $ref: '#/components/schemas/TaskResult'
        totalCreditsUsed:
          type: number
          description: Sum of credits billed across all tasks in the run.
        status:
          type: string
          enum:
            - processing
            - completed
            - failed
        createdAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
          description: Populated once `status !== "processing"`.
        liveEvents:
          type: array
          description: >-
            Only present when `includeEvents=true` on `GET /:id`. Per-task
            in-flight event streams for tasks that haven't yet finished. Aligned
            by index with `results`; completed tasks get an empty array.
          items:
            type: array
            items:
              $ref: '#/components/schemas/RunnerEvent'
        error:
          type: string
          description: Set only when `status === "failed"`.
    Task:
      type: object
      required:
        - url
      properties:
        url:
          type: string
          format: uri
          description: Starting page URL the agent navigates to.
        instruction:
          type: string
          description: >-
            Natural-language goal. What should the agent accomplish on this
            page?
        input:
          type: object
          additionalProperties:
            type: string
          description: >-
            Form values, credentials, queries — any string inputs the agent
            should use during the task. Keys referenced from `instruction` (e.g.
            "Search for {{query}}") are interpolated before the run.
        output:
          type: object
          description: >-
            JSON Schema describing the shape of data to extract. When present,
            the agent's final output is validated against this schema before
            returning `SUCCESS`. If omitted, the agent returns unstructured text
            in `data`.
        successCondition:
          type: string
          description: >-
            Optional natural-language assertion. Task is marked `SUCCESS` only
            if this condition holds at the end (e.g. "the page shows a
            confirmation with an order number").
        timeoutMs:
          type: integer
          default: 240000
          minimum: 5000
          description: >-
            Per-task max runtime in milliseconds. Default 240000 (4 min). When
            reached, the task status becomes `TIMEOUT` and is not billed.
    TaskResult:
      type: object
      properties:
        url:
          type: string
        instruction:
          type: string
        input:
          type: object
          additionalProperties:
            type: string
        status:
          type: string
          description: Only `SUCCESS` is billed; every failure mode is free.
          enum:
            - SUCCESS
            - PENDING
            - ERROR
            - TIMEOUT
            - BLOCKED
            - CAPTCHA_FAILED
            - LOOP
            - NO_TARGET
        data:
          description: >-
            Extracted data, validated against the task's `output` schema. `null`
            when no `output` was specified or the task failed.
        creditsUsed:
          type: number
          description: >-
            Credits billed for this task. 1 credit = $0.10. Floor 0.1 for
            SUCCESS, scales up with task complexity. Zero for non-SUCCESS
            statuses.
        durationMs:
          type: integer
          description: Wall-clock runtime of the task.
        error:
          type: string
          description: Failure reason, populated when `status` is not `SUCCESS`.
        events:
          type: array
          description: >-
            Full per-step event timeline. Only present when the caller requested
            `includeEvents=true`.
          items:
            $ref: '#/components/schemas/RunnerEvent'
    RunnerEvent:
      type: object
      description: >-
        One step in the agent's execution. Internal fields (sandbox provider,
        LLM model, token counts) are stripped before the event is returned.
      properties:
        type:
          type: string
          enum:
            - navigation
            - plan
            - action
            - screenshot
            - validation
            - done
            - error
        data:
          type: object
          description: >-
            Event-specific payload. See the Get a Run docs for per-type field
            lists.
        ts:
          type: string
          format: date-time
  securitySchemes:
    apiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: >-
        API key for authentication. Get your API key from
        https://pre.dev/projects/playground (Solo) or
        https://pre.dev/enterprise/dashboard?page=api (Enterprise). Use format:
        Bearer YOUR_API_KEY
      x-default: YOUR_API_KEY

````