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 runs ~30–60 seconds).
  • 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. 5 minutes) 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 on an editable image ad sends the resource back to processing. Treat patches as new async jobs.