Config Reference
Every template holds one YAML config. You edit it in the editor, either visually or as text in the YAML tab, and the API reads and writes the same document.
Top-level structure
version: 1
project: ...
llm: ... # optional: override the language model for this config
input: ...
timeline: ...
pipeline: ...
output: ... # optional: FCPXML / SRT export targets
outputs: ... # optional: named MP4 / FCPXML outputs
version must be 1. llm is optional and names a language-model backend to
use for this config (Puffin Ship's default is used when omitted). You use
output:, outputs:, or both: see the two sections at the end.
project
project:
name: "My Project"
resolution: [1080, 1920] # [width, height]; portrait = width < height
fps: 30 # integer, or [num, den] for fractional e.g. [24000, 1001]
sample_rate: 48000 # audio sample rate in Hz
Defaults: resolution [1920, 1080], fps 30, sample_rate 48000.
input
Named file references used throughout the pipeline. Values are glob patterns matching files you uploaded to the workspace (folder paths included, e.g. broll/*.mp4).
input:
- id: speaker-footage
select: "speaker.MP4"
- id: broll-clips
select: "broll/*.mp4" # matches multiple files
- id: transcript
select: "transcript.txt" # text files are loaded as strings by ai_agent
Steps reference these by id in their input blocks.
timeline
timeline:
levels:
- name: music
order: -1 # rendered below the backbone
type: audio
backbone: false
- name: background
order: 0
type: any
backbone: false
- name: main
order: 1
type: any
backbone: true # exactly one level must be backbone
- name: broll
order: 2
type: video
backbone: false
- name: overlay
order: 3
type: video
backbone: false
- name: captions
order: 4
type: video
backbone: false
rules:
gap_fill: black # how to fill timeline gaps: black | freeze | none
overflow: error # what to do if clips exceed timeline: error | trim | warn
Backbone level: drives total timeline duration. All other levels' clips are positioned relative to it in FCPXML. The backbone maps to the FCP <spine>; other levels use FCP lane attributes.
Not supported yet. timeline.duration and timeline.transitions are accepted by the parser but have no effect on the exported timeline: duration always comes from the backbone level, and every cut is hard. They are documented here only so you know not to reach for them.
Level order: lower numbers render first (further back). Use negative values for tracks that should sit below the backbone.
Level types: video, audio, any, title. FCP uses this to determine clip compatibility.
pipeline
An ordered list of steps. Each step runs one processor.
pipeline:
- id: my_step # unique step ID; used by other steps as input
processor: trim # processor name (see Processors doc)
input: # what this step reads; every item requires id:
- id: source # label for this input (required on every item)
file: speaker-footage # reference a project-level input ID
- id: sources # multiple project inputs
files:
- clip-a
- clip-b
- id: transcript # give each input a meaningful label
step: transcribe_main # reference another step's output
- id: clips
file: broll-clips
cache_deps: # step IDs that invalidate this step's cache
- ai_cut # re-run when ai_cut changes (even without using its output)
params: # processor-specific settings
key: value
prompt: | # prompt text for AI processors
...
output:
timeline: main # which level this step writes clips to
level: main # short form of output: {timeline: main}; see below
cache: true # save output for reuse inside the run group
review: false # pause for human approval before continuing
mode: "" # optional; "plan" enables ai_agent's production-planner mode
Step fields: id, processor, input, cache_deps, params, prompt, output, level, cache (default true), review (default false), and mode (optional; only plan is defined, used by ai_agent). output takes timeline only.
level: main is a short form of output: {timeline: main}. The visual editor
writes level:. If a step has both, output.timeline wins. A step needs only
one of the two.
Not supported yet. output.placement, output.anchor, and output.workspace parse but are never read: nothing positions a step's clips from them. Where a clip lands is decided by the processor. To pin something to a moment in the transcript, use a processor that takes an anchor of its own.
Input IDs
Every item in input: must have a non-empty id:. This makes YAML configs explicit and validates cleanly at load time. Standard conventions:
| ID | Meaning |
|---|---|
source |
Primary file or step input (single-input processors) |
sources |
Multiple file inputs (stitch) |
transcript |
Step output containing a transcript |
cuts |
Step output containing edit decisions |
backbone |
Step output defining backbone positions (captions) |
clips |
File input for a directory of media clips (broll_place) |
reference |
Optional text reference/script file |
Step output types
Most steps write EditDecisions (clip placements) to a timeline level. Some steps produce other output types that downstream steps consume as inputs:
| Output type | Produced by | Consumed by |
|---|---|---|
Analysis |
generate_transcript, analyze_footage, workspace_analyze, assemble_transcript, elevenlabs_tts, transcript_aiprep |
AI processors via step: input |
Script |
write_script |
transcript_aiprep via step: input |
SegmentPlan |
video_director (primary); footage_assembler (secondary, alongside EditDecisions) |
broll_place, broll_imagen_generate, broll_screenshot_websource, drawings_ai_placer |
TransformPatches |
reframe, motion_center, speaker_center, scale_transform, volume_adjust |
Nothing: applied to the clips they target automatically |
SourceRemaps |
chroma_key, noise_reduce |
Nothing: applied to the clips they target automatically |
SegmentPlan is the main inter-step coordination mechanism for the footage-director workflow: footage_assembler produces EditDecisions (placed clips) and a secondary SegmentPlan (unfilled gaps); video_director takes that as input and outputs a refined SegmentPlan as its primary output for downstream broll processors.
Cache deps
Saved output is reused only inside the run group that produced it, and only when nothing the step depends on has changed. A fresh run from the template starts a new group and reuses nothing. See Reusing work between runs.
To invalidate a step when an upstream step changes (even if you don't use its output as data), list the step IDs under cache_deps::
- id: background
processor: fill_level
input:
- id: source
file: background-image
cache_deps:
- ai_cut # background re-runs when ai_cut changes
params:
duration_step: ai_cut
Review mode
Steps with review: true pause after execution and wait for you in the run
view. You can approve the output, approve with edits, reject it (the
run ends), or regenerate (re-run the step for new output). See
Review Gates.
Variables and secrets
Anywhere in a config, a {{name}} token is replaced with a variable's value,
and a {{secret:NAME}} token is replaced with a custom secret. This works in
params, prompt, and other string fields:
params:
voice_id: "{{narrator_voice}}"
api_key: "{{secret:ELEVENLABS_KEY}}"
If a run starts with a token that has no value, the run stops before it starts.
Where values come from. Variables and custom secrets are set in the
editor's Settings slide-over, under Variables & Secrets, or at
Workspace Settings > Variables & Keys. Service API keys (OPENAI_API_KEY
and the other keys listed in Getting Started)
are a separate, curated list in the same Settings slide-over, under Service
API Keys.
A variable can be set at more than one scope: workspace, template, run group, and run. When the same name is set at more than one, the more specific scope wins: run beats run group, run group beats template, template beats workspace.
Each variable has a type (string, number, boolean, file, or select) and an
optional description. At template scope, a variable can also be marked
Required, meaning it must be set before the template can run. Custom
secrets are encrypted, write-only, and referenced as {{secret:KEY}}: once
saved, a secret's value is never shown again.
Not this syntax. Two other templating syntaxes exist for narrower
purposes and are not interchangeable with {{name}}: {{ .Text }} and
{{ secret "KEY" }} inside a media processor spec (see
Bring your own media service), and
{{ params.x }} / {{ loop.item }} / {{ loop.index }} inside a block
manifest (see Authoring a block).
output
output:
- save_finalcut:
path: "./output/project.fcpxmld" # FCP X bundle
- save_srt:
step: correct_transcript # which transcript step to export
path: "./output/project.srt"
The path values are not used. They date from when a pipeline ran on your
own machine and wrote files to disk. Runs now happen on the server and you
download the results from the run page, so nothing is written to the path you
give. path is still required by the config format, so an existing config keeps
working, but changing it changes nothing.
Prefer outputs below for new configs: it names the file you
download and supports video rendering as well as Final Cut Pro. The .fcpxmld
bundle can be opened directly in Final Cut Pro.
outputs
An alternative output declaration that supports both FCPXML and direct video rendering:
outputs:
- type: fcpxml
name: my-project # the Final Cut Pro bundle you download from the run page
- type: video
name: my-project-1080p # the MP4 you download from the run page
crf: 18 # H.264 CRF quality (lower = better; default 18)
| Field | Type | Description |
|---|---|---|
type |
string | "fcpxml" or "video" |
name |
string | Output filename without extension (.fcpxmld added for fcpxml, .mp4 for video) |
crf |
int | H.264 CRF value for type: video (default 18) |
Use outputs: when you want to produce an MP4 directly from Puffin Ship, or when you need multiple named output files. Use output: for path-controlled FCPXML or SRT export.