速率限制與並行
桶怎麼算、哪些請求會佔額度、以及並行為什麼不受速率限制約束。
一個帳號一個桶
開放 API 面(/api/v1/*)按帳號做速率限制,不是按金鑰——按金鑰限制的話,多建幾把金鑰就能繞過。
預設保護閾值:1000 次 / 10 秒,約等於持續 100 RPS,並允許 1000 次突發。它是一道防止惡意流量和失控用戶端拖垮服務的高位保險絲,不是正常付費生成流量的低配額。
用的是權杖桶而不是固定時間窗計數:桶容量 1000,10 秒補滿整桶(約每秒補 100 個)。固定時間窗會在兩個時間窗的交界處放過兩倍突發,權杖桶則會平滑地補充額度。
upload-url 另有一道同樣為 1000 次 / 10 秒的獨立高位保險絲,疊加在上面這個桶之上。它單獨保護物件儲存寫入授權,正常上傳不應靠近該閾值。
usage 的獨立限流則反過來,比通用閾值緊得多:30 次 / 分鐘,一樣疊加在上面這個桶之上。它要聚合歷史任務,一次請求的成本遠高於一次普通讀取,因此不把高位保險絲當成它唯一的保護。把它用在對帳,不要當輪詢介面用。這個桶也不做故障放行:速率限制判定不可用時它一律回傳 429,不像其他桶那樣可能被營運設成放行。
全部的桶
速率限制按面分組掛載,同一個面下的介面共用一條中介軟體鏈。作為 API 使用者你只會撞到前三個:
| 桶 | 涵蓋範圍 | 計數維度 | 預設額度 |
|---|---|---|---|
open_api | /api/v1/* 全部介面(含 models 目錄) | 帳號 | 1000 次 / 10 秒(約持續 100 RPS) |
upload_url | /api/v1/common/upload-url | 帳號 | 1000 次 / 10 秒(疊加在 open_api 之上) |
api_usage | /api/v1/usage | 帳號 | 30 次 / 分鐘(疊加在 open_api 之上) |
| 控制台面 | /console/v1/* 中需要登入的介面 | 帳號 | 120 次 / 分鐘 |
catalog | 官網登陸頁用的公開唯讀目錄(不是 /api/v1/models) | 來源 IP | 300 次 / 分鐘 |
按帳號,不是按金鑰
open_api 的計數主體是帳號。多建幾把金鑰並不會讓你多出額度——按金鑰限制的話,繞過它只需要再點一次「新建金鑰」。
反過來說,upload_url 與 api_usage 各自是獨立的桶:傳一張參考圖會同時從 open_api 和 upload_url 裡各取一個權杖,讀一次 usage 則從 open_api 和 api_usage 裡各取一個,任一見底都會回傳 429。
閾值是可熱更新的營運設定,上面寫的是預設值。以回應標頭 X-RateLimit-Limit 為準,不要把預設值寫死在你的用戶端。
回應標頭
| 標頭 | 出現在 | 說明 |
|---|---|---|
X-RateLimit-Limit | 每個回應 | 目前時間窗內的額度 |
X-RateLimit-Remaining | 每個回應 | 剩餘權杖數 |
Retry-After | 僅 429 | 建議等待的秒數,至少為 1 |
X-RateLimit-Remaining 在成功回應上也會帶,所以你可以在觸發速率限制之前就主動降速,而不是撞上 429 再退。
觸發速率限制長什麼樣
{
"code": 429,
"msg": "Too many requests, please retry later",
"request_id": "req_…"
}HTTP 狀態同為 429,並帶 Retry-After。
429 不會排隊,只會被拒
超限的請求立即回傳,不會被排到佇列裡等。把超限請求排隊只會讓延遲無限累積,你拿到的是一個遲遲不回傳的連線——那比一個明確的 429 更難處理。
正確的處理方式
讀 Retry-After 再重試,不要用固定間隔硬撞。
async function callRespectingLimits(url, init) {
for (let i = 0; i < 5; i++) {
const res = await fetch(url, init);
if (res.status !== 429) return res;
// 伺服器端已經告訴你該等多久了,別自己猜
const wait = Number(res.headers.get('retry-after') ?? 1);
await new Promise((r) => setTimeout(r, wait * 1000));
}
throw new Error('持續觸發速率限制');
}import time, requests
def call_respecting_limits(method: str, url: str, **kwargs) -> requests.Response:
for _ in range(5):
res = requests.request(method, url, **kwargs)
if res.status_code != 429:
return res
# 伺服器端已經告訴你該等多久了,別自己猜
time.sleep(float(res.headers.get("retry-after", 1)))
raise RuntimeError("持續觸發速率限制")並行與速率限制是兩件事
速率限制管的是提交頻率,不是執行中的任務數
任務一旦被受理進入佇列,就不再佔用速率限制額度。同時有一百個任務在生成也不會觸發 429。
在途任務數受以下約束,它們都與速率限制無關:
- 可用餘額 —— 每個在途任務都凍結著一筆預估金額,這不是已產生的費用。餘額見底時新任務回傳
40201。 - 消費上限 —— 金鑰日上限、金鑰月上限、金鑰累計上限、平台日上限,任一觸頂回傳
40202;團隊的日/月預算也回這個碼。 - 模型的執行容量 —— 某個模型目前無法執行時回傳
50301。
所以「我想同時跑更多任務」的答案通常是儲值或調高消費上限,而不是提高速率限制額度。
提高額度
穩定執行一段時間後可以在控制台申請提高速率限制額度。說明你的用量形態——峰值 QPS、是否有批次處理情境、單次批次的規模——會讓審核快很多。
在那之前,用戶端仍應設定有上限的並行數並遵循 Retry-After。並行數應根據業務、生成耗時和餘額動態設定,不必為了配合預設的速率限制而固定在很小的數字。
速率限制判定不可用時
我們的速率限制仰賴一個外部計數服務。它故障時的取向由營運設定決定:可能放行(保證服務可用),也可能拒絕(保證額度不被擊穿)。預設是拒絕——「設定讀不到」絕不能等於「不做速率限制」。
設定為拒絕時你會收到 429 + Retry-After: 1,而不是 500。用 429 是刻意的:500 的語義是「我們壞了」,用戶端 SDK 普遍會立刻重試,正好在故障期間再疊一層壓力;429 帶 Retry-After 的語義是「退避後再來」,而那正是這時候需要你做的事。
所以:不要把 429 當成「一定是我送得太快了」。 按 Retry-After 退避的處理方式在兩種情況下都是對的。

