Skip to Content
Build with GavanaCanvas API Reference

Canvas API Reference

The Canvas API is the backend contract used by the Gavana CLI and MCP adapter. Most people should use the CLI; use the raw API when you are building your own integration, client library, or automation.

Machine-readable contract

Open the OpenAPI 3.1 document 

The document is the machine-readable contract for every stable V1 path, required input, common output shape, permission scope, error code, and request ID. Tools such as Postman, Bruno, Insomnia, and OpenAPI client generators can import it directly.

For tools that read Markdown or llms.txt, use the compact Markdown reference , LLM index , or complete LLM context . Production builds fail when these generated files drift from the OpenAPI contract.

The production base URL is:

https://app.gavana.ai/api/canvas-agent/v1

Authentication

Create an Agent Access Token, then send it as a bearer token:

Authorization: Bearer cba_... X-Gavana-Agent-Surface: api

Never put a token in a URL, screenshot, log, or support message.

Safe retries

Mutations to an existing canvas require both:

  • baseRevision from the latest canvas read
  • an idempotencyKey containing 8–200 characters

If somebody changes the canvas first, the API returns 409 conflict. Read the canvas again, update baseRevision, and retry with a key that represents the same intended change. Creating a new canvas is a separate operation; follow its exact OpenAPI schema rather than adding these fields by assumption.

Errors and support IDs

Every response includes X-Request-ID. Errors also have a stable code and may identify the exact input fields to fix:

{ "error": { "code": "input_validation_error", "message": "baseRevision is required.", "fields": [ { "field": "baseRevision", "message": "Read the canvas and pass its current revision." } ] } }

Share the request ID—not the token—when asking for support.

Paginating lists

Canvas, Recipe, asset, Action, model, and provider list responses keep their existing named array and also include page metadata:

{ "canvases": [], "page": { "limit": 25, "hasMore": true, "nextCursor": "opaque-value" } }

Pass limit to choose the page size. When hasMore is true, send nextCursor back as the next request’s cursor. The cursor is opaque and is valid only with the same endpoint and filters. Do not decode it or construct one yourself.

Canvas and asset pages are newest first, with stable handles breaking ties. Owned and shared canvases are merged into the same recent-first order while each request stays bounded for large accounts. Canvas pages contain at most 25 summaries because each summary currently reads its complete graph document.

Recipe Runs and shared Runs

Recipe discovery, forking, and execution are separate. POST /recipes/{recipeId}/fork creates one private, editable Recipe instance and never executes it. POST /recipes/{recipeId}/runs is the explicit operation that binds typed inputs, materializes declared outputs, and starts sequential text or image work:

{ "canvasId": "canvas:OWNER_UID:CANVAS_ID", "baseRevision": "LATEST_CANVAS_REVISION", "idempotencyKey": "product-direction-001", "inputs": { "product-context": "A matte black travel bottle for a quiet premium campaign" } }

Pass instanceNodeId to run an already connected private Recipe. Otherwise, Gavana creates a private instance from the requested catalog version. Each value must match a named port returned by GET /recipes/{recipeId}. Image ports require a durable image node: or asset: handle. Written ports accept plain text, a text/sticky node: handle, or an image node:/asset: handle when the Recipe asks for a reference image or written note. Terminal outputs include typed durable node: handles plus asset: handles for images. The current Recipe start route requires image:generate and its dependent scopes for every Recipe, including a text-only Recipe.

Every Recipe, image, and Action start returns a shared run: handle and pollUrl. Use GET /runs/{runId} until the status is terminal, or DELETE that route to cancel. The older /jobs/{jobId} route and job: handle remain an image and Action compatibility alias; Recipe Runs never have a job: handle. Recipe Run records currently persist without the temporary image-job TTL described below.

Deterministic Image Actions

Image Actions make exact raster changes without invoking an AI model or spending AI-generation credits. GET /actions lists the stable catalog and GET /actions/{actionKey} returns each Action’s ordered image inputs, typed parameters, defaults, bounds, and output contract.

The first stable catalog contains:

  • action:resize
  • action:crop
  • action:change-aspect-ratio
  • action:side-by-side-composite
  • action:add-text-to-image
  • action:overlay-image
  • action:color-grade
  • action:rotate

Run one with POST /actions/{actionKey}/runs:

{ "canvasId": "canvas:OWNER_UID:CANVAS_ID", "baseRevision": "LATEST_CANVAS_REVISION", "idempotencyKey": "resize-product-card-001", "targetNodeId": "node:OUTPUT_IMAGE_NODE", "inputs": ["node:SOURCE_IMAGE_NODE"], "params": { "width": 1080, "height": 1350, "fit": "cover" } }

The raw API requires an existing empty image target; create it with a revision-safe canvas operation first. The CLI and browser do that setup automatically. Action runs require canvas:read, canvas:write, and asset:read, but not image:generate. They return the same shared run: state, signed webhook option, durable asset: result, and durable node: result as AI image work.

That scope statement applies to the raw Action request, which accepts existing node: and asset: handles. The CLI can also upload a local file, stdin, or the macOS clipboard before starting the Action; that separate private asset upload currently requires image:generate.

In the browser, an editor can right-click an existing image, choose Image Actions…, select one of the eight transformations, and receive a new result beside the untouched original.

Discovering image models

GET /models returns only models Gavana can run through the managed image runtime or your saved provider connections. Each entry names its exact connection, supported operations, typed parameters, allowed values, and an estimated duration:

{ "handle": "model:opaque-key", "modelId": "gpt-image-1", "provider": "openai", "capabilities": ["image.generate", "image.edit", "image.variations"], "estimatedSeconds": 35, "parameters": [{ "name": "count", "type": "integer", "min": 1, "max": 4 }] }

Use the returned opaque model: handle with GET /models/{modelKey}. Do not decode or construct model keys. Image start requests may pass the same handle as model; Gavana resolves both the saved connection and provider model without a second lookup. Duration is an operational estimate, not a billing or delivery guarantee; Gavana does not invent credit costs for bring-your-own provider keys.

Understanding image and Action job retention

Image and Action starts return a shared run: handle and a legacy job: alias for the same temporary queue record. Both observation handles expire together; neither is permanent history. Read the Run until status is succeeded, failed, canceled, or expired. Every state includes an estimatedSeconds value from the selected model or deterministic Action catalog, plus observed timing:

{ "id": "job:...", "run": "run:...", "kind": "image", "pollUrl": "/api/canvas-agent/v1/runs/...", "status": "failed", "estimatedSeconds": 35, "durationMs": 42118, "timing": { "queueDurationMs": 812, "executionDurationMs": 41306, "totalDurationMs": 42118 }, "failure": { "code": "provider_rate_limited", "message": "The image provider is rate limiting requests. Wait briefly, then retry.", "retryable": true, "providerStatus": 429 } }

A provider failure is a normal terminal job response, not an authentication error from Gavana. Use failure.retryable instead of guessing from the message. For potentially paid image, workflow, or video work, ask for explicit approval before retrying. Starting the same intended request with the same idempotency key replays its job; use a new key only when intentionally starting a new attempt.

By default, an unacknowledged job result expires after 24 hours. After the first server finalization—either an authenticated Run GET or the worker finalization that precedes a signed callback—its temporary job record expires after 15 minutes. The expired tombstone remains for seven days before the handle returns 404. Operators may override these retention windows. Fetched asset: and node: outputs remain durable; store those handles rather than treating either the image/Action run: or job: resource as permanent history. Recipe run: records currently do not use this temporary TTL.

Signed completion webhooks

Polling is optional. Signed webhooks require an Agent Access token. Add a caller-owned webhook to a Recipe, image, or Action start request:

{ "webhook": { "url": "https://automation.example.com/hooks/gavana", "secret": "a-caller-owned-secret-containing-at-least-32-characters" } }

Gavana accepts only public HTTPS destinations, encrypts the secret at rest, never returns it, and sends one stable terminal event. Delivery is at least once: Gavana makes at most four total attempts. Retryable network failures, 408, 425, 429, and 5xx responses back off for 10 seconds, 60 seconds, then 5 minutes. Redirects and other 4xx responses stop delivery. With job:manage, inspect run.webhook to see pending, delivering, delivered, or failed without exposing the endpoint or secret. A start-only callback token cannot query later delivery state. For a Recipe, Gavana stores a revocable one-Run worker capability and refreshes the original Agent Access delegation, so --no-wait execution continues independently of the starting HTTP or CLI process. If that delegation expires or is revoked before completion, the Run fails with delegation_revoked and Gavana sends the signed failed callback.

Verify the raw request body before parsing it. The signature is the lowercase hex HMAC-SHA256 of:

<Gavana-Webhook-Timestamp>.<raw request body>

using your secret. Compare it to the v1 value in Gavana-Webhook-Signature with a constant-time comparison. Use Gavana-Webhook-Id plus the event id to ignore duplicate deliveries. The event says which Run became terminal. Recipe events include typed output statuses and durable handles. Successful Image and Action callbacks are not sent until Gavana has finalized the provider response into durable canvas, node:, and asset: results; those successful event payloads include the images, so a callback-only integration does not need an extra GET to make them durable. Actions use the same image_job.* event family as generated images; inspect data.run.operation and data.run.actionId to identify an Action.

If a provider succeeds but Gavana cannot durably store its output after internal retries, the callback type is image_job.finalization_failed, data.run.status is failed, and data.run.failure.code is output_finalization_failed with retryable: true. If the caller also has job:manage, it can inspect the shared Run; otherwise, start a new intended attempt with a new idempotency key after correcting the storage or access problem.

Stable and deprecated surfaces

Canvas, Recipe, asset, Action, image, video, model, provider, Run, and job-alias endpoints are the supported V1 surface. Campaign endpoints remain in the contract only for compatibility and are marked deprecated. Campaign records remain readable, but campaign mutations return 410 Gone by default unless a server operator explicitly enables legacy compatibility. New integrations should use explicit Recipe Runs, shared Runs, and revision-safe canvas operations instead.

Last updated on