Agent mode preview
M2M route available
This page has a stable machine-readable Markdown route for AI assistants and developer agents. The human-visible page remains the same source of truth.
API reference
Use tabs for routing and lifecycle, DOM for safe page interaction, events for reactivity, and errors when a flow needs diagnosis.
The helper pages show when you can avoid repeating raw payload shapes in your own code.
Overview
RTO uses window.postMessage to communicate between your page and the extension content script.
The extension proxies actions to the background (tabs APIs live there), then responds back to the page.
You can use the raw protocol directly, but we strongly recommend the helper:
RTO_form_api.js.
Events
Events are posted from the extension to the page. They are one-way notifications and do not carry a requestId.
| Event | Direction | Notes |
|---|---|---|
tabStatus |
extension → page |
Status stream for controlled tabs (activated, URL changes, loading/complete, removed, etc.).
Subscribe using RTOForm.onStatus(fn) or listen to window.message directly.
|
<script>
window.addEventListener("message", function (ev) {
var d = ev && ev.data;
if (!d || typeof d !== "object") return;
// Extension-originated status stream:
// { "origin":"rto-extension", "type":"tabStatus", "tabKey":"demo", "tabId":123, "event":"loading|complete|url|activated|removed|...", "url":"https://..." }
if (d.origin === "rto-extension" && d.type === "tabStatus") {
console.log("tabStatus:", d);
}
}, false);
</script>
External add-on API — introduced in v8.4.0, current in v8.5.0
RTO v8.4.0 introduced a secure inter-extension API for trusted RTO add-ons. It remains current in RTO v8.5.0. This API is not a public website API and does not bypass RTO Lock, the allow-list or capability checks.
detectandgetCapabilitiesallowlistCheck,allowlistListandallowlistAddRequestopenTab,navigate,focusTab,closeTab,listTabsandadoptTabdom.waitFor,dom.click,dom.setValue,dom.extractanddom.highlightflow.runandflow.cancel
Actions
Actions are initiated by the page and answered by the extension with a matching requestId.
Each action has a type and a corresponding typeResult reply.
| Action type | Reply type | Description |
|---|---|---|
detect |
detectResult |
Quick check that the extension is present and listening. |
openTab |
openTabResult |
Open/control a tab using a stable tabKey. Since 8.5.0, calls are serialized per key to prevent duplicate tabs. |
adoptTab |
adoptTabResult |
Attach a known tabId to a tabKey (advanced). |
navigate |
navigateResult |
Navigate an existing controlled tab. |
focusTab |
focusTabResult |
Bring the controlled tab to front. |
closeTab |
closeTabResult |
Close a controlled tab. |
listTabs |
listTabsResult |
List known controlled tabs (debug/UI). |
allowlistAddRequest |
allowlistAddRequestResult |
Request a host addition from a page; confirmation happens on the matching master tab. |
registerMasterTab |
registerMasterTabResult |
Register the current page tab as master tab for later focus return and visible plugin-side confirmations. |
focusMasterTab |
focusMasterTabResult |
Ask extension background to focus the registered master tab. |
command |
<action>Result (preferred) or commandResult |
Send predefined commands (DOM focus/style/actions, waits, etc.). |
command, the helper expects <action>Result (e.g. waitForSelectorResult),
but will also accept commandResult.
tabKey per workflow.
Since 8.5.0, openTab resolves inside a per-key queue, so a second near-simultaneous call reuses the same tab instead of racing into a duplicate.
Helpers
The documentation provides helper wrappers to normalize errors, timeouts, and expected reply types.
The primary helper is RTO_form_api.js.
| Helper method | Signature | What it does |
|---|---|---|
RTOForm.detect(timeoutMs:number=1500) |
() |
Detect the extension quickly. |
RTOForm.openTab(args, opts) |
(args:{ tabKey,url,focus?,newTab?,reuse? }, opts:{ timeoutMs }) |
Open/control a tab by tabKey. Default focus is true; newTab is opt-in; reuse:false asks the extension not to reuse the current controlled tab for that call. |
RTOForm.adoptTab(args, opts) |
(args:{ tabKey,tabId }, opts:{ timeoutMs }) |
Attach a known tabId to a tabKey (advanced). |
RTOForm.navigate(args, opts) |
(args:{ url,tabKey?,tabId?,focus? }, opts:{ timeoutMs }) |
Navigate an existing controlled tab. |
RTOForm.focusTab(args, opts) |
(args:{ tabKey?,tabId? }, opts:{ timeoutMs }) |
Focus a controlled tab. |
RTOForm.closeTab(args, opts) |
(args:{ tabKey?,tabId? }, opts:{ timeoutMs }) |
Close a controlled tab. |
RTOForm.listTabs(args, opts) |
(args:{}, opts:{ timeoutMs }) |
List controlled tabs. Typical helper usage: RTOForm.listTabs({}, { "timeoutMs": 8000 }). |
RTOForm.send("allowlistAddRequest", payload, opts) |
(payload:{ host }, opts:{ timeoutMs }) |
Low-level way to request a host addition from a page. Prefer RTO_domainList.js for a friendlier wrapper. |
RTOForm.registerMasterTab(args, opts) |
(args:{ masterKey? }, opts:{ timeoutMs }) |
Register current UI page tab as master tab. |
RTOForm.focusMasterTab(args, opts) |
(args:{ tabId?,masterKey? }, opts:{ timeoutMs }) |
Focus previously registered master tab from plugin background. |
RTOForm.command(msg, opts) |
(msg:{ action,... }, opts:{ tabKey?,tabId?,timeoutMs }) |
Send a command action. |
RTOForm.send(type, payload, opts) |
(type:string, payload:object, opts:object) |
Low-level generic sender. |
RTOForm.bestEffort(promise, opts) |
(promise, opts:{ ignoreCodes? }) |
Ignore selected error codes for non-critical steps. |
RTOForm.onStatus(handler) |
handler(event) |
Subscribe to tabStatus events from the extension (status stream). |
RTOForm.offStatus(handler) |
handler(event) |
Unsubscribe a previously registered status handler. |
RTOForm.waitForStatus(filter, timeoutMs:number=15000) |
filter(event) |
Resolve with the next tabStatus event matching filter. |
Public response envelope: ok / data / error
Website-facing bridge responses use this shape:
{
"origin": "rto-extension",
"type": "detectResult",
"requestId": "...",
"ok": true,
"data": {}
}
Error responses use:
{
"origin": "rto-extension",
"type": "openTabResult",
"requestId": "...",
"ok": false,
"error": {
"code": "DOMAIN_NOT_ALLOWED",
"message": "..."
}
}
Do not document or generate success / result as the canonical bridge response shape.
Errors
Errors are normalized by the helper to a predictable object with code and message.
| Code | Meaning | Typical fix |
|---|---|---|
TIMEOUT |
No reply within the configured timeout. | Increase timeout, confirm the tab is allowed and responsive, and use longer waits for approval-driven allow-list flows. |
POSTMESSAGE_FAILED |
Failed to postMessage from the page. | Check browser restrictions and frame contexts. |
ACTION_FAILED |
Generic failure returned by the extension. | Inspect raw field (if present) and extension console logs. |
EXT_NOT_DETECTED |
Detect did not receive a response. | Ensure extension is installed/enabled and site is allow-listed. |
DOMAIN_NOT_ALLOWED |
Target host is not allow-listed in the extension. | Request the host through the allow-list flow, approve it on the master tab, then retry. |
detect timeout |
Detect did not complete. | First verify the outgoing message includes origin: "rto-page", includes a unique requestId, and the response listener filters origin === "rto-extension". Only then check allowlist / extension state. |
Common advanced automation diagnostics
| Code / symptom | Meaning | First fix | Do not assume |
|---|---|---|---|
UNKNOWN_ACTION |
The message type or command shape is not accepted by the bridge/runtime. | Check whether the operation should be a top-level type such as dom.extract instead of type: "command" + action: "dom.extract". |
Do not assume this is a security block or Google protection. |
INVALID_TABID |
The call targeted an invalid or missing tabId, or used a targeting form not accepted by that operation. |
Check whether the operation requires tabId instead of tabKey. Use listTabs to get the current tabId when needed. |
Do not invent “tab not ready”, “zombie tab”, or “adoption security” unless documented by the response. |
INVALID_STEPS |
The flow.run step payload is malformed. |
Use top-level steps, and each step must use type plus args. |
Do not assume the flow engine is blocked by allowlist until the step schema is valid. |
fields: {} |
The extraction command ran but found no fields, or the field schema/selectors did not match the current DOM. | Check the exact payload shape, field names, selectors, current page URL/title, and whether dom.waitFor succeeded first. |
Do not claim cookies, anti-bot, AI-safe, or allowlist as the cause without a supporting error or page evidence. |
UNINJECTABLE_URL |
The extension could not inject into the current URL at that moment or for that URL class. | Wait for a normal web URL, check tabStatus, then retry a documented DOM primitive. |
Do not claim the whole extension failed. |
Notes
- Background constraint: tab events come from the extension background layer, not the content script. Firefox v8.5.0 uses a persistent background; Chrome uses MV3 service worker packaging.
- tabStatus stream: treat it as informational; use it to update UI and wait for readiness.
- Security: always validate allow-lists and never accept commands from untrusted origins.