Manifest files

Describe a canvas in a *.takes.yaml file, complete it in your editor, and check it before it is built.

A canvas can be written down. A manifest is a small *.takes.yaml file that describes one canvas — which nodes exist, which model each one uses, and what feeds what — so a graph can live in a repository, be reviewed in a pull request, and be checked before anything is built.

This is how an agent works on your canvases without guessing. It writes the file, your editor completes it against a published schema, and the server tells it whether the graph is legal.

What it looks like

# yaml-language-server: $schema=https://takes.sh/schema/takes-manifest.v1.json
version: 1
project: takes-site
canvas: pricing-hero
nodes:
  brief:
    type: text
    text: "Hero image for the pricing page. Quiet, warm, no people."
  hero:
    type: image
    model: openai/gpt-image-2
    settings:
      aspectRatio: "16:9"
      quality: high
    count: 3
    inputs: [brief]
outputs:
  hero: ./public/hero/

project and canvas are names, not ids. Everything under nodes is a node you would otherwise have added from the toolbar, and the name you give it — brief, hero — is that node's identity: how you point other nodes at it, and how errors refer back to your own file.

What is not in it

A manifest is what you want, not what happened. Runs, generated images and videos, versions, costs and node positions all belong to the canvas and never appear in the file. Neither does media: an asset is named by a path or a library id, never embedded.

That is what makes it safe to commit. The file is a few dozen lines that read as intent, and re-applying it does not duplicate a canvas or discard its history.

Wiring nodes together

inputs is how edges are written. In the short form you list what feeds a node and the handle is chosen for you, the same way it is when you drag a connection on the canvas:

inputs: [brief, logo]

Text goes to the prompt input; an image goes to the reference input of an image node, or the start frame of a video node. When a node has several media inputs and you need to say which, name it:

inputs:
  prompt: motion-brief
  start-frame: product-still

A video node exposes three outputs rather than one, so a ref can name which of them you mean:

inputs:
  start-frame: { from: opening-shot, handle: out-end-frame }

Media you already have

assets names the images and videos a canvas starts from. Each one becomes a source node, and its name can be used anywhere a node name can:

assets:
  logo: ./brand/logo.png
  mascot: { id: "ast_9f2k", kind: image }

A path is a local file, and its kind is read from the extension — png, jpg, jpeg, webp, gif and avif are images, mp4, mov, webm and m4v are videos. Anything else needs kind spelled out. An id is an asset already in your Stocks library.

Where results go

outputs says which directory a node's media should be written to:

outputs:
  hero: ./public/hero/

Any node except a text note can have one.

Checking a manifest

POST /api/manifests/validate checks a manifest against the same rules that apply when a canvas is actually built. Sign in with a browser session, or use a personal token from Settings → MCP as a bearer credential. It is free and it changes nothing: it only looks up whether the project and canvas names already exist, never reads or changes canvas content, and generates nothing.

curl -s -X POST https://abuck.app/api/manifests/validate \
  -H "Authorization: Bearer $TAKES_TOKEN" \
  -H 'content-type: application/yaml' \
  --data-binary @canvas.takes.yaml

You get one of three answers:

  • Valid — the resolved graph, every edge handle filled in, plus whether a project and canvas of those names already exist.
  • The file is wrong — every shape and type problem at once, each with the path in the file it came from. Naming problems, such as an input that points at nothing, are reported once the shape is right.
  • The graph is wrong — the first rule the graph breaks, with the same message you would have got had you built it, naming nodes by your keys. A model id that does not exist, a setting the model does not offer, or a loop in the graph is caught here rather than after the write.

If the model catalog cannot be reached, the answer is a 502 rather than a verdict on your file. Try again.

A model's settings are checked against the live catalog, so the values a manifest may use are exactly the values that model offers today.

Planning a manifest

POST /api/manifests/plan tells you what a file would do before it does it: which nodes it would add, update or leave alone, and what running the result would cost. Nothing is written — no project, no canvas, no run — so it is the thing to check before you apply, and the thing to paste into a review.

