---
title: 'Compositor (compv3) headless contract'
description: 'Drive the free compv3 compositor over the API: node config shape, layers, stage size and the rendered PNG output.'
updated: 2026-06-20
---

The compositor (`compv3`) renders layered images/text/shapes to a single PNG at a
fixed canvas ("stage") size. Rendering is **free** (no AI credits) and returns a
durable `cdn.wireflow.ai` URL. This page is the contract for driving it via the API.

## Node config shape

The canonical location is **`node.data.config`**:

```jsonc
{
  "nodeType": "compv3",
  "config": {
    "stage": { "width": 1080, "height": 1350 },   // canvas size in px
    "layerOrder": ["bg", "headline", "logo"],       // back-to-front draw order
    "layers": {
      "bg":       { "type": "image", "x": 0, "y": 0, "width": 1080, "height": 1350, "fit": "cover" },
      "headline": { "type": "text",  "text": "HEADLINE", "x": 70, "y": 900, "width": 940, "fill": "#fff", "fontFamily": "Anton", "fontSize": 120 },
      "logo":     { "type": "image", "x": 820, "y": 60, "width": 180, "height": 180, "fit": "contain" }
    }
  }
}
```

Layer types: `image`, `text`, `rectangle`, `gradient`, `draw`. The editor-state
shape `data: { stage, layers, layerOrder }` is also accepted, but `config.*` is
canonical — author there.

**Binding:** a layer receives dynamic content **by key** (no `{{tokens}}`). An
incoming edge whose target handle is the layer key, or a value under that key in
the `data` batch port / `inputs`, overrides the layer: a string sets `.text` for a
text layer or `.url` for an image layer.

## Image fit modes (sizing to the layer box)

Every image layer with an explicit `width` **and** `height` is scaled to that box:

| `fit`                  | behavior                                                    |
| ---------------------- | ----------------------------------------------------------- |
| `cover`                | fill the box, crop overflow (preserves aspect)              |
| `contain`              | fit inside the box, letterboxed (preserves aspect)          |
| `fill` / `stretch`     | stretch to the box exactly (ignores aspect)                 |
| _(unset, sized layer)_ | **defaults to `contain`** — honors the box, never overflows |
| _(unset, no box)_      | native size (full-bleed)                                    |

> Set `fit` explicitly when you want crop (`cover`) or stretch (`fill`). A sized
> layer with no `fit` now scales into its box (previously it drew at the source's
> natural resolution and could overflow the layout).

## Setting an `input:image` node's media via the API

You do **not** need a human to drag-and-drop. Set the URL in the node's `config`
when you create/PUT the workflow — any of these fields works (first present wins):
`imageUrl`, `image`, `mediaUrl`, `videoUrl`, `url`.

```jsonc
{ "nodeType": "input:image", "config": { "imageUrl": "https://cdn.wireflow.ai/your-asset.png" } }
```

The node then resolves as an upstream media source for downstream nodes (compositor
layers, generators, etc.) headlessly.

## Headless preview (true export, no billed run)

Render a compositor to a PNG at stage resolution **without** running the workflow:

```
POST /api/v1/workflows/:id/compositor/preview
Authorization: Bearer <API_KEY>          # scope: workflows:read
{
  "nodeId": "comp",                        // optional — defaults to the first compv3
  "inputs": { "bg": "https://…/photo.png" },  // optional per-layer-key port values
  "layers": { … },                          // optional — override the saved layout
  "layerOrder": ["bg","headline"],          // optional
  "stage": { "width": 1080, "height": 1350 }// optional — override the canvas size
}
→ 200 { "data": { "nodeId", "url", "width", "height", "inputsHash", "warnings?", "failedImages?", "failedFonts?" } }
```

Use it to iterate on a layout and **see** the real output before committing or
running the graph. The returned `url` is a durable CDN PNG. Empty body previews the
node exactly as saved.

## Discovering the contract programmatically

`GET /api/v1/workflows/:id/schema` returns a `compositors[]` array describing each
compv3 node: `nodeId`, `label`, `stage`, the `layers` (key + type + whether
AI-bindable), and where the output image lands. Call it instead of reverse-engineering.

## Notes

- A compositor's output is cached and reused on re-run only while its inputs **and
  stage size** are unchanged — a stage resize re-renders at the new size.
- Normalization re-adds a canonical `layer_1` input port to compv3 nodes (a
  defensive default to avoid dangling edges); an empty one is harmless — just leave
  it or wire/override the keys you use.

---

Documentation index: fetch https://www.wireflow.ai/llms.txt for the full list of Wireflow docs.
