Skip to Content

CLI quickstart

The Gavana CLI is a JSON-first client for the same Canvas API this section documents. Every command maps onto an operation you can see in Resources — and gavana api lets you call any operation raw, including the handful with no dedicated command.

That makes it two useful things at once: a fast way to explore the API before you write code, and a way to script against it without writing a client at all.

Installing and logging in is covered on the Agents side of the help center: Install and log in to the Gavana CLI. Come back here once gavana auth status works. The CLI reference lists every command.

Confirm your setup

gavana auth status
{ "authenticated": true, "authType": "agent", "email": "you@example.com", "scopes": ["canvas:read", "canvas:write"], "agentLabel": "Quickstart" }

This is GET /auth/status verbatim. Everything the CLI prints is the API response, which is what makes it a good exploration tool — what you see is what your own client will get.

If something is misconfigured, gavana doctor checks the profile, base URL, and credential in one shot.

Commands map to operations

CommandOperation
gavana auth statusGET /auth/status
gavana canvas listGET /canvases
gavana canvas getGET /canvases/{canvasId}
gavana canvas applyPOST /canvases/{canvasId}/operations
gavana canvas renderGET /canvases/{canvasId}/render
gavana node getGET /canvases/{canvasId}/nodes/{nodeId}
gavana asset list · get · uploadGET /assets · GET /assets/{assetId} · POST /assets
gavana model list · getGET /models · GET /models/{modelKey}
gavana provider listGET /providers
gavana action list · get · runGET /actions · GET /actions/{actionKey} · POST /actions/{actionKey}/runs
gavana image generate · edit · variationsPOST /images/generate · /images/edit · /images/variations
gavana video generate · gavana video downloadPOST /videos/generate · GET /jobs/{jobId}/output
gavana recipe search · get · fork · runGET /recipes · GET /recipes/{recipeId} · POST /recipes/{recipeId}/fork · POST /recipes/{recipeId}/runs
gavana run get · cancelGET /runs/{runId} · DELETE /runs/{runId}
gavana job get · cancelGET /jobs/{jobId} · DELETE /jobs/{jobId}

Each generated resource page repeats this mapping per operation under CLI equivalent, so you can move in either direction.

Three operations have no dedicated command — POST /canvases/{canvasId}/workflows, POST /images/import, and GET /assets/preview. Use gavana api for the first two; preview URLs are handed to you inside asset and job responses.

The same flow as the other quickstarts

List canvases

gavana canvas list --limit 10

Read one and keep its revision

gavana canvas get canvas:product-launch

The response contains canvas.revision. Everything that writes needs it.

Validate a change without writing

gavana canvas apply always applies. For the dry run, go through the raw passthrough and set validateOnly yourself:

REV=$(gavana canvas get canvas:product-launch | jq -r '.canvas.revision') gavana api POST /canvases/product-launch/operations --json "$(jq -n --arg rev "$REV" '{ validateOnly: true, baseRevision: $rev, operations: [ { type: "node.create", clientId: "client:cli-note", node: { type: "sticky", title: "CLI quickstart", position: { x: 0, y: 0 }, width: 240, height: 160, metadata: { content: "Written from the CLI quickstart." } } } ] }')"

You get back proposal, destructiveImpact, and validation — the dry-run result described in Revisions. Nothing is written and no idempotency key is consumed.

Apply it

gavana canvas apply canvas:product-launch \ --idempotency-key 'cli-quickstart-note-001' \ --json '{ "operations": [ … ] }'

The CLI reads the canvas and fills in baseRevision for you when you do not pass --base-revision. That convenience is exactly what you must implement yourself in a raw client — see Revisions.

Two behaviours worth knowing before you script this:

  • A batch containing node.delete or connection.delete refuses to run without --yes. That gate exists in the CLI, not in the API — a raw client gets no such protection, which is why the dry run above matters.
  • If you omit --idempotency-key, the CLI generates a fresh UUID per invocation. That is fine interactively and wrong in a retry loop; pass a stable key whenever a command might be run twice.

gavana api: the raw passthrough

gavana api sends an arbitrary request to the Canvas API using your stored credential and base URL. Paths are restricted to /api/canvas-agent/v1, so you write the path relative to that prefix.

# GET is the default when the first argument is a path gavana api /auth/status # An explicit method gavana api GET /canvases --field limit=10 # --field repeats; on a GET the fields become query parameters gavana api GET /models --field capability=image.generate --field provider=openai # On a non-GET the fields become a flat JSON body gavana api POST /canvases --field title='Concepts' --field canvasType=agent # For anything nested, pass JSON gavana api POST /canvases/product-launch/operations --json '{ "baseRevision": "2026-08-04T09:12:44.318726Z", "idempotencyKey": "raw-passthrough-001", "operations": [ { "type": "node.move", "nodeId": "node:8f2a91c4", "position": { "x": 400, "y": 0 } } ] }' # DELETE gavana api DELETE /runs/2c9a1f3e

This is the fastest way to try an operation you are about to implement. It is also the only CLI route to operations with no dedicated command:

# Import a public HTTPS image as a durable canvas node gavana api POST /images/import --json '{ "canvasId": "canvas:product-launch", "imageUrl": "https://example.com/reference.png", "title": "Reference", "idempotencyKey": "import-reference-001" }'

gavana api does no validation, fills in no baseRevision, and adds no idempotencyKey. You are responsible for both, exactly as you would be in your own client. It will also happily call a paid operation — treat POST /images/*, POST /videos/generate, and POST /recipes/{recipeId}/runs as spending real money.

Flags that mirror API fields

These appear on the commands that write, and each maps to a body field you would send yourself:

FlagBody fieldNotes
--base-revision VALUEbaseRevisionEnforce a revision you read earlier. Omit and the CLI reads the canvas first.
--idempotency-key VALUEidempotencyKeyMake retries deterministic. Omitted on some commands means a fresh UUID per invocation — which is not what you want in a retry loop.
--webhook-url URLwebhook.urlOne signed callback when the Run finishes
--webhook-secret-env VARwebhook.secretReads the secret from an environment variable. Defaults to GAVANA_WEBHOOK_SECRET. Requires --webhook-url.
--yes—CLI-side confirmation gate for destructive batches and cancellations. No API equivalent.

--webhook-secret-env is a small but good design detail worth copying: the secret never appears in argv, so it never reaches shell history or ps.

Scripting against it

Every command prints JSON to stdout, so jq composes naturally:

# Every accessible canvas handle gavana canvas list --limit 25 | jq -r '.canvases[].handle' # Image models, sorted by estimated runtime gavana model list --capability image.generate | jq -r '.models | sort_by(.estimatedSeconds)[] | "\(.handle)\t\(.modelId)\t~\(.estimatedSeconds)s"' # The revision of a canvas, ready to feed into a raw call gavana canvas get canvas:product-launch | jq -r '.canvas.revision' # Walk every page of assets CURSOR='' while :; do PAGE=$(gavana asset list --limit 200 ${CURSOR:+--cursor "$CURSOR"}) printf '%s' "$PAGE" | jq -r '.assets[].handle' CURSOR=$(printf '%s' "$PAGE" | jq -r 'if .page.hasMore then .page.nextCursor else empty end') [ -n "$CURSOR" ] || break done

Errors go to stderr as a single JSON object, and the exit code is non-zero:

{ "ok": false, "error": { "code": "forbidden", "message": "This agent token is missing required permissions: canvas:write." } }

So set -e plus a stderr capture is enough for a robust script — you do not need to parse stdout to detect failure.

When to stop using the CLI

The CLI is a great client until you need something it deliberately does not do: custom retry policy, a webhook receiver, long-lived connection pooling, or running inside a service. At that point port your gavana api calls into a real client — the TypeScript quickstart is about eighty lines and has no dependencies.

Next

Last updated on