rtrvr.ai
DocsChrome / Cloud / API / MCP
Search docs⌘ K

RUN

Send one job./agent

Give rtrvr a goal, starting pages, and the response shape. It plans the route, uses the browser and tools, and returns the result.

Agent API Playground

POST/agent

Planner + tools engine in API mode.

Try an example

Get from rtrvr.ai/cloud

Sent as recordingContext: the agent follows the recorded walkthrough as its guide. Record one in the extension, then find its id under Recordings. Sign in to pick from your library.

CLI

Run the same job from your terminal with rtrvr CLI.

rtrvr run "Extract products"
Watch the walkthrough video
Agent API Walkthrough
Base URLhttps://api.rtrvr.ai

Primary endpoints: /agent (planner + tools) and /scrape (raw page data).

Use your API key in the Authorization header:

Header
Authorization: Bearer rtrvr_your_api_key

The Bearer prefix is optional (Authorization: rtrvr_your_api_key works), and x-api-key: rtrvr_your_api_key is accepted too. In n8n, Make or Zapier Header Auth, set Name to Authorization and Value to your key, or Name to x-api-key.

Security: Always keep your key on the server side (e.g. backend, serverless). Never embed it in browser code or ship it to clients.
POSThttps://api.rtrvr.ai/agent

Send a single JSON payload describing what you want. The planner orchestrates browser tabs, tools, and in-memory sheets to get the job done.

cURL
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Summarize the main points of this page in 5 bullet points.",
    "urls": ["https://example.com/blog/ai-trends-2025"],
    "response": { "verbosity": "final" }
  }'

Internally, this maps to an execution trajectory. New requests get a new trajectoryId; continuations reuse it.

For low-level raw page data, see the Scrape API docs (/scrape).

Both endpoints share the same browser + proxy infra but are optimized for different jobs.

Dimension/agent/scrape
What it doesFull agent run: planner + tools + browser + optional Sheets/Docs/etc.Loads pages and returns extracted text + accessibility tree.
Typical latencyHigher – dominated by LLM calls and multi-step tools.Lower – usually just browser + proxy round-trips.
CreditsInfra credits + model/tool credits.Infra-only credits (browser + proxy); no model/tool usage.
Best forEnd-to-end automations, multi-step workflows, writing back to external systems.Feeding your own LLM/RAG stack, ad-hoc scraping, prefetching page data.
CapabilitiesPlanner, tools, Sheets workflows, Docs/PDF generation, ask_user, etc.Extracted text, accessibility tree, elementLinkRecord, usage metrics.

Start with an API key. For native Google operations, connect Google Drive in the same Cloud account. Attach files only when the task reads them.

01

Create an API key

Create it in Cloud and keep it on your server.

02

Connect Google Drive

Google tools use your API key owner’s saved connection and refresh it automatically.

  1. 01 Open Cloud → Sheets and connect Google Drive
  2. 02 Select existing files through the Google picker
  3. 03 Enable the Google tools your API request needs
03

Attach files when needed

Upload PDFs, images, or documents, then pass their Storage URLs in files.

Trajectory & Phase

A trajectory is a stable ID for a workflow. Use it to group related phases (e.g. discovery → enrichment → reporting) and continuations.

  • Omit trajectoryId to start fresh.
  • Reuse the same trajectoryId with continuePlanning = true and the returned history to continue.
  • phase (default 1) lets you structure long-running projects into multiple stages.

Planner + Tools

You don't call tools directly. Instead, you describe the task and optionally configure which enableAdditionalTools to allow. Support for tools.enableAdditionalTools in the public API will come soon.

Under the hood, the planner can call tools like act_on_tab, crawl_and_extract_from_tab, sheets_workflow, create_sheet_from_data, and more. Only a subset (Docs, Slides, PDFs, persistent Sheets, ask_user, etc.) is gated behind enableAdditionalTools to control cost and latency.

Tabular Inputs & In-Memory Sheets

Use dataInputs to attach CSV/TSV/JSON, text, markdown, or binary formats (XLSX/Parquet via URL or storage). The system:

  • Infers the format from extension or content type when omitted.
  • Parses header and row schema.
  • Creates an in-memory sheet (no Google Drive write) exposed to tools like sheets_workflow.

The files parameter lets you attach PDFs, images, and documents for the agent to analyze or use. This is different from dataInputswhich is specifically for tabular/structured data.

What the agent can do with files:
  • Read and analyze PDF documents
  • Process and describe images (screenshots, diagrams, photos)
  • Fill out PDF forms with extracted or provided data
  • Upload files to web forms during browser automation
  • Compare multiple documents and find differences

ApiExecuteRequestFile schema

files[].displayNamestringrequired

Human-readable filename shown to the agent (e.g., 'Q3-Report.pdf', 'screenshot.png')

files[].uristringrequired

File location. Accepts Firebase Storage URL, GCS URI (gs://bucket/path), or public HTTPS URL

files[].mimeTypestringrequired

MIME type (e.g., 'application/pdf', 'image/png', 'image/jpeg')

Supported file types

PDF(application/pdf)
PNG(image/png)
JPEG(image/jpeg)
GIF(image/gif)
WebP(image/webp)
Word(docx)
Text(text/plain)
Markdown(text/markdown)

Ways to provide file URIs

Recommended1. Firebase Storage URL

Upload files via Cloud → Files and copy the Storage URL, or programmatically via the upload_file MCP tool (base64, URL import, or signed upload URL for local files). This is the most reliable option.

https://firebasestorage.googleapis.com/v0/b/bucket/o/path%2Ffile.pdf?alt=media&token=...
2. GCS URI

If you have files in Google Cloud Storage, use the gs:// URI directly.

gs://your-bucket/path/to/file.pdf
3. Public HTTPS URL

Any publicly accessible URL. Must not require authentication.

https://example.com/documents/report.pdf

Example: Analyze a PDF report

PDF Analysis
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Extract the key financial metrics from this quarterly report and summarize them.",
    "files": [
      {
        "displayName": "Q3-2024-Report.pdf",
        "uri": "https://firebasestorage.googleapis.com/v0/b/bucket/o/reports%2Fq3.pdf?alt=media&token=abc",
        "mimeType": "application/pdf"
      }
    ],
    "schema": {
      "type": "object",
      "properties": {
        "revenue": { "type": "string" },
        "profit": { "type": "string" },
        "growth": { "type": "string" },
        "highlights": { "type": "array", "items": { "type": "string" } }
      }
    }
  }'

Example: Analyze an image

Image Analysis
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Describe this screenshot and identify any UI/UX issues.",
    "files": [
      {
        "displayName": "app-screenshot.png",
        "uri": "gs://my-bucket/screenshots/app-v2.png",
        "mimeType": "image/png"
      }
    ]
  }'

Example: Compare multiple documents

Multi-file comparison
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Compare these two contracts and list all the differences.",
    "files": [
      {
        "displayName": "contract-v1.pdf",
        "uri": "https://firebasestorage.googleapis.com/.../contract-v1.pdf?...",
        "mimeType": "application/pdf"
      },
      {
        "displayName": "contract-v2.pdf", 
        "uri": "https://firebasestorage.googleapis.com/.../contract-v2.pdf?...",
        "mimeType": "application/pdf"
      }
    ],
    "schema": {
      "type": "object",
      "properties": {
        "differences": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "section": { "type": "string" },
              "v1_text": { "type": "string" },
              "v2_text": { "type": "string" },
              "significance": { "type": "string" }
            }
          }
        }
      }
    }
  }'

Example: Upload file to web form

File upload during automation
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Go to the application page, upload my resume, and fill out the form with name: John Doe, email: john@example.com",
    "urls": ["https://company.com/careers/apply"],
    "files": [
      {
        "displayName": "resume.pdf",
        "uri": "https://firebasestorage.googleapis.com/.../resume.pdf?...",
        "mimeType": "application/pdf"
      }
    ]
  }'
Limits & notes:
  • Maximum file size: 128 MB per file
  • The LLM reads files up to 20 MB; longer documents may use more credits
  • Over 20 MB, the LLM sees only the file's name, type and size. The agent can still upload the file to a web form or pass it on to tools and later steps
  • Binary files are base64-encoded internally for LLM processing
  • Use dataInputs for tabular data (CSV, JSON, Excel) - it's more efficient
  • Local files: upload first via the upload_file MCP tool (base64, URL import, or signed upload URL) or Cloud → Files, then pass the returned storage URL as the uri

The full request shape is AgentApiRequest:

AgentApiRequest (conceptual)
interface AgentApiRequest {
  // ─────────────────────────────────────────
  // CORE PARAMETERS
  // ─────────────────────────────────────────
  
  /** Stable ID for a workflow. Omit to start new; reuse to continue. */
  trajectoryId?: string;
  
  /** Phase index within a trajectory (default: 1). Use ≥2 for multi-stage workflows. */
  phase?: number;

  /** The trajectoryId of an earlier run that ended unfinished: this new run starts from the progress it saved. */
  retryOf?: string;
  
  /** Main user instruction - REQUIRED */
  input: string;
  
  /** Optional Google OAuth override; otherwise Google work uses the API key owner's Cloud connection. */
  authToken?: string;

  /** URLs to open in browser tabs */
  urls?: string[];
  
  /** JSON Schema describing expected result shape */
  schema?: Schema;

  /**
   * Credit budget for this run: its per-task credit ceiling. Shorthand for
   * options.limits.creditCeiling.credits (wins when both are sent);
   * max_credits is accepted too. At the budget the run pauses with a
   * Continue / Stop question (a schedule stops instead).
   */
  maxCredits?: number;

  /**
   * Only the selected model, at the reasoning level asked for, for this run
   * (default: your Disable fallbacks setting). A provider serving the same
   * model may still answer; the run never switches to another model, a lower
   * reasoning level, or credits when your own key fails. A step the model
   * cannot finish ends the run with code MODEL_UNAVAILABLE (details in
   * modelUnavailable), status "error", or "partial" when it had already
   * produced output. disableFallbacks is accepted too, as are "true",
   * "false", 1, 0, "1" and "0". Never saved.
   */
  disable_fallbacks?: boolean;

  /**
   * Exact values for this run, by name: a signed file URL, the text to post,
   * a schedule time. Refer to them by name in input; the agent reads each one
   * as data and never copies it out of input or retypes it. Names: a letter
   * or _, then letters, digits or _ (max 64). Values: string, number or
   * boolean; at most 32 entries of 20,000 characters each. A JSON string of
   * the object is accepted too. Anything else is a 400 (INVALID_VARIABLES).
   * A continuation that omits it keeps the run's values; one that sends it
   * replaces them.
   */
  variables?: Record<string, string | number | boolean>;

  // ─────────────────────────────────────────
  // FILE INPUTS (PDFs, Images, Documents)
  // ─────────────────────────────────────────
  
  /**
   * File attachments for the agent to process.
   * Supports PDFs, images, and documents up to 128 MB each.
   * The model reads files up to 20 MB; for a larger file it sees only
   * the name, type and size, and the agent can still upload the file.
   *
   * The agent can:
   * - Read and analyze file contents
   * - Extract text from PDFs
   * - Analyze images
   * - Upload files to web forms
   */
  files?: ApiExecuteRequestFile[];

  // ─────────────────────────────────────────
  // TABULAR DATA INPUTS
  // ─────────────────────────────────────────
  
  /**
   * Tabular data to load as in-memory sheets.
   * Supports CSV, TSV, JSON, text, markdown, XLSX, Parquet.
   */
  dataInputs?: ApiTabularInput[];