curl -s -X POST https://abuck.app/api/manifests/plan \
  -H "Authorization: Bearer $TAKES_TOKEN" \
  -H 'content-type: application/yaml' \
  --data-binary @canvas.takes.yaml

The answer lists every generation the canvas would perform — the node it comes from, its model, and its price — and totals them. That total is the same number a run checks against its own spending limit, and it is a ceiling: a run can come in under it, never over it.

If the price cannot be worked out — a node that names no model, no membership, a model account not connected yet, or a model whose provider publishes no rate — you still get the list of changes, and a short note saying which it was.

Applying a manifest

POST /api/manifests/apply turns the file into the canvas. The project and the canvas are created if they do not exist yet, every node in the file is matched to the canvas by its name, and everything your runs produced stays where it is. Applying the same file twice changes nothing; changing one prompt changes one node and keeps every earlier take.

curl -s -X POST https://abuck.app/api/manifests/apply \
  -H "Authorization: Bearer $TAKES_TOKEN" \
  -H 'content-type: application/yaml' \
  --data-binary @canvas.takes.yaml

The answer says what happened to each node — added, updated, unchanged, or left as an orphan because the file no longer names it — and gives you the canvas URL. Orphans stay on the canvas until you apply with ?prune=1, which removes them and their connections. A node you made in the browser is never touched, and a file that breaks a rule writes nothing at all.

Two things to know. An asset named by a local path cannot be applied yet: upload the file in the browser once and reference it by its library id. And a video node whose model can generate sound gets generateAudio: false written for it when the file says nothing, so a server-side run can price it; write generateAudio: true yourself to ask for sound.

Pulling a canvas into a file

POST /api/canvases/:id/manifest goes the other way: it reads a canvas and hands you the file that describes it, as an object and as the *.takes.yaml text to save.

curl -s -X POST https://abuck.app/api/canvases/$CANVAS_ID/manifest \
  -H "Authorization: Bearer $TAKES_TOKEN"

What comes back is what the canvas is, not what it has done: node types, prompts, models, settings and wiring, plus any Stocks media it uses, by library id. Takes, versions, costs and node positions stay on the canvas.

A node you added from the toolbar has no name a file can use, so pull gives it one — text-1, image-2 — and writes it onto the node, so a later apply recognises it instead of building a second one beside it. That is the whole point: apply the file you just pulled and nothing changes. Add ?adopt=false to look without writing anything, remembering that the file you get then describes new nodes rather than the ones you have.

A video node that never said whether it wants sound comes back asking for it, and the canvas is set to match, because that is what Play would have done with it.

Three things do not come back, and the answer names each one and why. An image or video you pasted onto the canvas is not in your Stocks library, so no file can point at it — upload it to Stocks and place it from there. A node type a manifest has no words for yet, such as a camera, is left out. So is a list that never settled on whether it holds text, images or video. A canvas made only of those says so rather than handing you an empty file.

Completion in your editor

The schema is published at https://takes.sh/schema/takes-manifest.v1.json. The first line of every manifest points at it:

# yaml-language-server: $schema=https://takes.sh/schema/takes-manifest.v1.json

VS Code, Zed, Neovim and JetBrains editors read that line and will then complete node types, handle names and setting keys, and underline a field that does not belong. In JSON, use a $schema key instead. Until the move to takes.sh is complete, the same file is served from the current host at https://abuck.app/schema/takes-manifest.v1.json; point the directive there if your editor reports that the schema cannot be loaded.

What a server-side run can do today

Everything a manifest can describe runs from the API or an AI client: an image count above one, several image models compared in one node, a fan-out over a list, speech and sound effects. Two limits remain. Music is refused until its provider's price is known to the run engine, and a list filled earlier in the same run cannot also be read in it — run the node that fills the list first, then the node that reads it.

See MCP setup for connecting a client, and the node reference for what each node type takes in and produces.

© 2026 Accolade Imaging Incorporationabuck.app