WildfieldDocs

Wildfield is an AI-native ad creative builder. The north star is simple: an agent receives a brief, connects over MCP, and sets the whole campaign up - design system, scenes, imagery, motion, background video, sound, and a render. This page is the contract that makes that possible: every tool, schema, and motion rule the editor actually ships.

Quickstart MCP Setup MCP Endpoints Design System Schema Project Schema Scene Schema Format Specs Vector Expressions Animate & Guide Physics Editor Feature Map Agent Skills Templates API Fallback Critical Rules

Quickstart

From a brief to a ready campaign. Call the tools in this order; skip a step only when the project already has that piece.

1get_credit_balance - confirm the account can spend before you generate
2list_design_systems then create_design_system or update_design_system - brand first, always
3create_project - one idea per scene (hook, product, proof, offer). Image scenes get overlay gradient. Colours inherit from the design system. Response includes openUrl (/app?project=<uuid>). Auto-review is the approval step in your own agent harness that sits in front of the call, not a Wildfield gate. The hosted MCP function is capped at 300s, so a long hang (observed ~14m) is the agent or its approver waiting, not the server. Prefer reusing an existing project via list_projects; if create times out, list again (it may still have saved) or use REST /api/projects/save. See docs/agent-socials-friction.md
4generate_image with format + designSystemId. Attach with patch_scene, not a full update_project
5preview_scene then critique_scene. You are not done until you have looked. Fail means patch_scene and look again
6get_motion_skill, create_vector_layer, stamp_soft_plate then apply_physics_recipe (editor keys) or Guide morphs via stamp_formation + link_layers (glyphScale/markSvg for particle size/marks; set_particle_marks to adjust later) (same seed, different formations). Homepage labels like Mark orbit / Type lockup are marketing demos on /#motion only, not recipe keys. Do not invent gsapCode
7Background video is a project-level track (bgVideoTrack), one clip under the whole timeline. Do not put a video on every scene
8get_active_render then render_video with projectId (preferred: server builds HTML, no ~170KB MCP arg). Or export_composition_html then render_video with html+width+height. One render at a time. Poll get_render_progress. Stop with cancel_render if needed

MCP Server Setup

Add the Wildfield MCP server to your agent's configuration. You'll need an API key from the Admin panel.

Hosted server (recommended, zero-install)

Nothing to clone, no npm, no npx. Point your agent at the hosted endpoint and pass your API key as a header. This is the easiest way to connect: the server runs on wildfield.io.

claude mcp add --transport http wildfield https://wildfield.io/api/mcp \
  --header "X-API-Key: adk_your_api_key_here"

Or as JSON (Claude Desktop / any MCP client that speaks the Streamable HTTP transport):

{
  "mcpServers": {
    "wildfield": {
      "type": "http",
      "url": "https://wildfield.io/api/mcp",
      "headers": {
        "X-API-Key": "adk_your_api_key_here"
      }
    }
  }
}

Your key authenticates every request and attributes usage/credits to your account. A missing or invalid key returns 401. The hosted server exposes the exact same tools as the local methods below.

The endpoint is stateless Streamable HTTP, so it accepts POST only: GET and DELETE return 405 (there is no SSE stream to open and no session to terminate). It authenticates with the X-API-Key header only. An Authorization: Bearer token is never read here, even though it appears in the CORS allow-list, so a client that sends one instead of the key gets 401. There is also a per-key burst limit of 120 requests per 60 seconds (fail-open), and the function itself is capped at 300 seconds.

Local server (offline / dev only)

Only needed if you're running offline or hacking on the server itself. Most people should use the hosted URL above. You'll need a checkout of the Wildfield repo and Node 18+. The mcp-server/ folder on its own is not enough: its tools import shared code from the repo's lib/ folder, so keep the two side by side.

First install its dependencies (just @modelcontextprotocol/sdk and zod; the repo root's npm install is not needed):

cd mcp-server && npm install

Then point your agent at the server's absolute path and pass your key via the environment. ADFLOW_API_KEY is required; ADFLOW_URL is optional (defaults to https://wildfield.io).

{
  "mcpServers": {
    "wildfield": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-server/index.js"],
      "env": {
        "ADFLOW_API_KEY": "adk_your_api_key_here",
        "ADFLOW_URL": "https://wildfield.io"
      }
    }
  }
}

Replace /absolute/path/to/mcp-server/index.js with the real path on your machine (e.g. /Users/you/wildfield/mcp-server/index.js). A published npx wildfield-mcp package is coming soon. It is not on npm yet, so don't rely on it today.

Authentication reference

Every request is authenticated with your Wildfield API key. Create keys in Admin → API Keys. How you supply the key depends on the method:

X-API-Key headerHosted server (and any direct REST call). The key travels as an HTTP header on each request.
ADFLOW_API_KEY envLocal stdio server: required. The mcp-server/index.js process reads it from the environment.
ADFLOW_URL envLocal stdio server: optional. Overrides the API base URL (defaults to https://wildfield.io).
Authorization: BearerSupabase JWT token for browser sessions. Not accepted by /api/mcp, which reads X-API-Key only.

MCP Endpoints

57 tools available via the MCP server. All return JSON. You call these by tool name (not by HTTP verb) - the coloured badge below each tool indicates the HTTP method of the REST endpoint it wraps, for reference when using the Direct API Fallback.

GET list_design_systems

List all design systems you have access to.

No parameters. Returns an array of full design system objects.

POST create_design_system

Create a new design system. All scenes inherit colours, fonts, logo, and layout from here.

No admin required: any authenticated, approved user can create one. The only limit is the per-tier capacity quota, which returns 402 when it is switched on.

ParamTypeDescription
namestringrequiredDisplay name (e.g. "Acme Corp")
primaryColorhexrequiredPrimary brand colour
secondaryColorhexoptionalSecondary brand colour
darkColorhexoptionalDark colour (default #111111)
lightColorhexoptionalLight colour (default #ffffff)
fontHeadlinestringoptionalHeadline font stack
fontBodystringoptionalBody font stack
fontImportURLoptionalGoogle Fonts import URL
fontH1 / fontH2 / fontH3 / fontCtastringoptionalPer-element font overrides
h1Weight / h2Weight / h3Weight / ctaWeightstringoptionalFont weights (e.g. "800")
h1Transform / h2Transform / ctaTransformstringoptional"none" or "uppercase"
h1LetterSpacing / h2LetterSpacing / ctaLetterSpacingstringoptionalCSS letter-spacing
ctaRadiusstringoptionalCTA border-radius multiplier
logoSvgURLoptionalLogo SVG URL
logoPositionstringoptional9-grid position (e.g. "top-left")
logoScalenumberoptionalLogo size (100 = default)
contentPositionstringoptional"top", "middle", or "bottom"
contentAlignstringoptional"left" or "center"
ctaAlignstringoptional"left" or "center"
disclaimerstringoptionalLegal disclaimer text
disclaimerAlignstringoptional"left" or "center"
fontDisclaimerstringoptionalDisclaimer font override
globalGenPromptstringoptionalDefault AI prompt prefix
uiAccenthexoptionalUI accent colour for the editor chrome
figmaUrlURLoptionalSource Figma file URL
layoutPerFormatobjectoptionalPer-format layout overrides keyed by format
globalAudioUrlURLoptionalGlobal background audio (use upload_audio for a URL)
globalAudioFileNamestringoptionalAudio filename for display
audioVolumenumberoptionalGlobal audio volume 0-100 (default 100)

PATCH update_design_system

Update any properties on an existing design system.
ParamTypeDescription
idstringrequiredDesign system slug (e.g. "acme-corp")
dataobjectrequiredKey-value pairs to update (see full schema below)

GET list_projects

List all projects with metadata.

No parameters. Returns id, filename, name, designSystemId, sceneCount, savedAt.

GET get_project

Get full project data including all scenes.
ParamTypeDescription
idstringrequiredProject UUID

POST create_project

Create a new project with scenes. Colours, layout, and logo position are inherited from the design system automatically. Timeout: this MCP call can take many minutes. On timeout, check list_projects before retrying (the project may already exist), or use the Direct API Fallback /api/projects/save.
ParamTypeDescription
namestringrequiredProject name (e.g. "June Campaign")
designSystemstringrequiredDesign system slug
formatenumoptionalActive format: square, portrait, vertical, landscape, wide
scenesarrayrequiredArray of scene objects (see below)

Scene object

FieldTypeDescription
h1stringrequiredHeadline. 4-7 words. No fluff, no em dash
roleenumoptionalhook, product, proof, offer, cta
h2stringoptionalSupporting text
h3stringoptionalThird line of text
ctastringoptionalCTA button text
bgTypeenumoptionalcolour, gradient, or image
bgColorhexoptionalBackground colour (default: DS Dark)
bgImageURLoptionalBackground image URL
bgGradientFromhexoptionalGradient start colour
bgGradientTohexoptionalGradient end colour
bgGradientAnglenumberoptionalGradient angle degrees (default 180)
textColorhexoptionalText colour (default: DS Light)
ctaBgColorhexoptionalCTA bg (default: DS Primary)
ctaTextColorhexoptionalCTA text (default: DS Light)
contentPositionenumoptionaltop, middle, bottom
contentAlignenumoptionalleft or center
logoPositionstringoptional9-grid position (e.g. "top-left")
overlayTypeenumoptionalnone, fill, gradient
overlayColourhexoptionalOverlay colour
overlayOpacitynumberoptionalOverlay opacity 0–1
notesstringoptionalDescription / AI image prompt
durationnumberoptionalSeconds, 0.1 to 30 (default 3; 0 or omitted means 3). Under 0.1s is refused: Scene duration must be at least 0.1s. 0.1s is 3 frames at 30 fps.
audioUrlURLoptionalAudio URL (use upload_audio)
audioFileNamestringoptionalAudio filename for display
audioPlayModeenumoptionalloop, once, fade
audioVolumenumberoptionalVolume 0–100 (default 100)
audioOffsetnumberoptionalStart offset in ms
audioCropStartnumberoptionalTrim start in ms
audioCropEndnumberoptionalTrim end in ms
audioFadeOutnumberoptionalFade out duration in ms

PUT update_project

Update an existing project. Fetch the project with get_project, modify it, then pass the full object back with its revision. If the project was saved since you read it (usually in the open editor), nothing is written and the tool says to read it again and re-apply: the editor's work is never overwritten.
ParamTypeDescription
idstringrequiredProject UUID
filenamestringrequiredProject filename
projectobjectrequiredFull project object
revisionnumberoptionalThe revision get_project returned. Strongly recommended: without it the write cannot tell that the editor saved since you read the project.

POST link_scenes

Put scenes into one linked family (a shared stateGroupId) for Show linked scenes and review. Link only subtle variations of one main ad, or beats of one storyboard or reel; do not group unrelated ads. Prefer this over update_project. REST: POST /api/agent/link-scenes.
ParamTypeDescription
projectIdstringrequiredProject UUID
sceneIdsstring[]requiredScene ids, head first. At least 2 unless unlink
groupIdstringoptionalstateGroupId to assign; omit to mint one or reuse the set's existing id
sharebooleanoptionalShow the linked family on share and review (default true)
unlinkbooleanoptionalClear stateGroupId on these scenes instead of linking

DELETE delete_project

Permanently delete a project.
ParamTypeDescription
idstringrequiredProject UUID

POST patch_scene

Patch a few fields on one scene. Prefer this over update_project for copy, overlay, image attach, and ad copy. Attaching bgImage turns overlay gradient on if overlay was none. New words for a layout-linked object (h1, h2, h3, cta with its link on in typeLink) go to every scene of the same set with that object linked, as in the editor; the reply lists them in sharedWith. Colours stay on the patched scene.
ParamTypeDescription
projectIdstringrequiredProject UUID
sceneIdstringoptionalScene id
sceneIndexnumberoptional0-based index if you do not have the id
fieldsobjectrequiredWhitelist: name, h1, h2, cta, bgImage, overlayType, colours, layout (contentAlign, ctaAlign, logoPosition), duration (0.1 to 30 s; 0 means the 3 s default; under 0.1 s is refused), role, loop, hold, cinematicShowLogo, hidden, adCopy
adCopy: Single post copy, independent of artwork: {version:2, channel:"meta"|"google"|"both"|"unset", meta:{primaryText,headline,description,cta,websiteUrl,displayUrl}, google:{headline,description,finalUrl,path1,path2}}. Share-card Copy is directly editable by signed-in project editors, with Save/Cancel. Reviewers remain read-only. Use linked scenes for variations and scene controls for artwork text. Legacy selections are ingested once; active variants, bindings and export combinations are no longer supported.

POST preview_scene

Look at the ad. Returns a PNG at the requested format plus critique fails. You are not done until you have looked at every format you shipped.
ParamTypeDescription
projectIdstringrequiredProject UUID
sceneIdstringoptionalScene id
sceneIndexnumberoptional0-based index
formatenumoptionalsquare, portrait, vertical, landscape, wide
tnumberoptionalScrub time 0-1 across the anim window (default 1, the final look; 0 for frame 0)

POST critique_scene

Return fails, not new copy. Overlay missing on a photo, H1 too long, fluff words, low contrast, repeated headlines. Fail means patch_scene.
ParamTypeDescription
projectIdstringrequiredProject UUID
sceneIdstringoptionalOmit to critique the whole project
sceneIndexnumberoptional0-based index
formatenumoptionalFormat used to judge type length

POST apply_physics_recipe

Apply a named Animate Physics recipe. Creates a vector layer + glyph if needed. Writes enabled physics without gsapCode. The editor rebakes on load and shows the result as ordinary modules on the Vector layer (Count, Motion and any others it fills), editable like any others. Always call get_motion_skill first and verify keys against its output (list can grow).
ParamTypeDescription
projectIdstringrequiredProject UUID
recipestringrequiredEditor keys only: leaves, tornado, orbits, ripple, rain, emberRise, snowfall, vortex, galaxy, heartbeat, starburst, hexGrid, radialBurst, crossfade (plus any new keys from get_motion_skill)
sceneIdstringoptionalScene id
sceneIndexnumberoptional0-based index
layerIdstringoptionalExisting vector layer id (omit to create one)
createNewbooloptionalMint a new vector layer instead of reusing Vector 1 (ignored with layerId)
formatenumoptionalFormat to stamp (default the active format, copied to all)
seednumberoptionalStable seed
loopbooloptionalTurn Perfect loop on
glyphstringoptionalCharacter to emit if the scene has no mark (ignored when markSvg set)
glyphScalenumberoptionalParticle mark size (20-200). Default for new MCP marks: 40
markSvgstring|string[]optionalParticle MARK SVG markup or public URL(s)
particleMarks[{svg|url, on?}]optional1-N tiny SVG mix-stack particle marks

Homepage /#motion demos vs editor recipe keys

Marketing canvas labels on the landing page are not apply_physics_recipe keys. Closest editor keys (or no 1:1):

Homepage demoClosest editor keyNotes
Mark orbitorbitsClosest 1:1
Hex latticehexGridClosest 1:1
Burst settleradialBurst or starburstPick by look; verify with get_motion_skill
Type lockupno 1:1Use vector type + forces / Guide Physics, not a named recipe
Flag waveno 1:1Dial wave (and related forces) from get_motion_skill
Phyllotaxis bloomno 1:1Form Sources / Guide formation, not a recipe key
Lissajous knotno 1:1Form Sources / Guide formation, not a recipe key

POST stamp_formation

Stamp a formation (the Shape card; formerly Guide Physics) onto a vector layer (physics.guide.formation.points in [-1,1], y-down). Accepts points[] and/or svg markup/URL from generate_vector. Enables physics, calms weather so the shape can read, and leaves gsapCode unset so editor/export rebake. For unique-fact morphs: stamp different formations on two scenes with the same seed, then call link_layers.
ParamTypeDescription
projectIdstringrequiredProject UUID
points[[x,y],...]optionalFormation points in [-1,1], y-down (prefer when already sampled)
svgstringoptionalSVG markup or public URL (sampled server-side)
textstringoptionalFormation text via the stroked digit atlas (0-9 % ~ . + -), e.g. "~18%"
sampleModeenumoptionalpath, fill or auto (default): path for ridges and strokes
fillModeenumoptionalWith fill: silhouette (default) or edge
countnumberoptionalPoint budget when sampling svg (12-400, default 80)
sceneId / sceneIndexstring/numberoptionalTarget scene
layerIdstringoptionalExisting vector layer id
createNewbooloptionalMint a new vector layer (ignored with layerId)
formatenumoptionalFormat to stamp (default the active format, all standard formats)
seednumberoptionalStable physics seed (same seed on both morph scenes)
loopbooloptionalPerfect loop
promptstringoptionalGuide prompt label (physics.guide.prompt)
glyphstringoptionalCharacter if creating a mark (default *)
color / glyphColorhexoptionalParticle colour, also tints previewPng
glyphScalenumberoptionalParticle mark size (glyph.scale, 20-200). Default for new MCP marks: 40 (~29px). 100 ≈ 72px.
markSvgstring|string[]optionalParticle MARK SVG markup or public URL(s). Not formation sampling (svg). Writes glyph.svg / svgStack and clears char.
particleMarks[{svg|url, on?}]optional1-N tiny SVG mix-stack particle marks (e.g. a few cats)

POST set_particle_marks

Set particle mark size and/or custom SVG marks on a vector layer without restamping formation. Writes glyph.scale and glyph.svg / glyph.svgStack (clears char). Prefer stamp_formation with glyphScale+markSvg when stamping; use this to shrink or swap marks after. Leaves gsapCode unset.
ParamTypeDescription
projectIdstringrequiredProject UUID
sceneId / sceneIndexstring/numberoptionalTarget scene
layerIdstringoptionalExisting vector layer id
glyphScalenumberoptionalParticle mark size (20-200). Default for new marks: 40
color / glyphColorhexoptionalParticle colour (sets colorOv for currentColor marks)
glyphstringoptionalFallback character when creating a layer with no SVG marks
markSvgstring|string[]optionalParticle MARK SVG markup or public URL(s)
particleMarks[{svg|url, on?}]optional1-N SVG mix-stack particle marks

POST link_layers

Link vector layers across two scenes (linkNext + shared linkId) so particles morph A→B when each scene has its own Guide formation and the same seed. Creates a same-named partner in the target scene if missing. Does not invent gsapCode.
ParamTypeDescription
projectIdstringrequiredProject UUID
fromSceneId / fromSceneIndexstring/numberrequiredSource scene (links forward)
toSceneId / toSceneIndexstring/numberrequiredTarget continuation scene
layerId / fromLayerIdstringoptionalSource vector layer id
toLayerIdstringoptionalTarget layer id; else match by name or auto-create
seednumberoptionalOptional seed synced onto both layers
matchCountsbooloptionalForce equal particle counts (default false keeps unequal populations)
preservePoolsbooloptionalKeep each layer's particle pool as authored (FormationStack plates)
parent / toParentstringoptionalParent layer ids for multi-plate morph continuity

POST create_vector_layer

Mint a new vector layer (Vector N); never reuses Vector 1. Use it for a second, independent effect pool (wind or leaves over an existing morph, SoftPlate on its own stack), then pass the returned layerId to apply_physics_recipe, stamp_formation, set_particle_marks or stamp_soft_plate. Those tools also take createNew: true to do it in one call.
ParamTypeDescription
projectIdstringrequiredProject UUID
sceneId / sceneIndexstring/numberoptionalTarget scene
glyphstringoptionalCharacter for the new mark (default *)
glyphScalenumberoptionalParticle mark size (20-200, default 40)
namestringoptionalLayer name (default Vector N)
color / glyphColorhexoptionalParticle colour (aliases)

POST stamp_soft_plate

SoftPlate v1: thin greyscale frosted glass under a mix stack (not a metal slab). Writes glyph.softPlate, which preview and export bake in. Pass layerId for an existing vector layer, or createNew: true to mint one.
ParamTypeDescription
projectIdstringrequiredProject UUID
sceneId / sceneIndexstring/numberoptionalTarget scene
layerIdstringoptionalExisting vector layer id (omit to reuse Vector 1)
createNewbooloptionalMint a new vector layer for the plate
recipestringoptionalOnly "glass" (default); metal and chrome are refused
opacitynumberoptional0.35-0.55 (default 0.45)
radiusnumberoptionalSoft corner and extent, about 36-48 (default 42)
blurnumberoptionalFrost blur in px, about 12-18 (default 14)
color / plateColorhexoptionalGreyscale colour, forced grey (default #8a8a8a)

POST stamp_formation_stack

FormationStack v1: stamp assemble, settle, hold and morph across one or two scenes without inventing gsapCode. Optional dual-population compare, a scene geoMask, multi-plate parallax and SoftPlate glass under the stack (or call stamp_soft_plate).
ParamTypeDescription
projectIdstringrequiredProject UUID
sceneId / sceneIndexstring/numberoptionalFrom scene (assemble, settle, hold)
toSceneId / toSceneIndexstring/numberoptionalMorph target scene
structureenumoptionalsilhouette (default) or lattice
plateTypeenumoptionalbw, greyscale or color (Socials default bw: white particles on black)
a / bobjectoptionalFormation A and morph target B: points, svg, text, count, sampleMode
phasesobjectoptional{ assemble, settle, hold, morph } in seconds or fractions of the total
fieldobjectoptionalCalm-field overrides (numbers)
seednumberoptionalStable physics seed
loopbooloptionalPerfect loop
glyphScalenumberoptionalParticle mark size (20-200)
markSvgstring|string[]optionalParticle mark SVG markup or URL(s)
particleMarksarrayoptionalSVG mix-stack particle marks
dualCompareobjectoptional{ massA, massB, a, b }: proportional mass for a two-population compare
platesobject[]optionalMulti-layer B/W plates for parallax and drift
geoMaskobjectoptionalScene mask { points, svg, invert, id }
forceFromenumoptionalscene or layer: where the layers take their mask and forces from
glyphOverridesobjectoptionalHybrid type cells (see set_glyph_overrides)
softPlatebool|objectoptionalBake SoftPlate thin glass under the stack
formatenumoptionalsquare, portrait, vertical, landscape or wide

POST set_geo_mask

Set a scene-level geoMask (and optional force group) and stamp forceFrom: "scene" onto the vector layers, so particle and data layers share one mask and one force group. Leaves gsapCode unset.
ParamTypeDescription
projectIdstringrequiredProject UUID
sceneId / sceneIndexstring/numberoptionalTarget scene
geoMaskobjectoptional{ points, svg, invert, id }: points are [x,y] pairs
forceGroupobjectoptionalShared force values (numbers)
forceFromenumoptionalscene or layer
layerIdsstring[]optionalLayers to stamp (default every vector layer)

POST set_glyph_overrides

Compose or read glyphOverrides for hybrid type cells (svg, char, colorOv, scale, slots). Preferred over the interim svgOverlay. There is no generator UI. Leaves gsapCode unset.
ParamTypeDescription
projectIdstringrequiredProject UUID
sceneId / sceneIndexstring/numberoptionalTarget scene
layerIdstringoptionalVector layer id
overridesobjectoptionalCell overrides to write; omit to read the current ones

Characters

A character lives on its design system and a project picks some by id (preset.characterIds), as in the editor. Its fields: name, prompt (free notes), details (role, age, height, bodyType, personality, faceShape, eyes, eyebrows, nose, lips, skin, hair, makeup), marks ({ text, side }, side is the character's own left, right, both or centre), wardrobe ({ name, material, colour }, colour a hex), panels (profile, turnaround, face, costume, palette on; expressions, poses off) and photos ({ url, role, label }, role identity, clothing, shoes or accessory).

Reads also return portrait (the full-resolution, front-facing portrait on white: the character's thumbnail and the face its sheets are drawn from; portraitSource is portrait, or front still / close-up still for a character made before portraits), sheet (main = character sheet, detail = detail sheet, current false once the look changed) and sceneDetails, the CHARACTER DETAILS text every scene prompt gets. Typical flow: create_character with projectId, generate_character_sheet, then generate_image with characterIds.

GET list_characters

Every character of a design system, or of a project's design system, with all details, sheets and photos. get_project also lists the project's picked characters in this shape.
ParamTypeDescription
designSystemIdstringoptionalDesign system slug (or pass projectId)
projectIdstringoptionalProject UUID: uses its design system and also returns projectCharacterIds
characterIdstringoptionalReturn only this character

POST create_character

Create a character in a design system's library, as the editor's Characters overlay saves one. Photos are stored in Wildfield (a data:image URI is uploaded, another https link imported). Then call generate_character_sheet.
ParamTypeDescription
namestringrequiredCharacter name, e.g. "Mara"
designSystemIdstringoptionalDesign system slug (or pass projectId)
projectIdstringoptionalProject UUID: the character goes into its design system and is picked for the project
promptstringoptionalFree notes, added after the fields in sheet and scene prompts
detailsobjectoptionalThe 13 look fields (see Characters above)
marksobject[]optional{ text, side }
wardrobeobject[]optional{ name, material, colour }
panelsobjectoptionalSheet panels on or off
photosobject[]optionalReference photos { url, role, label }

POST update_character

Change only what you pass: details and panels merge (an empty string clears a detail), marks and wardrobe replace the list. A look change marks an existing sheet out of date: scenes keep its face and take the outfit from the details until the sheet is drawn again.
ParamTypeDescription
characterIdstringrequiredCharacter id (from list_characters)
designSystemIdstringoptionalDesign system slug (or pass projectId)
projectIdstringoptionalProject UUID (uses its design system)
namestringoptionalNew name
promptstringoptionalFree notes
detailsobjectoptionalLook fields to merge
marksobject[]optionalReplaces the marks list
wardrobeobject[]optionalReplaces the wardrobe list
panelsobjectoptionalPanels to merge
addPhotosobject[]optionalPhotos to append
removePhotoIdsstring[]optionalPhoto ids to remove
photoUpdatesobject[]optional{ id, role, label }: change a photo's role or label

POST generate_character_sheet

The editor's Generate sheet. A character with no portrait first gets one (front-facing, on white); the character sheet and the detail sheet are then drawn from it on Nano Banana Pro at 2K. A new character is 3 generations, a redraw 2 (the portrait and face are kept), charged as in the editor. Blocking, 1 to 6 minutes; the portrait and the character sheet are each saved as soon as they land, so on a timeout check list_characters and get_credit_balance before retrying.
ParamTypeDescription
characterIdstringrequiredCharacter id
designSystemIdstringoptionalDesign system slug (or pass projectId)
projectIdstringoptionalProject UUID (uses its design system)
newPortraitbooloptionalDraw a fresh portrait first (one more generation). Default false: keep the face

POST duplicate_character

Copy a character with every photo, field and both sheets, saved after the original. The way to a second outfit: duplicate, change wardrobe, generate the sheet again.
ParamTypeDescription
characterIdstringrequiredCharacter id to copy
designSystemIdstringoptionalDesign system slug (or pass projectId)
projectIdstringoptionalProject UUID: also picks the copy for the project
namestringoptionalName for the copy (default "<name> copy")

POST delete_character

Remove a character from its library (cannot be undone). Sheets and photos stay in storage.
ParamTypeDescription
characterIdstringrequiredCharacter id
designSystemIdstringoptionalDesign system slug (or pass projectId)
projectIdstringoptionalProject UUID: also unpicks the character there

POST set_project_characters

Set a project's picked characters (the editor's + Character chips). They must belong to the project's design system; pass their ids to generate_image as characterIds to use them in a scene.
ParamTypeDescription
projectIdstringrequiredProject UUID
characterIdsstring[]requiredCharacter ids, in order ([] clears)

POST generate_image

Generate an AI image for use as a scene background. Pass format so the plate matches the ad. Pass designSystemId so the brand globalGenPrompt is appended.
ParamTypeDescription
promptstringrequiredImage generation prompt
filenamestringoptionalOutput filename
modelenumoptionalAI model (see table below)
formatenumoptionalSets size when width/height are omitted
designSystemIdstringoptionalAppends globalGenPrompt and scopes storage
widthnumberoptionalWidth in px (default: format size, else 2160)
heightnumberoptionalHeight in px (default: format size, else 2160)
characterIdsstring[]optionalCharacters to put in the image (needs designSystemId). Built like the editor's Generate Image with characters picked: the prompt gets CHARACTER DETAILS, and each character's sheets (or photos) plus its clothing, shoes and accessory photos are attached with their roles. With references the call runs as an edit.
sceneImageUrlstringoptionalCurrent scene image to keep as the background and composition (image 1)
projectIdstringoptionalProject the image is for
image_urlsstring[]optionalReference images. The maximum follows the selected model. Over the cap is rejected, not trimmed.

Available models

gpt-image-1ChatGPT Image v2 (default, best quality)
flux-devFlux Dev (fast)
flux-proFlux Pro
imagen4Google Imagen 4
imagen4-fastGoogle Imagen 4 Fast
nano-banana-proNano Banana Pro
ideogram-v3Ideogram v3
recraft-v3Recraft V3
recraft-v3-transparentRecraft V3 (transparent background PNG)
flux-2-proFLUX 2 Pro (optional, up to 9 reference images)
flux-3FLUX 3 (optional, not the default, up to 10 reference images)
ideogram-4.5Ideogram 4.5 (optional, not the default, 5 images including the source)

GET list_generations

Browse generated images, optionally scoped to a design system (client). Non-admins always see only images they have access to.
ParamTypeDescription
designSystemIdstringrequiredDesign system slug (e.g. "beau-brummell")

POST upload_image

Upload an image from a URL to Blob storage for use as a scene background.
ParamTypeDescription
urlstringrequiredPublic URL of the image
filenamestringoptionalFilename for the stored image

POST outpaint_image

Expand an image's edges using AI outpainting. The source image is downloaded and sent as image data. Default model is fal-ai/bria/expand (fal-ai/flux-2-pro/outpaint also available). At least one expand_* value must be greater than 0.
ParamTypeDescription
image_urlstringrequiredPublic URL of the source image (downloaded and sent as image data)
expand_topnumberoptionalPixels to expand at top
expand_bottomnumberoptionalPixels to expand at bottom
expand_leftnumberoptionalPixels to expand at left
expand_rightnumberoptionalPixels to expand at right
promptstringoptionalHint for what to paint into the expanded area
modelenumoptionalfal-ai/bria/expand (default) or fal-ai/flux-2-pro/outpaint
filenamestringoptionalOutput filename (default: extended.png)
design_system_idstringoptionalDesign system slug to scope storage to a client
original_width / original_heightnumberoptionalExact source size in px (decoded when omitted)

POST generate_vector

Generate a clean editable SVG icon/shape via fal.ai (Recraft V4.1).
ParamTypeDescription
promptstringrequiredDescription of the SVG to generate
modelstringoptionalfal.ai model (default: recraft-v4.1)
filenamestringoptionalOutput filename

POST duplicate_project

Duplicate a project (creates a copy with "(Copy)" suffix).
ParamTypeDescription
idstringrequiredProject UUID to duplicate

POST upload_audio

Import an audio file from a public URL into Wildfield storage. Returns a Blob URL. Full-length songs are fine: up to 500 MB per file (a 10 minute WAV is about 100 MB, a 10 minute MP3 about 10 to 25 MB). Wildfield fetches the URL itself, so the file never passes through the call. For a song under the whole video, set it as project.timelineAudio ({ url, fileName, cropStart: 0, volume: 100 }) with update_project: it plays from cropStart (seconds) for as long as the scenes run, so add scenes (0.1 to 30 s each) until they add up to the song. For audio under one scene, set that scene's audioUrl instead; a scene plays only its own duration of a clip.
ParamTypeDescription
urlstringrequiredPublic http(s) URL of the audio file (mp3, wav, ogg, aac, m4a, webm), up to 500 MB
filenamestringoptionalFilename for stored audio

DELETE delete_design_system

Delete a design system (cannot delete "default"). Pass reassignTo to move leftover projects first.
ParamTypeDescription
idstringrequiredDesign system slug to delete
reassignTostringMove leftover projects onto this design system (usually default)

GET list_approval_items

List approval-feed items for a design system review token (pending and decided). Pass the token from Share / Submit for approval. REST: GET /api/approval/feed.
ParamTypeDescription
tokenstringrequiredDesign system approval_feed_token (from the /review/<token> URL)
includeDecidedbooleanoptionalInclude decided items (default true)

POST create_review_link

Mint or retrieve the approval review link for a design system. Returns the token and public review URL (https://wildfield.io/review/<token>). If the token already exists, returns the existing one. REST: POST /api/approval/share.
ParamTypeDescription
designSystemIdstringrequiredDesign system ID (slug), e.g. "firefly-express"

Returns { ok, designSystemId, token, reviewUrl, minted }.

POST submit_for_approval

Submit scenes for approval. Creates pending approval items in the design system's feed. Auto-mints the review token if missing. REST: POST /api/approval/submit.
ParamTypeDescription
projectIdstringrequiredProject UUID
sceneIdsstring[]requiredArray of scene IDs to submit
designSystemIdstringoptionalDesign system ID (derived from project if omitted)
previewsobjectoptionalPreview HTML keyed by sceneId
sceneMetaobjectoptionalScene metadata (animated, animationReasons, durationSec, loop, width, height) keyed by sceneId
sequencebooleanoptionalSubmit as a single sequence item instead of individual scenes
allowEmptyCopybooleanoptionalSubmit when Meta primaryText is missing (default false: 400 missing_primary_text)

Returns { ok, token, reviewUrl, count, itemIds }.

POST delete_approval_items

Delete specific items from a design system's review feed, for example stale cards left by deleted projects or scenes. Does not rotate the review token, so the same review URL keeps working. REST: POST /api/approval/delete.
ParamTypeDescription
tokenstringrequiredDesign system approval_feed_token (from the /review/<token> URL)
itemIdsstring[]requiredApproval item UUIDs to delete

Returns { ok, deleted, count }.

POST export_composition_html

Build render-ready composition HTML from a projectId. This is the same buildComposition(scenes, { mode: "video", stripEditor: true, autoplay: false, hyperframes: true }) string the editor POSTs to /api/renders/video. Prefer render_video with projectId when you only need to start a render (server builds HTML). Use this when you need the HTML itself. REST: POST /api/agent/export-composition. Not the same as preview_scene / buildPreviewHtml (critique still only).
ParamTypeDescription
projectIdstringrequiredProject UUID
formatenumoptionalsquare / portrait / vertical / landscape / wide (default: project activeFormat)
scopeenumoptionaltimeline (all scenes, default) or scene
sceneIdstringoptionalWhen scope=scene
sceneIndexnumberoptional0-based index when scope=scene

Returns html, width, height, format, durationSec. Optional for renders: prefer render_video({ projectId }).

POST render_video

Start a server-side MP4 render. Prefer projectId (server builds composition HTML so agents avoid shipping ~170KB+ HTML through MCP args). Or pass html + width + height from export_composition_html / the editor. Returns a renderId + executionArn - poll get_render_progress until SUCCEEDED for the video URL. REST: POST /api/renders/video accepts the same shapes. One render at a time per account. Check first with get_active_render. Stop with cancel_render.
ParamTypeDescription
projectIdstringoptionalProject UUID (preferred). Server builds HTML then starts the render. Require this or html+width+height
htmlstringoptionalFull composition HTML (required with width+height if projectId omitted)
widthnumberoptionalOutput width in pixels (required with html)
heightnumberoptionalOutput height in pixels (required with html)
formatstringoptionalWith projectId: ad format (square/portrait/...). With html: container (default mp4)
scopeenumoptionalWith projectId: timeline (default) or scene
sceneIdstringoptionalWith projectId + scope=scene
sceneIndexnumberoptional0-based scene index when scope=scene
fpsnumberoptionalFrames per second (default 30)
qualitystringoptionalRender quality (default "high")
loopsnumberoptionalExport the video repeated 1 to 8 times (default 1). Seamless on a Perfect-loop scene. The composition is rendered once and the finished MP4 is repeated, so the credit price does not multiply - but pass the same loops to get_render_progress, which is where the repeat happens

GET get_render_progress

Poll the progress of a video render started with render_video. When status is SUCCEEDED, videoUrl holds a signed download URL (valid ~1 hour).
ParamTypeDescription
executionArnstringrequiredexecutionArn returned by render_video
filenamestringoptionalDownload filename for the signed URL (default: output.mp4)
loopsnumberoptionalRepeat the finished video 1 to 8 times (default 1). Must match the loops given to render_video. The picture is stream-copied so the join is frame-exact; the audio is re-encoded once from a sample-exact loop so the join does not click
durationSecnumberoptionalOne pass length from render_video; with loops above 1 it trims each pass

GET get_active_render

Check whether this account currently has a video render in flight, wherever it was started (this tool, another agent, or the Wildfield editor). Returns the running render or null. Call this before render_video if you are unsure.

No parameters.

POST cancel_render

Stop a running video render server-side and discard it. Stops the AWS execution, refunds the credits charged at start, and deletes partial output. Safe to call twice, and safe after the render has already finished (it then reports what happened and changes nothing).
ParamTypeDescription
executionArnstringrequiredexecutionArn from render_video or get_active_render

GET get_motion_skill

Load the Wildfield Motion Skill: the authoritative knowledge for Animate Physics, Guide Physics, and layout motion. Returns a training prompt plus structured JSON of every parameter, range, default, and named recipe. Call this before you write any motion onto a layer, then get_project → set layers[].physics / scene.timelineAnim → update_project.

No parameters. Do not invent force values; use the ranges this tool returns.

GET recall_motion

Recall the most recent exported (confirmed) motion patterns for the signed-in user, optionally scoped to one design system. Replay a recalled pattern when the brief says "like the last one". Returns an empty list unless the MOTION_AGENT flag is on.
ParamTypeDescription
designSystemIdstringoptionalScope to one client
limitnumberoptionalMax patterns (default 8)

GET get_credit_balance

Check YOUR OWN credit balance and recent spend. Use it before a generation, and after a timeout to confirm whether you were charged. A failed or timed-out generation is not charged. The usage part counts every operation in the period at Wildfield prices (the same numbers as admin Usage & Costs), whether or not it was charged to the wallet: a billing exempt account, or any account while wallet charging is off, has usage but no wallet charges (exempt, billing_enforced).
ParamTypeDescription
periodstringoptionalUsage window, e.g. 7d or 30d (1-90 days, default 30d)

POST render_screenshot

Render composition HTML to a PNG and return the image. Max dimension 4096px. To persist it to a public URL, pass a data URI to save_render.
ParamTypeDescription
htmlstringrequiredFull composition HTML to screenshot
widthnumberrequiredOutput width in pixels (max 4096)
heightnumberrequiredOutput height in pixels (max 4096)

POST save_render

Save a rendered image (base64 data URI) to storage and return a public URL. Use to persist a PNG produced by render_screenshot or an in-app export.
ParamTypeDescription
dataUristringrequiredBase64 data URI, e.g. "data:image/png;base64,..."
filenamestringoptionalOutput filename (default: render.png)

POST inpaint_image

AI-fill a masked region of an image from a prompt (generative fill). Provide the source image URL and a mask URL where white marks the area to regenerate.
ParamTypeDescription
image_urlstringrequiredPublic URL of the source image
mask_urlstringrequiredPublic URL of the mask image (white = region to fill)
promptstringoptionalWhat to paint into the masked region
modelstringoptionalInpaint model (default: fal-ai/bria/genfill)
filenamestringoptionalOutput filename (default: retouched.png)
design_system_idstringoptionalDesign system slug to scope storage to a client

POST retouch_image

Retouch an image by erasing/removing content within a masked region (object removal). Use mode "inpaint" to regenerate the region from a prompt instead.
ParamTypeDescription
image_urlstringrequiredPublic URL of the source image
mask_urlstringrequiredPublic URL of the mask image (white = region to retouch)
modeenumoptionalerase removes content (default); inpaint fills from the prompt
promptstringoptionalFill prompt (used when mode is "inpaint")
modelstringoptionalOverride model (default depends on mode)
filenamestringoptionalOutput filename (default: retouched.png)
design_system_idstringoptionalDesign system slug to scope storage to a client

POST edit_image

Edit an image with a natural-language instruction (e.g. "remove the background", "make it night-time"). Some models (e.g. ideogram-rembg) need no prompt. Pass format or width/height so the result matches the ad (a 9:16 scene must not come back landscape).
ParamTypeDescription
image_urlstringrequiredPublic URL of the source image
promptstringoptionalEdit instruction (required for most models)
modelenumoptionalEdit model (see table below)
filenamestringoptionalOutput filename (default: edited.png)
design_system_idstringoptionalDesign system slug to scope storage to a client
formatstringoptionalAd format key (square, portrait, vertical, landscape, wide) so the edit keeps that aspect
widthnumberoptionalOutput width in pixels (with height)
heightnumberoptionalOutput height in pixels (with width)
image_urlsstring[]optionalExtra reference images, not including image_url. The total, including the source, cannot exceed the model cap. Over the cap is rejected.

Available models

nano-banana-proNano Banana Pro (default)
nano-banana-2Nano Banana 2.1 (up to 14 images)
nano-bananaNano Banana
flux-kontextFlux Kontext
qwen-editQwen Edit
seedream-editSeedream Edit
ideogram-rembgIdeogram background removal (no prompt needed)
flux-2-proFLUX 2 Pro (optional, up to 9 images)
flux-3FLUX 3 (optional, not the default, up to 10 images)
ideogram-4.5Ideogram 4.5 (optional, not the default, source plus up to 4 references)
gpt-image-2GPT Image 2 (optional, up to 16 images)
gpt-image-2.5-flareGPT Image 2.5 Flare (optional here, up to 16 images)
gpt-image-2.5-sunburstGPT Image 2.5 Sunburst (optional here, up to 16 images)

POST enhance_image

Upscale / enhance an image's resolution and detail.
ParamTypeDescription
image_urlstringrequiredPublic URL of the source image
modelenumoptionalaura-sr (default), creative-upscaler, or clarity-upscaler
strengthnumberoptionalCreative strength 0–1 (0 = faithful upscale, higher invents more detail; default 0)
filenamestringoptionalOutput filename (default: enhanced.png)
design_system_idstringoptionalDesign system slug to scope storage to a client

POST crop_image

Crop an image by percentage from each edge. At least one crop value must be greater than 0.
ParamTypeDescription
image_urlstringrequiredPublic URL of the source image
crop_topnumberoptionalPercent to crop from the top (0–100)
crop_bottomnumberoptionalPercent to crop from the bottom (0–100)
crop_leftnumberoptionalPercent to crop from the left (0–100)
crop_rightnumberoptionalPercent to crop from the right (0–100)
filenamestringoptionalOutput filename (default: cropped.png)
design_system_idstringoptionalDesign system slug to scope storage to a client

POST delete_generation

Delete a generated or uploaded image from storage by its Blob URL. Only image-gen/ and uploads/ URLs are deletable, and you must own the generation (or be an admin).
ParamTypeDescription
urlstringrequiredThe Blob URL of the image to delete

POST capture_url

Capture a screenshot of a public web page and return the PNG. Private/local hosts are blocked.
ParamTypeDescription
urlstringrequiredPublic http(s) URL to capture
widthnumberoptionalViewport width in px (1–4096, default 1280)
heightnumberoptionalViewport height in px (1–4096, default 720)
fullPagebooleanoptionalCapture the full scrollable page height (default false)

Design System Schema

The complete field reference for design systems. All fields are required for correct rendering.

FieldTypeDescription
idstringURL-safe slug (e.g. beau-brummell)
namestringDisplay name
clientNamestringClient display name
colorVarsarray4 named colour swatches: [{hex, name}] (Primary, Secondary, Dark, Light)
fontHeadlinestringHeadline font stack (e.g. "Montserrat, sans-serif")
fontBodystringBody font stack (e.g. "Inter, sans-serif")
fontImportURLGoogle Fonts import URL
customFontsarrayUploaded fonts: [{name, url, fileName}]
fontH1 – fontH3stringPer-element font overrides (blank = inherit)
fontCtastringCTA button font (blank = inherit fontBody)
h1Weight – ctaWeightstringCSS font-weight values
h1Transform – ctaTransformstringnone or uppercase
h1LetterSpacing – ctaLetterSpacingstringCSS letter-spacing
ctaRadiusstringButton radius multiplier (e.g. 0.5)
ctaAlignstringleft or center
contentAlignstringleft or center
contentPositionstringtop, middle, or bottom
logoPositionstring9-grid position (e.g. top-left, bottom-center)
logoScalenumberLogo size percentage (100 = default)
logoSvgstringLogo SVG blob URL
logoFileNamestringLogo filename
disclaimerstringLegal disclaimer text (blank if none)
disclaimerAlignstringleft or center
globalGenPromptstringDefault AI image generation prompt prefix
globalLocksobjectLocked fields applied across all scenes

Project Schema

The full project object structure returned by get_project and expected by update_project.

{
  "version": 2,
  "designSystemId": "client-slug",
  "preset": { /* full design system snapshot */ },
  "activeFormat": "square",
  "activeSceneId": "scene-uuid",
  "totalDuration": 12,
  "scenes": [ /* array of scene objects */ ]
}
FieldTypeDescription
versionnumberSchema version (always 2)
designSystemIdstringLinked design system slug
presetobjectFull design system embedded in project
activeFormatenumsquare, portrait, vertical, landscape, wide
activeSceneIdstringCurrently selected scene
totalDurationnumberSum of all scene durations. When a background video track is set, the editor can fit the timeline to the video length
scenesarrayOrdered array of scene objects
bgVideoTrackobject|nullProject-level background video. One clip under the whole timeline (not per scene). { url, ref, fileName, poster, muted, srcW, srcH }. Preview and Lambda export share this single element so scene cuts do not flash black. Do not write scenes[].bgVideo for a campaign bed - set this instead
timelineAudioobject|nullProject-level audio bed (same idea as the video track): one track across the timeline. { url, fileName, cropStart, volume, fadeIn, fadeOut }, times in seconds. The whole file is kept, however long; it plays from cropStart for the length of the scenes. Render Timeline carries it continuously across the stitched ads.

Scene Schema

A scene may contain any of these fields. Almost all are optional. Missing fields fall back to design-system and editor defaults, so a scene only needs the fields it actually sets.

FieldTypeDescription
Content
idstringUnique scene identifier
namestringScene display name
h1stringHeadline text
h2stringSupporting text
h3stringThird line
ctaTextstringCTA button label
disclaimerOverridestringPer-scene disclaimer (blank = use preset)
durationnumberScene duration in seconds
Layout
logoPositionstring9-grid logo placement
logoScalenumberPer-scene logo scale (100 = default)
contentPositionstringtop, middle, bottom
contentAlignstringleft or center
ctaAlignstringleft or center
paddingPerFormatobjectPer-format padding: {square: {left, right}, ...}
layoutPerFormatobjectPer-format layout overrides (position/align/scale), inherited leftward across formats
Layout Template & Grid
layoutTemplatestringApplied layout-template id, one of classic, centered, hero, topLead, bottomBar, footer, logoBottom, cornerMark, minimal, grid, cinematic (Centered logo), none, logoBottomLeft, logoBottomRight. Sets the slot fields above; a template is a starting point, not a constraint.
gridEnabledbooleanOpt-in free-placement mode. When true, slot typography is suppressed and freeEls is rendered instead.
freeElsarrayFreely placed elements: [{id, role, text, cx, cy, w, h, angle, rotated}], where cx/cy/w/h are fractions of the frame (frame-fractional poses), so they hold across formats.
gridColsnumberEditor snap-lattice columns for placing/dragging grid elements
gridRowsnumberEditor snap-lattice rows
gridPerFormatobjectPer-aspect-ratio grid: {square: {freeEls, gridCols, gridRows}, vertical: {...}}, swapped in/out on format switch (like bgPerFormat)
Colours
bgColorhexSolid background colour
textColorhexText colour
ctaBgColorhexCTA background
ctaTextColorhexCTA text
themeModestring"", dark, light, brand
colorOverridesobjectPer-element overrides: {logo: "light", h1: "brand"}
textOpacitynumberText layer opacity (0–1)
Scale
h1Scale–ctaScalenumberElement size percentage (default 100)
disclaimerScalenumberDisclaimer size percentage (default 100)
Background
bgTypeenumcolour, gradient, image. A campaign video bed is project.bgVideoTrack, not a per-scene video
bgImageURLBackground image URL
bgImageFileNamestringBackground image filename
bgVideoURLLegacy per-scene video. Prefer project.bgVideoTrack so one decoder covers the whole ad
bgVideoFileNamestringLegacy per-scene video filename
bgVideoTrimStartnumberLegacy trim start (0-1). The project track uses the video's own in-point / duration
bgVideoTrimEndnumberLegacy trim end (0-1)
bgGradientFromhexGradient start colour
bgGradientTohexGradient end colour
bgGradientAnglenumberGradient angle in degrees
bgScalenumberBackground zoom (100 = default)
bgFlipHbooleanFlip background horizontally
bgFlipVbooleanFlip background vertically
bgCropTop–bgCropRightnumberBackground crop pixels per side
focusPinobject{x: 50, y: 50}, the image focal point
focusPinsobjectPer-format focus: {square: {x,y}, ...}
bgPerFormatobjectPer-format bg overrides: {square: {bgType, bgColor, ...}}
bgFolderIndexnumberActive index in folder bg mode
Overlay
overlayTypeenumnone, fill, gradient
overlayColourhexOverlay colour
overlayOpacitynumberOverlay opacity (0–1)
overlayGradientDirstringtop, bottom, both, radial
overlayGradientColourhexGradient overlay colour
SVG Layers & Glyphs
svgLayersarray[{id, svg, fileName, scale, offsetX, offsetY, colorOv}]
glyphsarray[{id, char, font, x, y, scale, rotation, colorOv}]
maskLayerIdstringId of the canvas layer used as a mask for the layer stack (layer-as-mask)
layers[].contrastnumberLayer-global contrast (100 = default) - applied to the whole layer, not keyframed
layers[].saturationnumberLayer-global saturation (100 = default) - applied to the whole layer, not keyframed
layers[].positionobject{ x, y, scale, rotation } - the layer's one authored pose. Scale is percent (100 = identity). On a linked pair each scene stores its own pose; playback holds it until kf.t, then morphs
layers[].kfobject{ t } - when that pose lands, 0-1 through the scene. Dragged on the scene bar. There is no Start / Mid / End pose any more
layers[].linkNextbooleanHand this layer to the same-named layer in a later scene
layers[].linkEasestringJunction ease id (e.g. easeOutExpo). Empty = Auto (velocity-matched). Shapes both pose and physics across the cut
layers[].physicsobjectAnimate Physics. { enabled, seed, loop, kf: { start, mid?, end? }, guide }. Call get_motion_skill before writing this
AI Generation
genAiPromptstringAI image prompt for this scene
genAiModelstringAI model key
genAiCamerastringCamera angle (e.g. "eye-level")
genAiLensstringLens type (e.g. "standard")
genAiShotSizestringShot size (e.g. "medium-shot")
genAiLightingstringLighting style
genAiBackgroundstringBackground description
genAiStylestringVisual style (e.g. "photo-realistic")
genAiMoodstringMood/atmosphere
genAiImagesobjectPer-format generated images
bgImageHistoryobjectPer-format image history arrays
bgImageHistoryIndexobjectPer-format history index
Cinematic
cinematicbooleanEnable cinematic mode (hides text)
cinematicShowLogobooleanShow logo in cinematic mode
cinematicLogoScalenumber|nullLogo scale override for cinematic
cinematicLogoPositionstring|nullLogo position override for cinematic
Audio
audioUrlURLAudio file URL
audioFileNamestringAudio filename
audioOffsetnumberAudio offset in ms
audioPlayModestringloop, once, fade
audioVolumenumberVolume 0–100
audioCropStart–audioCropEndnumberAudio trim in ms
audioFadeOutnumberFade out duration in ms
Capture
captureEnabledbooleanEnable capture mode (overrides imaging)
captureUrlURLURL to capture
captureFilterstringCSS filter preset name
captureAnimModestringnone or animate
captureEasingstringEasing function name
captureKeyframesobjectPer-format keyframes
Advanced
htmlOverridestringCustom HTML content
useHtmlOverridebooleanEnable HTML override mode
notesstringInternal notes
animationNotesstringAnimation description
elementAnimationsobjectPer-format element animation data

Format Specifications

Font sizes are calculated by the renderer, not stored. The root unit determines all text sizes via multipliers.

FormatDimensionsRootH1 (3x)H2 (2x)H3 (1.25x)CTA (1x)
Square1080 × 108024px72px48px30px24px
Portrait1080 × 135028px84px56px35px28px
Vertical1080 × 192028px84px56px35px28px
Landscape1920 × 108022px66px44px28px22px
Wide1200 × 62822px66px44px28px22px

Per-scene scale overrides: h1Scale, h2Scale, h3Scale, ctaScale (percentage, default 100).

Vector Expressions

Vector text layers accept inline expression tokens wrapped in curly braces. Each token evaluates live against the scene clock, so a single text string can count a number, cycle through words, type on, or print a stable random digit, with no keyframes. Tokens evaluate identically in preview and in server render, and a token persists across linked scenes (the linked clips share one timeline window, so a {0-100} ramps once across the whole chain rather than restarting at each cut).

Where to type them: Canvas mode → a Vector layer → the text field (placeholder Type text…). Any glyph/string entry in the layer's text stack accepts tokens. There is no MCP parameter: expressions live inside the text content itself.

TokenTypeWhat it does
{A-B}NumberTweens linearly from A to B over the layer's timeline window. {0-100} counts up; {100-3} counts down. A and B may be negative and/or decimal. Flags: |$, |%, |th, |commas, |step:N, |ease:out, |prefix:X, |suffix:X, hold @0.2-0.8.
{w1, w2, …}WordsSplits the window into N equal segments and shows each word in turn. {monkey, cat} = 50 / 50; the last word holds to the end. Weight a line with {Save:2, Shop:1}. {day:Mon,Tue} is the same idea.
{random}RandomA single stable digit 1-9. Seeded once (never flickers) and identical in preview and every render chunk. Per particle when Animate Physics is on.
{rand:A-B}RandomA stable integer in the range, seeded the same way as {random}.
{pick:a, b}RandomA stable word pick. Same seed, same word, every render.
{type:TEXT}RevealTypewriter across the scene. {type:TEXT:2} types in 2 seconds, then holds. Seconds never run past the scene end. Hold is extra time after that.
{scramble:TEXT}RevealDecode from random glyphs into the target by progress.
{time:0:30-0:00}ClockCountdown or count-up as m:ss. Also accepts seconds: {time:90-0}.
{bar:0-10}BarA text bar of █ / ░ blocks that fill with the tween.
{date:1-31}NumberInteger tween (no decimals).
{…:s0.5}StaggerFade each character on over 0.5 seconds. One word: {WORLD:s0.4}. Restarts when a word cycle changes. Also |s:0.5.
{…:d0.5}DelayWait 0.5 seconds, then start. Combos: {cat:d0.2:s0.2}. Also |d:0.5. Seconds never run past the scene end.

Number animation: decimals and direction

The decimal precision of the output matches the inputs: the result is shown to max(decimals of A, decimals of B) places. Both-integer inputs stay integer (rounded). Descending ranges (B < A) animate backwards.

You typeAt 0%At 50%At 100%
Score: {0-100}Score: 0Score: 50Score: 100
{100-3} left100 left52 left3 left
{0-1.5}x0.0x0.8x1.5x
{-20-20}°-20°0°20°

Examples

Raised {0-100}% this quarter      → counts 0 → 100 over the scene
Only {50-0} left!                    → counts down 50 → 0
{Save big, Move fast, Win}          → three words, ~33% each, last holds
Gate {random}, Seat {random}          → two independent stable digits
{0-100}% / {loading, almost, done}     → a counter and a word cycle in one line
{0-9999|$} off                              → $0 … $9,999
{dogs,cats,cows:s0.5}                      → words, each character fades on over 0.5s
{type:LIMITED TIME}                         → types on across the scene

Behaviour & gotchas

Multiple tokensEvery {…} in the string is evaluated. Each {random} gets its own stable digit (seeded by its position).
Plain text untouchedAnything outside braces is printed verbatim: $, %, punctuation, spaces all survive.
Unknown tokensA brace group that matches none of the forms (e.g. {hello}) is left as the literal text {hello}. No error, no blank.
Nested bracesNot supported. {{0-100}} won't tween; keep tokens flat.
Number vs wordsA comma inside the braces makes it a word cycle; a single dash between two numbers makes it a tween. {0,1,2} is words; {0-2} is a number.
Start / endValues clamp at the window edges: a number holds its first value before the window and its last value after it; a word cycle holds the final word.
Per-word colourA separate markup that shares the same copy: [[b2|two words]] paints that run in palette colour b2. It uses neither brace, and the runs are parsed before the token scanner, so a token can sit inside a coloured run ([[b1|{0-100}%]]) and keep its colour while it counts. Highlight words in the copy field and pick a swatch to write it.

Verified against app.html: the token runtime vexprEvalCore (parse + interpolation) and the live updater __vectorExprRuntime.

Animate & Guide Physics

Animate Physics is a particle engine on a Vector layer. Each glyph or SVG becomes many copies. Eight forces push those copies around the frame. The same numbers drive preview and the cloud MP4, because the runtime is a pure function of time and seed. An untouched panel is a no-op (all forces 0). Flip the Engine switch on, then either dial forces or pick a Recipe.

Call get_motion_skill then apply_physics_recipe. Do not invent ranges or gsapCode.

How a field feels

ForceWhat it doesSliderDefault
windA steady breeze. Every particle is pushed the same way, like leaves crossing the frame0-500
turbulenceChaotic noise. Higher values get jittery and unpredictable0-600
gravityA downward pull. Particles fall and settle. Off in Perfect loop0-500
swirlSpin around the centre, like water down a drain0-1000
orbitCircle the centre at a steady radius instead of spiralling in0-400
waveA rolling ripple across the field, like a flag or water surface0-500
floatGentle upward lift with a soft bob: balloons, embers, bubbles0-600
springElastic pull back to each particle's home. Off in Perfect loop0-500

Layout and globals

Layout is structural. On a linked chain, count is owned by the first scene so particle i stays the same particle across the cut. Forces and globals can ramp from Start to End on that scene; they also lerp across a LINK, the same way Scale does.

GroupControlsSliderType beyondDefault
Layoutcount (how many copies)1-800 (FORMATION_MAX)same cap1
Layoutfill (cluster to frame to overshoot)0-12050060
LayoutfanOut0-100-0
Globalspeed (tempo; 50 = 1x)0-50100 (2x)50
Globalintensity (multiplies every force)0-150-100
Globalrandomness0-100-50
Globalstagger0-100-20

Click a number to type past the slider. The track is the useful band; extremes are opt-in.

Engine, Perfect loop, Trail, Recipes

EngineMaster on/off. Off keeps every force, guide and recipe; keyframe tweens take back the layer
Perfect loopScene-level. Last frame matches the first. Gravity and Spring switch off on particles. Animate gradient wraps too. Periodic forces lock to whole cycles
Trailcopies (0-12), copyTimeOffset, copyFade: a time-offset tail behind each particle
RecipesLive tiles (crosswind dunes, supernova, ticker count…). A tile writes the same physics fields you can set by hand
RandomizeRolls a coherent recipe (breeze, tornado, orbits) and a new seed. Amber padlocks pin a value so Randomize leaves it

Guide Physics and Form Sources

Guide Physics sits on top of the running field. A prompt such as "form a ring" or "rotate 360" bakes a { formation, modifier } spec. Physics still runs underneath. Clear the shape and you are back to pure forces; the modifier (Motion's Displace), Follow and Emit stay.

In the Canvas panel (v1.999.0) this is three cards over the same physics.guide fields: Shape (saved id guide-physics: the formation from a prompt, a generator, an image, an SVG or data, and its Reveal), Draw (id drawing: the path or mask in layer.draw, Smoothness, Show guide, Fill mask) and Follow / Emit (id follow-emit: guide.followPath or guide.emitter, one job at a time). No field moved, so saved projects render exactly as before.

Form Sources (on by default) feed that formation from something other than a prompt:

SourceYou supplyHow particles assemblePoints
PromptA sentenceAI returns a shape and/or a motionup to 800
ShapesA generator (phyllotaxis, spiral, sphere…)Deterministic pointsup to 800
ImagePNG / JPG / WebPSilhouette or edge sample12-800 (160)
SVGAn .svgTrace along the path. Optional draw-on12-800 (80)
DataNumbersChart polyline12-800 (64)
DrawA path or mask on the canvasFollow-path or fill-mask. Hide the guide without deleting itup to 800

A to B across a LINK: each linked scene can hold its own formation. Particles melt from shape A to shape B across the cut. Count stays locked so identity holds. Ease on the Keyframes panel (Slow finish, etc.) shapes both the pose and the physics clock.

Gradient FX (animated linear / conic / mesh / aurora) is on by default. Opt out with ?gradientFx=off.

Verified against app.html v1.484.0: PHYSICS_DEFAULTS, slider arrays, FORMATION_MAX 800, FORM_SOURCES / GRADIENT_FX default on, one pose per layer at kf.t.

Editor Feature Map

The editor's user-facing creative surface, beyond the API/MCP layer above. Most features are always visible; a few are flag-gated (enable via URL param or localStorage as shown above). This map reflects the shipping product. See the dedicated sections above for expressions and physics.

AreaWhat's thereStatus
Modes & layers
ModesInput · Layout · Canvas · Output, plus contextual Imaging (Retouch) & Sound.Live
Layer sourcesMedia (image/video), Gradient, Stream (capture), Vector (glyph/SVG). Per-layer visibility, duplicate, reorder, bring-to-top, blend modes, lock.Live
Motion & effects
Vector expressionsInline {A-B} / {w1,w2} / {random} plus format, type, scramble, pick, time, bar, and :s0.5 stagger. → full referenceLive
Animate PhysicsForce-field particle motion, Engine / Trail, Recipes, Randomize, locks, Guide Physics. Hold and Perfect loop live in Finish on every layer mode. → full referenceLive
Form SourcesPrompt, Shapes, Image, SVG, Data, Draw. A→B morph on a LINK. Opt out with ?formSources=off.Live (default on)
Gradient FXAnimated linear / conic / mesh / aurora gradients with Randomize. Perfect loop wraps the field so the last frame matches the first. Opt out with ?gradientFx=off.Live (default on)
KeyframesOne pose per layer. Drag the marker on the scene bar. Physics forces still have Start / End slots. LINK + Ease under Keyframes.Live
Linked scenesLINK hands pose and physics to the next scene. Authored Scale (and other pose sliders) hold until the keyframe, then morph. Count stays locked across the chain.Live (default on)
Background videoOne project-level bgVideoTrack under the whole timeline. Timeline can fit to the video length. Prefer this over per-scene bgVideo.Live
Colour & type
Per-object colourMaster and per-glyph colour overrides with an eyedropper; right-click to reset to inherit; colour is keyframeable.Live
TypographyGoogle Fonts + custom uploads, per-element font / weight / transform / letter-spacing; alignment (H & V).Live
Capture & imaging
Live feedThe camera button top right of the preview puts a feed behind or over the ad: YouTube (muted), Web page, Share screen, Camera or Off, with Opacity, Over / Behind and Fit / Cover. Saved to your account per project; only you see it, and it is never rendered or exported. Web page takes an https link: the server first reads the page's framing headers (POST /api/capture/frame-check). A page that allows embedding plays live in a sandboxed frame (no top navigation, no referrer); turn on Interact to click and scroll it, off to give the editor its clicks back. A page that blocks embedding (X-Frame-Options or CSP frame-ancestors), is not https, or cannot be reached shows a still snapshot (the capture_url headless Chrome capture: no credits, the render rate limit) with a Share screen button for live and a manual Refresh snapshot. Wildfield's own pages embed too, so you can show and record the editor itself for tutorials; inside an embedded Wildfield the Web page source is off, so it never nests twice.Live
Live FrameLaunch Frame over a live feed, compose the shot, then SNAP a still or REC a clip into the Library (and the scene). YouTube and a live web page record through Chrome / Edge Element Capture of this tab; camera, screen and a web page snapshot work in every browser.Live
AI imagingRetouch (erase / inpaint), AI Fill (extend), enhance / upscale, background removal, poster-frame edit. (Also via MCP: see endpoints.)Live
Sound
Sound modeDrag a track in → waveform with crop; threshold slider; Detect transients → auto-scenes on the beat; voice-over track + mix; master volume. Per-scene fades preview today.Live (default on)
Formats & output
Formats & safe-zoneSquare, Portrait, Vertical, Landscape, Wide + Custom; safe-zone overlay (Instagram / TikTok / Custom presets); snap-to-guides; apply-to-all-formats.Live
ExportPNG (active scene / all-scenes ZIP), MP4 (server render), self-contained HTML ZIP, HyperFrames scene/timeline for Figma embed, project JSON. Campaign grouping.Live
Systems & account
Design systemsLoad / create / edit / delete; auto-save brand changes; shared asset library & cloud folder; DS-scoped project lists.Live
Billing & creditsCredit balance, top-up now, auto-top-up (threshold + daily cap, min $5), card management via Stripe.Live

Agent Skills

Skills are prompt modules your agent can invoke to get context-aware guidance for specific tasks. Install the wildfield skill in your agent to unlock all MCP tools and workflow knowledge.

Core Skill

wildfield

Primary skill for all Wildfield operations. Covers MCP tool usage, design system creation, project workflows, scene field requirements, and AI image generation. Triggers on wildfield, design system, create a project, build scenes, generate ad creative.

get_motion_skill

Runtime skill, fetched over MCP. Call it before writing layers[].physics. Returns every force, range, default, and named recipe so an agent can turn a brief ("leaves in a breeze across two scenes") into real field values instead of guessing.

JSON Templates

Download these templates, fill in your client's brand details, and import via the API or MCP tools.

design-system.json project.json

Design System Template

{
  "id": "your-client-slug",
  "name": "Your Client Name",
  "uiAccent": "#2563eb",
  "colorVars": [
    { "hex": "#2563eb", "name": "Primary" },
    { "hex": "#1e40af", "name": "Secondary" },
    { "hex": "#0a0a0a", "name": "Dark" },
    { "hex": "#f5f5f5", "name": "Light" }
  ],
  "fontHeadline": "'Inter', sans-serif",
  "fontBody": "'Inter', sans-serif",
  "fontH1": "'Inter', sans-serif",
  "fontH2": "'Inter', sans-serif",
  "fontH3": "'Inter', sans-serif",
  "fontCta": "'Inter', sans-serif",
  "fontImport": "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap",
  "h1Weight": "900",
  "h2Weight": "400",
  "h3Weight": "400",
  "ctaWeight": "700",
  "h1Transform": "none",
  "h2Transform": "none",
  "ctaTransform": "uppercase",
  "h1LetterSpacing": "-0.02em",
  "h2LetterSpacing": "0",
  "ctaLetterSpacing": "0.05em",
  "ctaRadius": "4px",
  "ctaAlign": "center",
  "contentAlign": "left",
  "contentPosition": "middle",
  "logoPosition": "top-left",
  "logoScale": 100,
  "logoSvg": "",
  "logoFileName": "",
  "disclaimer": "",
  "disclaimerAlign": "left"
}

Project Template

{
  "name": "My Campaign Name",
  "designSystem": "your-client-slug",
  "scenes": [
    {
      "h1": "Your Main Headline",
      "h2": "Supporting copy goes here",
      "h3": "",
      "cta": "Call To Action",
      "bgType": "colour",
      "bgColor": "#0a0a0a",
      "notes": "Scene description or AI image prompt",
      "duration": 6
    },
    {
      "h1": "Second Scene",
      "h2": "Another supporting message",
      "h3": "",
      "cta": "Learn More",
      "bgType": "gradient",
      "bgColor": "#1e40af",
      "notes": "",
      "duration": 6
    }
  ]
}

Preview loop vs Perfect loop vs Export loops

Three controls share the word “loop” and mean different things:

Preview loopTimeline transport button. Editor-only boomerang (off / this scene / full timeline). Not a seamless export join.
Perfect loopFinish panel / scene.loop + physics.loop. Seamless join for playback and export. MCP loop: true turns this on.
Export loopsOutput / render_video.loops (1–8). Repeats the finished MP4. Seamless only when Perfect loop is on.

Project deep link for Socials/MCP: https://wildfield.io/app?project=<uuid> (also returned as openUrl from create/list/get/duplicate). Full agent notes: docs/agent-socials-friction.md.

Direct API Fallback

When MCP tools aren't available, use the REST API directly with your API key.

# List design systems
curl -s -H "X-API-Key: YOUR_KEY" "https://wildfield.io/api/design-systems/list"

# Save / update design system
curl -s -X POST -H "X-API-Key: YOUR_KEY" -H "Content-Type: application/json" \
  "https://wildfield.io/api/design-systems/save" \
  -d '{"id": "client-slug", "data": { ... }}'

# List projects
curl -s -H "X-API-Key: YOUR_KEY" "https://wildfield.io/api/projects/list"

# Get project by UUID
curl -s -H "X-API-Key: YOUR_KEY" "https://wildfield.io/api/projects/PROJECT_UUID"

# Create new project
# project blob MUST include version, designSystemId, preset.name, activeFormat
curl -s -X POST -H "X-API-Key: YOUR_KEY" -H "Content-Type: application/json" \
  "https://wildfield.io/api/projects/save" \
  -d '{"filename": "project-slug", "project": {"version":2, "designSystemId":"client-slug", "preset":{"name":"Client Name"}, "activeFormat":"square", "scenes":[...]}}'

# Update existing project
curl -s -X POST -H "X-API-Key: YOUR_KEY" -H "Content-Type: application/json" \
  "https://wildfield.io/api/projects/save" \
  -d '{"filename": "project-slug", "projectId": "UUID", "project": {"version":2, "designSystemId":"client-slug", "preset":{"name":"Client Name"}, "activeFormat":"square", "scenes":[...]}}'

# ─── AI generation (genai/*): all POST, all return a blob URL ───

# Generate an image from a text prompt
# model: flux-dev (default), flux-pro, imagen4, imagen4-fast, gpt-image-1, nano-banana-pro, ideogram-v3, recraft-v3, recraft-v3-transparent
curl -s -X POST -H "X-API-Key: YOUR_KEY" -H "Content-Type: application/json" \
  "https://wildfield.io/api/genai/generate" \
  -d '{"model":"flux-dev", "prompt":"a red sports car at sunset", "width":2160, "height":2160, "filename":"car.png", "design_system_id":"client-slug"}'

# Edit an existing image with an instruction (the editing suite)
# Same shape applies to /api/genai/inpaint, /retouch, and /enhance
curl -s -X POST -H "X-API-Key: YOUR_KEY" -H "Content-Type: application/json" \
  "https://wildfield.io/api/genai/edit" \
  -d '{"model":"nano-banana-pro", "prompt":"make the sky stormy", "image_url":"https://.../car.png", "filename":"car-edited.png"}'

# Outpaint / extend an image (fill new pixels around the frame)
curl -s -X POST -H "X-API-Key: YOUR_KEY" -H "Content-Type: application/json" \
  "https://wildfield.io/api/genai/outpaint" \
  -d '{"image_data":"data:image/png;base64,...", "expand_top":0, "expand_bottom":256, "expand_left":0, "expand_right":0, "prompt":"more road", "filename":"car-extended.png"}'

# Animate an element / scene into a video clip
curl -s -X POST -H "X-API-Key: YOUR_KEY" -H "Content-Type: application/json" \
  "https://wildfield.io/api/genai/animate" \
  -d '{"prompt":"slow pan across the car", "duration":6, "format":"mp4", "width":1080, "height":1080}'

# List your past generations
curl -s -H "X-API-Key: YOUR_KEY" "https://wildfield.io/api/genai/list"

# ─── Renders (renders/*) ───

# Rasterise composition HTML to a PNG screenshot (server-side Puppeteer)
curl -s -X POST -H "X-API-Key: YOUR_KEY" -H "Content-Type: application/json" \
  "https://wildfield.io/api/renders/screenshot" \
  -d '{"html":"<!doctype html>...", "width":1080, "height":1080}'

# Render composition HTML to a video on AWS Lambda (returns an executionArn)
curl -s -X POST -H "X-API-Key: YOUR_KEY" -H "Content-Type: application/json" \
  "https://wildfield.io/api/renders/video" \
  -d '{"html":"<!doctype html>...", "width":1080, "height":1080, "fps":30, "quality":"high", "format":"mp4"}'

# Poll render progress (executionArn returned by /renders/video)
curl -s -H "X-API-Key: YOUR_KEY" "https://wildfield.io/api/renders/progress?executionArn=ARN"

Critical Rules

Font names must use SINGLE QUOTES.
Font values are injected into HTML style="" attributes. Double quotes break the attribute, silently dropping all CSS properties after the font declaration. Text renders at 16px with no styling.

WRONG: "Inter", sans-serif
RIGHT: 'Inter', sans-serif
All design system fields are required.
Missing fields cause the renderer to fall back to DEFAULT_PRESET values (system font, black text, white background). Always provide the complete field set.
Scenes inherit from the design system.
The create_project tool automatically inherits textColor, ctaBgColor, ctaTextColor, contentAlign, contentPosition, and logoPosition from the design system. Image backgrounds default to overlay gradient. Later edits go through patch_scene, not a full update_project.
Font sizes are read-only.
Font sizes are calculated by the renderer from format dimensions. Do not attempt to set font sizes in design systems or scenes.
Projects MUST include designSystemId in the project blob.
The /api/projects/save endpoint reads project.designSystemId to populate the design_system_id DB column. Projects are filtered by design system in the UI: if this field is missing or null, the project will not appear in the project list. You must also include version: 2, preset: {name: "..."}, and activeFormat for the project to load correctly.

WRONG: {"project": {"designSystem": "slug", "scenes": [...]}}
RIGHT: {"project": {"version": 2, "designSystemId": "slug", "preset": {"name": "Client"}, "activeFormat": "square", "scenes": [...]}}
Editing while the project is open.
Keep the project open in the editor and use your agent at the same time. Since v1.1066.0 your own agent (your API key, same account) writes straight through your editor's lock, and the open editor shows the change within a few seconds with a note, Updated by your agent: 12 scenes changed, and an Undo. Unsaved edits in the editor are kept: a change the agent made to other fields or scenes is merged with them, and only when both changed the same value does the editor ask which version to keep (it never discards either). Agent writes are versioned, so they can never overwrite a save the editor made a moment earlier: patch_scene and the other agent tools retry once on the newer copy by themselves, and update_project asks you to read the project again (pass revision from get_project).

What the errors mean. 423 "Sam has this open in the Wildfield editor right now...": another person is editing the project; this is an editing lock, not a permissions problem, and it frees itself about 90 seconds after their editor closes, so tell the user and retry once, never in a loop. "You are editing this design system's brand...": brand edits in your own editor hold the design system until you finish them. 409 "The project was saved since you read it": read it again, re-apply, retry once. 403 "You have view-only access to this design system": the only real permissions case; ask its owner to make you an editor.
Background video is a project track.
Set project.bgVideoTrack once. Do not stamp a video onto every scene. One element spans the timeline so cuts stay clean in preview and in the Lambda render.
Call get_motion_skill then apply_physics_recipe.
Pick a named recipe. The 15 keys are leaves, tornado, orbits, ripple, rain, emberRise, snowfall, vortex, galaxy, heartbeat, starburst, hexGrid, radialBurst, crossfade, fireworks. Do not invent gsapCode. The editor rebakes enabled physics that has no snippet on load.

Wildfield - AI-native ad creative builder - wildfield.io