---
name: runchat-canvas-api
description: Interact with a Runchat workflow canvas via REST API. Read, create, connect, update, and run nodes programmatically. Use when given a Runchat editor URL (runchat.com/editor?id=...) or asked to build/modify a workflow on a Runchat canvas.
---

# Runchat Canvas API

You have access to a Runchat canvas — a visual node-based workflow editor.
Use the tools below to read, create, connect, and run nodes on the canvas.

## Base URL

All tool calls are made via a single endpoint:

```
POST https://runchat.com/api/v1/{runchat_id}/canvas
Authorization: Bearer {api_key}
Content-Type: application/json

{
  "tool": "<tool_name>",
  "args": { ... }
}
```

The response is always:
```json
{ "result": { ... } }
```

## Important Notes

- **Ask before running**: The `run_nodes` tool triggers execution which may consume credits. Always confirm with the user before calling it.
- **Node types**: promptNode (LLM), inputNode (user input), codeNode (JavaScript), imageNode (media generation), noteNode (documentation), runChatNode (sub-workflow).
- **Workflow**: Typically: `get_canvas` → `read_nodes` → create/update/connect → `organize_nodes` → (optionally) `run_nodes`.
- **Connections**: Use `connect_nodes` to wire outputs of one node to inputs of another. Use `read_nodes` to discover handle names.
- **Published tools**: Use `search_tools` to find published tools/runchats by keyword (returns each tool's runchat_id and input/output parameter names). Then `execute_tool` to run one directly and get its outputs, `inspect_tool` to read how it's built, or `place_tool` (tool_id) to place it on this canvas. For `inspect_tool`/`execute_tool` the `runchat_id` in `args` names the TOOL to act on — the `{runchat_id}` in the URL is only the canvas/auth scope (it is ignored by these tools).

**Common handles:**
- promptNode — inputs: `messages` (any type), `prompt` (string) | outputs: `messages`
- codeNode — inputs: `code` (string) | outputs: `result`
- inputNode — outputs: `content`
- imageNode — inputs: from `get_model_params` | outputs: `image`

---

## Node Creation Tools

### `create_prompt_node`

Create an LLM node (promptNode). Inputs: 'prompt' (the task), 'messages' (context, prior outputs, images — any type). Outputs: 'messages' (the conversation), 'artifacts' (structured output when output_format is set).

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `prompt` | string | yes | The task. One task only — N values fed in later run the node N times. Persona goes in `instruction`. Real line breaks, never literal \n. |
| `model_id` | string | no | Optional LLM api_id (list_models type 'llm'). Omit for the default. |
| `instruction` | string | no | Optional system prompt / persona (hidden input). |
| `output_format` | string | no | Optional output contract on the 'artifacts' handle: "list" (array of strings), "html" (complete document — always for a website/page), "code" (one code block), or a JSON schema string. Omit for plain text. |
| `tools` | string[] | no | Optional runchat_ids of published tools the model may call while it runs. |
| `label` | string | no | Optional display label for the node. |
| `input_depths` | object | no | Optional matching mode per input, keyed by input name: "each" (one run per item), "all" (whole list as ONE grouped value in a single run), "flatten" (unwrap one nesting level); 0/1/-1 accepted. promptNode "messages" and array-typed model params default to "all". See DATA MATCHING. |
| `position` | object | no | Optional placement. PREFER the relative form ({right_of|left_of|below|above: nodeId, gap?}) — positioned server-side from the anchor's real size. Raw {x,y} = offsets from the top-left of the user's viewport. Omit to leave placement automatic (or, on update, unchanged). |

### `create_input_node`

Create a user-input widget (inputNode); its value is on the 'content' output. Pick the widget with `format`; one starting value with `value`, a LIST with `values` (one node holds the whole list — never N nodes), `options`/`selected` for a select. Image values are hosted URLs, never base64/data URLs.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `format` | `string` | `slider` | `toggle` | `image` | `file` | `select` | `button` | `code` | no | 'string' text (default), 'slider' number, 'toggle' boolean, 'image', 'file', 'select' dropdown, 'button' trigger, 'code' editor. |
| `value` | string | no | Initial single value (an image URL for format 'image'). Ignored for 'select'. |
| `values` | string[] | no | Initial LIST held by this one node (e.g. 3 prompts, 5 image URLs); downstream nodes run once per item. |
| `options` | string[] | no | format 'select' only: the dropdown options. Omit for other formats. |
| `selected` | string | no | format 'select' only: initially selected option (default: first). |
| `selected_items` | integer[] | no | With `values`: 0-based indices of the items that flow downstream (e.g. [0] sends only the first); the rest stay unselected in the collection. Omit to send all. |
| `minimum` | number | no | format 'slider' only: lowest value (set with maximum). |
| `maximum` | number | no | format 'slider' only: highest value. Set the range whenever the value has known bounds (e.g. 0-100 percent). |
| `step` | number | no | format 'slider' only: increment (e.g. 0.1). |
| `label` | string | no | Optional display label for the node. |
| `position` | object | no | Optional placement. PREFER the relative form ({right_of|left_of|below|above: nodeId, gap?}) — positioned server-side from the anchor's real size. Raw {x,y} = offsets from the top-left of the user's viewport. Omit to leave placement automatic (or, on update, unchanged). |

### `create_image_node`

Create a media generation node (imageNode) — image, video, 3D or audio — for a model_id from AVAILABLE MODELS. Call get_model_params first; its parameter names are the `params` keys. Output on the 'image' handle. Load use_skill "image-prompting" before your first image node if not already loaded. Image inputs are hosted URLs, never base64/data URLs. Image inputs reject PDFs: view_image url#page=N with format "render" returns that page as a PNG URL to pass instead.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `model_id` | string | yes | Model api_id from the AVAILABLE MODELS catalog. |
| `params` | object | no | Values keyed by the parameter names from get_model_params, e.g. { "prompt": "a red chair", "aspect_ratio": "16:9" }. |
| `label` | string | no | Optional display label for the node. |
| `input_depths` | object | no | Optional matching mode per input, keyed by input name: "each" (one run per item), "all" (whole list as ONE grouped value in a single run), "flatten" (unwrap one nesting level); 0/1/-1 accepted. promptNode "messages" and array-typed model params default to "all". See DATA MATCHING. |
| `position` | object | no | Optional placement. PREFER the relative form ({right_of|left_of|below|above: nodeId, gap?}) — positioned server-side from the anchor's real size. Raw {x,y} = offsets from the top-left of the user's viewport. Omit to leave placement automatic (or, on update, unchanged). |

### `create_code_node`

Create a code node (codeNode). Load the language's skill first (use_skill) if not already loaded — it defines the sandbox contract. Code runs on the SERVER only when the node is run. Output on the 'result' handle, or on the handles declared in `outputs` when the code returns an object with those keys. `inputs` declares named input handles, read in code as same-named variables.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `language` | `javascript` | `html` | `rhino-python` | `blender-python` | `revit-csharp` | `archicad-python` | no | 'javascript' (default): sandboxed JS/TS for API calls and data transforms (ENV.key for credentials; multi-file and npm via create_files). 'html': a page rendered in a live preview iframe — app frontends live here (window.runchat); declared inputs inject as globals and re-render the preview. CAD languages run in the connected CAD application via the bridge. |
| `code` | string | no | The source code. |
| `inputs` | object | no | Optional named inputs with defaults, e.g. { "shape": "cube" }. Each is a global variable of that exact name in the code (`shape`, not `inputs.shape`). A list default (e.g. { "files": ["a.png", "b.png"] }) arrives as the whole array in one run. |
| `outputs` | string[] | no | Optional output handle names, e.g. ["summary", "imageUrl"] — ONLY when the code returns an object with exactly these keys; each becomes connectable immediately. Omit for a plain return value (lands on "result"). |
| `label` | string | no | Optional display label for the node. |
| `input_depths` | object | no | Optional matching mode per input, keyed by input name: "each" (one run per item), "all" (whole list as ONE grouped value in a single run), "flatten" (unwrap one nesting level); 0/1/-1 accepted. promptNode "messages" and array-typed model params default to "all". See DATA MATCHING. |
| `position` | object | no | Optional placement. PREFER the relative form ({right_of|left_of|below|above: nodeId, gap?}) — positioned server-side from the anchor's real size. Raw {x,y} = offsets from the top-left of the user's viewport. Omit to leave placement automatic (or, on update, unchanged). |

### `create_note`

Create a markdown note node (noteNode) — documentation, findings, links, images, or checklists (`- [ ] item` renders as a tickable checkbox). Never empty.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `content` | string | yes | The markdown content of the note. |
| `label` | string | no | Optional display label for the node. |
| `position` | object | no | Optional placement. PREFER the relative form ({right_of|left_of|below|above: nodeId, gap?}) — positioned server-side from the anchor's real size. Raw {x,y} = offsets from the top-left of the user's viewport. Omit to leave placement automatic (or, on update, unchanged). |

### `place_tool`

Place a published tool (from search_tools) on the canvas. Default: one collapsed reusable node with auto-configured inputs — right when the tool is one step in the user's workflow. expand:true copies its full internal node graph as editable nodes to ADAPT its internals; the copy includes the tool's example input nodes, so wire your own data in and delete the examples. If the tool's published inputs don't cover the data you need (e.g. the user's image), it is an example built for manual input — place it with expand:true and adapt the copies.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `tool_id` | string | yes | The runchat_id of the published tool (from search_tools). |
| `expand` | boolean | no | Copy the tool's internal node graph as editable nodes instead of one collapsed node. |
| `inputs` | object | no | Optional values keyed by the tool's input parameter names. |
| `input_depths` | object | no | Optional matching mode per input, keyed by input name: "each" (one run per item), "all" (whole list as ONE grouped value in a single run), "flatten" (unwrap one nesting level); 0/1/-1 accepted. promptNode "messages" and array-typed model params default to "all". See DATA MATCHING. |
| `position` | object | no | Optional placement. PREFER the relative form ({right_of|left_of|below|above: nodeId, gap?}) — positioned server-side from the anchor's real size. Raw {x,y} = offsets from the top-left of the user's viewport. Omit to leave placement automatic (or, on update, unchanged). |

### `create_artifact_node`

Create an artifact node (artifactNode) that saves ('create') or fetches ('get') an artifact AS A WORKFLOW STEP — only when the workflow must regenerate the artifact each time it runs; otherwise use create_artifact / update_artifact directly. Type is inferred from content (markdown → blog, HTML → website, HTML using window.runchat → app). Author app HTML in an html code node first and connect its result into `content`. use_skill "app-artifact" before building one.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `operation` | `create` | `get` | no | 'create' (default) saves content; 'get' fetches by id. |
| `name` | string | no | Artifact name (create mode). |
| `content` | string | no | Markdown (→ blog), HTML (→ website) or window.runchat app HTML (→ app). |
| `artifact_id` | string | no | Existing artifact id: required for 'get'; in create mode makes every run UPDATE that artifact in place (stable URL). |
| `folder` | string | no | Optional folder path, e.g. "myFolder/subFolder". |
| `label` | string | no | Optional display label for the node. |
| `position` | object | no | Optional placement. PREFER the relative form ({right_of|left_of|below|above: nodeId, gap?}) — positioned server-side from the anchor's real size. Raw {x,y} = offsets from the top-left of the user's viewport. Omit to leave placement automatic (or, on update, unchanged). |

---

## Canvas Tools

### `get_canvas`

Overview of the canvas: node ids, types, labels, positions and edges, plus the runchat_id (also the published tool id) and latest published version (null if unpublished). 50 nodes per page — when has_more is true call again with next_offset. Not for verifying nodes you just created.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `offset` | number | no | Pagination offset (next_offset from the previous page). |

### `read_nodes`

Full state of nodes by id: each parameter's name, type, label, status and data (truncated to data_limit chars; original lengths are reported). Use it for values, handle names and node data.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `node_ids` | string[] | yes | Node ids to read. |
| `data_limit` | number | no | Max characters of each parameter's data (default 4000; 0 = untruncated). |
| `data_offset` | number | no | Character offset into each parameter's data, to page long values. |

### `connect_nodes`

Connect an output handle of one node to an input handle of another — one edge, or many via `edges`. Give source_handle/target_handle unless the match is unambiguous; ambiguous calls return the available handles. Common handles — promptNode in: "prompt", "messages" (any type) / out: "messages", "artifacts"; codeNode in: "code" / out: "result" plus declared outputs; inputNode out: "content"; imageNode in: from get_model_params (prompt, image_url, first_frame_url…) / out: "image". Hidden parameters (instruction, format) cannot be connected. An edge overwrites the target input's whole value — never also use a {{nodeId.param}} template there.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `source_node_id` | string | no | Source node id (data flows from here). |
| `target_node_id` | string | no | Target node id (data flows to here). |
| `source_handle` | string | no | Output handle on the source (e.g. 'content', 'image', 'messages'). |
| `target_handle` | string | no | Input handle on the target (e.g. 'prompt', 'image_url'). |
| `edges` | object[] | no | Several edges in one call (preferred when wiring a workflow), each with the same fields as a single call. Top-level fields are then ignored; failures are reported per edge. |

### `update_node`

Update an existing node's label, model, output_format, input values, input_depths, published params, position or lock state. Not for code — use edit_file. artifactNode settings (operation_mode, write_behavior, auto_publish, visibility) go through `inputs`. Image inputs are hosted URLs, never base64.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `node_id` | string | yes | The node id. |
| `label` | string | no | New display label. |
| `model_id` | string | no | New model api_id (promptNode, codeNode or imageNode). |
| `output_format` | string | no | promptNode only: "list", "html", "code", a JSON schema string, or "none" for plain text (as create_prompt_node). Re-run the node afterwards. |
| `inputs` | object | no | Input values keyed by parameter name (read_nodes lists them); strings or arrays. codeNode: a new key creates a new input handle. promptNode: keys must match existing inputs (model settings like reasoning_effort route to the model config); structured output goes through output_format. Select inputNode: { "input": { "data": ["opt1", "opt2"], "selected": "opt1" } }. |
| `input_depths` | object | no | Optional matching mode per input, keyed by input name: "each" (one run per item), "all" (whole list as ONE grouped value in a single run), "flatten" (unwrap one nesting level); 0/1/-1 accepted. promptNode "messages" and array-typed model params default to "all". See DATA MATCHING. |
| `publish_params` | string[] | no | imageNode only: model parameter names to expose as connectable input handles (e.g. ["seed"]) so upstream nodes can drive them; each is seeded from its current config value. |
| `position` | object | no | Optional placement. PREFER the relative form ({right_of|left_of|below|above: nodeId, gap?}) — positioned server-side from the anchor's real size. Raw {x,y} = offsets from the top-left of the user's viewport. Omit to leave placement automatic (or, on update, unchanged). |
| `locked` | boolean | no | true pins the node: it won't recompute and run_nodes skips it, freezing its output (protects reference nodes). false unlocks. |
| `selected_items` | integer[] | no | inputNode only: 0-based indices of the list items that flow downstream. Omit to keep the current selection. |

### `organize_nodes`

Auto-layout nodes by their connections, avoiding overlaps. Call once after creating and connecting. Pass the ids you created — connected nodes are included automatically; nodes the user positioned stay put as anchors.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `node_ids` | string[] | yes | Node ids to organize (connected nodes are included). |

### `run_nodes`

Execute the listed nodes in dependency order. Unlisted upstream nodes do NOT run — list every node that must recompute. Returns per-node status and, per output handle, the item count and type produced. If the estimated cost exceeds this turn's auto-run budget nothing runs and status is "approval_required" with estimated_credits — get the user's approval, then re-call with approved_credits.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `node_ids` | string[] | yes | Every node that must recompute. |
| `approved_credits` | number | no | The estimated_credits from an "approval_required" response, only after the user explicitly approved it. |
| `session` | string | no | Optional CAD session id for bridge code nodes when several CAD windows are open (the result lists them). Default: most recently connected. |

### `delete_nodes`

Delete nodes (and their edges) from the canvas.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `node_ids` | string[] | yes | Array of node IDs to delete. |

### `delete_edges`

Delete edges — disconnect nodes without deleting them.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `edge_ids` | string[] | yes | Array of edge IDs to delete (from get_canvas or connect_nodes). |

---

## Tool Discovery

### `search_tools`

Search published Runchat tools and your own saved runchats by keyword. Returns runchat_id, name, description and input/output parameter names — enough to execute_tool, place_tool or inspect_tool. Call it whenever you need a capability you don't have built in; pass several phrasings in `queries` in ONE call rather than searching repeatedly.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | no | Keywords to match against tool names and descriptions (e.g. 'remove background', 'pdf to text'). Omit to list popular tools. |
| `queries` | string[] | no | Up to 3 alternative phrasings (outcome, technique, subject) searched together; results are merged and deduped. |
| `limit` | number | no | Max results to return. Defaults to 8, max 25. |

### `inspect_tool`

How a published tool is built: its inputs/outputs (names + precise handles — the contract for execute_tool and app UIs) and a digest of its internal nodes and connections. Omit runchat_id for the CURRENT canvas's published contract. Not needed to run (execute_tool) or adapt (place_tool expand:true) a tool.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `runchat_id` | string | no | The runchat_id of the tool to inspect (from search_tools). Omit to inspect the current canvas's own published tool. |

### `execute_tool`

Run a published tool directly and return its outputs (spends credits; 'run' permission). Call it straight after search_tools with inputs keyed by the parameter names it returned; unmatched inputs are reported. Image outputs are attached to your next turn automatically — no view_image needed. A tool whose published inputs don't cover the data you need (e.g. no image input) is an EXAMPLE built for manual input — adapt it with place_tool expand:true instead of executing it.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `runchat_id` | string | yes | The runchat_id UUID (from search_tools or publish_runchat), never the /t/ page slug — slugs change on re-publish. |
| `inputs` | object | no | Values keyed by input parameter name, e.g. { "query": "eames lounge chair" }; strings or arrays of strings. When several inputs share a name, an array distributes across them positionally, or key by the precise handle ("param_nodeId"). Outputs come back by exact handle and by base name. |
| `version_num` | number | no | Optional specific published version to run. Defaults to the latest released version. |

---

## Code Editing Tools

### `edit_file`

Edit one file in a code or note node (a note is the single file 'note.md'). Targeted edit: pass old_text (exact, unique in the file) and new_text; empty new_text deletes the span. Full overwrite: pass only new_text. read_files first. Do not also paste the file content in your reply.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `node_id` | string | yes | The ID of the code or note node. |
| `file` | string | yes | File path, as returned by read_files. |
| `old_text` | string | no | Exact unique substring to replace (add context if not unique). Omit to overwrite the whole file. |
| `new_text` | string | yes | Replacement text, or the full new file when old_text is omitted. |

### `create_files`

Add files to a JavaScript or html code node: package.json for npm dependencies, modules, assets. JavaScript: extra server modules (the node upgrades to the bundler sandbox). Html: browser ES modules and stylesheets referenced from index.html via <script type="module" src="/src/main.js"> / <link rel="stylesheet">, bundled into one page at render time (import CSS via <link>, never from JS). Errors on existing paths (use edit_file); the entry file already exists.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `node_id` | string | yes | The ID of the JavaScript code node. |
| `files` | object[] | yes | Files to create. Paths must not collide with the entry file or any existing file. |

### `delete_files`

Delete one or more non-entry files from a JavaScript or html code node. Cannot delete the entry file.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `node_id` | string | yes | The ID of the JavaScript code node. |
| `files` | string[] | yes | Paths of files to delete. |

---

## Code Context Tools

### `read_files`

Read a code or note node's files: path, line-numbered content, length. Pass `files` for specific paths; omit to read everything (do this first when you don't know the paths). Notes have one file, note.md.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `node_id` | string | yes | The code or note node id. |
| `files` | string[] | no | Optional specific paths; omit for all files. |

### `read_status`

A code node's current errors, status, preview and worker messages — for debugging a failing run.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `node_id` | string | yes | The ID of the code node to check. |

---

## Media Tools

### `get_model_params`

Parameter names, types and defaults for one or more models — use them as the `params` keys for create_image_node. Pass model_id or model_ids (from AVAILABLE MODELS or list_models). Once per model.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `model_id` | string | no | A single api_id of the model (from the AVAILABLE MODELS catalog or list_models) |
| `model_ids` | string[] | no | An array of api_ids to fetch details for multiple models at once |

### `list_models`

List the user's models (id, name, modality, tags, cost). type 'llm' for create_prompt_node model ids beyond the catalog picks; 'media' (default) repeats the AVAILABLE MODELS catalog, so only call it to re-check.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `type` | `media` | `llm` | `all` | no | 'media' (default) = generation models. 'llm' = language models for promptNodes. 'all' = both. |
| `query` | string | no | Optional case-insensitive filter on model name, id, or tags. |
| `modality` | string | no | Optional modality filter (e.g. image, video, audio, 3d, llm). |

---

## Vision Tools

### `view_image`

See an image, webpage, PDF page or html node render. `url`: an image you haven't seen yet (not one already in the conversation); SVGs are rasterised (format 'source' returns the markup). A webpage URL returns a viewport screenshot (fetch_page to READ text); a PDF URL attaches the whole document if your model reads files; url#page=N with format 'render' (or any PDF url when your model can't read files) screenshots that page as a PNG for image models. `node_id`: screenshot an html code node's rendered preview — the only way to SEE what your HTML/Three.js renders. If your model can't see images, pass `question` and a vision model answers in text.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `url` | string | no | Image, webpage or PDF URL (PNG, JPEG, GIF, WebP, SVG). Either url or node_id. |
| `node_id` | string | no | An html code node id: its current document (inputs injected) is rendered headlessly and captured at first paint. |
| `format` | `render` | `source` | no | SVG: 'render' (default) rasterises to PNG; 'source' returns the XML markup. PDF: 'render' returns the #page=N page as a PNG instead of attaching the document. |
| `question` | string | no | Optional question about the image, answered by a vision model when your own model can't see images. |
| `compare_to` | string | no | Optional url of the image `url` was edited or rendered from (e.g. the user's screenshot). Returns one sheet: compare_to | url | both overlaid at 50%, so a moved camera, changed geometry or added objects show as ghosting. |

---

## Web Tools

### `web_search`

Web search: title/url/snippet results, 10 credits per call. For reference IMAGES use search_reference_images (1 credit, direct image URLs); for specialised search (papers, crawling, extraction) find a tool with search_tools.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | yes | The search query. Use a few specific keywords. |
| `count` | number | no | Number of results to return (1-20, default 8). |
| `freshness` | `pd` | `pw` | `pm` | `py` | no | Only return results from the past day (pd), week (pw), month (pm), or year (py). Omit for all time. |

### `search_reference_images`

Real reference photographs as direct image URLs (title, source, page url, photographer). The default for visual precedent — style, material, lighting, mood — at 1 credit per call (web_search is 10 and returns pages). URLs work directly as image inputs. For images from one SPECIFIC site, fetch_page that site and view_image candidates instead.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | yes | A few concrete keywords (subject, material, light). |
| `count` | number | no | Results to return (1-12, default 6). |
| `sources` | string[] | no | Default ['unsplash','pexels'] (stock, 1 credit). ['archdaily','divisare','unsplash'] for architecture/interior/landscape. Add 'brave' only for SPECIFIC named things (a product, a named building) — it bills the call at 10 credits. |

### `search_docs`

Search the Runchat documentation (docs.runchat.com): matching sections with urls. Free. Use BEFORE answering anything about the product itself or when the user is stuck in the UI. fetch_page a result url for the whole page.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `query` | string | yes | A few specific keywords (e.g. 'publish workflow as tool', 'rhino plugin install'). |
| `count` | number | no | Sections to return (1-10, default 5). |

### `fetch_page`

Fetch a webpage in a headless browser (JavaScript executed). 'markdown' (default) returns readable text with links; 'links' the outbound links; 'html' the raw markup. Long pages come in windows — pass the returned next_offset as offset to continue. For visual appearance use view_image on the URL.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `url` | string | yes | The http(s) URL to fetch. |
| `format` | `markdown` | `html` | `links` | no | 'markdown' (default), 'links' or 'html'. |
| `offset` | number | no | Character offset to continue a long page (next_offset from the previous call). |
