Editable image ads
Every image ad is editable. PNG plus a structured JSON tree you can change by instruction, script or PATCH; the PNG re-renders in the background. Versioned and Figma-syncable.
Every image ad is editable. In addition to the Meta-ready PNG, you get a JSON tree — a structured document representing every visual element. Change it with a plain-English instruction or your own design script (
POST /v1/image-ads/:id/edits), or save a whole new tree withPATCH. The API saves the change immediately and re-renders the PNG in the background.
Ways to edit
- Design edits: one request with an
instruction(our AI Design Agent, model cost + 30%) or ascript(your own agent, free). Atomic and synchronous. - Full tree save: for a custom ad editor, or programmatic variants (e.g., 50 ads with the same layout but different headlines).
- Figma: round-trip an ad through Figma with the plugin.
Lifecycle
stateDiagram-v2
[*] --> processing: POST /v1/image-ads (202)
processing --> completed: image_url + current_json_tree ready
processing --> failed
completed --> completed: PATCH, design edit or Figma sync
completed --> [*]A PATCH, an applied design edit or a Figma sync validates and saves the new tree, increments tree_revision, and returns right away with render_status: "pending". The image is then rendered in the background at 2× (a 1080-wide canvas exports 2160 wide). The image matches the tree once rendered_tree_revision equals tree_revision. A failed render sets render_status: "failed" and render_error, and keeps the saved tree and the previous image. The ad stays completed throughout. The previous tree is kept as a version (iav_…) at most once every 10 minutes.
| Field | Meaning |
|---|---|
tree_revision | Increments on every saved tree (PATCH, design edit, Figma sync). Send it back as base_revision. |
rendered_tree_revision | The revision image_url was rendered from. |
render_status | idle, pending, rendering, or failed. |
render_error | { code, message, details } of the last failed render, or null. |
To wait for a render without downloading the tree each time, poll GET /v1/image-ads/:id/render-status. It returns { id, tree_revision, rendered_tree_revision, render_status, render_error, image_url, thumbnail_url, updated_at }.
Endpoints
Beyond the core Image ads endpoints, these work on an ad's tree:
| Method | Path | Purpose |
|---|---|---|
POST | /v1/image-ads/:id/edits | Edit with an instruction or script |
GET | /v1/image-ads/:id/edits | List design edits |
GET | /v1/image-ads/:id/outline | Layer outline |
GET | /v1/image-ads/:id/render-status | Render status |
PATCH | /v1/image-ads/:id | Update the JSON tree |
POST | /v1/image-ads/:id/assets | Upload an image for the tree |
GET | /v1/image-ads/:id/versions | List versions |
GET | /v1/image-ads/:id/versions/:versionId | Get a specific version |
POST | /v1/image-ads/sync | Sync from Figma |
Common patterns
Generate, then change a headline
const create = await fetch("https://api.staticadslab.com/v1/image-ads", {
method: "POST",
headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({
reference_ad_url: "https://cdn.example.com/reference-ad.png",
brand_id: "brand_...",
product_id: "prod_...",
audience_id: "aud_...",
}),
});
const { data: created } = await create.json();
const ad = await waitForCompletion(created.id);
const tree = ad.current_json_tree; // { schemaVersion: 2, colorProfile: "SRGB", root }
const headline = findTextNodeByCharacters(tree.root, "Limited time");
// Replace the text and keep one style covering all of it.
const [style] = headline.styledTextSegments;
headline.characters = "Black Friday: 40% off";
headline.styledTextSegments = [{ ...style, start: 0, end: headline.characters.length }];
const response = await fetch(`https://api.staticadslab.com/v1/image-ads/${ad.id}`, {
method: "PATCH",
headers: { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" },
body: JSON.stringify({ current_json_tree: tree, base_revision: ad.tree_revision }),
});
const body = await response.json();
if (!response.ok) {
// 400 VALIDATION_ERROR / INVALID_IMAGE_URL, or 409 REVISION_CONFLICT.
// error.details names each offending path (or the current revision). Nothing was saved.
throw new Error(`${body.error.code}: ${body.error.message}`);
}
// Saved. Wait for the background render of this revision.
const rendered = await waitForRender(ad.id, body.data.tree_revision);
console.log(rendered.image_url); // the new renderfindTextNodeByCharacters and waitForRender are in the edit and re-render guide.
List versions
const response = await fetch(
`https://api.staticadslab.com/v1/image-ads/${adId}/versions`,
{ headers: { "X-API-Key": "YOUR_API_KEY" } },
);
const { data } = await response.json();
data.forEach((v) => console.log(v.version_number, v.image_url));Pitfalls
current_json_treeisnulluntil the ad completes. Checkeditor_supportedbefore editing an older ad.- The tree must be a schema-v2 document. Ads created before schema v2 are returned in the retired format and cannot be patched; generate them again to edit them.
- Use fonts we ship (Supported fonts). Any other font renders in Inter at the closest style; the tree keeps its
fontName. - Images are
IMAGEpaints whosesal.urlis an absolute publichttp(s)URL; anything else returns400 INVALID_IMAGE_URL. Upload local files withPOST /v1/image-ads/:id/assets(about 4 MB at most). The API does not accept base64 image data in JSON. image_urllags the tree until the background render finishes. Wait forrendered_tree_revision == tree_revisionbefore downloading.- Versions are immutable. Once written, a version's
json_treeandimage_urlcannot be changed.
Related
- Image ads
- Edit ads with instructions or scripts
- Design scripts
- JSON trees
- Figma sync
- Edit and re-render guide
Prompt for your agent
Read https://www.staticadslab.com/docs/resources/editable-image-ads.mdx and write a function patchHeadline(adId, newHeadline) that updates an ad's headline node, rebuilds its styledTextSegments to cover the new text, saves it with PATCH (sending base_revision), then polls GET until rendered_tree_revision equals tree_revision and returns the new image_url.