  // ─────────────────────────────────────────
  // TOOL CONFIGURATION
  // ─────────────────────────────────────────
  
  tools?: ApiToolsConfig;

  // ─────────────────────────────────────────
  // SETTINGS & HISTORY
  // ─────────────────────────────────────────
  
  /**
   * Per-request settings override. The fields most callers use:
   *   llmIntegration: { model, reasoningEffort }   // which model, how hard it thinks
   *   llmIntegration.disableCreditsFallback        // true: only that model (as disable_fallbacks)
   *   cookieSync:     { enabled }                  // restore stored website sessions
   *   proxyConfig:    { mode: 'none' | 'default' | 'custom' }
   *   browser:        { timezone, locale }          // the cloud browser's clock: IANA zone + language
   *   limits:         { creditCeiling: { credits, onExceed } }
   *   proxyConfig.location: { country, region?, city? }  // where the managed proxy exits (mode 'default'); one field may list up to 5
   */
  settings?: Partial<UserSettings>;

  /**
   * A recording id from rtrvr.ai/cloud → Recordings. The agent follows the
   * recorded walkthrough as its guide for this task.
   */
  recordingContext?: string;

  /** Continue the previous workflow state (top-level wins over history.continue). */
  continuePlanning?: boolean;

  /** Continuation state from previous runs */
  history?: {
    continue?: boolean;
    previousSteps?: PlannerPreviousStep[];
    lastToolPreviousSteps?: ToolPreviousSteps;
  };

  // ─────────────────────────────────────────
  // RESPONSE CONFIGURATION
  // ─────────────────────────────────────────
  
  response?: {
    /** 'final' (default) | 'steps' | 'debug' */
    verbosity?: ApiVerbosity;
    /** Max bytes for inline output (default: 1MB) */
    inlineOutputMaxBytes?: number;
  };

  // ─────────────────────────────────────────
  // ARTIFACT REUSE (Advanced)
  // ─────────────────────────────────────────
  
  /**
   * Control how the agent reuses existing Google artifacts.
   * Useful for appending to existing Sheets/Docs.
   */
  reuseArtifacts?: ReuseArtifacts;

  // ─────────────────────────────────────────
  // INTERNAL OPTIONS
  // ─────────────────────────────────────────
  
  options?: {
    skipToolsStorageLoad?: boolean;
    pinTools?: boolean;
    pinSettings?: boolean;
    
    /** Execution trigger context */
    trigger?: {
      type: 'schedule' | 'ui' | 'api';
      context?: ScheduleContext;
    };
    
    /** UI and VNC live view settings */
    ui?: {
      /** Enable VNC live browser viewing. Default: false */
      enableVnc?: boolean;
      
      /** 
       * Which browser sessions to expose:
       * - "root": Main browser only (default)
       * - "all": Main + all batch worker browsers
       */
      vncScope?: 'root' | 'all';
      
      /**
       * Emit progress events to Firestore.
       * Opt-in only: set true to enable progress event writes.
       */
      emitEvents?: boolean;
    };

    /** Per-task spend guard. Overrides the saved setting for this run. */
    limits?: {
      creditCeiling?: {
        /** Credits this run may spend before it stops to ask. */
        credits?: number;
        /**
         * What to do at the ceiling. Defaults to 'stop' for
         * trigger.type === 'schedule' and 'pause' everywhere else.
         * 'off' opts this run out of the check entirely.
         */
        onExceed?: 'pause' | 'stop' | 'off';
      };
    };

    /**
     * Evidence for the data the run returns. screenshots: true makes the agent
     * screenshot every page it takes data from and tag each record with
     * evidenceArtifactId; the screenshots come back in response.artifacts.
     */
    evidence?: {
      screenshots?: boolean;
    };
  };

  // ─────────────────────────────────────────
  // WEBHOOKS (Async notifications)
  // ─────────────────────────────────────────

  /**
   * Webhook subscriptions for async delivery of execution results.
   * Up to 5 webhooks per request. Delivered via Cloud Tasks (best-effort).
   */
  webhooks?: WebhookSubscription[];
}

// Webhook subscription type
interface WebhookSubscription {
  /** Webhook endpoint URL (HTTPS required in production) */
  url: string;

  /** Events to subscribe to. If omitted, all events are delivered. */
  events?: (
    | "rtrvr.execution.succeeded"
    | "rtrvr.execution.failed"
    | "rtrvr.execution.cancelled"
    | "rtrvr.execution.requires_input"
  )[];

  /** HTTP method (currently only POST supported) */
  method?: "POST";

  /** Custom headers to include in webhook requests */
  headers?: Record<string, string>;

  /** Authentication config */
  auth?: {
    type: "bearer";
    token: string;
  } | {
    type: "basic";
    username: string;
    password: string;
  };

  /** Secret for HMAC signature (X-Rtrvr-Signature header) */
  secret?: string;

  /** Request timeout in ms (1000-30000, default: 8000) */
  timeoutMs?: number;

  /** Retry policy: "default" (with retries) or "none" */
  retry?: { mode: "default" | "none" };
}

// File input type
interface ApiExecuteRequestFile {
  /** Human-friendly filename, e.g. "Resume-2025.pdf" */
  displayName: string;
  
  /**
   * File location. Accepts:
   * - Firebase Storage URL: https://firebasestorage.googleapis.com/...
   * - GCS URI: gs://bucket/path/to/file
   * - Public HTTPS URL: https://example.com/file.pdf
   */
  uri: string;
  
  /** MIME type, e.g. "application/pdf", "image/png" */
  mimeType: string;
}

// Tabular input type
interface ApiTabularInput {
  /** Optional client-provided correlation ID */
  id?: string;
  
  /** Description for the sheet (used as title) */
  description?: string;
  
  /** Format hint: "csv" | "tsv" | "json" | "text" | "markdown" | "xlsx" | "parquet" */
  format?: InputFormat;
  
  /** Inline data content */
  inline?: string;
  
  /** Remote URL to fetch data from */
  url?: string;
  
  /** Backend storage reference (advanced) */
  storageRef?: StorageReference;
}

// Tools configuration
interface ApiToolsConfig {
  /**
   * Additional tool families to enable.
   * Core tools (browser, extraction, in-memory sheets) are always available.
   */
  enableAdditionalTools?: (
    | "ask_questions"      // Pause for user input
    | "generate_sheets"    // Write to Google Sheets
    | "generate_docs"      // Create Google Docs
    | "generate_slides"      // Create Google Slides
    | "generate_websites"  // Generate web dashboards
    | "generate_pdfs"      // Create new PDFs
    | "pdf_filling"        // Fill PDF forms
  )[];
  
  /** Names of user-defined tools to make available */
  userDefined?: string[];
  
  /**
   * Tool loading mode:
   * - "profile": Load user's saved tools (default)
   * - "allowlist": Only use tools in userDefined
   * - "none": No user-defined tools
   */
  mode?: "allowlist" | "profile" | "none";
}

// Artifact reuse configuration
interface ReuseArtifacts {
  /** 'off' (default) | 'auto' | 'force' */
  mode?: 'off' | 'auto' | 'force';
  
  targets?: {
    sheets?: {
      sheetId: string;
      tabTitle?: string;
      tabId?: number;
      /** 'SAME_TAB' | 'NEW_TAB' */
      tabMode?: 'SAME_TAB' | 'NEW_TAB';
    };
    docs?: {
      docId: string;
      /** 'APPEND' | 'OVERWRITE' */
      mode?: 'APPEND' | 'OVERWRITE';
    };
    slides?: {
      presentationId: string;
      mode?: 'APPEND' | 'OVERWRITE';
    };
    pdfs?: {
      templateFileId?: string;
    };
  };
}

Core fields

inputstringrequired

Natural-language task description; what you want the system to do.

authTokenstring

Optional Google OAuth override for this request. When omitted, Google work uses and refreshes the API key owner’s saved Cloud connection. Supply and refresh overrides yourself on every request, including continuations; a failed override never switches to the saved account.

urlsstring[]

Optional list of URLs to open. The first real URL loads full content; others default to text-only for efficiency.

schemaSchema

Optional OpenAPI-style JSON Schema describing the desired final JSON shape. Planner and tools will try to honor it when producing result.json.

trajectoryIdstring

Stable ID for a workflow. Omit to start a new trajectory; reuse to continue or add phases.

phasenumberdefault 1

Phase index within a trajectory. Use ≥2 for multi-stage workflows.

retryOfstring

The trajectoryId of an earlier run of yours that ended unfinished (stopped, failed, timed out, out of credits, or partial). This request starts a new run, under its own trajectoryId, that begins with the progress that run saved and picks up where it stopped instead of redoing finished items. Not with continuePlanning. A run that finished successfully, ran in Private Mode, or stopped before its first plan script saved no progress; the new run then starts from the beginning.

Example schema: bulleted summary
{
  "type": "object",
  "properties": {
    "bullets": {
      "type": "array",
      "items": { "type": "string" }
    },
    "sourceUrl": {
      "type": "string"
    }
  },
  "required": ["bullets"]
}

Exact values (variables)

A value the agent must use exactly (a signed file URL, the text of a post, a time) goes in variables, not in input. Name it in the instruction ("attach the video at mediaUrl"). The agent reads the value as data and hands the same characters to the page. A value it has to copy out of the task text, or retype, can lose a character or keep the sentence's final period, and a signed URL then fails with HTTP 403.

variablesRecord<string, string | number | boolean>

Names: a letter or _, then letters, digits or _ (at most 64). Values: strings, numbers or booleans; at most 32 entries of 20,000 characters each. A JSON string of the object is accepted too. A bad name, an object or array value, or a value over the limit is a 400 (INVALID_VARIABLES); nothing is dropped silently.

continuationsbehavior

Every step of a run sees the same values. A continuation (trajectoryId + continuePlanning) that omits variables keeps the run's values; one that sends variables replaces them, e.g. a signed URL that expired while the run waited for an answer.

Example: schedule a note with a video
{
  "input": "On Substack, create a Note with the text in noteText, attach the video at mediaUrl, and schedule it for scheduleTime.",
  "urls": ["https://substack.com/home"],
  "variables": {
    "mediaUrl": "https://prod-files-secure.s3.us-west-2.amazonaws.com/…/clip.mp4?X-Amz-Algorithm=…&X-Amz-Signature=…",
    "noteText": "Day 3 of climate week: the river is back.",
    "scheduleTime": "2026-10-03T10:00:00-07:00"
  },
  "settings": { "browser": { "timezone": "America/Los_Angeles" } }
}

n8n: in the HTTP Request node, set Body Content Type to JSON. With Specify Body set to Using JSON, write each value as an expression with no quotes around it: JSON.stringify adds the quotes and escapes any quote or line break in the text, which a quoted "{{ $json.note_text }}" does not.

n8n HTTP Request: Specify Body = Using JSON
{
  "input": "On Substack, create a Note with the text in noteText, attach the video at mediaUrl, and schedule it for scheduleTime.",
  "urls": ["https://substack.com/home"],
  "variables": {
    "mediaUrl": {{ JSON.stringify($json.media_url) }},
    "noteText": {{ JSON.stringify($json.note_text) }},
    "scheduleTime": {{ JSON.stringify($json.schedule_time) }}
  },
  "settings": { "browser": { "timezone": "America/Los_Angeles" } }
}

With Using Fields Below, the body is flat (a field named variables.mediaUrl is sent as that literal key), so send variables as one JSON string. The API reads it as the object.

n8n HTTP Request: Specify Body = Using Fields Below
Body Content Type   JSON
Specify Body        Using Fields Below

