Firefly video generation is a single endpoint with a short contract, and almost everything that goes wrong in a first integration goes wrong on one of four details: the path, the mandatory header, the asynchronous handoff, or the fact that the clip length is fixed. Adobe publishes all four on one reference page, and third-party catalogues get at least one of them wrong.
Here is the contract as Adobe documents it, then runnable code for each step.
The endpoint contract in one table
| Item | Value |
|---|---|
| Method and path | post /v3/videos/generate |
| Host | firefly-api.adobe.io |
| Required header | x-model-version: video1_standard |
| Security | X-Api-Key and AccessToken |
| Body fields | bitRateFactor, image, prompt, seeds, sizes, videoSettings |
| Accepted response | 202 with cancelUrl, jobId, statusUrl |
| Defined length | five seconds |
That last row is not a default you can override. Adobe defines the operation as generating a five second video, and the request body carries no duration field. If your edit needs eight seconds, you generate twice and cut, or move the work to the web app.
Authentication and the model header
Firefly APIs use an IMS access token, not a long-lived API key. Adobe’s getting started page has your server exchange the client ID and secret for a token at the IMS endpoint, and the docs note each token is valid for 24 hours. Pair the token with your client ID in x-api-key, which is the same value you used for the token request.
export FIREFLY_SERVICES_CLIENT_ID='paste-client-id-here'
export FIREFLY_SERVICES_CLIENT_SECRET='paste-client-secret-here'
curl --location 'https://ims-na1.adobelogin.com/ims/token/v3' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode "client_id=$FIREFLY_SERVICES_CLIENT_ID" \
--data-urlencode "client_secret=$FIREFLY_SERVICES_CLIENT_SECRET" \
--data-urlencode 'scope=openid,AdobeID,session,additional_info,read_organizations,firefly_api,ff_apis'
The response carries access_token, token_type and expires_in.
What video1_standard selects
x-model-version is the only required header parameter on the video operation, and video1_standard is the only value its enum lists. Treat it as a constant set once in configuration.
Submit a job with curl
The body below uses only fields that appear in Adobe’s reference, and the videoSettings values are the four keys from Adobe’s own sample request.
export FIREFLY_SERVICES_ACCESS_TOKEN='paste-access-token-here'
curl -s -X POST 'https://firefly-api.adobe.io/v3/videos/generate' \
-H "Authorization: Bearer $FIREFLY_SERVICES_ACCESS_TOKEN" \
-H "x-api-key: $FIREFLY_SERVICES_CLIENT_ID" \
-H 'x-model-version: video1_standard' \
-H 'Content-Type: application/json' \
-d '{
"prompt": "A slow aerial pass over a desert at sunrise, warm light, no text",
"sizes": [{ "height": 1080, "width": 1920 }],
"seeds": [1842533538],
"bitRateFactor": 18,
"videoSettings": {
"cameraMotion": "camera pan left",
"promptStyle": "anime",
"shotAngle": "aerial shot",
"shotSize": "close-up shot"
}
}' | tee job.json
Two field notes. bitRateFactor is an integer from 0 to 63, defaults to 18, and Adobe suggests staying between 17 and 23; 0 means lossless and produces the largest file. seeds accepts exactly one entry today, and a second value is silently ignored rather than rejected.
Poll the status URL
What the 202 returns
The submit call answers 202 Accepted with three strings and nothing else: cancelUrl, jobId and statusUrl. There is no links wrapper here, so do not write a parser copied from the image endpoints. Adobe’s async how-to shows the same three keys and says to use statusUrl and cancelUrl for status and cancellation, with jobId for logging.
STATUS_URL=$(jq -r .statusUrl job.json)
while true; do
POLL=$(curl -s "$STATUS_URL" \
-H "Authorization: Bearer $FIREFLY_SERVICES_ACCESS_TOKEN" \
-H "x-api-key: $FIREFLY_SERVICES_CLIENT_ID")
STATUS=$(echo "$POLL" | jq -r .status)
echo "status: $STATUS"
[ "$STATUS" = "succeeded" ] && break
[ "$STATUS" = "failed" ] && { echo "$POLL"; exit 1; }
sleep 5
done
echo "$POLL" | jq '.result.outputs'
Adobe’s own polling loop stops when status leaves the running state and reaches succeeded or failed, and a job still in flight answers with status set to running. The success payload carries a result object whose outputs array holds the asset URLs; Adobe’s worked example uses an image job, so print the array once before you hard-code a path.

