Hosted tool guide
The hosted tools reference is generated from the application’s capability module and answers which tools exist. This page answers how to call them. Every parameter below is taken from the tool’s registered input schema, so the types, bounds, defaults, and required-ness are the ones the server actually enforces.
Four tools here — guide_search, guide_get, canvas_validate, and canvas_apply_batch — are the load-bearing ones for safe canvas work and had no documentation before this page.
How to read the parameter tables
- Required means the schema has no default and no
.optional(). A missing required parameter fails input validation before the handler runs, and the error names the exact field. - Every string bound is a character count. Every numeric bound is inclusive.
- Tools that write take an
idempotencyKeyof 8–200 characters. Reuse it only when retrying the identical payload; a changed intent needs a new key. - A tool call that fails scope checks returns
insufficient_scopewith HTTP403and aWWW-Authenticatechallenge naming what to reconnect with. It never partially applies.
Three tools can incur provider cost: run_canvas_workflow, generate_image_in_canvas, and generate_video. Get explicit approval in the current turn before calling one, and never retry one automatically after a failure.
guide_search
Search the canonical Gavana Canvas Agent Guide. This is the first call an agent should make in a session, and the call to make again whenever an operation is unfamiliar.
The guide is served from the application, not from documentation, so it cannot drift from the behaviour it describes. It is versioned — guide version 1.0.0 at the time of writing — and every result carries that version.
Read-only. Requires no scope at all — it works on a token with the narrowest possible grant. No provider cost.
Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
query | string, max 240 chars | No, defaults to "" | Free text. An empty query returns every topic, which is a cheap way to see the whole corpus. |
limit | integer, 1–10 | No, defaults to 10 | Maximum results. The tool’s input schema enforces the range, so a value outside 1–10 is rejected as invalid input rather than clamped. |
What it returns
{
"guideVersion": "1.0.0",
"indexUri": "gavana://guides/canvas/v1/index",
"query": "delete a node safely",
"results": [
{
"id": "existing-canvases",
"title": "Editing Existing Canvases Safely",
"description": "Preserve user structure, concurrent edits, and unrelated content while making scoped changes.",
"uri": "gavana://guides/canvas/v1/existing-canvases",
"score": 21
}
],
"availableGuideIds": ["getting-started", "notes-text-sections", "..."]
}availableGuideIds is always the complete list of topic IDs, regardless of how the search scored. If a search returns nothing useful, pick an ID from that list and call guide_get directly.
How matching works
The query is lowercased and split on non-alphanumeric characters. Tokens of one character are discarded. Each surviving token scores against each topic:
| Match location | Points |
|---|---|
| Topic ID | 12 |
| Title | 8 |
| Keywords | 5 |
| Description | 3 |
| Body text | 1 |
Topics scoring zero are dropped, results sort by score then by ID, and the list is capped at limit and at 10 overall. This is deterministic lexical scoring — there is no embedding model behind it, so exact vocabulary matters. Searching overlap finds the layout topic; searching things bumping into each other does not.
The ten topics
| ID | Covers |
|---|---|
getting-started | The required inspect → guide → validate → edit → review sequence, and the non-negotiable safety rules |
notes-text-sections | When to use a sticky, a text node, or a Section; the Section JSON shape |
sections-layout | Placement, spacing, bounding-box maths for additions, the “add is not reorganise” rule |
connections | Direction, the four connection modes, batch-local client: references |
prompt-lists | Prompt List node shape, listItems, per-row referenceBindings |
generated-assets | Preparing media nodes, paid execution boundaries, what “durable” actually means |
existing-canvases | The preservation contract, 409 handling, idempotency key reuse |
paid-action-safety | The intent boundary and the retry boundary |
validation-recovery | Reading findings, correcting invalid batches, evidence-based review |
examples-common-mistakes | Copyable good patterns and eleven named anti-patterns |
Worked example
{ "name": "guide_search", "arguments": { "query": "section header overlap layout", "limit": 3 } }Returns sections-layout and notes-text-sections at the top. Follow with guide_get on the winner.
When it fails
| Symptom | Cause | Fix |
|---|---|---|
Input validation error naming query | Query longer than 240 characters | Shorten it. Long natural-language questions score no better than three good keywords. |
Empty results array | No token matched anything | Retry with vocabulary from the topic table above, or read availableGuideIds and call guide_get. |
guide_get
Read one full guide topic as Markdown.
Read-only. Requires no scope. No provider cost.
Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
guideId | string, 1–600 chars | Yes | A topic ID (existing-canvases), the literal string index, or a full gavana://guides/canvas/v1/... URI. |
What it returns
{
"id": "paid-action-safety",
"title": "Paid Action Safety",
"description": "Separate preparation from execution and prevent accidental or repeated provider charges.",
"uri": "gavana://guides/canvas/v1/paid-action-safety",
"mimeType": "text/markdown",
"guideVersion": "1.0.0",
"markdown": "# Paid Action Safety\n\nGuide version: 1.0.0\n\n## Intent boundary\n..."
}Passing index returns the guide index — a Markdown list of every topic with its description and URI — with id set to index.
Resources versus tools
A client that supports MCP resources can list and read the same content under gavana://guides/canvas/v1/, including the index at gavana://guides/canvas/v1/index. The two paths return identical Markdown and the same guide version, so use whichever your client supports. Tool-only clients lose nothing by using guide_search and guide_get.
Worked example
{ "name": "guide_get", "arguments": { "guideId": "index" } }Then follow a URI straight from the index:
{ "name": "guide_get", "arguments": { "guideId": "gavana://guides/canvas/v1/connections" } }When it fails
| Symptom | Cause | Fix |
|---|---|---|
Error code unknown_canvas_guide | The ID or URI does not exist | The error message lists every valid guide ID. Pick one from it — do not retry with a variation. |
Input validation error naming guideId | Empty string, or longer than 600 characters | Send a real topic ID. |
canvas_validate
Lint a canvas, or dry-run a proposed batch of operations against the real server-side engine without applying it. This is the single most useful tool on the hosted surface and the one most often skipped.
Read-only. Requires canvas:read. No provider cost.
Validation never mutates the canvas and never consumes an idempotency key. You can call it as many times as you like.
Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
canvasId | string, 1–600 chars | Yes | A canvas: handle. A plain canvas id works for a canvas you own. |
baseRevision | string, 1–200 chars | No | The revision from canvas_get. Only meaningful together with operations. If the canvas has moved on, validation returns 409 instead of validating a stale proposal. |
operations | array of objects, max 200 | No | Proposed canvas_apply_batch operations. Presence of this array is what switches the tool into dry-run mode. |
Two modes
Audit mode — omit operations. The server reads the canvas and lints the current graph.
Dry-run mode — pass operations. The server runs the canonical operation engine with validateOnly set, producing the graph that would exist, then lints that. Nothing is persisted and no idempotency key is consumed.
What it returns
Audit mode:
{
"guideVersion": "1.0.0",
"scope": "current",
"canvas": {
"handle": "canvas:abc123:my-canvas",
"revision": "rev_00291",
"nodeCount": 34,
"connectionCount": 21
},
"summary": {
"errors": 0,
"warnings": 2,
"info": 1,
"truncated": false,
"passed": true,
"reviewRequired": true
},
"findings": [
{
"code": "node_overlap",
"severity": "warning",
"message": "node:a1b2 overlaps node:c3d4.",
"nodeHandles": ["node:a1b2", "node:c3d4"],
"guideUri": "gavana://guides/canvas/v1/sections-layout"
}
]
}Dry-run mode returns the same validation shape plus three extra keys: currentCanvas (the graph as it stands today), proposal (what the operations would produce), and destructiveImpact.
Reading the summary
summary.passed is true when there are zero errors. It is not a green light on its own.
summary.reviewRequired is true when there are errors, or warnings, or findings were truncated. Treat reviewRequired: true as “a human or the agent must look at this before writing”, and treat a warning you intend to keep as something to explain rather than silently ignore.
summary.truncated means findings hit a per-code cap of 50 and the list is incomplete; truncatedCodes names which codes were cut. Overlap detection also gives up and marks itself truncated on very large or very spread-out canvases — it buckets nodes into a 2,048-unit grid, refuses any node spanning more than 64 cells, and stops after 100,000 pairwise comparisons.
Every finding code
| Code | Severity | Means |
|---|---|---|
invalid_node_geometry | error | Position or size is non-finite, non-positive, or out of range. Coordinates must be within ±10,000,000; width and height must be positive and no larger than 10,000. |
broken_connection | error | A connection points at a node that does not exist. |
unclear_connection | error | A node connects to itself, a first-frame/last-frame edge does not run image → video, or a video target has more than one first-frame or last-frame edge. |
unclear_connection | warning | A Section is used as a workflow endpoint, a list edge has no List at either end, or a reference edge starts somewhere other than an image. |
unclear_connection | info | Two media nodes are connected with no mode. Confirm that a plain transformation flow is what you meant. |
duplicate_connection | warning | Two or more visible connections share the same source, target, and mode. |
node_overlap | warning | Two ordinary nodes overlap by more than 8 units on both axes. Sections and deliberate batch overlays are exempt. |
malformed_section | error | A node has metadata.isSection set but is not a text node of at least 200 × 160. |
fake_section_header | warning | A wide, short, or large-font text node with no connections is imitating a Section header. Use a real Section. |
unconnected_generated_output | warning | A generated image or video has no incoming connection and no output parent, so its lineage is unreadable. |
orphan_media_placeholder | warning | An empty media node with no stored result and no relationship to a prompt, source, List, or stage. |
broken_generated_lineage | error | A lineage field — batchRootId, listOutputId, firstFrameNodeId, referenceNodeIds, a listItems reference binding, and others — points at a node that is not on the canvas. |
proposed_destructive_change | warning | Dry-run only. The batch deletes a node or connection. The message names the node and how many attached connections go with it. |
Every finding carries a guideUri pointing at the guide topic that explains the rule, so an agent can resolve a finding without asking.
destructiveImpact
Present only in dry-run mode. It is the deletion summary you should show a human before applying anything:
{
"deletedNodeHandles": ["node:a1b2"],
"deletedConnectionHandles": ["connection:x9y8"],
"mediaNodeHandles": ["node:a1b2"],
"generatedNodeHandles": ["node:a1b2"],
"findings": []
}mediaNodeHandles flags deletions of image or video nodes. generatedNodeHandles flags deletions of nodes that were produced by a generation run, an AI job, an Action, or a variation — in other words, work that cost something to make. A non-empty generatedNodeHandles deserves an explicit confirmation, not a summary line.
Worked example
Audit an existing canvas before proposing anything:
{ "name": "canvas_validate", "arguments": { "canvasId": "canvas:abc123:brand-refresh" } }Dry-run a proposed deletion, pinned to the revision you read:
{
"name": "canvas_validate",
"arguments": {
"canvasId": "canvas:abc123:brand-refresh",
"baseRevision": "rev_00291",
"operations": [{ "type": "node.delete", "nodeId": "node:a1b2" }]
}
}The full sequence is on Validate a batch before applying.
When it fails
| Symptom | Cause | Fix |
|---|---|---|
409 | The canvas changed after the baseRevision you passed | Call canvas_get again, rebase your operations onto the newer graph, and re-validate. Someone else edited while you were thinking. |
403 insufficient_scope | Token lacks canvas:read | Reconnect. The challenge header names the scope. |
404 | Wrong canvas handle | Use a handle from canvas_list; do not assemble one. |
Input validation error naming operations | More than 200 operations | Split into smaller batches. 200 is the hard cap on both validate and apply. |
summary.truncated is true | Too many findings of one code, or a canvas too large for overlap analysis | Fix the reported instances and re-run; the next pass surfaces the ones that were cut. |
canvas_apply_batch
Apply related node and connection operations atomically against one exact canvas revision. This is the only way the hosted server writes to a canvas graph.
Not read-only. Annotated destructive. Requires canvas:read and canvas:write. No provider cost — a batch never starts generation.
Atomic means all or nothing. If one operation is rejected, the canvas is untouched — there is no partial graph to clean up.
Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
canvasId | string, 1–600 chars | Yes | A canvas: handle, or a plain id for a canvas you own. |
baseRevision | string, 1–240 chars | No | The revision from canvas_get. Omitting it makes the server read the latest revision immediately before writing — convenient, and strictly less safe. See below. |
idempotencyKey | string, 8–200 chars | Yes | Caller-stable retry key. |
operations | array of objects, 1–200 | Yes | The batch. Shapes below. |
Omitting baseRevision is not the same as being safe. It means “apply this to whatever the canvas looks like right now”, which defeats conflict detection. Pass the revision you actually read and inspected. Omit it only when you just created the canvas and nothing else can have touched it.
Operation shapes
The tool’s own schema accepts operations as free-form objects and defers to the canonical Canvas API engine, which enforces these eight shapes. Full field-level schemas are on the Canvases API resource.
type | Required fields | Optional |
|---|---|---|
canvas.update | — | title (max 160), backgroundMode (dots | lines | blank), showImageInfo, viewport |
node.create | node | clientId |
node.update | nodeId, patch | — |
node.move | nodeId, position | — |
node.resize | nodeId, width, height | — |
node.delete | nodeId | — |
connection.create | either connection, or both from and to | clientId, mode |
connection.delete | connectionId | — |
A node is one of four types — image, video, text, sticky — with title (max 160), position, width and height (each 40–10,000), and metadata. A connection carries an optional mode of list, reference, first-frame, or last-frame; omit mode for a plain dependency flow.
Node and connection references accept node:<id>, a bare id, or client:<clientId>.
Batch-local client: references
Within one batch you can connect nodes that do not exist yet. Give a node.create a clientId, then reference it as client:<clientId> from a later operation in the same batch:
{
"canvasId": "canvas:abc123:brand-refresh",
"baseRevision": "rev_00291",
"idempotencyKey": "brief-and-generator-2026-08-04-01",
"operations": [
{
"type": "node.create",
"clientId": "brief",
"node": {
"type": "text",
"title": "Campaign brief",
"position": { "x": 2400, "y": 200 },
"width": 480,
"height": 320,
"metadata": { "content": "Warm, editorial, no hard shadows." }
}
},
{
"type": "node.create",
"clientId": "generator",
"node": {
"type": "image",
"title": "Hero variations",
"position": { "x": 2960, "y": 200 },
"width": 512,
"height": 512
}
},
{
"type": "connection.create",
"clientId": "brief-to-generator",
"from": "client:brief",
"to": "client:generator"
}
]
}This is why a batch is worth building even for two nodes: creating them separately leaves a window where the graph is connected to nothing.
What it returns
The apply result — including the new revision and the created handles — plus a validation block. The server runs the graph validator on the post-write canvas automatically and returns it in the same response, so you do not need a second canvas_validate call just to see the outcome.
Read validation.summary.reviewRequired on every write. A batch can be structurally valid and still leave a warning worth reporting.
Hard rules the engine enforces
- Media content is server-owned. You cannot write image or video bytes, storage keys, or arbitrary media URLs into
metadata.content. A non-emptymetadata.contentis rejected when the node is, or is becoming, an image or video. Usesave_image_to_canvasor a generation tool. - A Section is a text node with
metadata.isSectionset totrue, at least 200 × 160. Anything else marked as a Section is a validation error. - Geometry is bounded. Width and height 40–10,000. Positions must be finite.
- 200 operations maximum, 1 minimum.
Idempotency and conflicts
One stable key per intended payload. Retrying the identical batch with the same key is safe. If your intent changes — you corrected a position, added an operation, rebased after a conflict onto a different graph — use a new key. Reusing a key for a different payload is the one way to get a confusing result.
On 409, the canvas moved after you read it. Do not retry blindly:
- Call
canvas_getagain. - Look at what changed. Someone else’s work is now in the graph.
- Rebase your intended addition around it, preserving their change.
- Apply again with a new key if the payload changed.
Worked example
The complete read → validate → apply → verify sequence, including how to handle a 409 mid-flight, is on Validate a batch before applying.
When it fails
| Symptom | Cause | Fix |
|---|---|---|
409 | Revision conflict | Re-read, rebase, preserve the concurrent change, retry. Never resolve it by dropping baseRevision. |
403 insufficient_scope | Token lacks canvas:write | Reconnect with write permission. |
| Operation rejected, nothing applied | A malformed operation | The batch is atomic — the canvas is unchanged. Fix the named operation. Do not re-send the batch minus the failing operation unless that is genuinely your intent. |
Rejected for metadata.content | Tried to put media content on an image or video node | Use save_image_to_canvas or generate_image_in_canvas. |
malformed_section in the returned validation | Section is not a text node, or is smaller than 200 × 160 | Resize it and re-apply. |
| Node created but disconnected | The connection operation was in a different batch | Put creates and their connections in one batch. |
canvas_list
List the canvases you own or can access, with exact handles, roles, revisions, and graph counts. The correct first call when the user has not named a canvas.
Read-only. Requires canvas:read. No provider cost.
| Parameter | Type | Required | Notes |
|---|---|---|---|
limit | integer, 1–25 | No, defaults to 10 | Maximum canvases to return. |
{ "name": "canvas_list", "arguments": { "limit": 25 } }When it fails. A 403 means the token lacks canvas:read — reconnect. An empty list on an account that has canvases means the token is delegating a different account than you expect; check which account you approved during OAuth.
canvas_get
Read the complete graph and current revision of one canvas. Call this before reasoning about or changing any existing canvas — the revision it returns is what makes a later write safe.
Read-only. Requires canvas:read. No provider cost.
| Parameter | Type | Required | Notes |
|---|---|---|---|
canvasId | string, 1–600 chars | Yes | A canvas: handle, or a plain id for a canvas you own. |
Keep the returned revision — it is the baseRevision for canvas_apply_batch and for a canvas_validate dry-run.
When it fails. 404 means the handle is wrong; get it from canvas_list rather than reconstructing it. 403 means the token cannot read that canvas, which on a shared canvas may mean your role changed.
get_canvas_image
Read one image node and return an inline preview the model can actually look at, plus a temporary full-resolution link.
Read-only. Requires canvas:read. No provider cost.
| Parameter | Type | Required | Notes |
|---|---|---|---|
canvasId | string, 1–600 chars | Yes | A canvas: handle. |
nodeId | string, 1–600 chars | Yes | A node: handle, or a plain node id. |
Call canvas_get first when the user has not identified which node they mean. Guessing a node id here wastes a round trip at best.
What it returns
A structured block with the node handle, asset id, title, media type, width, height, a scoped previewUrl, and how many seconds that preview stays valid — plus the image itself, inlined so the model can inspect it, and a resource link.
The inline copy is re-encoded as WebP, capped at 2,048 pixels on the longest edge at quality 88. If that still exceeds 5 MB it is retried at 1,536 pixels and quality 72. The preview URL is validated to be a Gavana-origin scoped asset URL with exactly one token query parameter and nothing else, so a compromised upstream cannot redirect the client somewhere arbitrary.
That token parameter makes the preview URL a credential. It expires — previewExpiresInSeconds tells you when — but until then anyone holding the URL can fetch the image. Do not paste one into a shared document, a ticket, or a log. When you want to show a person the image, give them an open_canvas review link instead.
When it fails.
| Symptom | Cause | Fix |
|---|---|---|
400 “not an image” | The node is a text, sticky, or video node | Read the canvas and pick an image node. |
409 “does not have a stored result” | The image node is an empty placeholder — generation has not finished or never started | Check the run or job. Do not start a new generation to work around it. |
413 | Source over 50 MB, or the re-encoded preview still over 5 MB | Use the full-resolution link instead of the inline preview. |
415 | Stored bytes are not PNG, JPEG, WebP, or GIF | Nothing to do client-side; report it. |
open_canvas
Return a verified browser link to a canvas. It checks access first, so the link you hand a human is one that will actually open.
Read-only. Requires canvas:read. No provider cost.
| Parameter | Type | Required | Notes |
|---|---|---|---|
canvasId | string, 1–600 chars | Yes | A canvas: handle. |
Returns canvasId, title, and url. This is how an agent should end any turn that touched a canvas: with a link the person can click.
When it fails. 404 for a bad handle. A 502 means the service returned a canvas without an id or owner, which is a server-side problem — report the X-Request-ID rather than retrying.
save_image_to_canvas
Download one public HTTPS image, verify it, store it in Gavana, and add it as a durable image node. This is how an image created elsewhere — in ChatGPT, or at any public URL — becomes real canvas content with a node: handle you can pass to other tools.
Not read-only. Requires canvas:read, canvas:write, asset:read, and image:generate. No provider cost.
This tool needs image:generate even though it generates nothing. That scope is the one that governs writing image bytes into Gavana storage, so a token scoped only to canvas:write cannot save an image. If this call returns insufficient_scope and you were expecting write access to be enough, that is why.
Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
image | object | One of image or imageUrl | A client-supplied image file. Fields: download_url (URL, max 4,096), file_id (1–1,000), mime_type (optional), file_name (optional). Prefer this when your client has a file. |
imageUrl | URL string, max 4,096 | One of image or imageUrl | Must start with https://. |
destination | string, 1–600 chars | No, defaults to agent-canvas | agent-canvas for the persistent Agent Canvas, or an exact canvas handle. |
title | string, 1–160 chars | No, defaults to Image from ChatGPT | Node title. Set something meaningful. |
idempotencyKey | string, 8–200 chars | Yes | |
x | number, −100,000 to 100,000 | No | Explicit position. Omit to let Gavana place it. |
y | number, −100,000 to 100,000 | No |
Supplying neither image nor imageUrl fails validation with “Provide an image file or imageUrl.”
Worked example
{
"name": "save_image_to_canvas",
"arguments": {
"imageUrl": "https://example.com/hero-draft.png",
"destination": "agent-canvas",
"title": "Hero draft — warm editorial",
"idempotencyKey": "hero-draft-2026-08-04-01"
}
}Keep the returned node: handle. It is what generate_video wants as a firstFrame, and what create_canvas_workflow wants as a sourceNodeId default. The full flow is on Save a generated image.
When it fails. A non-HTTPS URL fails validation immediately. A URL that 404s or serves something that is not a raster image fails at fetch or sniff time — fix the URL rather than retrying. insufficient_scope almost always means the missing scope is image:generate.
create_canvas_workflow
Create a private, reusable Recipe and a workflow card on a canvas. This writes the workflow graph and never starts generation — that separation is deliberate, and it is what lets an agent build something on request without spending anything.
Not read-only. Requires canvas:read and canvas:write. No provider cost.
Parameters
The schema is strict — an unrecognised key is rejected rather than ignored.
| Parameter | Type | Required | Notes |
|---|---|---|---|
destination | string, 1–600 chars | No, defaults to agent-canvas | |
name | string, 1–160 chars | Yes | |
description | string, max 1,200 | No, defaults to "" | |
category | enum | No, defaults to Product Visualization | One of Brand & Visual Design, Product Visualization, Marketing & Ads, Content Package. |
tags | array of strings, each 1–80, max 12 | No, defaults to [] | |
inputs | array of input objects, 1–16 | Yes | |
outputs | array of output objects, 1–8 | Yes | |
idempotencyKey | string, 8–200 chars | Yes | |
x, y | number, −100,000 to 100,000 | No | Card position. |
Each input object:
| Field | Type | Required | Notes |
|---|---|---|---|
key | string matching A-Z a-z 0-9 _ -, 1–80 | Yes | Stable key referenced by outputs. |
label | string, 1–80 | Yes | |
description | string, max 200 | No, defaults to "" | |
type | image | text | sticky | Yes | |
sourceNodeId | node reference | No | An existing node on the destination canvas. This is how you pin a fixed image default. |
value | string, 1–8,000 | No | Starting text for text or sticky inputs only. Images must use sourceNodeId. |
useAsDefault | boolean | No, defaults to false | Keep the node or text as a reusable default so later runs may omit this input. |
Each output object:
| Field | Type | Required | Notes |
|---|---|---|---|
key | string matching A-Z a-z 0-9 _ -, 1–80 | Yes | |
label | string, 1–80 | Yes | |
description | string, max 200 | No, defaults to "" | |
type | image | text | Yes | Note: no sticky on outputs. |
prompt | string, 1–8,000 | Yes | The generation instruction. |
inputKeys | array of input keys, 1–16 | Yes | Which declared inputs feed this output. |
model, size, quality | strings | No | Per-output overrides. |
The pattern that makes reuse work
Save the fixed images first, then reference them:
save_image_to_canvasfor each image that stays the same every run. Keep thenode:handles.create_canvas_workflowwith those handles assourceNodeIdanduseAsDefault: true.- Later runs pass only the inputs that change.
Get this wrong and every run has to re-supply the product shot.
Returns the workflow — including its workflowNodeId and a browser url — and the canvas. Keep workflowNodeId; it is the only thing run_canvas_workflow accepts.
When it fails. A strict-schema rejection names the unexpected key. An image-typed input carrying value instead of sourceNodeId is the most common mistake. An output whose inputKeys names a key that no input declares is a modelling error worth catching before you build the card.
run_canvas_workflow
Run a workflow card, passing only the inputs that should change.
This can incur provider cost. Call it only when the current user message explicitly asks to run or generate. Never retry it automatically after a failure, a timeout, or an ambiguous response.
Not read-only. Requires canvas:read, canvas:write, asset:read, image:generate, and job:manage. Strict schema.
| Parameter | Type | Required | Notes |
|---|---|---|---|
destination | string, 1–600 chars | No, defaults to agent-canvas | Must be the canvas the workflow card lives on. |
workflowNodeId | node reference | Yes | The exact workflowNodeId from create_canvas_workflow. |
inputs | object, max 16 entries, values 1–8,000 chars | No, defaults to {} | Only the inputs to replace. Keys may be stable input keys, exact portNodeId values, or input labels. Image values must be node: or asset: handles. Stored defaults may be omitted. |
idempotencyKey | string, 8–200 chars | Yes | |
model, size, quality | strings | No | Run-level overrides. |
waitSeconds | integer, 1–50 | No, defaults to 45 | How long to wait inline before handing back a resumable handle. |
Asynchronous by design
The tool waits up to waitSeconds for the run to finish. If it does, you get the completed run with image outputs — each with a scoped preview. If it does not, you get the queued run back with its status, the destination, and the workflowNodeId.
A timeout is not a failure. Poll get_canvas_workflow_run with the returned run: handle. Do not call run_canvas_workflow again — that starts a second paid run.
Worked example
{
"name": "run_canvas_workflow",
"arguments": {
"destination": "canvas:abc123:product-shots",
"workflowNodeId": "node:wf7k2m",
"inputs": { "background": "matte terracotta, soft window light" },
"idempotencyKey": "terracotta-run-2026-08-04-01",
"waitSeconds": 45
}
}The full sequence is on Run a canvas workflow.
When it fails.
| Symptom | Cause | Fix |
|---|---|---|
400 “not a reusable workflow card on this canvas” | workflowNodeId is a plain node, or lives on a different canvas than destination | Point destination at the canvas holding the card. |
409 “missing its Recipe identity” | The card lost its Recipe provenance | Recreate the workflow. Do not retry. |
| Returns while still queued | Normal — the run exceeded waitSeconds | Poll get_canvas_workflow_run. Never re-run. |
| Terminal provider failure | The provider rejected or failed the job | Report it and stop. Ask for fresh intent before any new attempt. |
get_canvas_workflow_run
Check or resume one workflow run. Call it repeatedly while the run is queued or running.
Not annotated read-only — it participates in run lifecycle rather than being a pure read. Requires canvas:read and job:manage. No provider cost — polling never charges. Strict schema.
| Parameter | Type | Required | Notes |
|---|---|---|---|
runId | string matching run: followed by 1–220 of A-Z a-z 0-9 _ - | Yes | Exactly the handle run_canvas_workflow returned. |
A succeeded run with image outputs comes back with each output resolved to its canvas node, including a scoped preview the model can inspect.
When it fails. 400 “not a reusable workflow run” means the handle belongs to a different kind of run — check you did not pass an image job handle. A malformed handle fails the regex before any request is made.
generate_image_in_canvas
Create an image-generation target on a canvas, queue the connected provider, and wait briefly for a durable result.
This can incur provider cost. Explicit current-turn approval only. No automatic retries.
Not read-only. Requires canvas:read, canvas:write, asset:read, image:generate, and job:manage.
| Parameter | Type | Required | Notes |
|---|---|---|---|
prompt | string, 1–8,000 chars | Yes | |
destination | string, 1–600 chars | No, defaults to agent-canvas | |
idempotencyKey | string, 8–200 chars | Yes | |
model | string, 1–600 chars | No | A connected model handle or id. Omit to use the configured default. |
size | string, 1–80 chars | No | |
quality | string, 1–80 chars | No | |
count | integer, 1–4 | No, defaults to 1 | Number of images. Each one costs. |
title | string, 1–160 chars | No, defaults to Generated image | |
waitSeconds | integer, 1–50 | No, defaults to 45 |
count is the parameter to be careful with. Four images is four times the cost, and the approval you got was probably for one.
Gavana prepares the destination and the target nodes before queueing, so the canvas shows where the result will land even while it is still generating. On timeout you get the queued job plus the destination and target node ids — poll rather than re-calling.
When it fails. A conflict on target preparation (“Automatic image targets are incomplete”) means a previous partial attempt reused this idempotency key — use a new key. A terminal provider failure is reported and stops there.
find_video_models
Find connected video models and their capabilities. Always call this before generate_video, because a guessed model handle reaches the provider layer rather than failing validation.
Read-only and free, but requires the video:generate scope. Looking up a model mutates nothing; discovering video models is still part of the generation path, so a token without video:generate cannot call it.
| Parameter | Type | Required | Notes |
|---|---|---|---|
query | string, 1–240 chars | Yes | The model or provider name the user asked for, for example Seedance 2. |
capability | enum | No, defaults to video.generate | One of video.generate, video.generate.fromImage, video.generate.fromFrames, video.generate.fromReferences. |
limit | integer, 1–25 | No, defaults to 10 |
Match capability to the inputs you actually intend to pass. If a first frame is required, ask for video.generate.fromImage — a model that only supports text-to-video will accept the call and ignore your frame.
{ "name": "find_video_models", "arguments": { "query": "Seedance", "capability": "video.generate.fromImage", "limit": 5 } }Take a model: handle from the result verbatim.
When it fails. An empty result means no connected model matches — the user has not connected that provider. Say so; do not substitute a different model. 403 means the token lacks video:generate.
generate_video
Start a video generation with an exact connected model.
This can incur provider cost, typically the highest on the platform. Explicit current-turn approval only. No automatic retries.
Not read-only. Requires canvas:read, asset:read, video:generate, and job:manage. Note that canvas:write is not required — the video job writes its own output rather than the caller editing the graph.
| Parameter | Type | Required | Notes |
|---|---|---|---|
model | string, 1–650 chars | Yes | An exact model: handle from find_video_models. Not shape-validated — a wrong value fails later and less clearly. |
prompt | string, 1–8,000 chars | Yes | |
destination | string, 1–600 chars | No, defaults to agent-canvas | |
idempotencyKey | string, 8–200 chars | Yes | |
aspectRatio | string, 1–40 chars | No | |
durationSeconds | integer, 1–120 | No | Longer usually costs more. |
resolution | string, 1–40 chars | No | |
generateAudio | boolean | No | |
firstFrame | image reference | No | A node: handle, an asset: handle, or a public HTTPS image URL. |
lastFrame | image reference | No | Requires firstFrame. Supplying lastFrame alone fails validation. |
references | array of image references, max 9 | No | Duplicates are rejected. |
waitSeconds | integer, 1–50 | No, defaults to 45 |
Starting from an image you have in the conversation
Call save_image_to_canvas first, then pass the returned node handle as firstFrame and the same canvas as destination. A video cannot start from an image that only exists in the chat.
{
"name": "generate_video",
"arguments": {
"model": "model:seedance-2-pro",
"prompt": "Slow push in, warm afternoon light, no camera shake.",
"destination": "canvas:abc123:product-shots",
"firstFrame": "node:img8q4",
"durationSeconds": 6,
"idempotencyKey": "hero-video-2026-08-04-01",
"waitSeconds": 45
}
}On timeout you get a resumable job: handle. Poll get_video_job; never re-call generate_video.
When it fails.
| Symptom | Cause | Fix |
|---|---|---|
Validation error on firstFrame | lastFrame supplied without firstFrame | Supply both or neither. |
Validation error on references | Duplicate entries | Deduplicate. |
| Rejected reference | Not a node: handle, asset: handle, or HTTPS URL | Save the image first and use its node handle. |
| Provider rejects the model | The handle was guessed or is for a different capability | Call find_video_models with the right capability. |
| Terminal failure | Provider failed the job | Report it and stop. |
get_video_job
Check or resume one video job.
Not annotated read-only. Requires job:manage only. No provider cost.
| Parameter | Type | Required | Notes |
|---|---|---|---|
jobId | string matching job: followed by a UUID | Yes | Exactly the handle generate_video returned. The UUID shape is enforced. |
A succeeded job returns its protected Gavana output link, served through a Gavana-origin URL rather than a raw provider URL.
When it fails. 400 “not a video generation” means the handle belongs to an image job. A malformed handle fails the regex locally. If a job never leaves queued, report the state — do not start a second video.
Not on the hosted server
If you need node-level surgery (node_create, node_move, connection_delete), deterministic credit-free image action_* tools, Recipe Library search and forking, asset listing, canvas rendering, or provider discovery, those exist only on the local stdio server. See the local tools reference and Other clients.