Name        Value
input       Create a Note with the text in noteText, attach the video at mediaUrl, and schedule it for scheduleTime.
variables   {{ JSON.stringify({ mediaUrl: $json.media_url, noteText: $json.note_text, scheduleTime: $json.schedule_time }) }}

The examples pin the browser to America/Los_Angeles, the zone the -07:00 time is written in, so Substack's schedule picker shows 10:00 AM as written (see Browser time zone and language). Fields mode sends each value as text and settings must be an object, so pin the clock with Using JSON.

Browser time zone and language (settings.browser)

Pages show dates and times, and date/time pickers take them, in the cloud browser's own clock. Without a pin, that clock is set when the browser starts, from where its traffic exits at that moment, so it can differ from where a later request exits; a run with a proxy location uses that place's clock. Set a pin whenever times matter (scheduling, bookings). When the location can't be looked up, the browser runs on America/Chicago in English (en-US), never a random zone. Either way the agent is told the browser's zone, its UTC offset (and the next daylight-saving change) and its local time. It converts a requested time into that clock before typing it into a picker, and says the converted value in the result.

settings.browser.timezonestring

IANA time zone for this run, e.g. "Asia/Singapore" or "America/New_York". An offset ("+08:00") or an abbreviation ("PST", "BST") is a 400 (INVALID_BROWSER_TIMEZONE). The agent sees the zone under the name the browser itself uses, so an alias can appear under its canonical name (Asia/Kolkata as Asia/Calcutta).

settings.browser.localestring

Browser language tag, e.g. "en-US" or "pt-BR": navigator.language, Accept-Language and how pages format dates. An unknown language is a 400 (INVALID_BROWSER_LOCALE).

precedencebehavior

A pin wins over the proxy's location: a mismatch is logged, not refused. Pin one of the two and the other still follows the browser's location. A pinned run can take a few more seconds to start, because the browser is launched on the pinned clock.

continuations & schedulesbehavior

A continuation keeps the clock its run started with while the run is still paused; one that starts on a fresh browser gets a fresh clock, so pin it if it matters. A schedule created from a run keeps the run's pin. Cloud runs only: in your own Chrome the agent uses your computer's clock.

Example: pin the clock to the zone the task's times are in
{
  "input": "Book the 9:30 AM slot on 2026-10-14 (Singapore time) for the dentist.",
  "urls": ["https://booking.example.com/dentist"],
  "settings": {
    "browser": { "timezone": "Asia/Singapore", "locale": "en-SG" }
  }
}

Tabular inputs (dataInputs)

dataInputsApiTabularInput[]

Optional list of tabular inputs to materialize as in-memory sheets. All dataInputs of one request together may hold up to 1,000,000 rows and 5,000,000 cells (rows x columns); a request past that fails with an error naming the input.

dataInputs[].descriptionstring

Human-readable description. Used as sheet title in the UI.

dataInputs[].format"text" | "markdown" | "csv" | "tsv" | "json" | "xlsx" | "parquet"

Optional explicit format. If omitted, inferred from file extension or content type.

dataInputs[].inlinestring

Raw content (CSV/TSV/JSON/text/markdown) embedded directly in the request. For XLSX/Parquet prefer URL or storageRef.

dataInputs[].urlstring

HTTP(S) URL to fetch as a tabular source (CSV/XLSX/Parquet files up to 16 MB).

dataInputs[].storageRefStorageReference

Advanced: backend-managed GCS object reference when clients upload to storage directly. Up to 16 MB.

dataInputs example: CSV (inline)
# CSV inline
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Enrich each company with website and description.",
    "dataInputs": [
      {
        "description": "Companies",
        "format": "csv",
        "inline": "company\\nOpenAI\\nDeepMind\\nAnthropic\\n"
      }
    ],
    "response": { "verbosity": "steps" }
  }'
dataInputs example: JSON (inline)
# JSON inline (array of objects)
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Infer seniority and return an updated JSON array.",
    "dataInputs": [
      {
        "description": "Contacts",
        "format": "json",
        "inline": "[{\"name\":\"Alice\",\"title\":\"VP Engineering\"},{\"name\":\"Bob\",\"title\":\"Software Engineer\"}]"
      }
    ],
    "response": { "verbosity": "steps" }
  }'
dataInputs example: XLSX (via URL)
# XLSX via URL
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Summarize opportunity pipeline from this Excel file.",
    "dataInputs": [
      {
        "description": "Sales pipeline",
        "format": "xlsx",
        "url": "https://example.com/sales-pipeline.xlsx"
      }
    ],
    "response": { "verbosity": "steps" }
  }'
dataInputs example: Parquet (via URL)
# Parquet via URL
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Compute daily active users per region from this Parquet dataset.",
    "dataInputs": [
      {
        "description": "Events parquet",
        "format": "parquet",
        "url": "https://example.com/events.parquet"
      }
    ],
    "response": { "verbosity": "steps" }
  }'

Tools configuration (tools)

tools.enableAdditionalToolsstring[]

Higher-power tool families to enable for this request.

ask_questionsgenerate_docsgenerate_slidesgenerate_websitesgenerate_pdfspdf_fillinggenerate_sheets
tools.userDefinedstring[]

Names of the tools you built under rtrvr.ai/cloud → Tools that this run may call. A tool switched off in the Tools list stays off, even when named here.

tools.modestringdefault profile

Which saved tools load: every saved tool (profile), only tools.userDefined (allowlist), or none.

profileallowlistnone

Core tools (browser actions, extraction, sheets_workflow on in-memory sheets, etc.) are always enabled. Additional tools control Docs, Slides, PDFs, persistent Sheets, and explicit ask_user behavior.

Google Integration Required

To use generate_sheets, generate_docs, or generate_slides:

  1. Connect Google Drive in the Cloud account that owns your API key.
  2. Enable the Google tools in your API request. The Authorization header contains your rtrvr API key; Google tokens are resolved and refreshed on the server.
  3. Select existing files through the Google picker and use a Google account with the required file permissions.

To use a different Google authorization, send an explicit authToken in the JSON body on every request, including continuations. Refresh that override yourself. Its failure never falls back to the saved account. Public/site keys cannot use an owner’s saved connection.

Response configuration (response)

response.verbosity"final" | "steps" | "debug"default "final"

Controls how much detail you get back.

finalstepsdebug
response.inlineOutputMaxBytesnumber

Hard cap (in bytes) for inline output blocks. Larger payloads are snapshot to storage and previewed.

History & continuation (history)

continuePlanningboolean

Canonical flag that this call should continue the previous workflow state. Top-level value wins over legacy history.continue.

history.continueboolean

Legacy alias for continuePlanning, accepted for backward compatibility.

history.previousStepsPlannerPreviousStep[]

Planner-internal state from previous runs. Returned in response.history for advanced clients.

history.lastToolPreviousStepsToolPreviousSteps

Tool execution state for the last tool. Used for precise continuations.

Advanced options

recordingContextstring

Id of a recording from rtrvr.ai/cloud → Recordings. The agent follows the recorded walkthrough as its guide for this task.

settingsPartial<UserSettings>

