Skip to main content
ElevenLabs Documentation Docs

Search documentation

Type to search this documentation.

On this pageOverview

Image & Video quickstart

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.

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

.env
ELEVENLABS_API_KEY=<your_api_key_here>

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

Python
pip install elevenlabs
pip install python-dotenv
TypeScript
npm install @elevenlabs/elevenlabs-js
npm install dotenv

Install the ElevenLabs CLI. Homebrew (macOS) and Scoop (Windows) are recommended.

Homebrew (macOS)

Homebrew (macOS)
brew install elevenlabs/tap/elevenlabs

Scoop (Windows)

Scoop (Windows)
scoop bucket add elevenlabs https://github.com/elevenlabs/scoop-bucket
scoop install elevenlabs

npm

npm
npm install -g @elevenlabs/cli

curl

curl
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/elevenlabs/cli/releases/latest/download/elevenlabs-cli-installer.sh | sh

Then authenticate — this opens your browser to authorize the CLI:

Bash
elevenlabs auth login

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.

Python
# example.py
import os

from dotenv import load_dotenv
from elevenlabs import ImageGenerationRequest_Gemini3ProImage, WebhookTarget_All
from elevenlabs.client import ElevenLabs

load_dotenv()

elevenlabs = ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))

generation = elevenlabs.flows.image.create(
    request=ImageGenerationRequest_Gemini3ProImage(
        prompt="A corgi in a tiny lifeguard chair on a sunlit beach at golden hour, photorealistic",
        aspect_ratio="16:9",
        resolution="2K",
        webhook=WebhookTarget_All(),
    )
)

print(generation.id, generation.status)
TypeScript
// example.mts
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import "dotenv/config";

const elevenlabs = new ElevenLabsClient();

const generation = await elevenlabs.flows.image.create({
  modelId: "gemini-3-pro-image",
  prompt:
    "A corgi in a tiny lifeguard chair on a sunlit beach at golden hour, photorealistic",
  aspectRatio: "16:9",
  resolution: "2K",
  webhook: { type: "all" },
});

console.log(generation.id, generation.status);

The CLI submits the same request as JSON, then polls until the generation completes and downloads the result:

Bash
# 1. Submit the generation (note the returned id)
elevenlabs flows image create --json '{
  "model_id": "gemini-3-pro-image",
  "prompt": "A corgi in a tiny lifeguard chair on a sunlit beach at golden hour, photorealistic",
  "aspect_ratio": "16:9",
  "resolution": "2K"
}'

# 2. Poll until the status is "completed"
elevenlabs flows image get --generation-id <id> --query status

# 3. Read the signed content URL, then download the image
elevenlabs flows image get --generation-id <id> --query content_url
curl -o corgi.png "<content_url>"

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

JSON
{
  "id": "JWr5N6X9ZTqf8jD2LmQb",
  "status": "pending"
}

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.

Python
import time

import requests

while True:
    result = elevenlabs.flows.image.get(generation.id)
    if result.status in ("completed", "failed"):
        break
    time.sleep(2)

if result.status == "failed":
    raise RuntimeError(f"{result.failure_reason}: {result.error_message}")

with open("corgi.png", "wb") as f:
    f.write(requests.get(result.content_url).content)
TypeScript
import { writeFile } from "fs/promises";

let result = await elevenlabs.flows.image.get(generation.id);

while (result.status === "pending" || result.status === "generating") {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  result = await elevenlabs.flows.image.get(generation.id);
}

if (result.status === "failed") {
  throw new Error(`${result.failureReason}: ${result.errorMessage}`);
}

const response = await fetch(result.contentUrl);
await writeFile("corgi.png", Buffer.from(await response.arrayBuffer()));

Either way, a completed generation carries the same fields:

JSON
{
  "id": "JWr5N6X9ZTqf8jD2LmQb",
  "status": "completed",
  "content_url": "https://storage.googleapis.com/generations/JWr5N6X9ZTqf8jD2LmQb",
  "content_mime_type": "image/png"
}
Python
python example.py
TypeScript
npx tsx example.mts

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.

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.

Python
from elevenlabs import VideoGenerationRequest_Veo31FastGenerate001, WebhookTarget_All

generation = elevenlabs.flows.video.create(
    request=VideoGenerationRequest_Veo31FastGenerate001(
        prompt="A corgi rides a tiny surfboard across a sunlit wave at golden hour, cinematic",
        duration_secs=8,
        aspect_ratio="16:9",
        resolution="1080p",
        generate_audio=True,
        webhook=WebhookTarget_All(),
    )
)

print(generation.id)
TypeScript
const generation = await elevenlabs.flows.video.create({
  modelId: "veo-3.1-fast-generate-001",
  prompt: "A corgi rides a tiny surfboard across a sunlit wave at golden hour, cinematic",
  durationSecs: 8,
  aspectRatio: "16:9",
  resolution: "1080p",
  generateAudio: true,
  webhook: { type: "all" },
});

