> ## Documentation Index
> Fetch the complete documentation index at: https://docs.makelocalads.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Polling

> Poll asynchronous resources to completion with backoff, without blocking your rate budget for new work.

Every generation request answers `202 Accepted` immediately and does its work
after the response. You read the result by polling the resource until its
status is terminal.

## What to poll

The `202` response carries a `Location` header with the URL to poll: the
campaign, photoshoot, moodboard, ad group, listing idea or video you started.
Poll that URL, with the same `Authorization` header as the original request.

## Terminal states

| Resource family | Terminal states |
| - | - |
| Campaigns, photoshoots, moodboards, videos, ChatGPT ads, listing idea slots | `completed`, `failed`, `canceled` |
| ChatGPT ad groups | `completed`, `failed` |
| Video concepts | `completed`, `failed` |
| Products | `ready`, `failed` |
| Audience generation | `ready`, `failed` |

A job can complete while single items inside it fail. Check item-level
statuses too; see [Jobs and lifecycle](/concepts/jobs-and-lifecycle).

## The loop

Poll every few seconds with exponential backoff: start around 2 seconds, grow
up to about 30 seconds, and stop after a few minutes for most jobs. A video is
usually ready in about five minutes.

```javascript theme={"theme":{"light":"github-light","dark":"github-dark"}}
async function pollUntilDone(url, token) {
  for (let delay = 2000; ; delay = Math.min(delay * 1.8, 30000)) {
    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${token}` },
    });
    const resource = await response.json();
    if (["completed", "failed", "canceled"].includes(resource.status)) {
      return resource;
    }
    await new Promise((r) => setTimeout(r, delay));
  }
}
```

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import time
import requests

def poll_until_done(url, token):
    delay = 2
    while True:
        resource = requests.get(
            url, headers={"Authorization": f"Bearer {token}"}
        ).json()
        if resource["status"] in ("completed", "failed", "canceled"):
            return resource
        time.sleep(delay)
        delay = min(delay * 1.8, 30)
```

## Polling never blocks generation

Reads have their own rate budget, separate from the budget for starting new
work, so a polling loop never uses up the capacity to accept a new job. See
[Rate limits](/concepts/rate-limits).

If a poll returns `429`, honor its `Retry-After` header and continue the loop
after waiting; the resource keeps making progress regardless.
