Static Ads Lab
Guides

Edit image ads with AI

Describe a change in plain language and get a corrected version of the same ad back — versioned, restorable, and billed only on success.

When a generated ad is almost right — wrong headline, a logo you want gone, a small element to swap — you don't need to regenerate it. POST /v1/image-ads/{id}/edit applies your plain-language instruction to the ad's current image and delivers the result as a new version of the same ad.

When to use this

  • Changing headline or CTA copy on a finished ad.
  • Removing or swapping a single element (logo, badge, background detail).
  • Fixing small visual issues without losing the composition that already works.

AI edits work on flat image ads (editable: false). For editable ads, mutate the JSON tree instead — see Edit a JSON tree and re-render.

How it works

Each edit runs a maintained two-step pipeline: your instruction is first refined into a precise image-editing prompt (element targeting, preservation constraints), then applied to the ad's current image at its original aspect ratio and resolution. Behind the scenes this is versioned like a git history:

  • The edit immediately creates a pending version (status: "processing").
  • On success the version completes and the ad's image_url moves to the edited image.
  • On failure the version is marked failed, the ad is untouched, and you are not charged.
  • Every previous image stays in the version history and can be restored for free.

$0.50 per successful edit (SKU image_ad_edit).

End-to-end flow

sequenceDiagram
  participant App as Your app
  participant API as Static Ads Lab API
  App->>API: POST /v1/image-ads/:id/edit { prompt }
  API-->>App: 202 { version, status: processing }
  Note right of API: refine → apply pipeline
  App->>API: GET /v1/image-ads/:id/versions/:versionId (poll)
  API-->>App: completed { image_url }
  Note right of API: ad's image_url now points at the edit

Submit an edit

const response = await fetch(
  `https://api.staticadslab.com/v1/image-ads/${imageAdId}/edit`,
  {
    method: "POST",
    headers: {
      "X-API-Key": process.env.STATICADS_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      prompt: "Remove the logo and change the headline to 'Summer Sale'",
    }),
  },
);

const { data: version } = await response.json();
// version.id            → "iav_..."  (poll this)
// version.status        → "processing"
// version.version_number → 2

Compound instructions are supported — put every change in one prompt, as in the example above. One edit runs at a time per ad: if a second edit is submitted while one is processing, the API returns 409 EDIT_ALREADY_IN_PROGRESS. A processing version older than 10 minutes is treated as abandoned and marked failed automatically (never charged), so a stuck run never blocks the ad's edits or restores.

Poll the version

async function waitForEdit(imageAdId, versionId, apiKey) {
  while (true) {
    const r = await fetch(
      `https://api.staticadslab.com/v1/image-ads/${imageAdId}/versions/${versionId}`,
      { headers: { "X-API-Key": apiKey } },
    );
    const { data } = await r.json();
    if (data.status === "completed") return data;
    if (data.status === "failed") throw new Error("Edit failed — no charge was made");
    await new Promise((res) => setTimeout(res, 3000));
  }
}

const edited = await waitForEdit(imageAdId, version.id, apiKey);
console.log(edited.image_url); // the edited image (also the ad's new image_url)

Version history

GET /v1/image-ads/{id}/versions lists every version. The first edit lazily creates v1 from the original image, so history always starts at the original:

const r = await fetch(
  `https://api.staticadslab.com/v1/image-ads/${imageAdId}/versions`,
  { headers: { "X-API-Key": apiKey } },
);
const { data: versions } = await r.json();
// [
//   { version_number: 1, source: "generation", status: "completed", ... },
//   { version_number: 2, source: "ai_edit", edit_prompt: "Remove the logo...", ... },
// ]

Key fields on each version:

FieldMeaning
statusprocessing, completed, or failed
sourcegeneration (the original), ai_edit, restore, or tree-snapshot sources for editable ads
edit_promptYour instruction, on ai_edit versions
image_urlThe version's image (null while processing or failed)

Restore a previous version

Restores are free and non-destructive: a new version is appended with the old image, and the ad's image_url moves to it. Nothing is ever deleted — a restore is itself restorable.

const r = await fetch(
  `https://api.staticadslab.com/v1/image-ads/${imageAdId}/versions/${versionId}/restore`,
  {
    method: "POST",
    headers: { "X-API-Key": apiKey },
  },
);
const { data: restored } = await r.json();
// restored.source → "restore", restored.status → "completed"

Errors

StatusCodeMeaning
402INSUFFICIENT_BALANCEWallet can't cover the $0.50 edit
404IMAGE_AD_NOT_FOUNDUnknown ad id
409EDIT_ALREADY_IN_PROGRESSAnother edit is processing on this ad — poll it, then retry
422IMAGE_AD_EDIT_NOT_SUPPORTEDThe ad is editable-mode; AI edits are flat-only
422IMAGE_AD_NOT_COMPLETEDThe ad hasn't finished generating
422IMAGE_AD_VERSION_NOT_RESTORABLERestore target is processing or failed