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

# Drive a page

> Find an element and click it through the HTTP API.

Start with a [running session](https://docs.tilion.dev/guides/create-browser.md). These examples open `example.com`, read the first visible link's selector, and click it. Set `TILION_API_KEY` and `TILION_SESSION_ID` first.

```python Python theme={"dark"}
import os
import requests

base = f"https://back.tilion.dev/v1/session/{os.environ['TILION_SESSION_ID']}"
headers = {"Authorization": f"Bearer {os.environ['TILION_API_KEY']}"}
def post(path, body):
    response = requests.post(f"{base}/{path}", headers=headers, json=body, timeout=70)
    response.raise_for_status()
    return response.json()

page = post("navigate", {"url": "https://example.com/"})
if page.get("status") != "ok":
    raise RuntimeError(page)
found = post("elements", {"selector": "a", "limit": 1})
if not found.get("ok") or not found.get("elements"):
    raise RuntimeError(f"No link found: {found}")
result = post("act", {"action": "click", "selector": found["elements"][0]["selector"]})
if not result.get("ok"):
    raise RuntimeError(result)
print(result)
```

```javascript Node.js theme={"dark"}
const base = `https://back.tilion.dev/v1/session/${process.env.TILION_SESSION_ID}`;
async function post(path, body) {
  const response = await fetch(`${base}/${path}`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.TILION_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body), signal: AbortSignal.timeout(70_000),
  });
  if (!response.ok) throw new Error(await response.text());
  return response.json();
}
const page = await post("navigate", { url: "https://example.com/" });
if (page.status !== "ok") throw new Error(JSON.stringify(page));
const found = await post("elements", { selector: "a", limit: 1 });
if (!found.ok || !found.elements?.length) throw new Error(JSON.stringify(found));
const result = await post("act", {
  action: "click", selector: found.elements[0].selector,
});
if (!result.ok) throw new Error(JSON.stringify(result));
console.log(result);
```

```bash curl theme={"dark"}
# Run in bash. Requires jq.
set -euo pipefail
BASE="https://back.tilion.dev/v1/session/$TILION_SESSION_ID"
post() {
  curl --fail-with-body --silent --show-error "$BASE/$1" \
    -H "Authorization: Bearer $TILION_API_KEY" \
    -H "Content-Type: application/json" -d "$2"
}
post navigate '{"url": "https://example.com/"}' \
  | jq -e 'if .status == "ok" then . else error(tostring) end'
SELECTOR=$(post elements '{"selector": "a", "limit": 1}' \
  | jq -er 'if .ok and (.elements | length > 0) then .elements[0].selector else error(tostring) end')
post act "$(jq -n --arg selector "$SELECTOR" '{action: "click", selector: $selector}')" \
  | jq -e 'if .ok then . else error(tostring) end'
```

`act` returns `ok`, `action`, and `error`. Check `ok` even when the HTTP status is `200`.

For form inputs, discover the selector with `elements`, then use `action: "fill"` with `selector` and `text`. Use `secret: true` for sensitive values. Password inputs receive this treatment automatically.

> [!NOTE]
>
> A main CDP client, native Playwright client, or VNC client prevents REST drive calls. Disconnect it before sending these requests. [CDP observers](https://docs.tilion.dev/guides/multiple-cdp-clients.md) can remain connected, but coordinate their commands with REST actions.

Use [`fetch`](https://docs.tilion.dev/guides/read-page) for protected-page reads. Close the session when finished. The [drive reference](https://docs.tilion.dev/sessions/driving.md) covers typing, scrolling, tabs, and screenshots.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.