Questions this answers
- What is async image generation API?
- How should developers evaluate async image generation API?
- What should teams verify before production?
An async image generation API lets you submit a generation request, get back a job identifier immediately, and retrieve the finished image later instead of holding an HTTP connection open. This tutorial covers the job lifecycle, when to poll versus use webhooks, and how to design retries so a slow or failed job doesn't corrupt your product experience.
Key Takeaways
- Image generation is job-based, not request-response, because generation latency (seconds to tens of seconds) is unreliable to hold on a synchronous connection.
- Polling is simpler to build and debug; webhooks reduce latency and request volume but require a public endpoint, signature verification, and idempotent handling of duplicate deliveries.
- Retry logic needs to distinguish submission failures, stuck jobs, and missed webhook deliveries; each needs a different recovery path.
- Exact endpoint names, field names, and webhook payload shapes differ by provider and by TokenLab's own API surface. Always confirm current specifics against docs.tokenlab.sh before shipping.
Why Image Generation APIs Are Async
Text completion APIs can often return a response on the same connection because token generation is fast enough to stream. Image generation models, whether diffusion-based or autoregressive, typically take longer and have more variable latency depending on resolution, model choice, and queue depth. Holding a synchronous HTTP request open for tens of seconds is fragile: client timeouts, load balancer idle limits, and mobile network drops all increase the chance of losing a completed result you already paid to generate.
The standard pattern, used across image generation providers, is a job model: you submit a request and receive a job identifier and an initial status (commonly something like queued or processing). You then either poll a status endpoint or receive a webhook notification when the job reaches a terminal state, and you fetch the final image URLs or binary data in a separate call.
TokenLab exposes access to multiple image models, including the Nano Banana 2, Nano Banana Pro, and Nano Banana 2 Lite family, GPT Image 2, Reve 2.0, and MAI-Image-2.5, through a single API surface. See the image model directory for the current list and the async image generation tasks guide for TokenLab-specific job endpoint behavior. The general pattern below applies regardless of which underlying model you call, but exact field names and status values are documented at docs.tokenlab.sh and should be verified there rather than assumed from this article.
The Job Lifecycle: Submit, Poll, Retrieve
At a conceptual level, an async image job has three stages:
- Submit: POST a prompt and parameters, receive a job ID and initial status.
- Check status: either poll a GET endpoint using the job ID, or wait for a webhook event.
- Retrieve output: once status is terminal (succeeded or failed), fetch the image URL(s) or error detail.
Here is an illustrative polling pattern in Python. Treat the endpoint paths and field names as placeholders; confirm the current TokenLab job endpoint shape in the API documentation before using this in production.
import time
import requests
API_BASE = "https://api.tokenlab.sh/v1" # verify current base URL in docs.tokenlab.sh
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def submit_image_job(prompt, model="nano-banana-2"):
resp = requests.post(
f"{API_BASE}/images/jobs",
headers=HEADERS,
json={"prompt": prompt, "model": model, "idempotency_key": generate_key()},
)
resp.raise_for_status()
return resp.json()["job_id"]
def poll_job(job_id, max_wait_seconds=120, interval=2, backoff=1.5):
waited = 0
while waited < max_wait_seconds:
resp = requests.get(f"{API_BASE}/images/jobs/{job_id}", headers=HEADERS)
resp.raise_for_status()
data = resp.json()
if data["status"] in ("succeeded", "failed"):
return data
time.sleep(interval)
waited += interval
interval = min(interval * backoff, 15)
raise TimeoutError(f"Job {job_id} did not complete within {max_wait_seconds}s")
job_id = submit_image_job("a studio product shot on white background")
result = poll_job(job_id)
if result["status"] == "succeeded":
image_url = result["output"]["url"]
else:
print("job failed:", result.get("error"))
The idempotency_key in the submit call matters: if a network error occurs after the job was created but before your client received the job ID, retrying the submit call with the same key should return the existing job rather than creating a duplicate generation. Confirm whether and how TokenLab's job endpoint supports idempotency keys in the current docs, since this is a common but not universal pattern across providers.
Polling vs. Webhooks: Tradeoffs
Both approaches are valid; the right choice depends on your traffic pattern and infrastructure.
Polling is simpler to implement and test locally, requires no public endpoint, and works fine for low-volume or batch workloads where a few extra seconds of latency don't matter. Its downsides are a latency floor equal to your poll interval, and unnecessary request volume if you poll too aggressively on long-running jobs.
Webhooks push a notification to your server when a job changes state, which lowers latency and cuts down on wasted status-check calls. The cost is operational: you need a publicly reachable HTTPS endpoint, signature verification to confirm the payload actually came from the provider, and handling for duplicate or out-of-order deliveries.
OpenAI's webhook events reference documents the general shape of this pattern for asynchronous operations: your endpoint receives an event with a type and an object identifier, and the recommended practice is to treat the webhook payload as a notification to go fetch the current state of the resource via the API, rather than trusting the webhook body as the final source of truth. That pull-after-push pattern is worth adopting regardless of which image provider you're integrating, because it protects you if a webhook payload is truncated, delayed, or delivered more than once.
Implementing Webhooks Safely
If you choose webhooks for image job completion, the following practices reduce the chance of silent failures:
- Verify the signature on every incoming webhook request before processing it. Reject anything that doesn't match, and log rejections separately from normal traffic so you can spot a misconfigured secret quickly.
- Respond fast, process later. Acknowledge the webhook with a 200 status as soon as you've validated it, then hand the actual work (fetching the image, writing to storage, notifying your user) to a background job or queue. Providers generally retry webhook delivery if they don't get a timely 2xx response, which can cause duplicate processing if your handler is slow and synchronous.
- Deduplicate by job ID. Store processed job IDs (or a hash of the event) so a retried delivery doesn't regenerate a notification or reprocess a file write.
- Re-fetch the resource using the job ID from the webhook payload rather than trusting embedded output URLs as necessarily final, consistent with the pull-after-push pattern described above.
A minimal handler sketch:
from flask import Flask, request, abort
app = Flask(__name__)
processed_job_ids = set() # use a real store in production
@app.route("/webhooks/image-jobs", methods=["POST"])
def handle_webhook():
if not verify_signature(request):
abort(401)
event = request.get_json()
job_id = event.get("job_id") or event.get("data", {}).get("id")
if job_id in processed_job_ids:
return "", 200 # already handled, acknowledge and skip
enqueue_background_task("fetch_and_store_image", job_id)
processed_job_ids.add(job_id)
return "", 200
Verify the exact webhook event names, payload structure, and signature header used for image job completion against the current provider documentation and, separately, against TokenLab's own webhook support as described at docs.tokenlab.sh, since these details are provider-specific and can change.
Retry Design: Three Failure Classes
Async image jobs fail in three distinct ways, and each needs its own handling:
- Submission failures: the POST to create a job returns a 4xx or 5xx. For 5xx and network errors, retry with exponential backoff and jitter, reusing the same idempotency key so you don't create duplicate jobs. For 4xx errors (bad prompt, invalid model, quota exceeded), retrying without changing the request will just fail again; surface the error to the caller instead.
- Stuck jobs: a job stays in a non-terminal status well past the expected generation time. Set a maximum wait threshold per model (generation time varies by model and resolution) and treat jobs that exceed it as failed for your application's purposes, even if the provider hasn't formally marked them failed yet. Log these separately, since a rising rate of stuck jobs often signals a provider-side incident.
- Missed webhook deliveries: your endpoint was down, or the delivery was dropped, and no event ever arrives. This is why a polling fallback is worth keeping even in a webhook-first design: a periodic sweep that checks the status of any job older than a few minutes without a terminal state catches jobs whose webhook silently failed to arrive.
Decision Checklist
Use this checklist when deciding how to wire up job completion for an image generation feature.
| Scenario | Recommended approach | Why |
|---|---|---|
| Low-volume, internal tool, or batch script | Polling | Simplest to build; no public endpoint needed |
| User-facing feature where latency matters | Webhooks, with polling fallback sweep | Lower latency; fallback catches missed deliveries |
| High job volume (thousands/day) | Webhooks | Avoids excessive status-check request volume |
| No ability to expose a public HTTPS endpoint | Polling | Webhooks require a reachable receiver |
| Need strict duplicate prevention | Idempotency keys on submit, dedupe on job ID at receipt | Protects against retried submissions and duplicate webhook deliveries |
| Multiple image models in one pipeline | Normalize job status and error handling in your own layer | Underlying providers (see the image model comparison) don't share identical status taxonomies |
Limitations
This article describes a general pattern for async image job APIs and does not assert exact endpoint paths, field names, timeout values, or webhook event names for TokenLab or for any specific underlying model provider beyond what is cited above. Job status vocabularies, retry-after headers, and webhook signature schemes vary between providers and can change over time; treat the code in this article as illustrative, not copy-paste production code, and confirm the current request and response shapes at docs.tokenlab.sh before shipping. This article does not cover pricing, rate limits, or throughput guarantees for any specific model.
FAQ
Should I always use webhooks instead of polling? No. Webhooks reduce latency and request volume at higher operational cost. For low-volume or internal use cases, polling is often the simpler and equally reliable choice. Many production systems use webhooks as the primary path with a periodic polling sweep as a fallback.
How do I avoid duplicate image generations on retry? Use an idempotency key on the job submission request so a retried POST after a network failure returns the existing job instead of creating a new one. Confirm whether your provider's job creation endpoint supports this before relying on it.
What happens if my webhook endpoint is down when the job completes? Behavior depends on the provider; some retry delivery for a period, others do not guarantee redelivery. A periodic polling sweep for jobs older than a few minutes without a terminal status is a practical safeguard regardless of the provider's retry policy.
If you're building an image generation feature and want to compare job-based access across multiple models in one API, review the image model directory and the async image generation tasks guide, then Get Started with TokenLab's API documentation to confirm current endpoint and webhook details for your build.
Sources
Price observed 2026-07-14
- OpenAI webhook eventsObserved 2026-07-14
- TokenLab API documentationObserved 2026-07-14
- TokenLab model directoryObserved 2026-07-14



