Static Ads Lab
Guides

Edit a JSON tree and re-render

Mutate an editable image ad's JSON tree via PATCH and wait for the new render.

Editable image ads return a JSON tree alongside the PNG. You can mutate the tree and PATCH the ad — the API re-renders and returns a new version.

When to use this

  • Building a custom ad editor.
  • Tweaking copy or images after the AI's first pass.
  • Producing many variants of one base ad with small differences.

For one-shot overrides, use node_overrides at creation time — simpler, no patch needed.

End-to-end flow

sequenceDiagram
  participant App as Your app
  participant API as Static Ads Lab API
  App->>API: POST /v1/image-ads (editable: true)
  API-->>App: 202 { id, status: processing }
  Note right of API: pipeline runs
  App->>API: GET /v1/image-ads/:id (poll/SSE)
  API-->>App: completed { image_url, current_json_tree }
  App->>App: mutate tree
  App->>API: PATCH /v1/image-ads/:id { current_json_tree }
  API-->>App: 200 { status: processing }
  Note right of API: re-render
  App->>API: GET /v1/image-ads/:id
  API-->>App: completed { new image_url }

Step by step

1. Generate the editable ad

const r = await fetch("https://api.staticadslab.com/v1/image-ads", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.SAL_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    design_template_id: "dt_...",
    brand_id: "brand_...",
    product_id: "prod_...",
    audience_id: "aud_...",
    editable: true,
  }),
});
const { data: created } = await r.json();
const ad = await waitForCompletion(created.id);

2. Locate the node you want to change

JSON trees vary by template. Walk the tree to find a text or image node by some heuristic — characters content for text, role/name for images.

function findTextNodeByCharacters(tree, query) {
  const stack = [tree];
  while (stack.length) {
    const node = stack.pop();
    if (node.type === "TEXT" && node.characters?.includes(query)) return node;
    for (const child of node.children ?? []) stack.push(child);
  }
  return null;
}

const headline = findTextNodeByCharacters(ad.current_json_tree, "Limited time");
headline.characters = "Black Friday: 40% off";

3. PATCH the full tree back

Always send the entire updated tree. The API does not accept diffs.

await fetch(`https://api.staticadslab.com/v1/image-ads/${ad.id}`, {
  method: "PATCH",
  headers: {
    "X-API-Key": process.env.SAL_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ current_json_tree: ad.current_json_tree }),
});

After PATCH, the resource transitions back to status: "processing" while the new render runs. Poll or SSE until completed.

4. Read the new render

const updated = await waitForCompletion(ad.id);
console.log(updated.image_url);

The previous render is preserved as a version (iav_…) — see GET /v1/image-ads/:id/versions.

Image swaps

Image fills carry a imageFillUrl — a public URL. To swap an image:

  1. Upload the new image via POST /v1/images (multipart).
  2. Use the returned data.src as the imageFillUrl.
  3. Update the tree node and PATCH.

Don't embed base64 image data in the JSON.

Versioning

Every successful PATCH creates a new version. List them:

GET /v1/image-ads/:id/versions

Get a specific version:

GET /v1/image-ads/:id/versions/:versionId

Versions are immutable. Use them as an audit log of changes, or to roll back by re-applying an older version's tree via PATCH.

Pitfalls

  • A PATCH on a flat ad (editable: false) returns an error — flat ads have no tree.
  • current_json_tree is null until the original generation completes. Wait for status: "completed" before reading or patching.
  • Don't PATCH while the resource is processing — wait for the previous job to complete first.
  • Sending a malformed tree (missing required fields, invalid imageFillUrl) returns 422 VALIDATION_ERROR.