Skip to content

HTTP API Reference

Sam LABBE edited this page Oct 13, 2026 · 1 revision

HTTP API Reference

All routes on 127.0.0.1 only, JSON bodies (Content-Type: application/json), except /screenshot which returns raw PNG bytes. With --token SECRET, every route except /health requires Authorization: Bearer SECRET (or X-Navette-Token). Request bodies are capped at 10 MB — larger requests are dropped.

The optional "session" field selects a named session (default "default"). Sessions are created lazily on first use; ghost windows are attached to the OS only when a screenshot needs them.

Core

Route Body Returns
GET /health — {ok, name, engine} — the probe. Also reports auth_ceremony on builds with the passkey plumbing. Exempt from token and Host guards.
GET /sessions — sessions with url + title
POST /navigate {url, session?, with_content?, format?} {ok, url, title[, content]} — with_content folds the read into one round-trip. Redirected navigations complete fine (URL-match completion on wry backends).
POST /read {session?, format?} {ok, content} — format: markdown (default) / text / html
POST /screenshot {session?} PNG bytes — native per engine (what the compositor drew, not a re-render), viewport-sized. Set the viewport before navigating for repeatable captures. Full-page capture is not implemented yet — tracked honestly rather than approximated.
POST /wait {selector, ms?, session?} {ok} — polls until the selector exists (or the timeout fires). The primary sync primitive on JS-heavy/SPA pages: always /wait the flow's ready selector after /navigate before reading.

Act on the page

Route Body Returns
POST /click {selector, session?, wait_navigation?} {ok} — real pointer+mouse events (pointerover/pointerdown/pointerup before the mouse events): Radix/HeadlessUI menus open like they do for a human. wait_navigation: true opts into auto-wait for form-POST navigations.
POST /type {selector, value, session?} {ok} — React-safe native setter (frameworks see the value).
POST /key {key, selector?, session?} {ok} — keydown+keyup on the focused element (optionally focused by selector). Attempt-first native OS events: CGEvent (macOS), SendInput (Windows), XTEST (Linux/X11) — isTrusted: true — falling back to synthetic dispatch and reporting which mode ran. Single characters ride with an attached unicode string, so the delivered e.key is what was asked for on any keyboard layout (verified on AZERTY). macOS delivery is verified in-page before mode: native is ever reported.
POST /hover {selector, session?} {ok} — mouseover/mousemove at the element's center.
POST /scroll {y?, selector?, session?} {ok, y} — absolute scroll, or scrollIntoView when selector is given.
POST /upload {selector, filename, content_base64, mime?, session?} {ok, files} — fills a file input with in-memory content via a page-side DataTransfer; no OS dialog, nothing written to the visitor's filesystem.
POST /evaluate {js, session?} {ok, result} — run JS in the page. Timeout NAVETTE_EVAL_TIMEOUT_SECS (default 20, clamp 5–300).
POST /sessions/viewport {width, height, session?} {ok, width, height} — default 1280x800.

Sessions lifecycle

Route Body Returns
POST /sessions/close {session} {ok} — close one session. Politeness rule: never close a session you didn't create (another workflow may depend on it).
POST /sessions/show {session?} {ok, visible} — put the session's window ON SCREEN: titled, keyable, centered, page state intact. The human-moments gate: one-time logins, OAuth, 2FA, captcha (the human does it — never the machine). On macOS this is also the focus gate for native key input: the window must be the key window before any CGEvent is posted.
POST /sessions/hide {session?} {ok, visible} — order the window back out. Session and state untouched.

State portability (cookies)

Route Body Returns
POST /sessions/state {session, store?} cookies JSON — the logged-in state. With store, the jar goes to the OS keychain under that name and only {stored, origin, cookies} returns — the jar never rides the wire.
POST /sessions/load {session, cookies} or {session, store} {ok, imported[, origin, saved_at]} — restore a logged-in state from JSON (the Playwright storageState equivalent) or from the keychain.
GET /states — stored states — names, origins, timestamps; never the jars.
POST /states/delete {name} {ok}

Cross-session isolation is CI-enforced on every engine: a cookie imported into session A must not appear in session B's exported jar, or the build fails. See Sessions and State and Security.

Keychain logins

Route Body Returns
POST /creds/set {site, username, password, session} {site, origin} — HUMAN step (the CLI prompts on hidden stdin: navette creds set SITE). The credential binds to the exact origin of the session's current page.
GET /creds — sites + origins + usernames — never passwords.
POST /creds/delete {site} {ok}
POST /login {site, session?, submit?} fill + submit the login form from the keychain. The password never appears in the request, the response, or a log — it is resolved inside the daemon and injected into the page. 403 unless the session is on the credential's origin — a hostile page or a prompt-injected agent can neither harvest the secret nor aim it elsewhere.

There is no password read-back API at all. The one honest limit: at fill time the password does reach the page — the same as human typing; the origin-binding is the defense.

Behavior notes and gotchas

  • JS dialogs (alert/confirm/prompt) are auto-handled in-page: alert logs and no-ops, confirm accepts, prompt returns its default — agents never deadlock on a hidden modal.
  • macOS bridge returns plist, not JSON: any /evaluate returning an object/array must be wrapped in JSON.stringify(...) — otherwise the result comes back as a plist string. This is the #1 recipe bug; every sites/ recipe marks it MANDATORY.
  • wry backends single-encode eval strings: /evaluate results return strings single-encoded through wry — macOS parity fix; don't double-decode.
  • A malformed JSON body makes /evaluate return {"ok":true,"result":""} silently — build payload files and validate them (python3 -m json.tool) before posting; on an empty result, re-run a trivial probe ({"js":"document.title"}) to separate a payload bug from page state.
  • Session-create failures are slow: if WebView/WebView2 session creation fails, callers wait out the command timeout (20–45 s) instead of getting a fast 500 (known residual, refactor noted in backend_wry.rs).
  • DNS-rebinding guard: non-local Host headers get 403 on everything except /health (--allow-host NAME extends the accepted list for containers/tunnels).
  • Linux WebKitGTK: /evaluate scripts that write document.cookie time out (reads work) — use /sessions/load for cookie writes (issue #7).

Clone this wiki locally