Static Ads Lab
Concepts

Image ads

The core API output — Meta-ready PNGs generated from a reference ad, brand, product, and audience.

An image ad is the API output. You combine a reference ad image with a brand, product, and audience, and the API generates a Meta-ready PNG asynchronously. Every image ad is editable.

Mental model

The API analyzes reference_ad_url, creates a new Perfect Ad, rebuilds it as editable layers in a JSON tree, and renders the final ad from that tree. You get a PNG plus the tree. See Editable image ads.

  • Price: $2.00 per ad (SKU editable_image_ad), including 1 generated image asset, plus $0.50 for each additional generated image asset. Only successful generations count; retries and failures are free. Billed once, when the ad completes.
  • Time: up to about 20 minutes.
  • Shape: the ad follows the reference ad's aspect ratio (1:1, 4:5 or 9:16).
  • Editing: a plain-English instruction or your own design script via design edits, or a full-tree PATCH.

ID prefix: ia_.

Fields

FieldTypeDescription
idstringia_…
reference_ad_url / brand_id / product_id / audience_idstringInputs
design_template_idstring | nullHistorical template source for legacy rows. New rows return null
product_variant_idstring | nullOptional variant override
promptstring | nullFree-form creative direction
optionsobject | nullReserved for legacy/internal rows. New public requests omit it
editablebooleanResponse only. true for every new ad
sku_codeenumeditable_image_ad for new ads. Ads from before every ad was editable may show flat_image_ad
statusenumprocessing | completed | failed
progressobject | null{ step, message, percentage } while processing
image_urlstring | nullMeta-ready PNG (when complete)
width / heightnumber | nullPixel dimensions
current_json_treeobject | nullThe ad's editable JSON tree (null until the ad completes)
editor_supportedbooleanWhether the ad can be edited: true for completed ads from the current pipeline. Some older ads keep current_json_tree but are not supported for editing
tree_revision / rendered_tree_revision / render_statusnumber / number / enumEdit and render state. See Editable image ads
figma_render_url / initial_figma_render_urlstring | nullFigma sync renders
errorobject | null{ code, message } when failed
duration_msnumber | nullWorker wall-clock time
batch_idstring | nullSet when generated as part of a batch
created_at / completed_atstringISO timestamps

Lifecycle

stateDiagram-v2
  [*] --> processing: POST /v1/image-ads (202)
  processing --> completed: image_url ready
  processing --> failed: error populated
  completed --> [*]
  failed --> [*]

You're billed only on completed. failed jobs do not deduct from your wallet.

Endpoints

MethodPathPurpose
POST/v1/image-adsGenerate an image ad
GET/v1/image-adsList image ads (supports ids=, status=, batch_id=)
GET/v1/image-ads/:idGet an image ad
PATCH/v1/image-ads/:idUpdate the JSON tree
POST/v1/image-ads/:id/editsEdit with an instruction or script
GET/v1/image-ads/:id/editsList design edits
GET/v1/image-ads/:id/outlineLayer outline for targeting edits
GET/v1/image-ads/:id/render-statusRender status, without the tree
DELETE/v1/image-ads/:idDelete an image ad
POST/v1/image-ads/syncSync from Figma
GET/v1/image-ads/:id/versionsList versions
GET/v1/image-ads/:id/versions/:versionIdGet a version

Common patterns

Generate an ad

const response = 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 } = await response.json();
// data.id, data.status === "processing"

Batch poll

When generating many ads, poll them in one request rather than N:

const ids = ["ia_a", "ia_b", "ia_c"].join(",");
const response = await fetch(
  `https://api.staticadslab.com/v1/image-ads?ids=${ids}`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } },
);
const { data } = await response.json();
const stillProcessing = data.filter((d) => d.status === "processing");

See Batch generation and Async jobs.

Pitfalls

  • product_id and brand_id must be linked. Using a product that doesn't belong to the brand returns 422.
  • reference_ad_url must be a publicly accessible image URL.
  • Don't poll faster than every 2 seconds. 4 seconds is the recommended interval for image ads.
  • current_json_tree is null until generation completes — wait for status: "completed" before reading it.
  • Don't send editable. Every ad is editable and the field returns 400 VALIDATION_ERROR.
  • Generation takes up to about 20 minutes. Cap your polling at 25 minutes or more, not a few minutes.

Prompt for your agent

Read https://www.staticadslab.com/docs/resources/image-ads.mdx and write generateAd({ referenceAdUrl, brandId, productId, audienceId }) that creates an ad, polls every 4 seconds (for up to 25 minutes) until completed, and returns the result. Throw on failed.