Static Ads Lab
Guides

Async jobs (polling)

How to track design template and image ad generation — polling and batch polling.

POST /v1/image-ads and POST /v1/design-templates are asynchronous. They return 202 Accepted with the resource in processing and the pipeline runs in the background. This guide covers how to know when it's done.

Status lifecycle

stateDiagram-v2
  [*] --> processing: POST returns 202
  processing --> completed: outputs ready
  processing --> failed: error populated
  completed --> [*]
  failed --> [*]

There are no intermediate states. The progress field updates during processing with pipeline-stage info; that's a UX hint, not a state machine.

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

Polling

The default. Periodically GET the resource until status changes.

async function waitForCompletion(imageAdId, apiKey) {
  while (true) {
    const r = await fetch(
      `https://api.staticadslab.com/v1/image-ads/${imageAdId}`,
      { headers: { "X-API-Key": apiKey } },
    );
    const { data } = await r.json();
    if (data.status === "completed") return data;
    if (data.status === "failed") throw new Error(data.error?.message ?? "failed");
    await new Promise((res) => setTimeout(res, 4000));
  }
}
import time
import requests

def wait_for_completion(image_ad_id: str, api_key: str) -> dict:
    while True:
        r = requests.get(
            f"https://api.staticadslab.com/v1/image-ads/{image_ad_id}",
            headers={"X-API-Key": api_key},
        )
        data = r.json()["data"]
        if data["status"] == "completed":
            return data
        if data["status"] == "failed":
            raise Exception(data["error"]["message"])
        time.sleep(4)
while true; do
  RESULT=$(curl -s https://api.staticadslab.com/v1/image-ads/ia_YOUR_ID \
    -H "X-API-Key: YOUR_API_KEY")
  STATUS=$(echo "$RESULT" | jq -r '.data.status')
  echo "Status: $STATUS"
  [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ] && break
  sleep 4
done
echo "$RESULT" | jq '.data.image_url'

Polling tips

  • 4 seconds is a good interval for image ads (generation takes up to about 20 minutes).
  • 2 seconds works for design templates (faster pipeline).
  • Don't poll from the browser. Poll server-side and push updates to clients over your own channel.
  • Always cap retries. Add a timeout (e.g. 25 minutes for image ads) and throw if exceeded — never poll forever.

Batch polling

When you have many ads in flight, ask for all their statuses in one request:

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

This is the right pattern for batch generation. See Batch generation.

Choosing a pattern

Use thisWhen
PollingOne or a few ads, simple control flow, retries are fine
Batch pollingTens or hundreds of ads in parallel

Pitfalls

  • Don't poll faster than every 2 seconds — you'll hit the rate limit.
  • Reading image_url while status === "processing" returns null. Wait for completed.
  • A PATCH or design edit saves the tree immediately and re-renders the image in the background. The ad stays completed; poll GET /v1/image-ads/:id/render-status until rendered_tree_revision equals tree_revision before reading the new image_url (see Edit and re-render).