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

# Actions

> All browser action types: navigate, observe, click, fill, select, press, scroll, wait_for, screenshot, and batch.

Actions are executed against a browser session via `POST /v2/browser_sessions/:id/actions`. Each session executes one action at a time.

## Action Types

| Type | Requires `observation_id` | Key Fields |
| - | - | - |
| `navigate` | No | `url` (max 2048), `wait_until?` (`load` \| `dom_stable` \| `none`, default `load`) |
| `observe` | — | (returns `observation_id` + `refs`) |
| `click` | **Yes** | `observation_id`, `ref`, `wait?` (`no_wait` \| `dom_stable` \| `url_change`, default `dom_stable`), `timeout_ms?` (100–15000, default 5000) |
| `fill` | **Yes** | `observation_id`, `ref`, `value` (max 4096 chars) |
| `select` | **Yes** | `observation_id`, `ref`, `value` (max 1024 chars) |
| `press` | No | `key` (`Enter` \| `Tab` \| `Escape` \| `Backspace` \| `ArrowUp` \| `ArrowDown` \| `ArrowLeft` \| `ArrowRight` \| `PageUp` \| `PageDown` \| `Home` \| `End`) |
| `scroll` | No | `delta_x?` (−5000–5000, default 0), `delta_y?` (−5000–5000, default 0) |
| `wait_for` | No | `condition` (object: `{kind: "url_pattern", pattern}`, `{kind: "ref", observation_id, ref}`, `{kind: "text", text}`, `{kind: "dom_stable"}`), `timeout_ms?` (100–15000, default 5000) |
| `screenshot` | No | (returns artifact) |
| `batch` | Per sub-action | `actions[]` (1–50, non-recursive) |

All actions accept optional `include_observation` (bool, default false) and `idempotency_key` (string).

## Common Fields

| Field | Type | Default | Description |
| - | - | - | - |
| `include_observation` | bool | false | Return a fresh observation in the response (saves a separate observe call). |
| `idempotency_key` | string | — | Dedup key. Reusing the key with an **identical** request replays the original resource. Reusing it with a **different** request returns `409 idempotency_conflict`. |

## Navigation

```bash theme={null}
curl -X POST https://api.telnyx.com/v2/browser_sessions/$SESSION_ID/actions \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "navigate",
    "url": "https://example.com/",
    "wait_until": "load"
  }'
```

`wait_until` controls when the action is considered complete:

* `load` (default) — wait for the page `load` event.
* `dom_stable` — wait until the DOM stops changing.
* `none` — return immediately after navigation is initiated.

Navigation to domains not in the session's `allowed_domains` returns HTTP 200 with the action in `status: "failed"` and `data.error.code: "policy_denied"` — execution failures like this are reported on the action resource, not as HTTP error statuses.

## Press

```bash theme={null}
curl -X POST https://api.telnyx.com/v2/browser_sessions/$SESSION_ID/actions \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "press",
    "key": "Enter"
  }'
```

## Scroll

```bash theme={null}
curl -X POST https://api.telnyx.com/v2/browser_sessions/$SESSION_ID/actions \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "scroll",
    "delta_y": 500
  }'
```

## Wait For

Wait for a condition to be met on the page:

```bash theme={null}
curl -X POST https://api.telnyx.com/v2/browser_sessions/$SESSION_ID/actions \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "wait_for",
    "condition": { "kind": "dom_stable" },
    "timeout_ms": 5000
  }'
```

Condition kinds:

* `{ "kind": "url_pattern", "pattern": "*/login" }` — wait until the URL matches a pattern.
* `{ "kind": "ref", "observation_id": "obs_...", "ref": "e1" }` — wait for an element to appear.
* `{ "kind": "text", "text": "Welcome" }` — wait for text to appear on the page.
* `{ "kind": "dom_stable" }` — wait until the DOM stops changing.

## Screenshot

```bash theme={null}
curl -X POST https://api.telnyx.com/v2/browser_sessions/$SESSION_ID/actions \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "screenshot"
  }'
```

Returns an artifact with `artifact_id`, `content_type`, and `size_bytes`. Download via:

```bash theme={null}
curl -s -X GET "https://api.telnyx.com/v2/browser_sessions/$SESSION_ID/artifacts/$ARTIFACT_ID" \
  -H "Authorization: Bearer ***" | jq -r '.data.download_url'
```

## Batch Actions

Execute multiple actions in a single request. Sub-actions run sequentially. A failed sub-action stops the batch (non-atomic — prior mutations persist).

```bash theme={null}
curl -X POST https://api.telnyx.com/v2/browser_sessions/$SESSION_ID/actions \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "batch",
    "actions": [
      { "type": "navigate", "url": "https://example.com/" },
      { "type": "observe" }
    ]
  }'
```

* `actions[]` — 1 to 50 sub-actions. An empty array is rejected (400), and the array is non-recursive (batch inside batch → 400).
* `include_observation` on a batch applies to the **last** sub-action only.
* A failed sub-action stops the batch with `status: "failed"`. Earlier sub-actions have already mutated the browser (they are not rolled back), but partial sub-action results are **not** returned — `results[]` is present only when every sub-action completes.