Per-request overrides of your account settings. Common ones: llmIntegration.model and .reasoningEffort. Set llmIntegration.inferencePolicy to gmi for managed GLM Flash served only through GMI, including supported helper calls, with no alternate-provider fallback. Normal authentication, plan limits and credit billing apply. Other settings include cookieSync.enabled (restore stored sessions from Chrome sync, imports, or earlier cloud sign-ins), proxyConfig.mode, browser.timezone and .locale (the cloud browser's clock, see Browser time zone and language), limits.creditCeiling. Other omitted settings keep the account value; proxy omission uses your profile default, or the managed proxy if none is set. Cloud chat-bar choices only override that chat. Select a saved custom proxy with selectedProxyName or selectedProxyId.

disable_fallbacksbooleandefault your Disable fallbacks setting

true: this run uses only the selected model, at the reasoning level asked for. A provider serving the same model may still answer, but the run never switches to another model or a lower reasoning level, and your own key never falls back to credits. A step the model cannot finish ends the run with code MODEL_UNAVAILABLE and modelUnavailable (product, effortRequested, blockedFallback, reason), status "error", or "partial" when it had already produced output; it is not retried. false allows fallbacks for this run while the setting is on. disableFallbacks is accepted too, as are "true", "false", 1, 0, "1" and "0"; any other value is a 400. Shorthand for settings.llmIntegration.disableCreditsFallback; never saved.

options.skipToolsStorageLoadboolean

Internal optimization flag when all tools are provided directly. Most clients should omit.

options.evidence.screenshotsbooleandefault false

true: the agent screenshots every page it takes data from, before leaving it, and tags each record from that page with evidenceArtifactId (the screenshot's artifacts[].id). The screenshots come back in artifacts with their page URL, capture time and SHA-256. Records whose shape a schema fixes keep it; match them to screenshots by pageUrl. Any other shape for options.evidence is a 400 (INVALID_EVIDENCE_OPTION). See Artifacts below.

settings.proxyConfig.location{ country, region?, city? }

Where Retriever's managed proxy exits for this run: a two-letter ISO country code, plus an optional ISO 3166-2 region part ("NY", "25") and English city name. The run never exits in another country, and the browser's time zone and language follow the exit. The response's egress says where it exited. See Choose where the managed proxy exits below.

Use a saved proxy from MCP or the API

Save your proxy in Cloud Settings → Proxy Config. Choose it as your default and save settings to use it across Cloud, API, and MCP. To override for one call, pass its exact name or ID. Credentials stay in your account.

{
  "settings": {
    "proxyConfig": {
      "mode": "custom",
      "selectedProxyName": "My Taiwan proxy"
    }
  }
}

For cloud_agent and cloud_scrape, put settings inside the MCP tool's arguments or the tool API's params. For POST /agent and POST /scrape, put it in the request body. A saved selectedProxyId can replace selectedProxyName.

  • Omit proxyConfig to use your profile default, falling back to Retriever's managed proxy when none is set. Use {"mode":"default"} to explicitly choose the managed proxy.
  • The Cloud chat-bar selection overrides routing for that chat without changing your profile default. Choose “Use my default route” to reset it. MCP list with kind: "proxies" shows saved proxies and marks the default; managed proxy connection details remain private.
  • Use {"mode":"none"} for a direct cloud connection.
  • Saved names are case-sensitive. Unknown names and IDs fail; duplicate names require an ID. If both selectors are supplied, they must identify the same proxy.
  • Schedules created from a run retain its routing choice. Permission to use another route belongs to the schedule, so a one-run fallback does not enable it.

You can also provide a proxy for just one request. Use an HTTP or HTTPS proxy that supports CONNECT; keep credentials out of shared snippets and client-side code.

{
  "settings": {
    "proxyConfig": {
      "mode": "custom",
      "selectedProxyId": "request-proxy",
      "customProxies": [
        {
          "id": "request-proxy",
          "host": "proxy.example.net",
          "port": 8080,
          "scheme": "http",
          "username": "YOUR_USERNAME",
          "password": "YOUR_PASSWORD"
        }
      ]
    }
  }
}

Go out through your own computer with Tailscale

A Tailscale tunnel sends a cloud run's traffic out through a computer you own, so sites see your home connection. It uses no proxy credits. Once saved, choose it like any saved proxy: as your default, in the chat bar, or by its exact name or ID with {"mode":"custom"}.

Tailscale connection failures retry once, then stop before the task starts. To allow managed fallback for an API request, send settings.proxyConfig.tailscaleFallback: "managed". Omit it or use "stop" to stay on Tailscale. Fallback uses a different IP and normal proxy charges apply.

Set up a Tailscale tunnel: the steps, the limits, and parallel runs.

Choose where the managed proxy exits

Add location to Retriever's managed proxy to run from a country, and optionally a region or city in it. The cloud browser's time zone and language follow the exit, so sites show dates, prices and pages the way a local visitor sees them.

{
  "settings": {
    "proxyConfig": {
      "mode": "default",
      "location": {
        "country": "IT",
        "city": "Milan"
      }
    }
  }
}
  • One field may list up to 5 places for an exit in any of them: countries ({"country": ["IT", "FR", "DE"]}, with no region or city), regions of one country ({"country": "US", "region": ["CA", "WA"]}, with no city) or cities of one country ({"country": "IT", "city": ["Milan", "Rome"]}). A comma-separated string such as "IT,FR,DE" works too. egress names the country the run used.
  • The result's egress says where the run exited, for example {"country":"IT","city":"Milan","timezone":"Europe/Rome"}. relaxed: true means the city or region had no exit and the run used more of the country.
  • A location goes only with the managed proxy (mode: "default", which it implies when you leave mode out). Next to a saved proxy or "none" the call is refused.
  • Same field everywhere: settings.proxyConfig.location on POST /agent and POST /scrape, and on the MCP tools cloud_agent and cloud_scrape. MCP and the tool API also take the shorthand proxy_location, such as "proxy_location": "IT", "proxy_location": "IT,FR,DE" or "proxy_location": {"country": "US", "region": "NY"}; with agent or scrape it runs the task in the cloud browser.
  • Save a location under Cloud Settings → Proxy Config next to the managed default to use it for every run that names no route, schedules included. In the Cloud chat bar, pick Retriever managed proxy and set Location for that chat only.
  • A few hosts that always connect directly from the cloud, such as some government sites and file storage, do not use the location.

Supported locations

A location names a country, and optionally a region or city in it. Each field takes one value, or a list of up to 5 for an exit in any of them.

FieldFormatExamples
countryTwo-letter ISO 3166-1 code (UK is read as GB). A list of countries takes no region or city.IT, US, ["IT", "FR", "DE"]
regionOptional. The part after the dash of an ISO 3166-2 code in that country. A list of regions takes no city.NY for US-NY, 25 for IT-25 (Lombardy)
cityOptional. The city's English name, in that country.Milan, New York, ["Milan", "Rome"]

If the city has no exit, the run tries its region, then the whole country. It never moves to another country. If none of the countries you asked for has an exit, the run does not start and the call returns 503 PROXY_LOCATION_UNAVAILABLE with a message that says so.

Availability is live: exits come and go with the residential pool, and every run checks its own location before it starts. GET https://api.rtrvr.ai/proxy-locations with your API key returns the country list below, with the date each country was checked, the format and the fallback rule. To check a region, a city or a list right now, send POST https://api.rtrvr.ai/proxy-location/check with {"location": {"country": "IT", "city": "Milan"}}. It answers {"available": true, "egress": {...}} with where a run would exit, or {"available": false, "message": "..."}. Cloud Settings and the chat bar run the same check and only take places that have an exit.

120 countries had an exit when checked on 11 November 2026
  • Åland Islands AX
  • Albania AL
  • American Samoa AS
  • Andorra AD
  • Anguilla AI
  • Antigua & Barbuda AG
  • Argentina AR
  • Armenia AM
  • Aruba AW
  • Australia AU
  • Austria AT
  • Azerbaijan AZ
  • Bahamas BS
  • Bahrain BH
  • Barbados BB
  • Belarus BY
  • Belgium BE
  • Belize BZ
  • Bermuda BM
  • Bosnia & Herzegovina BA
  • Brazil BR
  • British Virgin Islands VG
  • Bulgaria BG
  • Canada CA
  • Caribbean Netherlands BQ
  • Cayman Islands KY
  • Chile CL
  • China CN
  • Colombia CO
  • Costa Rica CR
  • Côte d’Ivoire CI
  • Croatia HR
  • Curaçao CW
  • Cyprus CY
  • Czechia CZ
  • Denmark DK
  • Dominica DM
  • Dominican Republic DO
  • Egypt EG
  • Equatorial Guinea GQ
  • Estonia EE
  • Falkland Islands FK
  • Faroe Islands FO
  • Finland FI
  • France FR
  • French Guiana GF
  • French Polynesia PF
  • Gabon GA
  • Georgia GE
  • Germany DE
  • Gibraltar GI
  • Greece GR
  • Greenland GL
  • Grenada GD
  • Guadeloupe GP
  • Guam GU
  • Guernsey GG
  • Hungary HU
  • Iceland IS
  • India IN
  • Iran IR
  • Ireland IE
  • Isle of Man IM
  • Israel IL
  • Italy IT
  • Jamaica JM
  • Japan JP
  • Jersey JE
  • Kazakhstan KZ
  • Kuwait KW
  • Latvia LV
  • Lithuania LT
  • Luxembourg LU
  • Malta MT
  • Martinique MQ
  • Mexico MX
  • Moldova MD
  • Monaco MC
  • Montenegro ME
  • Montserrat MS
  • Namibia NA
  • Netherlands NL
  • New Zealand NZ
  • North Macedonia MK
  • Norway NO
  • Oman OM
  • Palestinian Territories PS
  • Panama PA
  • Peru PE
  • Philippines PH
  • Poland PL
  • Portugal PT
  • Puerto Rico PR
  • Qatar QA
  • Romania RO
  • San Marino SM
  • Saudi Arabia SA
  • Serbia RS
  • Seychelles SC
  • Sint Maarten SX
  • Slovakia SK
  • Slovenia SI
  • South Korea KR
  • Spain ES
  • St. Kitts & Nevis KN
  • St. Lucia LC
  • St. Martin MF
  • St. Vincent & Grenadines VC
  • Sweden SE
  • Switzerland CH
  • Taiwan TW
  • Trinidad & Tobago TT
  • Türkiye TR
  • Turks & Caicos Islands TC
  • U.S. Virgin Islands VI
  • Ukraine UA
  • United Arab Emirates AE
  • United Kingdom GB
  • United States US
  • Uruguay UY
129 countries had no exit on 11 November 2026

These codes are accepted, but a run that asks only for them is refused until an exit appears. Check one before you rely on it, or list it with a country that has an exit.

  • Afghanistan AF
  • Algeria DZ
  • Angola AO
  • Antarctica AQ
  • Bangladesh BD
  • Benin BJ
  • Bhutan BT
  • Bolivia BO
  • Botswana BW
  • Bouvet Island BV
  • British Indian Ocean Territory IO
  • Brunei BN
  • Burkina Faso BF
  • Burundi BI
  • Cambodia KH
  • Cameroon CM
  • Cape Verde CV
  • Central African Republic CF
  • Chad TD
  • Christmas Island CX
  • Cocos (Keeling) Islands CC
  • Comoros KM
  • Congo - Brazzaville CG
  • Congo - Kinshasa CD
  • Cook Islands CK
  • Cuba CU
  • Djibouti DJ
  • Ecuador EC
  • El Salvador SV
  • Eritrea ER
  • Eswatini SZ
  • Ethiopia ET
  • Fiji FJ
  • French Southern Territories TF
  • Gambia GM
  • Ghana GH
  • Guatemala GT
  • Guinea GN
  • Guinea-Bissau GW
  • Guyana GY
  • Haiti HT
  • Heard & McDonald Islands HM
  • Honduras HN
  • Hong Kong SAR China HK
  • Indonesia ID
  • Iraq IQ
  • Jordan JO
  • Kenya KE
  • Kiribati KI
  • Kyrgyzstan KG
  • Laos LA
  • Lebanon LB
  • Lesotho LS
  • Liberia LR
  • Libya LY
  • Liechtenstein LI
  • Macao SAR China MO
  • Madagascar MG
  • Malawi MW
  • Malaysia MY
  • Maldives MV
  • Mali ML
  • Marshall Islands MH
  • Mauritania MR
  • Mauritius MU
  • Mayotte YT
  • Micronesia FM
  • Mongolia MN
  • Morocco MA
  • Mozambique MZ
  • Myanmar (Burma) MM
  • Nauru NR
  • Nepal NP
  • New Caledonia NC
  • Nicaragua NI
  • Niger NE
  • Nigeria NG
  • Niue NU
  • Norfolk Island NF
  • North Korea KP
  • Northern Mariana Islands MP
  • Pakistan PK
  • Palau PW
  • Papua New Guinea PG
  • Paraguay PY
  • Pitcairn Islands PN
  • Réunion RE
  • Russia RU
  • Rwanda RW
  • Samoa WS
  • São Tomé & Príncipe ST
  • Senegal SN
  • Sierra Leone SL
  • Singapore SG
  • Solomon Islands SB
  • Somalia SO
  • South Africa ZA
  • South Georgia & South Sandwich Islands GS
  • South Sudan SS
  • Sri Lanka LK
  • St. Barthélemy BL
  • St. Helena SH
  • St. Pierre & Miquelon PM
  • Sudan SD
  • Suriname SR
  • Svalbard & Jan Mayen SJ
  • Syria SY
  • Tajikistan TJ
  • Tanzania TZ
  • Thailand TH
  • Timor-Leste TL
  • Togo TG
  • Tokelau TK
  • Tonga TO
  • Tunisia TN
  • Turkmenistan TM
  • Tuvalu TV
  • U.S. Outlying Islands UM
  • Uganda UG
  • Uzbekistan UZ
  • Vanuatu VU
  • Vatican City VA
  • Venezuela VE
  • Vietnam VN
  • Wallis & Futuna WF
  • Western Sahara EH
  • Yemen YE
  • Zambia ZM
  • Zimbabwe ZW

A location costs nothing extra. Managed proxy traffic is billed per GB, about 500 credits per GB (pricing), the same with or without a location.

Page-read timing

Both /agent and /scrape open supplied URLs immediately and return page data as soon as it is ready. Configure timing under settings.extractionConfig. Request values override your saved profile. The defaults below apply when neither sets a value.

  • totalBudgetMs: one deadline for initial navigation, readiness, extraction, and fallback. Default: 15000. Values are bounded to 1500–30000 milliseconds. This is a maximum, not a mandatory wait.
  • pageLoadDelay: an optional minimum wait before capture for pages that render content late. Default: 0. A positive value deliberately waits even if the page becomes ready sooner. It consumes the total budget and is shortened if needed to leave time for extraction and fallback.
  • maxParallelTabs: up to 8 cloud tabs load together (default and maximum: 8). Set a lower value to load fewer at once, or 1 for sequential reads. Each active batch gets a fresh page-read budget, so a large batch of URLs can take longer than one budget.
Return when ready, with up to 15 seconds for the initial read
{
  "settings": {
    "extractionConfig": { "totalBudgetMs": 15000, "pageLoadDelay": 0 }
  }
}

For a page that needs a known 10-second rendering wait, set pageLoadDelay: 10000and totalBudgetMs: 20000. That leaves roughly 10 seconds for the remaining read work. The delay is not added on top of the deadline. Set pageLoadDelay: 0 explicitly to override a saved profile delay.

At the deadline, a read returns the available tree or a text fallback. If neither is readable, it reports an unreadable page rather than successful empty extraction. Text-only scrapes return text without a tree. An agent with no URLs starts with an empty page immediately; /scrape requires at least one URL.

This deadline covers page reads, not browser provisioning, model execution, or the whole API request. Later agent captures keep their normal 8-second default unless you set a budget. Cancellation can interrupt a running job. Keep the caller timeout long enough for all batches and agent work; after a lost response, recover by trajectory ID before retrying.

Spend guard (maxCredits, options.limits)

Every run carries a credit ceiling so a task that loops or crawls further than expected can't burn credits unattended — 100 credits unless you saved another value in Settings. Set maxCredits on a call to give that run its own budget: higher for a big job you launch knowingly, lower to cap a small one.

maxCreditsnumberdefault user setting, else 100 (25 on Free)

Credit budget for this run. Shorthand for options.limits.creditCeiling.credits, and it wins when both are sent. max_credits (the MCP spelling) is accepted too. A value that is not a positive number is a 400 (INVALID_MAX_CREDITS).

options.limits.creditCeiling.creditsnumberdefault user setting, else 100 (25 on Free)

Credits this run may spend before it stops to check in.

options.limits.creditCeiling.onExceed"pause" | "stop" | "off"default "stop" for trigger.type "schedule", else "pause"

pause finalizes requires_input with a Continue/Stop question and a resume descriptor, so a caller can decide programmatically; stop finalizes with the work done so far; off disables the check for this run.

pausestopoff

A paused run fires the rtrvr.execution.requires_input webhook and returns the usual resume descriptor, with the question keyed credit_ceiling_continue. Answering "Continue" grants a fresh budget and continues from the stored trajectory — no work is repeated; "Stop here" ends the run with what it produced.

UI & VNC options (options.ui)

Enable live browser viewing via VNC. Perfect for debugging, demos, or embedding real-time browser sessions in your app.

options.ui.enableVncbooleandefault false

Enable VNC live view for this execution. When true, you can retrieve an embeddable URL to watch the browser in real-time.

options.ui.vncScope"root" | "all"default "root"

Controls which browser sessions are visible. 'root' shows only the main browser. 'all' includes batch worker browsers when using parallel execution.

rootall
options.ui.emitEventsbooleandefault false

Opt-in only. When true, execution progress events are written to Firestore for SSE/polling clients. If omitted/false, no execution event stream is written.

emitEvents policy: API and extension/MCP executions only emit events whenoptions.ui.emitEvents: true. CLI streaming defaults on for run, agent, and scrape (use --no-stream to disable). Streamed payloads at or under 1MB stay inline; larger payloads include inline preview markers and storage references (`outputRef` / `resultRef` / `responseRef`) for full downloads.
Quick Start: To embed a live browser view, set options.ui.enableVnc: true, then call POST /vnc/share to get an iframe-ready URL. See the VNC Live View section for full details.

Webhooks (webhooks)

Get notified when your workflow completes via HTTP webhooks. Up to 5 webhooks per request. Webhooks are delivered asynchronously via Google Cloud Tasks with best-effort delivery and automatic retries.

webhooks[].urlstringrequired

Webhook endpoint URL. HTTPS required in production. HTTP allowed for localhost development.

webhooks[].eventsstring[]

Events to subscribe to. If omitted, all events are delivered.

rtrvr.execution.succeededrtrvr.execution.failedrtrvr.execution.cancelledrtrvr.execution.requires_input
webhooks[].secretstring

HMAC secret for request signing. If set, requests include X-Rtrvr-Signature header with format: t=timestamp,v1=hmac_sha256

webhooks[].authobject

Authentication config. Supports Bearer token ({ type: 'bearer', token: '...' }) or Basic auth ({ type: 'basic', username: '...', password: '...' })

webhooks[].headersRecord<string, string>

Custom headers to include in webhook requests

webhooks[].timeoutMsnumberdefault 8000

Request timeout in milliseconds (1000-30000)

webhooks[].retryobjectdefault { mode: "default" }

Retry policy. 'default' retries on failure, 'none' disables retries

Webhook subscription example
// Webhook request example
curl -X POST "https://api.rtrvr.ai/agent" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Scrape the homepage and summarize it",
    "urls": ["https://example.com"],
    "webhooks": [
      {
        "url": "https://your-server.com/webhook",
        "events": ["rtrvr.execution.succeeded", "rtrvr.execution.failed"],
        "secret": "your-signing-secret",
        "headers": {
          "X-Custom-Header": "my-value"
        }
      }
    ]
  }'

Webhook Payload

When an event triggers, rtrvr sends a POST request with this envelope:

Webhook payload envelope
{
  "id": "whd_abc123",           // Unique delivery ID
  "event": "rtrvr.execution.succeeded",
  "createdAt": "2025-01-15T10:30:00.000Z",
  "data": {
    "trajectoryId": "exec_xyz789",
    "status": "success",
    "taskRef": "gs://bucket/user-tasks/uid/exec_xyz789/workflow.json",
    "responseRef": { ... },    // Full response snapshot when > 1MB
    "output": { ... },         // Inline preview payload
    "outputRef": { ... },      // Optional large output download reference
    "resultRef": { ... },      // Optional large result download reference
    "artifacts": {             // The run's files, by kind (absent when none)
      "images": [{ "id": "…", "name": "…", "url": "…", "mimeType": "image/png", "kind": "screenshot" }],
      "pdfs": [{ "id": "…", "name": "…", "url": "…" }],
      "files": [{ "id": "…", "name": "…", "url": "…" }]
    },
    "usage": {
      "creditsUsed": 0.15,
      "creditsLeft": 99.85
    }
  }
}

Signature Verification

If you provide a secret, verify the signature:

Verify X-Rtrvr-Signature
// Node.js signature verification
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const [tPart, v1Part] = signature.split(',');
  const timestamp = tPart.split('=')[1];
  const receivedSig = v1Part.split('=')[1];

  const signedPayload = timestamp + '.' + JSON.stringify(payload);
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(receivedSig),
    Buffer.from(expectedSig)
  );
}
Response metadata: The API response includes metadata.webhooks: { requested: number, attempted: number } showing how many webhooks were configured and enqueued.
Store & reuse webhooks

