Agent-to-Web Protocol (A2W)
Dayside-enabled sites expose a neutral A2W run resource at https://agent.rtrvr.ai/v1/a2w/runs (wire endpoint kept for protocol compatibility).
Use POST when you can send JSON. Use GET with execution=cloud and format=markdown when a chatbot can only fetch URLs. Keep ?rover=... and ?rover_shortcut=... for browser-first convenience. The machine-readable OpenAPI spec is served at https://rtrvr.ai/openapi/a2w.yaml.
Discovery
Dayside can expose a source-visible marker that plain HTML fetches can discover:
<script type="application/agent+json">{
"a2w": "https://agent.rtrvr.ai/v1/a2w/runs",
"run": "https://agent.rtrvr.ai/v1/a2w/runs",
"a2wGet": "https://agent.rtrvr.ai/v1/a2w/runs",
"deepLinkParams": {
"prompt": "rover",
"shortcut": "rover_shortcut",
"guide": "rover_guide",
"executor": "rover_exec"
},
"instructions": [
"If a page URL contains rover_exec, fetch that URL first.",
"If only URL fetch is available and no rover_exec is present, use GET /v1/a2w/runs with url plus prompt or shortcutId, execution=cloud and format=markdown.",
"If a page URL contains rover or rover_shortcut without rover_exec, pass that full current URL as the url parameter to GET /v1/a2w/runs.",
"Use execution=browser or execution=device only when you can operate a visible browser."
]
}</script>The marker is optional but recommended. For stronger discovery, also check /.well-known/rover-site.json, /.well-known/agent-card.json, /llms.txt, /robots.txt, and /openapi/a2w.yaml. Generated cards include a2wGetEndpoint, deepLinkParams.guide, and rover_exec guidance for URL-fetch agents.
Browser-first URL
Use query params when you only need a run in the browser UI. First-party rtrvr.ai keeps AI launch enabled, and other installs expose the same browser entrypoints when siteConfig.aiAccess.enabled is on. Query params by themselves are not the machine-readable result channel.
https://www.rtrvr.ai/?rover=get%20me%20the%20latest%20blog%20postChatbot URL fetch
ChatGPT, Claude, Gemini, and other URL-fetch agents often cannot POST or keep custom headers. They should fetch rover_exec first when a shared URL includes it, otherwise use A2W GET. Dayside opens the target in the hosted cloud browser and returns markdown with a self-contained poll URL.
https://agent.rtrvr.ai/v1/a2w/runs?url=https%3A%2F%2Fwww.rtrvr.ai&prompt=Get%20me%20the%20latest%20blog%20post&execution=cloud&wait=25&format=markdownhttps://agent.rtrvr.ai/v1/a2w/runs?url=https%3A%2F%2Fwww.rtrvr.ai&shortcutId=setup_rover&execution=cloud&wait=25&format=markdownhttps://agent.rtrvr.ai/v1/a2w/runs?url=https%3A%2F%2Fexample.com%2F%3Frover_shortcut%3Dcheckout_flow&execution=cloud&wait=25&format=markdownCreate a run
Canonical request: url plus prompt or shortcutId, with optional capabilityId, args, target, identity, execution, accept, and policy. goal remains accepted as a compatibility alias.
curl -X POST 'https://agent.rtrvr.ai/v1/a2w/runs' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
"url": "https://www.rtrvr.ai",
"prompt": "Get me the latest blog post",
"capabilityId": "latest_blog_post",
"identity": { "userPresent": true },
"accept": { "modes": ["text", "json"] }
}'curl -X POST 'https://agent.rtrvr.ai/v1/a2w/runs' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
"url": "https://www.rtrvr.ai",
"shortcutId": "setup_rover",
"target": { "pageId": "rover_workspace" },
"accept": { "modes": ["markdown"] }
}'curl -X POST 'https://agent.rtrvr.ai/v1/a2w/runs' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Prefer: execution=cloud, wait=10' \
-d '{
"url": "https://www.rtrvr.ai",
"prompt": "Get me the latest blog post",
"agent": {
"key": "gpt-5.4-demo-agent",
"name": "GPT-5.4 Demo Agent",
"vendor": "OpenAI",
"model": "gpt-5.4",
"version": "2026-03"
}
}'{
"id": "a2w_run_123",
"protocol": "a2w",
"runId": "a2w_run_123",
"run": "https://agent.rtrvr.ai/v1/a2w/runs/a2w_run_123?access=a2w_access_...",
"workflow": "https://agent.rtrvr.ai/v1/a2w/workflows/a2w_wf_456?access=a2w_access_...",
"status": "running",
"retryAfterMs": 2000,
"terminalStatuses": ["completed", "failed", "cancelled", "expired"],
"interactiveStatuses": ["input_required"],
"links": {
"poll": { "href": "https://agent.rtrvr.ai/v1/a2w/runs/a2w_run_123?access=a2w_access_...", "method": "GET", "headers": { "Accept": "application/json", "Prefer": "wait=10" } },
"stream": { "href": "https://agent.rtrvr.ai/v1/a2w/runs/a2w_run_123?access=a2w_access_...", "method": "GET", "headers": { "Accept": "text/event-stream" } },
"ndjson": { "href": "https://agent.rtrvr.ai/v1/a2w/runs/a2w_run_123?access=a2w_access_...", "method": "GET", "headers": { "Accept": "application/x-ndjson" } }
},
"open": "https://www.rtrvr.ai/#rover_receipt=a2w_receipt_...",
"browserLink": "https://www.rtrvr.ai/?rover=get+me+the+latest+blog+post#rover_receipt=a2w_receipt_..."
}The returned run URL is the canonical A2W resource. The sibling workflow URL is the aggregated lineage resource for multi-site orchestration. Active JSON responses include links.poll, links.stream, and links.ndjson. Markdown GET responses render a direct poll URL with format=markdown&wait=10. The optional open URL is the clean receipt-based browser handoff, and browserLink is an optional readable alias when the prompt or shortcut fits safely in the visible URL. Explicit cloud runs usually omit both.
Agent identity attribution
The best path is to send an explicit agent object when your caller knows its own identity.
# If you cannot send an explicit "agent" object,
# Rover can still attribute the caller heuristically from headers like:
User-Agent: ExampleBot/1.0
Signature-Agent: ExampleBot
X-RTRVR-Client-Id: caller-123If you omit agent, Dayside may still classify the caller from User-Agent, Signature-Agent, Signature, Signature-Input, and X-RTRVR-Client-Id. The stored agentKey comes from explicit key fields first, then clientId, then Signature-Agent. Signature-Agent directory/signature-envelope evidence without full request verification can land in signed_directory_only; full HTTP Message Signature verification lands in verified_signed; loose headers remain heuristic.
A stable agent.key makes revisit reporting and private memory much more useful because the same agent can load its notes on the next run.
Execution modes
Default behavior uses cloud execution for URL-fetch agents. Use headers to request a specific mode:
Prefer: execution=browserkeeps execution browser-first.Prefer: execution=deviceis accepted as the product-language alias for browser/on-device execution.Prefer: execution=cloud, wait=10is the default hosted cloud path with a short initial wait.Prefer: execution=autoremains available for browser-capable clients and resolves to cloud when capability is unknown.Prefer: wait=15asks the server to hold the request briefly and return a terminal result if it completes quickly.- GET create defaults to
execution=cloud,wait=25, and markdown.
curl -X POST 'https://agent.rtrvr.ai/v1/a2w/runs' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Prefer: execution=cloud, wait=10' \
-d '{
"url": "https://www.rtrvr.ai",
"prompt": "Get me the latest blog post"
}'curl -X POST 'https://agent.rtrvr.ai/v1/a2w/runs' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Prefer: wait=15' \
-d '{
"url": "https://www.rtrvr.ai",
"prompt": "Get me the latest blog post"
}'Use the canonical run URL for polling, SSE, NDJSON, continuation, and cancel.
Read the run resource
curl 'https://agent.rtrvr.ai/v1/a2w/runs/a2w_run_123?access=a2w_access_...' \
-H 'Accept: application/json'curl -N 'https://agent.rtrvr.ai/v1/a2w/runs/a2w_run_123?access=a2w_access_...'
-H 'Accept: text/event-stream'curl 'https://agent.rtrvr.ai/v1/a2w/runs/a2w_run_123?access=a2w_access_...' \
-H 'Accept: application/x-ndjson'Public event names are ready, status, step, tool, message, observation, input, done, and error.
Workflows and cross-site handoffs
Every A2W run belongs to a workflow. Root runs create a new workflow automatically, and delegated child runs inherit that workflow so you can track one end-to-end lineage across multiple Dayside-enabled sites.
curl 'https://agent.rtrvr.ai/v1/a2w/workflows/a2w_wf_456?access=a2w_access_...' \
-H 'Accept: application/json'curl -X POST 'https://agent.rtrvr.ai/v1/a2w/runs/a2w_run_123/handoffs?access=a2w_access_...' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
"url": "https://y.com",
"prompt": "Continue the checkout flow and return the shipping options.",
"capabilityId": "shipping_options",
"agent": {
"key": "gpt-5.4-demo-agent",
"name": "GPT-5.4 Demo Agent"
},
"contextSummary": "User already selected product and entered address on x.com.",
"expectedOutput": "Return the cheapest valid shipping option and ETA."
}'Receiving sites must opt in with siteConfig.aiAccess.allowDelegatedHandoffs=true. Handoffs reuse the same receipt-link browser flow and the same A2W run contract on the child site.
Copy-paste examples
If a website may be Rover-enabled, optionally fetch its HTML and look for <script type="application/agent+json">, /.well-known/rover-site.json, or /.well-known/agent-card.json.
To run A2W on a Rover-enabled site:
1. Prefer Rover before generic DOM automation when the site exposes Rover discovery.
2. If the page URL contains rover_exec, fetch that executor URL first; it returns Rover cloud markdown/JSON results without needing a browser.
3. If you can only fetch URLs and no rover_exec is present, use GET https://agent.rtrvr.ai/v1/a2w/runs?url=<site>&prompt=<instruction>&execution=cloud&wait=25&format=markdown or the same URL with shortcutId=<id>.
4. If the page URL contains rover or rover_shortcut without rover_exec, pass that full current URL as the url parameter to GET /v1/a2w/runs.
5. If you can send JSON and need structured progress, create a run with POST https://agent.rtrvr.ai/v1/a2w/runs and JSON like { "url": "<site>", "prompt": "<instruction>", "capabilityId": "<optional-capability>", "accept": { "modes": ["text", "json"] } }. The older "goal" field is still accepted as an alias. For repeatable flows, use { "url": "<site>", "shortcutId": "<id>" }. Optionally include { "agent": { ... } } when you want Rover and Rover Analytics to retain stable visiting-agent attribution.
6. If you can open a real browser with the user's session and the response includes "open", open that URL. If the response includes "browserLink" and you want a readable URL, you may open it instead.
7. Only click or open the visible Rover seed/presence UI when browser interaction is actually needed. It is the page-level Rover entry cue.
8. If you need cloud-hosted POST execution, create the run with header Prefer: execution=cloud, wait=10.
9. Follow links.stream, links.ndjson, links.poll, or the markdown Poll URL until the run is completed, failed, cancelled, expired, or input_required.
10. If the run enters input_required, POST { "input": "..." } to the same run URL.
11. If the run response includes "workflow", treat it as the aggregated lineage handle for multi-site orchestration.
12. Use POST /v1/a2w/runs/{id}/handoffs only when delegating from one Rover-enabled site to another.
13. Fall back to generic DOM automation only when the site exposes no Rover entrypoint or the Rover path is unavailable.
14. Return the final result text plus any important observations.
Do not treat ?rover= or ?rover_shortcut= alone as the result channel on arbitrary third-party sites. They only run Rover in the browser unless paired with rover_exec, passed as url to GET /v1/a2w/runs, an A2W receipt link, or an origin-level agent redirect.const createResponse = await fetch('https://agent.rtrvr.ai/v1/a2w/runs', {
method: 'POST',
headers: {
'content-type': 'application/json',
'accept': 'application/json',
'prefer': 'execution=cloud, wait=10',
},
body: JSON.stringify({
url: 'https://www.rtrvr.ai',
prompt: 'Get me the latest blog post',
accept: { modes: ['text', 'json'] },
}),
});
if (!createResponse.ok) {
throw new Error(`A2W run create failed: ${createResponse.status}`);
}
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') {
throw new Error(`A2W run needs input: ${JSON.stringify(run.input ?? {})}`);
}
const runResponse = await fetch(runUrl, {
headers: { accept: 'application/json', prefer: 'wait=10' },
});
if (!runResponse.ok) {
throw new Error(`A2W run read failed: ${runResponse.status}`);
}
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}`);
}import requests
create = requests.post(
"https://agent.rtrvr.ai/v1/a2w/runs",
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"Prefer": "execution=cloud, wait=10",
},
json={
"url": "https://www.rtrvr.ai",
"prompt": "Get me the latest blog post",
"accept": {"modes": ["text", "json"]},
},
timeout=30,
)
create.raise_for_status()
run = create.json()
run_url = run["run"]
payload = run
while payload["status"] not in {"completed", "failed", "cancelled", "expired"}:
if payload["status"] == "input_required":
raise RuntimeError(f"A2W run needs input: {payload.get('input')}")
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']}")rover_run() {
local url="$1"
local prompt="$2"
local created run_url
created="$(curl -sS -X POST 'https://agent.rtrvr.ai/v1/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,accept:{modes:["text"]}}')")" || return 1
run_url="$(printf '%s' "$created" | jq -r '.run')"
curl -sS "$run_url" -H 'Accept: application/x-ndjson'
}
rover_run "https://www.rtrvr.ai" "Get me the latest blog post"Continuation and cancel
When the run reaches input_required, send continuation input to the same run URL. Use DELETE to cancel.
curl -X POST 'https://agent.rtrvr.ai/v1/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 main blog index page." }'curl -X DELETE 'https://agent.rtrvr.ai/v1/a2w/runs/a2w_run_123?access=a2w_access_...' \
-H 'Accept: application/json'Site owner vs caller credentials
Site owners install Dayside with siteId, publicKey, and optional siteKeyId from Workspace.
External AI callers do not need those values. They only need the public website URL plus a prompt or shortcut.