Adobe 把 Firefly API 的限流写在一页用法说明里:4 requests per minute (RPM) 与 9,000 requests per day (RPD),单位标注为 per organization——按整个组织算,不按 API key,也不按端点。处置方案只有两条:读 retry-after 响应头,或者做指数退避。
数字是最容易的部分,每分钟 4 次只是原型阶段的余量。批量流水线得围着它设计,而不是想办法绕过去。Firefly 视频接口笔记讲接口契约,本文讲另一半:官方公布了什么、429 长什么样、退避怎么写才不会白等、哪九个尺寸合法,以及哪些数字官方从未公布。
Adobe 公布的限流数字
两个数字,以及它们的计量单位
用法说明写得很直接:限流按组织计算,4 RPM 与 9,000 RPD。每日限额后面那句括号说明它何时才真正起作用——客户经理已经把每分钟上限提上去了,而每日上限没动。
「按组织计算」带来两个后果。四个 worker 共用同一个 client ID,分的是这四次,不是各拿四次;官方给的是整套 API 的总量,没有按端点拆分,所以文档里不存在「留给轮询的额外额度」。提额只能找客户经理,请求头和订阅档位都改变不了它。
429 响应与 retry-after 怎么读
官方文档明确承诺了什么
超过任何一条限额,返回 HTTP 429 Too Many Requests。官方处置写得很明确:「Implement retry logic via a retry-after HTTP header or an exponential backoff strategy.」staging 版本更进一步:用那个头「to determine the number of seconds you should wait before trying again」——等多久由响应头说了算,不由你猜。
429 也是 Generate video 列出的八个状态码之一,和 408、415 并排出现。但三者不该同样处理:415 说明 Content-Type 写错了,重试一百次还是错的;429 说明请求本身没问题,只是时机不对。
轮询状态地址时两个凭据都要带上:Authorization 与 x-api-key。漏掉后者,本来只是慢的任务会直接变成鉴权错误。
处理 429:retry-after 与退避
一段可以直接抄进 shell 的重试代码
第一段提交一个任务,同时把响应头和响应体留在磁盘上,方便判断到底撞上了什么。
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}]}'
# 状态行说明这一枪有没有被限流,retry-after 说明要等多久。
grep -i -E '^(HTTP|retry-after)' /tmp/headers.txt
第二段轮询任务状态,并在那个头出现时优先听它的。Adobe 官方异步示例的循环条件写成 status 到达 succeeded 或 failed 才停,所以这段把 429 当暂停,而不是当失败。
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
第三段值得留在生产环境:它按官方上限给每次调用排间隔,只有在响应头缺失时才退回带抖动的指数退避。
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 # 官方公布的 4 RPM,按组织计算
_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"))

把批量任务压在每分钟 4 次以内
共享配额下的批次节流
每分钟 4 次是组织级的,于是逼出一个和代码关系不大的决定:哪些任务配得上一次请求。一条五秒视频的成本是一次提交加若干次轮询,四十条就是八十次请求起步,而下载还没开始。
三个习惯能让批次待住。提交集中在一个进程,不要每个 worker 各自提交;轮询间隔跟着任务状态走,不要钉死成一秒,官方那句「check once every second」是节奏举例,照抄只会把额度烧在空轮询上;先把每日算术做完再承诺吞吐量,按 4 RPM 全天不停也只有 5,760 次,9,000 要等每分钟上限提上去才生效。
官方支持的画幅与尺寸档位
九个写进文档的尺寸
用法说明列出三个画幅下的九个合法输出尺寸,此外什么都没写。表外的尺寸只能算猜,API 不保证接受。
| 画幅 | 尺寸 |
|---|---|
| 16:9 | 1920w x 1080h |
| 16:9 | 1280w x 720h |
| 16:9 | 960w x 540h |
| 9:16 | 1080w x 1920h |
| 9:16 | 720w x 1280h |
| 9:16 | 540w x 960h |
| 1:1 | 1080w x 1080h |
| 1:1 | 720w x 720h |
| 1:1 | 540w x 540h |
三档不是装饰,请求哪个尺寸就等于付哪个价。Adobe 的 Operations 费率卡把视频消耗按每秒生成视频计算,沿分辨率分成三档:540p 每秒 0.4 个 Operation,720p 每秒 1 个,1080p 每秒 2 个。端点定义是五秒,请求体没有时长字段,同一次调用的账,最小方形是 2 个 Operation,最大宽屏是 10 个。
官方没有公布的部分
并发、耗时与价格
并发没有数字。用法说明承认 Adobe 对调用的 volume、frequency、concurrency 都设了限制,然后只公布前两项的数值。同一时刻允许多少任务在跑,官方未公布,也没有请求头能提它。
耗时与超时同样没有数字。「check once every second」是轮询节奏,不是对单条视频耗时的承诺。重试预算只能按次数写,按秒写没有依据。
价格是第三个空白。单次调用费用在参考页和用法说明里都没出现,别处引用的报价属于那个站自己。网页端一次生成花多少额度写在另一张表上,见额度与费用页,它和 Operations 费率卡不是同一张表。
常见问题
Firefly API 的限额是多少? 每分钟 4 次、每天 9,000 次,按组织计算,写在 Firefly API 用法说明页上。
怎么知道自己被限流了? 响应是 HTTP 429 Too Many Requests,处置是用 retry-after 响应头驱动重试,或者走指数退避。
Adobe 公布并发上限了吗? 文档承认 volume、frequency、concurrency 三项都有限制,随后只给出前两项数字,并发数值官方未公布。
一条视频要花多少次请求? 一次提交加若干次状态轮询。任务耗时官方未公布,轮询次数没法提前算准。
按公布的上限设间隔,在写批量循环之前先把 retry-after 接进重试逻辑,尺寸只从那九行里挑。接口参考是完整的请求契约,音视频 API 面讲的是一套共用凭据、但不共用这套作业模型的处理类接口。