Adobe publishes two rate limit numbers for the Firefly API, and both count against your whole organization rather than one key or one endpoint. They sit on one usage notes page, next to the remedy: a retry-after header or an exponential backoff strategy.

The Firefly Video API notes cover the endpoint contract. This page is narrower: the published limits, how a 429 arrives, what backoff should look like, which nine output sizes are legal, and which numbers Adobe has never published.

Firefly limits: what Adobe publishes

The two numbers and their unit

The usage notes state it plainly: “4 requests per minute (RPM)” and “9,000 requests per day (RPD)”, imposed “per organization”. The daily figure carries a parenthetical: it matters once an account manager has lifted the per-minute ceiling without touching the daily one.

“Per organization” has two consequences. Four workers sharing one client ID share those four requests; the allowance is not four each. And the published figure covers the API as a whole with no breakdown by endpoint, so no pocket of extra calls is reserved for status polls. Higher ceilings come from your account manager, never from a header.

Reading a 429 and retry-after

What the documentation commits to

Exceed either limit and you get HTTP 429 Too Many Requests. The remedy is as explicit: “Implement retry logic via a retry-after HTTP header or an exponential backoff strategy.” Adobe’s staging copy of the same guidance says what to do with the value — use that header “to determine the number of seconds you should wait before trying again”.

429 is one of the eight status codes the Generate video operation lists, and it sits beside 408 Request Timeout and 415 Unsupported Media Type. A 415 means your Content-Type header is wrong and will stay wrong; a 429 means the request was fine and the timing was not.

Both credentials travel on the status call too: Authorization and x-api-key. Forgetting the second one turns a slow job into an authentication error.

Handling 429s with retry-after

A retry loop for your shell

The first block submits one job and keeps both the headers and the body.

export FIREFLY_SERVICES_ACCESS_TOKEN='paste-access-token-here'
export FIREFLY_SERVICES_CLIENT_ID='paste-client-id-here'

curl -sS -D /tmp/headers.txt -o /tmp/body.json \
  -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","sizes":[{"height":1080,"width":1920}]}'

# The status line says whether you were limited; retry-after says for how long.
grep -i -E '^(HTTP|retry-after)' /tmp/headers.txt

The second polls the job and honours that header when it appears. Adobe’s async example stops once status leaves the running state for succeeded or failed, so this loop treats a 429 as a pause rather than a failure.

STATUS_URL=$(jq -r .statusUrl /tmp/body.json)

attempt=0
until [ "$attempt" -ge 5 ]; do
  attempt=$((attempt + 1))
  curl -sS -D /tmp/poll-headers.txt -o /tmp/poll.json "$STATUS_URL" \
    -H "Authorization: Bearer $FIREFLY_SERVICES_ACCESS_TOKEN" \
    -H "x-api-key: $FIREFLY_SERVICES_CLIENT_ID"

  CODE=$(head -n 1 /tmp/poll-headers.txt | awk '{print $2}')
  if [ "$CODE" = "429" ]; then
    WAIT=$(awk 'BEGIN{IGNORECASE=1} /^retry-after:/ {gsub(/\r/,""); print $2}' /tmp/poll-headers.txt)
    sleep "${WAIT:-$((2 ** attempt))}"
    continue
  fi

  [ "$(jq -r .status /tmp/poll.json)" = "running" ] || break
  sleep 5
done
jq . /tmp/poll.json

The third is the one worth keeping in production. It spaces calls at the published ceiling and only falls back to jittered backoff when the header is missing.

import os
import random
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",
}
MIN_INTERVAL = 60 / 4  # the published 4 RPM, counted per organization
_last_call = 0.0


def pace():
    global _last_call
    wait = MIN_INTERVAL - (time.monotonic() - _last_call)
    if wait > 0:
        time.sleep(wait)
    _last_call = time.monotonic()


def call(method, url, **kwargs):
    for attempt in range(6):
        pace()
        response = requests.request(method, url, headers=HEADERS, timeout=60, **kwargs)
        if response.status_code != 429:
            response.raise_for_status()
            return response.json()
        header = response.headers.get("retry-after")
        wait = float(header) if header else (2**attempt) + random.random()
        print(f"429 from {url}; waiting {wait:.1f}s")
        time.sleep(wait)
    raise RuntimeError("still rate limited after six attempts")


job = call(
    "POST",
    f"{BASE}/videos/generate",
    json={
        "prompt": "A slow aerial pass over a desert at sunrise",
        "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(payload.get("status"))
A sequence diagram of one Firefly API request answered with a 429 response, then waiting for the retry-after interval, then waiting again through exponential backoff, then a successful retry

Staying under four requests a minute

Pacing a batch on a shared quota

Four requests a minute across an organization forces a decision that has little to do with code: which jobs deserve a request. One clip costs a submit plus however many status polls it needs, so forty clips burn eighty requests before anything is downloaded.

Three habits keep a batch inside the ceiling. Submit from one process rather than from every worker. Space polls by how the job behaves instead of a fixed one-second interval — “check once every second” is an example of a cadence, not a requirement. And do the daily arithmetic before promising throughput: at four requests a minute, continuous calling tops out at 5,760 requests a day. The 9,000 figure only becomes reachable after the per-minute ceiling is raised.

Supported aspect ratios and sizes

The nine documented dimensions

The usage notes list nine legal output sizes across three aspect ratios. Nothing else is documented, so a size outside this table is a guess.

Aspect ratioDimensions
16:91920w x 1080h
16:91280w x 720h
16:9960w x 540h
9:161080w x 1920h
9:16720w x 1280h
9:16540w x 960h
1:11080w x 1080h
1:1720w x 720h
1:1540w x 540h

The tiers are not decoration: the size you request is also what you pay. Adobe’s operations rate card defines video consumption per second of generated video — 0.4 Operations at 540p, 1 Operation at 720p, 2 Operations at 1080p. A clip is five seconds with no duration field in the body, so the same call is two Operations in the smallest square and ten in the largest widescreen.

What Adobe does not publish

Concurrency, latency and price

Concurrency has no number. The usage notes acknowledge limits on “volume, frequency, and concurrency”, then publish figures for volume and frequency only. How many jobs may be in flight at once is not published by the vendor.

Latency and timeouts have no number either. “Check once every second” is a polling cadence, not a promise about duration.

Price is absent. Per-call cost appears on neither the reference page nor the usage notes, so a figure quoted elsewhere is that site’s own. What the web interface spends per generation sits in a separate table, on the credit and cost page.

Frequently asked questions

What exactly are the Firefly API rate limits? 4 requests per minute and 9,000 per day, counted per organization, on the Firefly API usage notes page.

Which response tells me I hit them? HTTP 429 Too Many Requests. The documented remedy is retry logic driven by the retry-after header or an exponential backoff strategy.

Does Adobe publish a concurrency limit? It states limits exist on volume, frequency and concurrency, then gives numbers for volume and frequency only.

How many requests does one clip cost? One submit plus however many status polls it needs. Job duration is not published, so the count cannot be predicted.

Set the interval from the published ceiling, wire retry-after into the loop, and choose a size from the nine documented rows. The endpoint reference holds the full request contract, and the audio and video API map covers the processing services that share these credentials but not this job model.