console.log(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.

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

Webhook delivery Polling
Best for The default for both modalities, and any production use Scripts and environments with no public endpoint
Requires An HTTPS endpoint subscribed to generation events Nothing
Cost of waiting None; you are called once the generation finishes One 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.

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:

Python
from elevenlabs import WebhookTarget_Ids

webhook = WebhookTarget_Ids(ids=["Q8mVr2LpXcT4nB6yJdKw"])
TypeScript
const webhook = { type: "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.

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.

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

Status Meaning
pending The generation is queued. This is the status of every newly created generation.
generating The model is running.
completed The output is ready. The response carries content_url and content_mime_type.
failed The generation did not produce an output. The response carries the failure details.

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

JSON
{
  "id": "JWr5N6X9ZTqf8jD2LmQb",
  "status": "failed",
  "failure_reason": "moderated",
  "error_message": "The prompt was rejected by content moderation. You were not charged for this generation."
}
failure_reason Cause
timeout The model did not return a result in time.
model_error The model provider returned an error or produced no output.
moderated The prompt or an input was rejected by content moderation.
invalid_parameters The parameters were rejected once the generation reached the model.
dependency_failed A referenced generation this one depends on failed.
charging_failed The workspace could not be charged for the generation.
internal_error An 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.

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.

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.

Python
page = elevenlabs.flows.image.list(page_size=20, status="completed")

for item in page.generations:
    print(item.id, item.content_url)

while page.has_more:
    page = elevenlabs.flows.image.list(page_size=20, status="completed", cursor=page.next_cursor)
    for item in page.generations:
        print(item.id, item.content_url)
TypeScript
let page = await elevenlabs.flows.image.list({ pageSize: 20, status: "completed" });

for (const item of page.generations) {
  console.log(item.id, item.contentUrl);
}

while (page.hasMore) {
  page = await elevenlabs.flows.image.list({
    pageSize: 20,
    status: "completed",
    cursor: page.nextCursor,
  });
  for (const item of page.generations) {
    console.log(item.id, item.contentUrl);
  }
}

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.

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.

model_id Reference images Output controls
gpt-image-1 Up to 5, plus a mask aspect_ratio (1:1, 3:2, 2:3), quality, background
gpt-image-1.5 Up to 5, plus a mask aspect_ratio (1:1, 3:2, 2:3), quality, background
gpt-image-2 Up to 10, plus a mask 15 aspect ratios, resolution (1K, 2K, 4K), quality
gpt-image-2.5-sunburst Up to 10, plus a mask 15 aspect ratios, resolution (1K, 2K, 4K), quality (up to max)
gpt-image-2.5-flare Up to 10, plus a mask 15 aspect ratios, resolution (1K, 2K, 4K), quality (up to max)
gemini-2.5-flash-image Up to 5 aspect_ratio
gemini-3-pro-image Up to 10 aspect_ratio, resolution (1K, 2K, 4K)
gemini-3.1-flash-image Up to 14 aspect_ratio (including 1:4, 4:1, 1:8, 8:1), resolution (512 to 4K)
gemini-3.1-flash-lite-image Up to 14 aspect_ratio, resolution (1K)
bytedance-seedream-5-lite Up to 10 aspect_ratio, resolution (2K, 3K), seed
bytedance-seedream-5-pro Up to 10 aspect_ratio, resolution (1K, 2K), seed

The GPT Image 2.5 models accept quality values of low, medium, high, xhigh, and max, and default to high. GPT Image 2 stops at high and defaults to medium.

model_id Media inputs Output controls
veo-3.1-generate-001 start_frame, end_frame, up to 3 images with a role duration_secs (4, 6, 8), aspect_ratio (16:9, 9:16), resolution (720p, 1080p, 4K), generate_audio
veo-3.1-fast-generate-001 start_frame, end_frame, up to 3 images with a role duration_secs (4, 6, 8), aspect_ratio (16:9, 9:16), resolution (720p, 1080p, 4K), generate_audio
bytedance-seedance-v2 start_frame, end_frame, up to 9 images, 3 videos, 3 audios duration_secs (4 to 15), 7 aspect ratios, resolution (480p to 4k), generate_audio
bytedance-seedance-v2-fast start_frame, end_frame, up to 9 images, 3 videos, 3 audios duration_secs (4 to 15), 7 aspect ratios, resolution (480p, 720p), generate_audio
bytedance-seedance-v2-mini start_frame, end_frame, up to 9 images, 3 videos, 3 audios duration_secs (4 to 15), 7 aspect ratios, resolution (480p, 720p), generate_audio
bytedance-seedance-v2.5 start_frame, end_frame, up to 30 images, 10 videos, 10 audios duration_secs (4 to 30), 7 aspect ratios, resolution (480p, 720p), generate_audio
creatify-aurora image and audio, both required resolution (480p, 720p), guidance_scale, audio_guidance_scale

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

Guide a generation with a previous generation, an uploaded asset, or inline media.

Receive the result of a generation instead of polling for it.

Explore the image, video and asset endpoints.

Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu