Image & Video quickstart

Learn how to generate images and videos from text prompts and reference media.

The Image & Video API is asynchronous. You submit a generation, and once it finishes you download the result from a signed URL. Images and videos have separate endpoints, but the request and response shapes are the same for both.

There are two ways to collect the result. Webhook delivery is the recommended one, and what the examples below use: ElevenLabs calls your endpoint the moment a generation reaches a terminal status, so nothing is spent waiting. Polling is the fallback for when you have no endpoint to receive a callback, and each example shows how to drop back to it.

The Image & Video API requires a Pro plan or above. Calls from a workspace below that tier are rejected with a 402 paid_plan_required error. Your API key must also carry the Image & Video or Flows permission for the workspace.

Generate an image

1

Create an API key

Create an API key in the dashboard here, which you’ll use to securely access the API.

Store the key as a managed secret and pass it to the SDKs either as a environment variable via an .env file, or directly in your app’s configuration depending on your preference.

.env
1ELEVENLABS_API_KEY=<your_api_key_here>
2

Install the SDK

We’ll also use the dotenv library to load our API key from an environment variable.

1pip install elevenlabs
2pip install python-dotenv
3

Submit the generation

Each model has its own request class, and the fields on it are the parameters that model accepts, so switching models can change which fields are available. Unknown fields are rejected rather than ignored.

webhook asks for the finished result to be delivered to your workspace’s webhooks, so the call returns as soon as the generation is queued. It requires a webhook subscribed to generation events; see Image & Video webhooks to set one up, or omit the field and poll instead.

1# example.py
2import os
3
4from dotenv import load_dotenv
5from elevenlabs import ImageGenerationRequest_Gemini3ProImage, WebhookTarget_All
6from elevenlabs.client import ElevenLabs
7
8load_dotenv()
9
10elevenlabs = ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))
11
12generation = elevenlabs.flows.image.create(
13 request=ImageGenerationRequest_Gemini3ProImage(
14 prompt="A corgi in a tiny lifeguard chair on a sunlit beach at golden hour, photorealistic",
15 aspect_ratio="16:9",
16 resolution="2K",
17 webhook=WebhookTarget_All(),
18 )
19)
20
21print(generation.id, generation.status)

The response contains the generation ID and nothing else. A newly created generation is always pending:

1{
2 "id": "JWr5N6X9ZTqf8jD2LmQb",
3 "status": "pending"
4}
4

Collect the result

Because the request opted into webhook, ElevenLabs posts a flows_generation event to your endpoint once the generation reaches completed or failed. The event’s data is identical to what the GET endpoint returns, and Image & Video webhooks walks through the handler that receives it.

Without an endpoint to receive callbacks, drop webhook from the request above and poll instead. Fetch the generation until its status is completed or failed, leaving at least two seconds between requests for an image — see Polling guidelines for the intervals to use per modality.

1import time
2
3import requests
4
5while True:
6 result = elevenlabs.flows.image.get(generation.id)
7 if result.status in ("completed", "failed"):
8 break
9 time.sleep(2)
10
11if result.status == "failed":
12 raise RuntimeError(f"{result.failure_reason}: {result.error_message}")
13
14with open("corgi.png", "wb") as f:
15 f.write(requests.get(result.content_url).content)

Either way, a completed generation carries the same fields:

1{
2 "id": "JWr5N6X9ZTqf8jD2LmQb",
3 "status": "completed",
4 "content_url": "https://storage.googleapis.com/generations/JWr5N6X9ZTqf8jD2LmQb",
5 "content_mime_type": "image/png"
6}
5

Execute the code

1python example.py

The generation is queued and its ID is printed. With webhook delivery the image arrives at your endpoint; with the polling variant it is saved to corgi.png.

Generate a video

Video generations use flows.video and follow the same submit-and-collect pattern. A video can take several minutes, so this example opts into webhook delivery with webhook rather than waiting on the result.

1from elevenlabs import VideoGenerationRequest_Veo31FastGenerate001, WebhookTarget_All
2
3generation = elevenlabs.flows.video.create(
4 request=VideoGenerationRequest_Veo31FastGenerate001(
5 prompt="A corgi rides a tiny surfboard across a sunlit wave at golden hour, cinematic",
6 duration_secs=8,
7 aspect_ratio="16:9",
8 resolution="1080p",
9 generate_audio=True,
10 webhook=WebhookTarget_All(),
11 )
12)
13
14print(generation.id)

The call returns as soon as the generation is queued, and the finished result is delivered to every webhook in your workspace subscribed to generation events. Video output is MP4, so the completed payload reports a content_mime_type of video/mp4. See Image & Video webhooks for configuring a webhook and writing the handler that receives this.

webhook requires at least one workspace webhook subscribed to generation events. Without one, the create call is rejected rather than starting a generation whose result has nowhere to go. Drop the field to fall back to polling with flows.video.get, and poll no more than once every 10 seconds.

Collecting results

Webhooks and polling return the same payload, so the choice is about how you wait for it rather than what you get.

Webhook deliveryPolling
Best forThe default for both modalities, and any production useScripts and environments with no public endpoint
RequiresAn HTTPS endpoint subscribed to generation eventsNothing
Cost of waitingNone; you are called once the generation finishesOne request per poll, per generation

Use webhooks wherever you can. Reach for polling when you have nowhere to receive a callback, and follow the intervals below when you do.

Choosing webhook targets

webhook accepts two forms. WebhookTarget_All reaches every webhook subscribed to generation events, which is the right default because it survives webhooks being rotated or replaced. WebhookTarget_Ids narrows delivery to specific webhooks, for when one workspace fans out to several consumers and a given job should reach only one of them:

1from elevenlabs import WebhookTarget_Ids
2
3webhook = WebhookTarget_Ids(ids=["Q8mVr2LpXcT4nB6yJdKw"])

Every ID must already be subscribed to generation events; naming an unsubscribed webhook is rejected rather than silently ignored. The delivered payload is identical to what the GET endpoint returns, so a handler written against one works for the other. The webhooks guide covers configuring a webhook, verifying the signature, and handling the event.

Polling guidelines

A generation’s runtime depends on the model, the resolution, and, for video, the duration, so poll on an interval matched to what you asked for rather than on a fixed loop:

  • Images: poll no more than once every 2 seconds. Most finish within a few seconds.
  • Video: poll no more than once every 10 seconds. Expect minutes, not seconds, and scale the interval with duration_secs and resolution.

Two rules apply to both. Back off when a generation runs long — doubling the interval up to about a minute keeps a slow generation from turning into hundreds of requests. And give the loop a ceiling, so a stuck generation ends as a timeout in your own code rather than an unbounded loop.

Polling faster than this earns you nothing: a generation’s status does not change any sooner because you asked twice. Sustained aggressive polling can return 429 responses, which you should handle with exponential backoff.

Generation lifecycle

A generation moves through four statuses. The two terminal statuses carry different fields, so branch on status before reading the rest of the response.

StatusMeaning
pendingThe generation is queued. This is the status of every newly created generation.
generatingThe model is running.
completedThe output is ready. The response carries content_url and content_mime_type.
failedThe generation did not produce an output. The response carries the failure details.

content_url is a signed URL that expires roughly an hour after the response is returned. Fetch the generation again for a fresh URL rather than storing the signed URL itself.

Handling failures

A failed generation reports a failure_reason category alongside a human-readable error_message:

1{
2 "id": "JWr5N6X9ZTqf8jD2LmQb",
3 "status": "failed",
4 "failure_reason": "moderated",
5 "error_message": "The prompt was rejected by content moderation. You were not charged for this generation."
6}
failure_reasonCause
timeoutThe model did not return a result in time.
model_errorThe model provider returned an error or produced no output.
moderatedThe prompt or an input was rejected by content moderation.
invalid_parametersThe parameters were rejected once the generation reached the model.
dependency_failedA referenced generation this one depends on did not complete.
charging_failedThe workspace could not be charged for the generation.
internal_errorAn unexpected error occurred.

Failed generations are not charged. Parameter problems that can be detected up front — an unsupported field, a value outside a model’s allowed range, or an invalid combination of reference inputs — are rejected by the create request instead, before any generation starts.

Pricing

Generations are charged in credits. The cost depends on the model, the parameters you choose such as resolution and duration, and the inputs you provide. A generation costs the same through the API as it does in the ElevenLabs app, where the cost is shown before you submit. See Image & Video in the playground for how the cost of a given model and setting combination is presented.

List your generations

Each endpoint lists the generations created through it, newest first. Results are scoped to your workspace and to this API, so generations created in the ElevenLabs app do not appear.

1page = elevenlabs.flows.image.list(page_size=20, status="completed")
2
3for item in page.generations:
4 print(item.id, item.content_url)
5
6while page.has_more:
7 page = elevenlabs.flows.image.list(page_size=20, status="completed", cursor=page.next_cursor)
8 for item in page.generations:
9 print(item.id, item.content_url)

page_size accepts 1 to 100 and defaults to 30. Pass status to return only generations in one lifecycle state, and model_id to return only generations of a single model. Treat next_cursor as opaque: pass the exact value back and stop when has_more is false.

Available models

The API exposes a subset of the models available in the ElevenLabs app. Each model accepts only the parameters listed for it — sending a field another model supports returns a validation error.

The ByteDance models, Seedance and Seedream, are disabled by default and require explicit approval before you can generate with them. Until access is granted, a request naming one of them is rejected with a model_access_denied error. Contact support to request access.

Image models

model_idReference imagesOutput controls
gpt-image-1Up to 5, plus a maskaspect_ratio (1:1, 3:2, 2:3), quality, background
gpt-image-1.5Up to 5, plus a maskaspect_ratio (1:1, 3:2, 2:3), quality, background
gpt-image-2Up to 10, plus a mask15 aspect ratios, resolution (1K, 2K, 4K), quality
gemini-2.5-flash-imageUp to 5aspect_ratio
gemini-3-pro-imageUp to 10aspect_ratio, resolution (1K, 2K, 4K)
gemini-3.1-flash-imageUp to 14aspect_ratio (including 1:4, 4:1, 1:8, 8:1), resolution (512 to 4K)
gemini-3.1-flash-lite-imageUp to 14aspect_ratio, resolution (1K)
bytedance-seedream-5-liteUp to 10aspect_ratio, resolution (2K, 3K), seed
bytedance-seedream-5-proUp to 10aspect_ratio, resolution (1K, 2K), seed

Video models

model_idMedia inputsOutput controls
veo-3.1-generate-001start_frame, end_frame, up to 3 images with a roleduration_secs (4, 6, 8), aspect_ratio (16:9, 9:16), resolution (720p, 1080p, 4K), generate_audio
veo-3.1-fast-generate-001start_frame, end_frame, up to 3 images with a roleduration_secs (4, 6, 8), aspect_ratio (16:9, 9:16), resolution (720p, 1080p, 4K), generate_audio
bytedance-seedance-v2start_frame, end_frame, up to 9 images, 3 videos, 3 audiosduration_secs (4 to 15), 7 aspect ratios, resolution (480p to 4k), generate_audio
bytedance-seedance-v2-faststart_frame, end_frame, up to 9 images, 3 videos, 3 audiosduration_secs (4 to 15), 7 aspect ratios, resolution (480p, 720p), generate_audio
bytedance-seedance-v2-ministart_frame, end_frame, up to 9 images, 3 videos, 3 audiosduration_secs (4 to 15), 7 aspect ratios, resolution (480p, 720p), generate_audio
bytedance-seedance-v2.5start_frame, end_frame, up to 30 images, 10 videos, 10 audiosduration_secs (4 to 30), 7 aspect ratios, resolution (480p, 720p), generate_audio
creatify-auroraimage and audio, both requiredresolution (480p, 720p), guidance_scale, audio_guidance_scale

For model capabilities, availability, and pricing, see the Image & Video overview.

Next steps