A Python client with backoff
Rate limits are per organization, so a batch job will meet 429 before it meets any other error. Adobe’s guidance is retry logic driven by the retry-after header or exponential backoff, which is what this wrapper does.
import json
import os
import time
import requests
BASE = "https://firefly-api.adobe.io/v3"
HEADERS = {
"Authorization": f"Bearer {os.environ['FIREFLY_SERVICES_ACCESS_TOKEN']}",
"x-api-key": os.environ["FIREFLY_SERVICES_CLIENT_ID"],
"x-model-version": "video1_standard",
"Content-Type": "application/json",
}
def call(method, url, **kwargs):
for attempt in range(5):
response = requests.request(method, url, headers=HEADERS, timeout=60, **kwargs)
if response.status_code != 429:
response.raise_for_status()
return response.json()
wait = int(response.headers.get("retry-after", 2**attempt))
print(f"429 received, retrying in {wait}s")
time.sleep(wait)
raise RuntimeError("rate limit did not clear after five attempts")
job = call(
"POST",
f"{BASE}/videos/generate",
json={
"prompt": "A slow aerial pass over a desert at sunrise, warm light, no text",
"sizes": [{"height": 1080, "width": 1920}],
"seeds": [1842533538],
"bitRateFactor": 18,
},
)
print("submitted", job["jobId"])
while True:
payload = call("GET", job["statusUrl"])
if payload.get("status") in {"succeeded", "failed"}:
break
time.sleep(5)
print(json.dumps(payload, indent=2))
Note that the status call needs both auth headers too. Adobe’s async example sends Authorization and x-api-key when it polls, and omitting either returns an auth error rather than a pending status.
Sizes, consumption and rate limits
Adobe publishes output sizes on the usage notes page and consumption on a separate operations rate card. The two tables line up by resolution tier.
| Aspect ratio | Dimensions | Rate card tier | Operations per second |
|---|---|---|---|
| 16:9 | 1920w x 1080h | 1080p | 2 |
| 16:9 | 1280w x 720h | 720p | 1 |
| 16:9 | 960w x 540h | 540p | 0.4 |
| 9:16 | 1080w x 1920h | 1080p | 2 |
| 9:16 | 720w x 1280h | 720p | 1 |
| 9:16 | 540w x 960h | 540p | 0.4 |
| 1:1 | 1080w x 1080h | 1080p | 2 |
| 1:1 | 720w x 720h | 720p | 1 |
| 1:1 | 540w x 540h | 540p | 0.4 |
Rate limits are 4 requests per minute and 9,000 requests per day per organization, and exceeding either returns 429 Too Many Requests. Higher limits come from an account manager conversation, not from a header. Consumption figures are per second of generated video, so a five second 1080p clip is ten Operations by Adobe’s own arithmetic. What an Operation costs in currency is not published.
Error codes on this endpoint
Firefly’s video operation lists eight status codes with their names, two of which are easy to misread.
| Code | Official name |
|---|---|
| 202 | Accepted |
| 400 | Bad Request |
| 403 | Forbidden |
| 408 | Request Timeout |
| 415 | Unsupported Media Type |
| 422 | Unprocessable Entity |
| 429 | Too Many Requests |
| 500 | Internal Server Error |
A 403 is usually credentials scoped to the wrong organization or a client missing the Firefly entitlement, not a malformed body. A 415 points at your Content-Type header, which is worth checking first when a request that works in a REST client fails in code.
Frequently asked questions
What is the endpoint path? post /v3/videos/generate on firefly-api.adobe.io. Third-party catalogues list a /videos/generate-async path that does not appear in Adobe’s reference.
Which header is mandatory? x-model-version, and the only documented value is video1_standard.
Why does my response have no video URL? Because the call is asynchronous. You get cancelUrl, jobId and statusUrl, and the asset arrives in the status payload’s result.outputs array after status reaches succeeded.
Can I set the duration? No. The operation is defined as generating a five second video and the body has no duration field.
When did the model become generally available? Adobe’s release puts general availability on 24 April 2025. Catalogues that date it to June 2025 are not quoting Adobe.
Keep video1_standard in configuration, poll statusUrl with both auth headers, and write backoff before you write the batch loop. The related API notes cover how this collection differs from Adobe’s audio and video processing APIs, the prompt structure guide covers what to put in the prompt field, the walkthrough covers the same model from the interface, and the cost page covers credit behaviour on the web side.