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
| Command | Operation |
|---|---|
gavana auth status | GET /auth/status |
gavana canvas list | GET /canvases |
gavana canvas get | GET /canvases/{canvasId} |
gavana canvas apply | POST /canvases/{canvasId}/operations |
gavana canvas render | GET /canvases/{canvasId}/render |
gavana node get | GET /canvases/{canvasId}/nodes/{nodeId} |
gavana asset list · get · upload | GET /assets · GET /assets/{assetId} · POST /assets |
gavana model list · get | GET /models · GET /models/{modelKey} |
gavana provider list | GET /providers |
gavana action list · get · run | GET /actions · GET /actions/{actionKey} · POST /actions/{actionKey}/runs |
gavana image generate · edit · variations | POST /images/generate · /images/edit · /images/variations |
gavana video generate · gavana video download | POST /videos/generate · GET /jobs/{jobId}/output |
gavana recipe search · get · fork · run | GET /recipes · GET /recipes/{recipeId} · POST /recipes/{recipeId}/fork · POST /recipes/{recipeId}/runs |
gavana run get · cancel | GET /runs/{runId} · DELETE /runs/{runId} |
gavana job get · cancel | GET /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 10Read one and keep its revision
gavana canvas get canvas:product-launchThe 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.deleteorconnection.deleterefuses 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/2c9a1f3eThis 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:
| Flag | Body field | Notes |
|---|---|---|
--base-revision VALUE | baseRevision | Enforce a revision you read earlier. Omit and the CLI reads the canvas first. |
--idempotency-key VALUE | idempotencyKey | Make retries deterministic. Omitted on some commands means a fresh UUID per invocation — which is not what you want in a retry loop. |
--webhook-url URL | webhook.url | One signed callback when the Run finishes |
--webhook-secret-env VAR | webhook.secret | Reads 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
doneErrors 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
- Install and log in to the CLI — if you have not yet
- CLI reference — every command and flag
- TypeScript quickstart — the same flow as code
- Platform — the mechanisms the CLI is hiding from you