Examples
Common integration patterns for onboarding, checkout, forms, and more.
Start with the generated production tag. The inline rover('boot') examples below are advanced overrides for sites that deliberately manage part of the runtime configuration in code. In those examples the loader has no data-* boot attributes, so Rover boots exactly once.
Rover v2 task boundaries are strict: normal sends always start a fresh task boundary. Only ask_user answers continue the current boundary.
Canonical Setup
Default production install. Runtime policy, Activity, discovery, and billing config come from the Rover site record.
<script
src="https://rover.rtrvr.ai/embed.js?v=your_site_key_id"
async
data-site-id="my-site"
data-public-key="pk_site_your_public_key"
data-site-key-id="your_site_key_id"></script>Journeys and async identity
Blend workspace journeys with runtime overrides and personalize greeting after login.
<script>
(function(){ var r = window.rover = window.rover || function(){ (r.q = r.q || []).push(arguments); }; r.l = +new Date(); })();
rover('boot', {
siteId: 'my-app',
publicKey: 'pk_site_your_public_key',
siteKeyId: 'your-site-key-id',
allowedDomains: ['app.example.com'],
// Workspace journeys are fetched by default via /v2/rover/session/open.
// Optional override in boot config:
ui: {
shortcuts: [
{ id: 'onboard', label: 'Onboard me', prompt: 'Guide me through onboarding', routing: 'planner', enabled: true }
],
greeting: { text: 'Hey {name}! Need setup help?', delay: 2000, duration: 8000 }
},
});
// Later, when auth completes:
rover('identify', { name: 'Alex', email: 'alex@example.com' });
</script>
<script
src="https://rover.rtrvr.ai/embed.js?v=your_site_key_id"
async></script>Voice dictation
Enable browser dictation so visitors can speak requests, edit the transcript, and manually send.
<script>
(function(){ var r = window.rover = window.rover || function(){ (r.q = r.q || []).push(arguments); }; r.l = +new Date(); })();
rover('boot', {
siteId: 'my-app',
publicKey: 'pk_site_your_public_key',
allowedDomains: ['app.example.com'],
ui: {
voice: {
enabled: true,
language: 'en-US',
autoStopMs: 2600
}
}
});
</script>
<script
src="https://rover.rtrvr.ai/embed.js?v=your_site_key_id"
async></script>Steering an in-flight task
While a task is running, the visitor's composer routes mid-run guidance through sendFeedback instead of starting a new task. Type or dictate guidance into the composer (placeholder switches to 'Guide Rover…'), or trigger steering programmatically from your own button.
<script>
(function(){ var r = window.rover = window.rover || function(){ (r.q = r.q || []).push(arguments); }; r.l = +new Date(); })();
rover('boot', {
siteId: 'my-app',
publicKey: 'pk_site_your_public_key',
allowedDomains: ['app.example.com'],
});
// Custom Steer button — only meaningful while a task is in flight.
document.querySelector('#steer-button')?.addEventListener('click', () => {
const ack = rover.sendFeedback('Actually use the search bar, not the menu');
if (ack === null) {
// No run in flight — sendFeedback is a no-op outside a run.
console.log('No active task to steer.');
}
});
</script>
<script
src="https://rover.rtrvr.ai/embed.js?v=your_site_key_id"
async></script>Onboarding flow
Auto-routing setup with planner fallback only when ACT has no usable outcome.
<script>
(function(){ var r = window.rover = window.rover || function(){ (r.q = r.q || []).push(arguments); }; r.l = +new Date(); })();
const isNewUser = !localStorage.getItem('onboarded');
rover('boot', {
siteId: 'my-app',
publicKey: 'pk_site_your_public_key',
allowedDomains: ['app.example.com'],
openOnInit: isNewUser,
taskRouting: {
mode: 'auto',
plannerOnActError: true, // Planner fallback runs only if ACT has no usable result
actHeuristicThreshold: 5
},
checkpointing: { enabled: true, autoVisitorId: true },
});
</script>
<script
src="https://rover.rtrvr.ai/embed.js?v=your_site_key_id"
async></script>Checkout assistant
Help visitors fill addresses, apply promos, and reach checkout. Payment and other consequential steps stop for explicit human confirmation.
<script>
(function(){ var r = window.rover = window.rover || function(){ (r.q = r.q || []).push(arguments); }; r.l = +new Date(); })();
rover('boot', {
siteId: 'my-store',
publicKey: 'pk_site_your_public_key',
allowedDomains: ['shop.example.com', 'checkout.example.com'],
domainScopeMode: 'registrable_domain',
allowActions: true,
taskRouting: { mode: 'auto', plannerOnActError: true },
});
</script>
<script
src="https://rover.rtrvr.ai/embed.js?v=your_site_key_id"
async></script>Form assistance
Guide users through complex multi-step forms with validation and auto-fill.
<script>
(function(){ var r = window.rover = window.rover || function(){ (r.q = r.q || []).push(arguments); }; r.l = +new Date(); })();
rover('boot', {
siteId: 'my-portal',
publicKey: 'pk_site_your_public_key',
allowedDomains: ['portal.example.com'],
sessionScope: 'tab',
taskRouting: { mode: 'planner' },
checkpointing: { enabled: true, autoVisitorId: true },
});
</script>
<script
src="https://rover.rtrvr.ai/embed.js?v=your_site_key_id"
async></script>Read-only support
Rover helps users find information and navigate, but doesn't take actions.
<script>
(function(){ var r = window.rover = window.rover || function(){ (r.q = r.q || []).push(arguments); }; r.l = +new Date(); })();
rover('boot', {
siteId: 'my-help',
publicKey: 'pk_site_your_public_key',
allowedDomains: ['help.example.com'],
allowActions: false,
});
</script>
<script
src="https://rover.rtrvr.ai/embed.js?v=your_site_key_id"
async></script>Strict host scope
Host-only install example for tightly controlled sites. Navigation behavior stays managed by Rover runtime.
<script>
(function(){ var r = window.rover = window.rover || function(){ (r.q = r.q || []).push(arguments); }; r.l = +new Date(); })();
rover('boot', {
siteId: 'secure-app',
publicKey: 'pk_site_your_public_key',
allowedDomains: ['app.example.com'],
domainScopeMode: 'host_only',
sessionScope: 'tab',
});
</script>
<script
src="https://rover.rtrvr.ai/embed.js?v=your_site_key_id"
async></script>A2W run examples
Use strict A2W POST when you can send JSON. Give URL-fetch chatbots a compact A2W GET link. Keep ?rover=..., ?rover_shortcut=..., and ?rover_playbook=... as visitor browser links.
Browser-first query param
Fastest way to make Rover run in the page UI on first-party rtrvr.ai. Other Rover installs expose ?rover= when AI launch is enabled for the site.
https://rtrvr.ai/?rover=get%20me%20the%20latest%20blog%20postCreate an A2W run
Machine path for any AI, CLI, or agent that needs progress and a final result back.
curl -X POST 'https://agent.rtrvr.ai/v2/a2w/runs' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
"url": "https://rtrvr.ai",
"prompt": "get me the latest blog post"
}'Cloud-hosted execution
Use cloud execution explicitly when the caller cannot open a real browser. The create call can return 202 before the run is finished; follow the returned links until terminal or input_required.
curl -X POST 'https://agent.rtrvr.ai/v2/a2w/runs' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Prefer: execution=cloud, wait=10' \
-d '{
"url": "https://rtrvr.ai",
"prompt": "get me the latest blog post"
}'Chatbot GET execution
Use this when the caller can only fetch a URL. Rover opens the target in the hosted cloud browser and returns markdown with a self-contained poll link.
https://agent.rtrvr.ai/v2/a2w/runs?url=https%3A%2F%2Frtrvr.ai&prompt=get%20me%20the%20latest%20blog%20post&execution=cloud&wait=25&format=markdownCreate a run from a Rover deep link
If a discovered URL already contains rover or rover_shortcut, pass that full URL as url to GET /v2/a2w/runs.
https://agent.rtrvr.ai/v2/a2w/runs?url=https%3A%2F%2Fexample.com%2F%3Frover_shortcut%3Dcheckout_flow&execution=cloud&wait=25&format=markdownStream or continue the run
Use the canonical run URL for SSE, NDJSON, polling, continuation, and cancel.
# SSE
curl -N 'https://agent.rtrvr.ai/v2/a2w/runs/a2w_run_123?access=a2w_access_...' \
-H 'Accept: text/event-stream'
# Continue input_required
curl -X POST 'https://agent.rtrvr.ai/v2/a2w/runs/a2w_run_123?access=a2w_access_...' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{ "input": "Use the newest post from the blog index page." }'
# Cancel
curl -X DELETE 'https://agent.rtrvr.ai/v2/a2w/runs/a2w_run_123?access=a2w_access_...' \
-H 'Accept: application/json'Cross-site handoff
Delegate a child task to another Rover-enabled site and keep one shared workflow lineage.
# Create the root run first
curl -X POST 'https://agent.rtrvr.ai/v2/a2w/runs' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
"url": "https://x.com",
"prompt": "Start checkout and hand off shipping selection when needed"
}'
# Later, delegate from the parent run
curl -X POST 'https://agent.rtrvr.ai/v2/a2w/runs/a2w_run_parent/handoffs?access=a2w_access_parent' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
"url": "https://y.com",
"prompt": "Continue the checkout flow and return the shipping options.",
"contextSummary": "User already selected product and entered address on x.com.",
"expectedOutput": "Return the cheapest valid shipping option and ETA."
}'
# Read the shared workflow
curl 'https://agent.rtrvr.ai/v2/a2w/workflows/a2w_wf_456?access=a2w_access_...' \
-H 'Accept: application/json'Node fetch
Self-contained hosted cloud example for agents running in Node 18+. On input_required it relays confirm/verify links to the human, posts sign-in links to auth-input, and answers ordinary questions — instead of giving up.
// askHuman(prompt) is your own human-in-the-loop prompt.
const createResponse = await fetch('https://agent.rtrvr.ai/v2/a2w/runs', {
method: 'POST',
headers: {
'content-type': 'application/json',
'accept': 'application/json',
'prefer': 'execution=cloud, wait=10',
},
body: JSON.stringify({
url: 'https://rtrvr.ai',
prompt: 'get me the latest blog post',
}),
});
const created = await createResponse.json();
const runUrl = created.run;
let run = created;
while (!['completed', 'failed', 'cancelled', 'expired'].includes(run.status)) {
if (run.status === 'input_required') {
const links = run.input?.links ?? {};
if (links.confirm || links.verify) {
// Payment/verification/sensitive-action gates: relay the link and poll.
console.log(`Ask the human to open: ${links.confirm ?? links.verify}`);
} else if (run.input?.reason === 'sign_in_link_required') {
// Human pastes the magic link / OTP (secret: one-shot, never echoed).
const value = await askHuman(run.authInput?.prompt ?? 'Paste the sign-in link or code:');
const authInputUrl = new URL(runUrl);
authInputUrl.pathname += '/auth-input';
await fetch(authInputUrl, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ value }),
});
} else {
// Ordinary question — or { answers: { key: value } } for multi-question parks.
const answer = await askHuman(run.input?.message ?? 'Rover needs input:');
await fetch(runUrl, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ input: answer }),
});
}
}
const runResponse = await fetch(runUrl, {
headers: { accept: 'application/json', prefer: 'wait=10' },
});
run = await runResponse.json();
console.log(run.status, run.result?.text ?? '');
}
if (run.status !== 'completed') {
throw new Error(run.result?.error || `A2W run ended with ${run.status}`);
}Python
Simple requests-based cloud execution loop that long-polls the returned run URL and handles input_required with the relay-or-continue pattern.
import requests
create = requests.post(
"https://agent.rtrvr.ai/v2/a2w/runs",
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"Prefer": "execution=cloud, wait=10",
},
json={
"url": "https://rtrvr.ai",
"prompt": "get me the latest blog post",
},
)
create.raise_for_status()
created = create.json()
run_url = created["run"]
payload = created
while payload["status"] not in {"completed", "failed", "cancelled", "expired"}:
if payload["status"] == "input_required":
req = payload.get("input") or {}
links = req.get("links") or {}
if links.get("confirm") or links.get("verify"):
# Payment/verification/sensitive-action gates: relay the link and poll.
print("Ask the human to open:", links.get("confirm") or links.get("verify"))
elif req.get("reason") == "sign_in_link_required":
# Human pastes the magic link / OTP (secret: one-shot, never echoed).
value = input((payload.get("authInput") or {}).get("prompt") or "Paste the sign-in link or code: ")
base, _, query = run_url.partition("?")
requests.post(f"{base}/auth-input?{query}", json={"value": value}, timeout=30)
else:
# Ordinary question — or {"answers": {key: value}} for multi-question parks.
answer = input(req.get("message") or "Rover needs input: ")
requests.post(run_url, json={"input": answer}, timeout=30)
current = requests.get(
run_url,
headers={"Accept": "application/json", "Prefer": "wait=10"},
timeout=30,
)
current.raise_for_status()
payload = current.json()
print(payload["status"], payload.get("result", {}).get("text", ""))
if payload["status"] != "completed":
raise RuntimeError(payload.get("result", {}).get("error") or f"A2W run ended with {payload['status']}")Shell helper
Create a cloud A2W run and stream NDJSON events. Requires jq.
rover_run() {
local url="$1"
local prompt="$2"
local created run_url
created="$(curl -sS -X POST 'https://agent.rtrvr.ai/v2/a2w/runs' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Prefer: execution=cloud, wait=10' \
-d "$(jq -nc --arg url "$url" --arg prompt "$prompt" '{url:$url,prompt:$prompt}')")" || return 1
run_url="$(printf '%s' "$created" | jq -r '.run')"
curl -sS "$run_url" -H 'Accept: application/x-ndjson'
}
rover_run "https://rtrvr.ai" "get me the latest blog post"Need another browser pattern? Check Configuration for the supported SDK surface. For server calls, use the API Reference.