Save your webhook endpoints in Cloud → Webhooks to quickly attach them to any execution without re-entering the URL, secret, and events each time.

Provide urls to open the starting pages before planning. Without URLs, the agent starts with empty page data and chooses where to go.

Save a unique trajectoryId before dispatch. If the response is lost, use GET /executions/TRAJECTORY_ID with the same API key to read its status and saved result. Use GET /executions for history and POST /agent/cancel with {"trajectoryId":"YOUR_TRAJECTORY_ID"} to cancel.

MCP list_executions, check_results, and cancel_execution use the same ID without requiring an extension device. Read the recovery and cancellation contract before retrying an unknown run.

Cancelling a run

In the dashboard, open Executions and choose Cancel on the run. Every running cloud run offers it, whether the chat, this API, MCP, a schedule or a webhook started it.

From code, post the run's trajectoryId to /agent/cancel with the same API key. The run stops within seconds, and the request still waiting on it returns status: "cancelled" (HTTP 200, or 499 REQUEST_ABORTED if the run had not started planning). Steps it already took stay done, and the credits they used are charged. A run that already finished is left as it was, and the reply carries its status.

Cancel a run
curl -X POST https://api.rtrvr.ai/agent/cancel \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"trajectoryId":"YOUR_TRAJECTORY_ID"}'

n8n: stopping an n8n execution does not close the requests it already sent, so their runs keep going until you cancel them. To cancel from a workflow, send your own trajectoryId with each request (8 to 160 letters, digits, _, : or -, new for every request), for example "trajectoryId": "n8n-{{ $execution.id }}-{{ $itemIndex }}", and post it to /agent/cancel from an HTTP Request node.

Every call returns an AgentApiResponse:

AgentApiResponse (conceptual)
interface AgentApiResponse {
  protocol: {
    name: 'rtrvr.agent';
    version: string;
    compatibleWith: Array<'a2w.run' | 'a2a.task'>;
  };
  run: {
    id: string;
    trajectoryId: string;
    executionId?: string;
    status: 'success' | 'partial' | 'error' | 'cancelled' | 'requires_input' | 'executing' | 'requires_tool';
    phase: number;
  };

  success: boolean;
  status: 'success' | 'partial' | 'error' | 'cancelled' | 'requires_input' | 'executing';

  trajectoryId: string;
  phase: number;

  // Rich output blocks
  output: ApiOutputBlock[];
  // Note: in debug mode, tool_result blocks may include outputRef/resultRef
  // when large per-step payloads are stored out-of-line.

  // Convenience view of final output
  result?: {
    text?: string;
    json?: any;
  };

  // Present when verbosity !== 'final'
  steps?: ApiStepSummary[];

  // Every file the run made: screenshots, generated images, PDFs and other
  // files, each with a signed download URL. Always an array; [] when none.
  artifacts: Array<{
    id: string;            // what evidenceArtifactId in your data refers to
    kind: 'screenshot' | 'image' | 'pdf' | 'file';
    name: string;
    title?: string;
    mimeType: string;
    sizeBytes?: number;
    url: string;           // signed; works until expiresAt
    expiresAt?: string;    // 7 days; renew with GET /artifacts/:trajectoryId
    stepId?: string;       // the run step that made it
    pageUrl?: string;      // the page a screenshot shows
    capturedAt?: string;   // ISO time it was captured
    sha256?: string;       // hex SHA-256 of the file's bytes
  }>;

  usage: {
    creditsUsed: number;
    creditsLeft?: number;
    currentCreditsUsed?: number;
    expiryReason?: string;
  };

  metadata: {
    taskRef: string;
    inlineOutputMaxBytes: number;
    toolsUsed: string[];
    outputTooLarge?: boolean;
    responseRef?: StorageReference;
  };

  warnings?: string[];
  error?: string;

  // Where the managed proxy exited (absent without it). requested/relaxed
  // appear when settings.proxyConfig.location pinned a place.
  egress?: {
    country: string;          // ISO 3166-1 alpha-2
    countries?: string[];     // every exit country, when more than one
    region?: string;
    city?: string;
    timezone?: string;        // the browser ran on this time zone
    requested?: { country: string; region?: string; city?: string };
    relaxed?: boolean;        // the city or region had no exit
  };

  // Continuation payload for advanced clients
  inputRequest?: {
    reason: string;
    questions?: PlannerQuestion[];
    expiresAt?: string;
    resume: {
      executionId: string;
      continuePlanning: true;
      phase: number;
      method?: 'POST';
      path?: string;
      expiresAt?: string;
    };
    browser?: {
      parkStatus?: 'parked' | 'reused' | 'restored' | 'expired' | 'unavailable';
      ttlMs?: number;
      expiresAt?: string;
      liveReuseAvailable?: boolean;
    };
  };
  history?: {
    previousSteps?: PlannerPreviousStep[];
    lastToolPreviousSteps?: ToolPreviousSteps;
  };
  browser?: {
    parkStatus?: string;
    expiresAt?: string;
    resumedFromPark?: boolean;
    restoredFromVault?: boolean;
    activeOrigin?: string;
    tabCount?: number;
  };
}

Output blocks & result

The low-level output is an array of blocks:

output[].type"text" | "json" | "tool_result"

Block type: final text, JSON payload, or detailed tool result (debug mode).

output[].textstring

Present when type = 'text'.

output[].dataany

Present when type = 'json'.

output[].tool_result…

When type = 'tool_result', includes stepId, toolName, args, inline output preview, optional outputRef/resultRef, thought, etc. Only present when verbosity = 'debug'.

result.text is the concatenation of all text blocks. result.json is either the single JSON block, or an array of JSON blocks if the workflow produced multiple.

Steps & usage

When response.verbosity is "steps" or "debug", you also get steps: ApiStepSummary[]:

steps[].toolNamestring

Which tool ran in this step (e.g. 'sheets_workflow', 'act_on_tab').

steps[].statusExecutionStatus

success, error, executing, etc. per step.

steps[].durationnumber

Execution time in ms for this step (when available).

steps[].creditsUsednumber

Credits consumed by this step, useful for analytics.

steps[].hasOutputboolean

Whether this step produced output or an outputRef.

steps[].hasSheetsboolean

Whether this step produced or touched tabular data.

steps[].hasGeneratedContentboolean

Whether this step generated external content (docs, slides, etc.).

usage mirrors your credit accumulator and is ideal for per-customer dashboards and server-side cost control.

Artifacts (screenshots, PDFs, files)

artifacts lists every file the run made, at any verbosity: screenshots, generated images, PDFs and other files. Each entry has a signed url that works for 7 days, until expiresAt. Download what you need to keep. Google Docs, Slides and Sheets are links, not files, so they are not listed here.

artifacts[].idstring

Stable id of the file. With options.evidence.screenshots, records carry it as evidenceArtifactId.

artifacts[].kind"screenshot" | "image" | "pdf" | "file"

screenshot: a capture of a page; image: any other picture the run made.

artifacts[].urlstring

Signed download URL. Treat it like a password: anyone with it can read the file until it expires.

artifacts[].expiresAtstring (ISO)

When url stops working. Get fresh URLs with GET /artifacts/:trajectoryId.

artifacts[].pageUrlstring

The page the file came from (a screenshot's tab URL), when known.

artifacts[].capturedAtstring (ISO)

When the file was captured or made.

artifacts[].sha256string

Hex SHA-256 of the stored bytes, to check a download or record it as evidence.

artifacts[].stepIdstring

The run step that made the file.

A run with options.evidence.screenshots: true
{
  "success": true,
  "status": "success",
  "result": {
    "json": {
      "records": [
        { "permitId": "B-2291", "status": "Issued", "evidenceArtifactId": "e19b6d1f-b5fb-46f3-b76e-f092b25424ba" }
      ]
    }
  },
  "artifacts": [
    {
      "id": "e19b6d1f-b5fb-46f3-b76e-f092b25424ba",
      "kind": "screenshot",
      "name": "screenshot_tab1914814576_1794379394825.png",
      "mimeType": "image/png",
      "sizeBytes": 81687,
      "url": "https://storage.googleapis.com/…/screenshot_tab1914814576_1794379394825.png?X-Goog-Algorithm=GOOG4-RSA-SHA256&…",
      "expiresAt": "2026-11-18T06:43:31.000Z",
      "stepId": "9d87c9a7-f4a5-4446-a07e-a2874e4098f2",
      "pageUrl": "https://permits.example.gov/records?id=B-2291",
      "capturedAt": "2026-11-11T06:43:14.509Z",
      "sha256": "45babf0eaba0fb3a87216ae6150a56f1d34988a92c6db0a548d07f773e94f33f"
    }
  ]
}

Links expire. Fetch fresh ones for a finished run with the same API key; only the run's owner can read them:

Renew artifact URLs
curl https://api.rtrvr.ai/artifacts/YOUR_TRAJECTORY_ID \
  -H "Authorization: Bearer YOUR_API_KEY"
# → { "success": true, "trajectoryId": "…", "artifacts": [ …same entries, fresh url and expiresAt… ] }

Large output handling

When the full response exceeds inlineOutputMaxBytes:

  • • The full response is snapshot to storage under metadata.responseRef.
  • • The inline output/result fields remain as preview content for UX.
  • • Debug tool_result blocks may include outputRef / resultRef for full per-step payloads.
  • • metadata.outputTooLarge is set to true.

Client pattern: render the preview for UX, but fetch responseRef.downloadUrl from your backend when you need the full payload.

status & success

status"success" | "partial" | "error" | "cancelled" | "requires_input" | "executing"

Execution-level status. success implies success = true; all others imply success = false.

  • • "success" – Final result is available in result and output.
  • • "partial": The run retained useful output but did not meet every completion requirement. Read completion.reasonCode and completion.missing. A forced Sheets destination without a confirmed data write reports SHEETS_DELIVERY_UNCONFIRMED.
  • • "error" – Workflow failed. You still get usage, steps (if enabled), and partial output if any. With Disable fallbacks on (disable_fallbacks: true or the setting), a step the selected model could not finish ends the run here (or as "partial" when it had already produced output) with code: "MODEL_UNAVAILABLE" and modelUnavailable (product, effortRequested, blockedFallback, reason) instead of switching to another model or a lower reasoning level. Do not retry it as is: try later, or send disable_fallbacks: false.
  • • "cancelled" – The run was cancelled (Executions → Cancel, POST /agent/cancel, MCP cancel_execution) or the client disconnected. Credits are charged for the work done.
  • • "requires_input" – Planner paused because it needs human answers. Read inputRequest for questions, resume ids, and browser park expiry.

Responses include an additive protocol.name = "rtrvr.agent" envelope so A2W/A2A-style clients can map runs, messages, output, history, and input requests consistently. Legacy fields remain top-level for existing clients.

Continuation pattern:
  1. When you see status: "requires_input", surface your own UI to collect missing info.
  2. On the next call, send inputRequest.resume.executionId as the same trajectoryId, set continuePlanning = true, and pass back the returned history.
  3. If inputRequest.browser.liveReuseAvailable is true, resume before inputRequest.expiresAt to continue in the same live browser; otherwise the agent restores cookies and the last active URL.

Control how the agent interacts with existing Google Sheets, Docs, Slides, and PDF templates. Instead of creating new artifacts each time, you can append to or update existing ones.

Google authorization: API-key requests use the saved Google connection belonging to the API key owner. Connect Google Drive at rtrvr.ai/cloud → Sheets. Select existing files through the Google picker and use an account with edit access. A pasted file ID does not grant access. API requests and Cloud schedules refresh saved Google authorization on the server. An explicit API authToken overrides the saved connection for that request.
Use cases:
  • Append scraped leads to an existing Google Sheet
  • Add new sections to a running Google Doc report
  • Build a presentation incrementally across multiple runs
  • Fill PDF forms using a template

Mode options

reuseArtifacts.mode"off" | "auto" | "force"default "off"

Controls reuse behavior. 'off' disables configured reuse. 'auto' prefers configured targets while respecting an explicitly requested destination. 'force' requires the configured targets; a configured Sheets target also requires a confirmed data write before completion.

"off"

Disable configured artifact reuse (default)

"auto"

Prefer configured targets

"force"

Require configured targets

Google Sheets

Write results to an existing Google Sheets spreadsheet. Requires generate_sheets in enableAdditionalTools.

targets.sheets.sheetIdstringrequired

The Google Sheets ID (from the URL: docs.google.com/spreadsheets/d/{sheetId}/edit)

targets.sheets.tabTitlestring

SAME_TAB: the actual tab name in any language. NEW_TAB: optional name prefix, up to 70 characters without [ ] : * ? / or backslash.

targets.sheets.tabIdnumber

SAME_TAB: stable numeric gid, including 0. Takes precedence over tabTitle and follows tab renames. A missing ID fails validation. Omit for NEW_TAB.

targets.sheets.tabMode"SAME_TAB" | "NEW_TAB"default "SAME_TAB"

SAME_TAB writes to the selected existing tab. NEW_TAB creates one tab per run when data is written, with a run-specific suffix.

Without a tab ID, the runtime resolves an exact name or a unique case-insensitive match. A missing/default Sheet1 name can resolve to the only tab. Ambiguous choices and missing custom names fail validation. Select an actual tab instead of translating its name or assuming an English default.

Forced extracted-data writes use RAW values to preserve date strings, decimal strings, leading zeros, and multilingual text. Custom append helpers can select valueInputOption; intentional formula updates use updateRange with allowFormulas: true. See the Sheets helper reference and Google's value input rules.

A forced Sheets target completes only after Google confirms a data write. A local fallback, extracted rows, or a model's success message is insufficient. An unconfirmed write produces status: "partial", success: false, and completion.reasonCode: "SHEETS_DELIVERY_UNCONFIRMED" while retaining the output. This also applies to zero-row results. Errors and cancelled runs retain their failure status.

Identical forced appends are deduplicated within a running request. There is no exactly-once guarantee across process restarts or new runs. After a timeout or lost write response, inspect the destination before rerunning. Cloud schedules additionally check destination access before starting the workflow; see schedule setup.

Example: Append leads to existing sheet
{
  "input": "Scrape new leads and add them to my tracking sheet",
  "urls": ["https://linkedin.com/search/results/people?keywords=CTO"],
  "reuseArtifacts": {
    "mode": "force",
    "targets": {
      "sheets": {
        "sheetId": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
        "tabTitle": "Leads",
        "tabMode": "SAME_TAB"
      }
    }
  },
  "tools": {
    "enableAdditionalTools": ["generate_sheets"]
  }
}

Google Docs

Append content to or overwrite an existing Google Doc. Requires generate_docs in enableAdditionalTools.

targets.docs.docIdstringrequired

The Google Docs ID (from the URL: docs.google.com/document/d/{docId}/edit)

targets.docs.mode"APPEND" | "OVERWRITE"default "APPEND"

APPEND adds new content at the end. OVERWRITE replaces the entire document content.

Example: Append research notes to existing doc
{
  "input": "Summarize this article and add it to my research notes",
  "urls": ["https://example.com/ai-research-paper"],
  "reuseArtifacts": {
    "mode": "force",
    "targets": {
      "docs": {
        "docId": "1abc123xyz_your_doc_id_here",
        "mode": "APPEND"
      }
    }
  },
  "tools": {
    "enableAdditionalTools": ["generate_docs"]
  }
}

Google Slides

Add slides to an existing presentation or overwrite it entirely. Requires generate_slides in enableAdditionalTools.

targets.slides.presentationIdstringrequired

The Google Slides presentation ID (from the URL)

targets.slides.mode"APPEND" | "OVERWRITE"default "APPEND"

APPEND adds new slides at the end. OVERWRITE replaces all slides.

Example: Add slides to existing presentation
{
  "input": "Create 3 slides summarizing the key metrics from this page",
  "urls": ["https://example.com/quarterly-report"],
  "reuseArtifacts": {
    "mode": "force",
    "targets": {
      "slides": {
        "presentationId": "1xyz789_your_presentation_id",
        "mode": "APPEND"
      }
    }
  },
  "tools": {
    "enableAdditionalTools": ["generate_slides"]
  }
}

PDF Form Filling

Fill a PDF form template with data. Requires pdf_filling in enableAdditionalTools.

targets.pdfs.templateFileIdstring

Google Drive file ID of the PDF template to fill. The template should have fillable form fields.

Example: Fill a PDF form
{
  "input": "Fill out this job application form with the candidate data",
  "files": [
    {
      "displayName": "candidate-data.json",
      "uri": "https://firebasestorage.googleapis.com/.../data.json",
      "mimeType": "application/json"
    }
  ],
  "reuseArtifacts": {
    "mode": "force",
    "targets": {
      "pdfs": {
        "templateFileId": "1abc_pdf_template_file_id"
      }
    }
  },
  "tools": {
    "enableAdditionalTools": ["pdf_filling"]
  }
}

Combined Example: Multi-artifact workflow

You can target multiple artifact types in a single request:

Example: Update both Sheet and Doc
{
  "input": "Scrape competitor pricing, add to my tracking sheet, and append a summary to my report doc",
  "urls": ["https://competitor.com/pricing"],
  "reuseArtifacts": {
    "mode": "force",
    "targets": {
      "sheets": {
        "sheetId": "1abc_sheets_id",
        "tabTitle": "Competitor Pricing",
        "tabMode": "SAME_TAB"
      },
      "docs": {
        "docId": "1xyz_docs_id",
        "mode": "APPEND"
      }
    }
  },
  "tools": {
    "enableAdditionalTools": ["generate_sheets", "generate_docs"]
  }
}
Important notes:
  • You must have edit access to the target Google artifacts
  • API-key requests use the key owner’s connected Google account, or the account authorized by an explicit authToken override
  • For Sheets, new data is typically appended below existing rows
  • Use mode: "force" to guarantee the specified targets are used

rtrvr supports view-only live VNC streaming for any execution. Watch the browser in real-time, embed it in your app via iframe, or build custom viewers using the VNC websocket URL.

Use Cases
  • • Debugging: Watch exactly what the browser is doing during automation
  • • Demos: Show clients real-time browser activity
  • • Dashboards: Embed live views in internal tools
  • • Quality assurance: Monitor batch jobs visually

Key Concepts

trajectoryId = executionId

In VNC endpoints, executionId refers to your trajectoryId. They are the same identifier.

vncScope

"root" = main browser only (default).
"all" = main + all batch worker browsers.

shareKey

A secret token for public/share endpoints. Anyone with it can view (not control) the session until it expires.

Sessions

root = main browser session.
batch = worker sessions (when vncScope="all").

Step 1: Enable VNC on Execution

Add options.ui.enableVnc: true to your execute request. Pro tip: Generate your own trajectoryId upfront so you can request the embed URL immediately without waiting for the response.

Enable VNC on execution
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "trajectoryId": "0f3f2d33-0f6a-4d79-bb3d-56c2d4d7c2a1",
    "input": "Go to example.com and summarize the homepage",
    "urls": ["https://example.com"],
    "options": {
      "ui": {
        "enableVnc": true,
        "vncScope": "root"
      }
    }
  }'

