> ## 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.

# Get Started

> Create a session, navigate, observe, click a current element ref, and download a screenshot.

Run the blocks below in order in the same **Bash** shell with `curl` and `jq` installed. Set `TELNYX_API_KEY` to your API key locally before starting; do not put it in shared logs. All resource IDs and element refs below come from your actual responses.

## Set Up Requests and Polling

```bash theme={null}
set -euo pipefail
: "${TELNYX_API_KEY:?Set TELNYX_API_KEY to your Telnyx API key first}"
API_HOST=https://api.telnyx.com
API_BASE="$API_HOST/v2"

# Each call is bounded; HTTP errors stop the example without retrying a mutation.
api() {
  curl --fail-with-body --silent --show-error --connect-timeout 10 \
    --max-time 90 -H "Authorization: Bearer $TELNYX_API_KEY" "$@"
}

# Submit once, then poll the returned ID. HTTP 200 alone does not mean success.
run_action() {
  local response action_id status remaining deadline=$((SECONDS + 90))
  response=$(api --max-time 35 -X POST \
    "$API_BASE/browser_sessions/$SESSION_ID/actions?wait_seconds=25" \
    -H "Content-Type: application/json" -d "$1") || return
  action_id=$(jq -er '.data.id' <<< "$response") || return
  while :; do
    status=$(jq -er '.data.status' <<< "$response") || return
    case "$status" in
      succeeded) printf '%s\n' "$response"; return 0 ;;
      failed|unknown) printf '%s\n' "$response" >&2; return 1 ;;
      queued|running) ;;
      *) printf 'Unexpected action status: %s\n' "$status" >&2; return 1 ;;
    esac
    remaining=$((deadline - SECONDS))
    if ((remaining <= 1)); then
      printf 'Polling budget reached. Inspect action %s; do not resubmit blindly.\n' "$action_id" >&2
      return 1
    fi
    sleep 1
    remaining=$((deadline - SECONDS))
    if ((remaining <= 0)); then return 1; fi
    response=$(api --max-time "$remaining" \
      "$API_BASE/browser_sessions/$SESSION_ID/actions/$action_id") || return
  done
}
```

`wait_seconds` limits the submission wait, not execution time. A 202 means the action is still queued or running; the helper polls until `succeeded`, `failed`, or `unknown`. It stops on failures and HTTP errors, including 503. See [recovery guidance](/docs/agent-browser/actions#recovering-from-execution_lost) before repeating a request with an uncertain outcome.

## Create a Session

Allow the example page and the IANA domains its link may navigate to, including subdomains:

```bash theme={null}
SESSION_RESPONSE=$(api -X POST "$API_BASE/browser_sessions" \
  -H "Content-Type: application/json" \
  -d '{"policy":{"allowed_domains":["example.com","iana.org","*.iana.org"]}}')
SESSION_ID=$(jq -er '.data.id' <<< "$SESSION_RESPONSE")
printf 'Session: %s\n' "$SESSION_ID"

SESSION_DEADLINE=$((SECONDS + 90))
while [[ $(jq -er '.data.status' <<< "$SESSION_RESPONSE") == provisioning ]]; do
  REMAINING=$((SESSION_DEADLINE - SECONDS))
  if ((REMAINING <= 1)); then
    printf 'Session is not ready within the polling budget.\n' >&2
    exit 1
  fi
  sleep 1
  REMAINING=$((SESSION_DEADLINE - SECONDS))
  if ((REMAINING <= 0)); then exit 1; fi
  SESSION_RESPONSE=$(api --max-time "$REMAINING" "$API_BASE/browser_sessions/$SESSION_ID")
done
jq -e '.data.status == "ready"' <<< "$SESSION_RESPONSE" >/dev/null
```

Creation returns HTTP 201. Only a `ready` session accepts new actions. If it is closing, closed, failed, or expired, stop instead of retrying actions on it.

## Navigate

```bash theme={null}
NAVIGATION_RESPONSE=$(run_action '{"type":"navigate","url":"https://example.com/"}')
jq '.data.result' <<< "$NAVIGATION_RESPONSE"
```

## Observe and Select a Current Ref

The page currently has a link to IANA. Select the link from the returned refs rather than assuming an ID such as `e13`.

Full observation refs are available only in the original successful POST response. GET polling returns metadata with empty refs. If the observe action needed polling, the bounded loop below requests a fresh observation after that action finishes. It never retries a click or other mutation.

```bash theme={null}
OBSERVATION_RESPONSE=''
for ATTEMPT in 1 2 3; do
  OBSERVATION_RESPONSE=$(run_action '{"type":"observe"}')
  if jq -e '(.data.result.observation.refs // {} | length) > 0' \
    <<< "$OBSERVATION_RESPONSE" >/dev/null; then
    break
  fi
done
OBSERVATION_ID=$(jq -er '.data.result.observation.observation_id' <<< "$OBSERVATION_RESPONSE")
LINK_REF=$(jq -er '[.data.result.observation.refs | to_entries[] |
  select(.value.role == "link")][0].key // error("No link ref available; inspect a fresh observation")' \
  <<< "$OBSERVATION_RESPONSE")
jq '.data.result.observation.refs' <<< "$OBSERVATION_RESPONSE"
```

If the page changes or no link ref is available, the script stops. Inspect a fresh observation instead of substituting a sample ref. For other pages, choose the intended element by its role and name.

## Click the Observed Link

```bash theme={null}
CLICK_BODY=$(jq -n --arg observation_id "$OBSERVATION_ID" --arg ref "$LINK_REF" \
  '{type:"click", observation_id:$observation_id, ref:$ref}')
CLICK_RESPONSE=$(run_action "$CLICK_BODY")
jq '.data.result' <<< "$CLICK_RESPONSE"
```

Navigation invalidates the prior observation. Obtain a new observation before any further interaction by ref.

## Screenshot and Download

```bash theme={null}
SCREENSHOT_RESPONSE=$(run_action '{"type":"screenshot"}')
ARTIFACT_ID=$(jq -er '.data.result.artifact.artifact_id' <<< "$SCREENSHOT_RESPONSE")
ARTIFACT_RESPONSE=$(api "$API_BASE/browser_sessions/$SESSION_ID/artifacts/$ARTIFACT_ID")
DOWNLOAD_PATH=$(jq -er '.data.download_url' <<< "$ARTIFACT_RESPONSE")
case "$DOWNLOAD_PATH" in
  /v2/artifact_downloads/*) ;;
  *) printf 'Unexpected artifact download path\n' >&2; exit 1 ;;
esac
api "$API_HOST$DOWNLOAD_PATH" -o screenshot.png
```

`download_url` is a signed relative path. Prepend the API host and include Bearer authentication on the download request too. The screenshot remains downloadable until its artifact expiry; an expired download signature requires a fresh metadata request.

## Close the Session

```bash theme={null}
CLOSE_RESPONSE=$(api -X DELETE "$API_BASE/browser_sessions/$SESSION_ID")
jq '.data.status' <<< "$CLOSE_RESPONSE"
```

Closing a ready session normally returns `closed` and releases its resources. An already terminal session is returned unchanged. If an earlier step stopped, inspect the printed session ID and close the session when appropriate; otherwise it expires according to its lifetime or idle timeout.
