spicyapi文件
主要內容

速率限制與並行

桶怎麼算、哪些請求會佔額度、以及並行為什麼不受速率限制約束。

一個帳號一個桶

開放 API 面(/api/v1/*)按帳號做速率限制,不是按金鑰——按金鑰限制的話,多建幾把金鑰就能繞過。

預設保護閾值:1000 次 / 10 秒,約等於持續 100 RPS,並允許 1000 次突發。它是一道防止惡意流量和失控用戶端拖垮服務的高位保險絲,不是正常付費生成流量的低配額。

用的是權杖桶而不是固定時間窗計數:桶容量 1000,10 秒補滿整桶(約每秒補 100 個)。固定時間窗會在兩個時間窗的交界處放過兩倍突發,權杖桶則會平滑地補充額度。

所有 /api/v1 介面共用這一個桶

createTaskretryrecordInfochat/creditusagemodelscommon/upload-urlcommon/download-url 都從同一個桶裡取權杖。

輪詢仍會和任務提交共享額度。 正常流量很難觸碰預設閾值,但失控的緊密輪詢仍可能耗盡桶並讓新任務回傳 429。正式環境仍建議用回呼,以減少無意義的請求和延遲。

同理,模型目錄也在這個桶裡。模型 Schema 不需要在每次建任務前重複取得;請按更新時間快取,並在驗證失敗時重新讀取。

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來源 IP300 次 / 分鐘

按帳號,不是按金鑰

open_api 的計數主體是帳號。多建幾把金鑰並不會讓你多出額度——按金鑰限制的話,繞過它只需要再點一次「新建金鑰」。

反過來說,upload_urlapi_usage 各自是獨立的桶:傳一張參考圖會同時從 open_apiupload_url 裡各取一個權杖,讀一次 usage 則從 open_apiapi_usage 裡各取一個,任一見底都會回傳 429

閾值是可熱更新的營運設定,上面寫的是預設值。以回應標頭 X-RateLimit-Limit 為準,不要把預設值寫死在你的用戶端。

回應標頭

標頭出現在說明
X-RateLimit-Limit每個回應目前時間窗內的額度
X-RateLimit-Remaining每個回應剩餘權杖數
Retry-After429建議等待的秒數,至少為 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 再重試,不要用固定間隔硬撞。

JavaScript
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('持續觸發速率限制');
}
Python
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 普遍會立刻重試,正好在故障期間再疊一層壓力;429Retry-After 的語義是「退避後再來」,而那正是這時候需要你做的事。

所以:不要把 429 當成「一定是我送得太快了」。Retry-After 退避的處理方式在兩種情況下都是對的。

本頁目錄