## include\_observation

Add `include_observation: true` to any action to get a fresh observation in the response, saving a separate `observe` round-trip:

```bash theme={null}
curl -X POST https://api.telnyx.com/v2/browser_sessions/$SESSION_ID/actions \
  -H "Authorization: Bearer ***" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "click",
    "observation_id": "obs_...",
    "ref": "e2",
    "include_observation": true
  }'
```

The response will include a new `observation_id` and `refs` for the resulting page.

## Action Failures vs HTTP Errors

There are two distinct error surfaces:

**Request errors** use HTTP error statuses and a top-level `error` object. The Code column is `error.code`:

| HTTP | Code | Meaning |
| - | - | - |
| 400 | `validation_error` | Invalid request body or parameters (unknown fields, missing required fields, out-of-range values). |
| 401 | `10009` | Missing or invalid API key. Authentication failures use the standard Telnyx `errors[]` envelope, unlike action errors. |
| 404 | `not_found` | Session, action, or artifact does not exist (or belongs to another organization). |
| 409 | `session_not_ready` | New actions require a ready session. Poll while provisioning; closing, closed, failed, and expired sessions cannot accept new actions. |
| 409 | `concurrent_action` | Another action is in flight. Wait for it to finish before submitting another. |
| 409 | `idempotency_conflict` | Same `idempotency_key` submitted with a different normalized request. |
| 503 | `execution_lost` | The request could not be completed reliably. The response has `retryable: true`, but does not prove that the action had no effect. Follow the recovery guidance below. |

Session creation, not action submission, returns the session quota error below. See [Sessions](/docs/agent-browser/sessions).

| Code | HTTP | Meaning | Retryable |
| - | - | - | - |
| `quota_exceeded` | 429 | Session creation rejected because the organization's concurrent-session quota is full. Close unneeded active sessions, or wait for the reconciler to release fenced slots, then retry. This is a session quota, not a per-key request-rate limit. | Yes |

**Execution failures** are returned on the action resource inside HTTP 200 as `data.status: "failed"` (or `unknown`) with `data.error`:

| Code | HTTP | Meaning | Retryable |
| - | - | - | - |
| `policy_denied` | 200 | URL not in `allowed_domains`. | No |
| `stale_observation` | 200 | `observation_id` is no longer current; re-observe first. | No |
| `stale_ref` | 200 | `ref` no longer resolves in the current observation. | No |
| `navigation_blocked` | 200 | The browser refused the navigation. | No |
| `timeout` | 200 | An execution deadline was exceeded, such as `timeout_ms` on `click` or `wait_for`; earlier effects are not rolled back. | Yes |
| `execution_lost` | 200 | The execution outcome could not be determined; the action is `unknown`. The session may also be `failed` or otherwise terminal. | No |
| `result_too_large` | 200 | Result exceeded the 256 KiB limit; reduce the result size. This is an action failure, not HTTP 413. | No |

These codes travel in `data.error.code` inside an HTTP 200 response — the `HTTP` column records that carrier status, not a request failure. A `failed` or `unknown` action is terminal. Inspect the cause and any effects before deciding whether a new action is safe. For staleness, obtain a fresh observation and select a current ref.

### Recovering from execution\_lost

Do not blindly resubmit a mutation: a lost response or an `unknown` outcome may mean the action already took effect. A new idempotency key or a new session does not protect against duplicating those effects.

1. If you have an action ID, retrieve that action and inspect its status. Also retrieve the session.
2. If the original submission included `idempotency_key`, repeating the **same normalized request with the same key on the same session** retrieves an existing record when one exists, even after the session closes. It does not re-execute a terminal action. If no record exists, that submission can start a new action; only attempt it when safe.
3. Without a known action ID or idempotency key, verify the outcome in the target application before repeating a mutation.
4. Poll a `provisioning` session until ready. A `closing`, `closed`, `failed`, or `expired` session will not become ready again; use a new session for further work only after reconciling the original outcome. `session_not_ready` carries `retryable: true`, but that flag does not make a terminal session reusable.

## Polling Actions

`wait_seconds` (0–25, default 10) limits only how long the submission request waits, not execution time. If the action is still `queued` or `running`, the response is `202 Accepted`; execution continues. Poll the returned action ID rather than submitting it again. GET returns HTTP 200 even while the action is still running, so inspect `data.status`:

Full observation `tree` and `refs` are delivered only in the original successful submission response when it completes within the wait. Polling and idempotent replays return observation metadata with an empty tree and refs. If you need element refs after polling, submit a new `observe` with `wait_seconds=25`; see [Get Started](/docs/agent-browser/getting-started).

```bash theme={null}
curl -s https://api.telnyx.com/v2/browser_sessions/$SESSION_ID/actions/$ACTION_ID \
  -H "Authorization: Bearer ***"
```