Step 2: Get the Embed URL

Call POST /vnc/share with the executionId (same as your trajectoryId). This returns an embedUrl ready for iframe embedding.

POSThttps://api.rtrvr.ai/vnc/share
executionIdstringrequired

The trajectoryId from your execute request

rotatebooleandefault false

Set true to generate a new share key (invalidates previous share URLs)

Get embed URL
curl -X POST https://api.rtrvr.ai/vnc/share \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "executionId": "0f3f2d33-0f6a-4d79-bb3d-56c2d4d7c2a1",
    "rotate": false
  }'

Response

Share response
{
  "ok": true,
  "executionId": "0f3f2d33-0f6a-4d79-bb3d-56c2d4d7c2a1",
  "embedUrl": "https://vnc-embed.rtrvr.ai/vnc/embed/0f3f2d33-0f6a-4d79-bb3d-56c2d4d7c2a1#key=<SHARE_KEY>",
  "expiresAt": 1730000000
}

Step 3: Embed in Your App

Use the embedUrl directly in an iframe. The hosted page handles everything: loading the VNC viewer, connecting to the relay, and auto-refreshing tokens.

Embed in iframe
<iframe
  src="https://vnc-embed.rtrvr.ai/vnc/embed/0f3f2d33-0f6a-4d79-bb3d-56c2d4d7c2a1#key=YOUR_SHARE_KEY"
  style="width: 100%; height: 720px; border: 0; border-radius: 12px;"
  allow="clipboard-read; clipboard-write"
></iframe>
Why the share key is in the fragment (#key=...)

The share key is placed in the URL fragment so it's never sent to servers in HTTP requests by default. The embed page reads it client-side and sends it in an Authorization header when calling VNC endpoints.

Complete Integration Example

Full VNC integration (JavaScript)
const API_URL = "https://api.rtrvr.ai";
const API_KEY = "YOUR_API_KEY";

async function startExecutionWithVnc(input, urls) {
  // 1. Generate your own trajectoryId for immediate embed
  const trajectoryId = crypto.randomUUID();
  
  // 2. Start execution with VNC enabled
  const executePromise = fetch(`${API_URL}/agent`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      trajectoryId,
      input,
      urls,
      options: {
        ui: {
          enableVnc: true,
          vncScope: "root"  // or "all" for batch workers
        }
      }
    }),
  });
  
  // 3. Immediately request embed URL (don't wait for execute)
  const shareRes = await fetch(`${API_URL}/vnc/share`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ 
      executionId: trajectoryId,
      rotate: false 
    }),
  });
  
  const shareData = await shareRes.json();
  
  if (shareData.ok) {
    // 4. Embed the VNC view
    const iframe = document.createElement("iframe");
    iframe.src = shareData.embedUrl;
    iframe.style.cssText = "width:100%;height:720px;border:0;border-radius:12px;";
    iframe.allow = "clipboard-read; clipboard-write";
    document.getElementById("vnc-container").appendChild(iframe);
  }
  
  // 5. Wait for execution to complete
  const executeRes = await executePromise;
  const result = await executeRes.json();
  
  return { result, embedUrl: shareData.embedUrl };
}

// Usage
startExecutionWithVnc(
  "Scrape the top 5 articles from Hacker News",
  ["https://news.ycombinator.com"]
).then(({ result, embedUrl }) => {
  console.log("Execution complete:", result);
  console.log("VNC embed URL:", embedUrl);
});
Full VNC integration (Python)
import uuid
import requests
import threading

API_URL = "https://api.rtrvr.ai"
API_KEY = "YOUR_API_KEY"

def start_execution_with_vnc(input_text: str, urls: list[str]):
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json"
    }
    
    # 1. Generate your own trajectoryId
    trajectory_id = str(uuid.uuid4())
    
    # 2. Start execution in background thread
    def execute():
        return requests.post(
            f"{API_URL}/agent",
            headers=headers,
            json={
                "trajectoryId": trajectory_id,
                "input": input_text,
                "urls": urls,
                "options": {
                    "ui": {
                        "enableVnc": True,
                        "vncScope": "root"  # or "all" for batch workers
                    }
                }
            },
            timeout=300
        ).json()
    
    execute_thread = threading.Thread(target=execute)
    execute_thread.start()
    
    # 3. Immediately get embed URL
    share_res = requests.post(
        f"{API_URL}/vnc/share",
        headers=headers,
        json={
            "executionId": trajectory_id,
            "rotate": False
        }
    ).json()
    
    if share_res.get("ok"):
        print(f"VNC Embed URL: {share_res['embedUrl']}")
        print(f"Share key expires: {share_res['expiresAt']}")
    
    # 4. Wait for execution
    execute_thread.join()
    
    return {
        "trajectory_id": trajectory_id,
        "embed_url": share_res.get("embedUrl")
    }

# Usage
result = start_execution_with_vnc(
    "Go to example.com and take a screenshot",
    ["https://example.com"]
)
print(f"Embed in iframe: {result['embed_url']}")
Full VNC flow (cURL)
# 1. Start execution with VNC enabled (use your own trajectoryId)
TRAJECTORY_ID="$(uuidgen)"

curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"trajectoryId\": \"$TRAJECTORY_ID\",
    \"input\": \"Go to example.com and summarize\",
    \"urls\": [\"https://example.com\"],
    \"options\": {
      \"ui\": {
        \"enableVnc\": true,
        \"vncScope\": \"root\"
      }
    }
  }" &

# 2. Immediately get the embed URL (don't wait for execute)
curl -X POST https://api.rtrvr.ai/vnc/share \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"executionId\": \"$TRAJECTORY_ID\",
    \"rotate\": false
  }"

# Response contains embedUrl - use it in an iframe

Advanced: Build a Custom Viewer

For custom UIs, use the public VNC endpoints to list sessions and get websocket URLs. They take a Bearer token that is either the shareKey from the embed URL, or a Firebase ID token for the account that owns the execution.

List Sessions

GET/vnc-public/sessions?executionId={executionId}
List VNC sessions (public endpoint)
curl "https://vnc-embed.rtrvr.ai/vnc-public/sessions?executionId=0f3f2d33-0f6a-4d79-bb3d-56c2d4d7c2a1" \
  -H "Authorization: Bearer YOUR_SHARE_KEY"

# Response:
# {
#   "ok": true,
#   "executionId": "0f3f2d33-0f6a-4d79-bb3d-56c2d4d7c2a1",
#   "sessions": [
#     {
#       "sessionId": "0f3f2d33-0f6a-4d79-bb3d-56c2d4d7c2a1",
#       "kind": "root",
#       "batchIndex": null,
#       "state": "running",
#       "expiresAt": 1730001234
#     }
#   ]
# }

Get Viewer Token & WebSocket URL

GET/vnc-public/token?executionId={id}&sessionId={id}
Get VNC token and websocket URL
curl "https://vnc-embed.rtrvr.ai/vnc-public/token?executionId=0f3f2d33-0f6a-4d79-bb3d-56c2d4d7c2a1&sessionId=0f3f2d33-0f6a-4d79-bb3d-56c2d4d7c2a1" \
  -H "Authorization: Bearer YOUR_SHARE_KEY"

# Response:
# {
#   "ok": true,
#   "executionId": "0f3f2d33-0f6a-4d79-bb3d-56c2d4d7c2a1",
#   "sessionId": "0f3f2d33-0f6a-4d79-bb3d-56c2d4d7c2a1",
#   "wsUrl": "wss://relay.rtrvr.ai/vnc?token=<JWT>",
#   "expiresAt": 1730000600
# }
Token refresh: Viewer tokens expire 3 minutes after they are minted. The relay checks the token only when the websocket connects, so call /vnc-public/token again right before each connect attempt rather than holding one.

Authenticated VNC Endpoints (Owner Access)

These endpoints use your API key directly (no share key needed). Useful when you don't want to expose share links.

GET /vnc/sessionsendpoint

List sessions for an execution. Add ?withToken=1 to include wsUrl for each session.

GET /vnc/tokenendpoint

Get viewer token + wsUrl. Add ?createIfMissing=1 to auto-create the session doc.

POST /vnc/shareendpoint

Generate/rotate a share key and get embedUrl.

Authenticated session list with tokens
curl "https://api.rtrvr.ai/vnc/sessions?executionId=YOUR_TRAJECTORY_ID&withToken=1" \
  -H "Authorization: Bearer YOUR_API_KEY"

VNC Endpoint Reference

VNC endpoints are split across two services. Authenticated endpoints live on the main API (api.rtrvr.ai), while public/share-key endpoints and the embed page are served by the embed service (vnc-embed.rtrvr.ai). The correct embed-service host is returned in the embedUrl from POST /vnc/share.

EndpointHostAuthDescription
POST /vnc/shareapi.rtrvr.aiAPI KeyGet embedUrl + shareKey
GET /vnc/sessionsapi.rtrvr.aiAPI KeyList sessions (owner access)
GET /vnc/tokenapi.rtrvr.aiAPI KeyGet wsUrl token (owner access)
GET /vnc-public/sessionsvnc-embed.rtrvr.aiShare KeyList sessions (public/share)
GET /vnc-public/tokenvnc-embed.rtrvr.aiShare KeyGet wsUrl token (public/share)
GET /vnc/embed/:executionIdvnc-embed.rtrvr.aiShare Key (in #fragment)Hosted noVNC viewer page

Security Best Practices

  • • Treat shareKey like a password: Anyone with it can view your browser session
  • • Use fragment URLs: The #key=... format prevents the key from appearing in server logs
  • • Rotate when needed: Call POST /vnc/share with rotate: true to invalidate old links
  • • Use vncScope wisely: Only use "all" if you need to see batch worker browsers
  • • View-only: Share links cannot control the browser, only view it
Google tools: Connect Google Drive in your API key owner’s account. These examples use the saved connection; authToken is an optional override. Open Cloud → Sheets.
cURL
# ═══════════════════════════════════════════════════════════════
# BASIC EXAMPLES
# ═══════════════════════════════════════════════════════════════

# 1. Simple page summarization
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Summarize the main points of this page in 5 bullet points.",
    "urls": ["https://example.com/blog/ai-trends-2025"],
    "response": { "verbosity": "final" }
  }'

# 2. With JSON schema for structured output
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Extract article title, author, and publish date.",
    "urls": ["https://example.com/blog/article"],
    "schema": {
      "type": "object",
      "properties": {
        "title": { "type": "string" },
        "author": { "type": "string" },
        "publishDate": { "type": "string" }
      },
      "required": ["title"]
    }
  }'

# ═══════════════════════════════════════════════════════════════
# FILE INPUT EXAMPLES
# ═══════════════════════════════════════════════════════════════

# 3. Analyze a PDF document
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Extract key financial metrics from this quarterly report.",
    "files": [
      {
        "displayName": "Q3-Report.pdf",
        "uri": "https://firebasestorage.googleapis.com/v0/b/bucket/o/files%2Freport.pdf?alt=media&token=abc",
        "mimeType": "application/pdf"
      }
    ],
    "schema": {
      "type": "object",
      "properties": {
        "revenue": { "type": "string" },
        "profit": { "type": "string" },
        "growth_rate": { "type": "string" }
      }
    }
  }'

# 4. Analyze an image
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Describe this UI screenshot and identify usability issues.",
    "files": [
      {
        "displayName": "app-screenshot.png",
        "uri": "gs://my-bucket/screenshots/app.png",
        "mimeType": "image/png"
      }
    ]
  }'

# 5. Compare two documents
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Compare these contracts and list all differences.",
    "files": [
      {
        "displayName": "contract-v1.pdf",
        "uri": "https://firebasestorage.googleapis.com/.../v1.pdf?...",
        "mimeType": "application/pdf"
      },
      {
        "displayName": "contract-v2.pdf",
        "uri": "https://firebasestorage.googleapis.com/.../v2.pdf?...",
        "mimeType": "application/pdf"
      }
    ]
  }'

# ═══════════════════════════════════════════════════════════════
# DATA INPUT EXAMPLES
# ═══════════════════════════════════════════════════════════════

# 6. CSV enrichment (inline)
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "For each company, find their website and a one-sentence description.",
    "dataInputs": [
      {
        "description": "Companies to enrich",
        "format": "csv",
        "inline": "company\nOpenAI\nAnthropic\nGoogle DeepMind"
      }
    ],
    "response": { "verbosity": "steps" }
  }'

# 7. JSON array processing
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Classify each review as positive, negative, or neutral.",
    "dataInputs": [
      {
        "description": "Reviews",
        "format": "json",
        "inline": "[{\"text\":\"Great product!\"},{\"text\":\"Terrible support.\"},{\"text\":\"It works okay.\"}]"
      }
    ]
  }'

# 8. XLSX from URL
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Summarize the sales pipeline metrics.",
    "dataInputs": [
      {
        "description": "Sales pipeline",
        "format": "xlsx",
        "url": "https://example.com/data/pipeline.xlsx"
      }
    ]
  }'

# ═══════════════════════════════════════════════════════════════
# COMBINED: FILES + DATA + BROWSING
# ═══════════════════════════════════════════════════════════════

# 9. Complex workflow: Analyze resume + scrape job posting + match
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Analyze the resume, scrape the job posting, and score the candidate fit (1-10) with reasoning.",
    "urls": ["https://company.com/careers/senior-engineer"],
    "files": [
      {
        "displayName": "candidate-resume.pdf",
        "uri": "https://firebasestorage.googleapis.com/.../resume.pdf?...",
        "mimeType": "application/pdf"
      }
    ],
    "schema": {
      "type": "object",
      "properties": {
        "fitScore": { "type": "number" },
        "strengths": { "type": "array", "items": { "type": "string" } },
        "gaps": { "type": "array", "items": { "type": "string" } },
        "recommendation": { "type": "string" }
      }
    }
  }'

# ═══════════════════════════════════════════════════════════════
# GOOGLE SHEETS
# ═══════════════════════════════════════════════════════════════

# 10. Create new Google Sheet
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Scrape all products and save to Google Sheets.",
    "urls": ["https://example.com/products"],
    "tools": { "enableAdditionalTools": ["generate_sheets"] }
  }'

# 11. Append to existing Google Sheet
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Scrape new leads and add to my existing sheet.",
    "urls": ["https://linkedin.com/search/..."],
    "reuseArtifacts": {
      "mode": "force",
      "targets": {
        "sheets": {
          "sheetId": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
          "tabTitle": "Leads",
          "tabMode": "SAME_TAB"
        }
      }
    },
    "tools": { "enableAdditionalTools": ["generate_sheets"] }
  }'

# ═══════════════════════════════════════════════════════════════
# GOOGLE SLIDES
# ═══════════════════════════════════════════════════════════════

# 12. Create new presentation
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Create a 5-slide presentation summarizing key points. Include title, 3 content slides, and conclusion.",
    "urls": ["https://example.com/blog/industry-trends-2025"],
    "tools": { "enableAdditionalTools": ["generate_slides"] }
  }'

# 13. Append to existing presentation
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Add 3 slides with competitor analysis to the existing presentation.",
    "urls": ["https://competitor.com/products"],
    "reuseArtifacts": {
      "mode": "force",
      "targets": {
        "slides": {
          "presentationId": "1abc123_your_presentation_id",
          "mode": "APPEND"
        }
      }
    },
    "tools": { "enableAdditionalTools": ["generate_slides"] }
  }'

# ═══════════════════════════════════════════════════════════════
# GOOGLE DOCS
# ═══════════════════════════════════════════════════════════════

# 14. Create new Google Doc
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Create a detailed research report about this company including products, leadership, and news.",
    "urls": ["https://example.com/about", "https://example.com/news"],
    "tools": { "enableAdditionalTools": ["generate_docs"] }
  }'

# 15. Append to existing Google Doc
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Add a new section summarizing this weeks market news.",
    "urls": ["https://news.example.com/markets"],
    "reuseArtifacts": {
      "mode": "force",
      "targets": {
        "docs": {
          "docId": "1abc123_your_doc_id",
          "mode": "APPEND"
        }
      }
    },
    "tools": { "enableAdditionalTools": ["generate_docs"] }
  }'

# ═══════════════════════════════════════════════════════════════
# PDF GENERATION & FILLING
# ═══════════════════════════════════════════════════════════════

# 16. Generate new PDF
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Create a PDF invoice: 3x Widget Pro at $99 each, customer: Acme Corp.",
    "tools": { "enableAdditionalTools": ["generate_pdfs"] }
  }'

# 17. Fill PDF form template
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Fill the W-9 form with: Name: John Smith, Business: Smith Consulting LLC",
    "reuseArtifacts": {
      "mode": "force",
      "targets": {
        "pdfs": { "templateFileId": "1xyz_w9_template_id" }
      }
    },
    "tools": { "enableAdditionalTools": ["pdf_filling"] }
  }'

# ═══════════════════════════════════════════════════════════════
# WEB DASHBOARDS
# ═══════════════════════════════════════════════════════════════

# 18. Generate interactive dashboard
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Create an interactive dashboard showing sales by region with charts.",
    "dataInputs": [{
      "description": "Sales data",
      "format": "csv",
      "inline": "region,sales,month\nNorth,50000,Jan\nSouth,42000,Jan\nEast,38000,Jan"
    }],
    "tools": { "enableAdditionalTools": ["generate_websites"] }
  }'

# ═══════════════════════════════════════════════════════════════
# INTERACTIVE WORKFLOWS (ask_questions)
# ═══════════════════════════════════════════════════════════════

# 19. Enable follow-up questions
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Help me find the best flight from NYC to London.",
    "urls": ["https://flights.example.com"],
    "tools": { "enableAdditionalTools": ["ask_questions"] }
  }'

# 20. Continue after requires_input status
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Direct flights only, economy class, morning departure.",
    "trajectoryId": "exec_from_inputRequest_resume_executionId",
    "continuePlanning": true,
    "history": { "previousSteps": [] }
  }'

# ═══════════════════════════════════════════════════════════════
# MULTI-PHASE WORKFLOWS
# ═══════════════════════════════════════════════════════════════

# 21. Phase 1: Discovery
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Find the top 10 AI startups in healthcare.",
    "urls": ["https://techcrunch.com/tag/ai-healthcare"],
    "phase": 1
  }'

# 22. Phase 2: Enrichment (same trajectoryId)
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Get funding details and executives for each startup.",
    "trajectoryId": "traj_from_phase1",
    "phase": 2,
    "tools": { "enableAdditionalTools": ["generate_sheets"] }
  }'

# 23. Phase 3: Report
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Create a presentation with investment recommendations.",
    "trajectoryId": "traj_from_phase1",
    "phase": 3,
    "tools": { "enableAdditionalTools": ["generate_slides"] }
  }'

# ═══════════════════════════════════════════════════════════════
# CUSTOM TOOLS
# ═══════════════════════════════════════════════════════════════

# 24. Use custom/user-defined tools
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "Find leads and add them to our CRM.",
    "urls": ["https://linkedin.com/search/..."],
    "tools": {
      "mode": "allowlist",
      "userDefined": ["add_to_crm", "enrich_lead"]
    }
  }'
  # ═══════════════════════════════════════════════════════════════
# VNC LIVE VIEW EXAMPLES
# ═══════════════════════════════════════════════════════════════

# 25. Start execution with VNC enabled
TRAJECTORY_ID="$(uuidgen)"
curl -X POST https://api.rtrvr.ai/agent \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"trajectoryId\": \"$TRAJECTORY_ID\",
    \"input\": \"Navigate to example.com and fill out the contact form\",
    \"urls\": [\"https://example.com/contact\"],
    \"options\": {
      \"ui\": {
        \"enableVnc\": true,
        \"vncScope\": \"root\"
      }
    }
  }"

# 26. Get embeddable VNC URL
curl -X POST https://api.rtrvr.ai/vnc/share \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"executionId\": \"$TRAJECTORY_ID\",
    \"rotate\": false
  }"

# 27. List VNC sessions (authenticated)
curl "https://api.rtrvr.ai/vnc/sessions?executionId=$TRAJECTORY_ID&withToken=1" \
  -H "Authorization: Bearer YOUR_API_KEY"

# 28. Get VNC token (authenticated)
curl "https://api.rtrvr.ai/vnc/token?executionId=$TRAJECTORY_ID&sessionId=$TRAJECTORY_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"

# 29. List sessions with share key (public — served by the embed service)
curl "https://vnc-embed.rtrvr.ai/vnc-public/sessions?executionId=$TRAJECTORY_ID" \
  -H "Authorization: Bearer YOUR_SHARE_KEY"

# 30. Get VNC token with share key (public — served by the embed service)
curl "https://vnc-embed.rtrvr.ai/vnc-public/token?executionId=$TRAJECTORY_ID&sessionId=$TRAJECTORY_ID" \
  -H "Authorization: Bearer YOUR_SHARE_KEY"

YOUR NEXT RUN

Run the example on a real site.

Use Chrome for the page in front of you. Use Cloud when the run should continue on a schedule or across many pages.