Spicy Schema 規範
統一請求信封、canonical fields、表單 Schema、非同步狀態、錯誤、結果儲存與相容性規則。
Spicy Schema 是所有新媒體模型共用的公開契約。串接一個新模型時,變化應收斂在它的 model、inputSchema 和價格上;身分驗證、任務、結果、錯誤與下載流程保持不變。
即時目錄才代表可呼叫
本文規定契約形狀,不證明任何模型已經上線。建立付費任務前必須從已認證 GET /api/v1/models 讀取模型,並同時確認 enabled: true 與 available: true。規劃中或已停用的目錄條目不可呼叫。
公開邊界
一個公開模型識別碼描述穩定能力,格式為 <family>/<version-or-variant>/<task>。provider 僅表示模型出品方,也就是模型的作者或研究機構。
公開回應、回呼、錯誤和下載結果只使用 SpicyAPI 自己的識別碼、錯誤碼與網址,不含內部處理細節、原始回應、原始錯誤或內部經營資料。任務結果先轉存到 SpicyAPI 的物件儲存,再以物件鍵回傳。
建立任務的統一信封
{
"model": "MODEL_ID_FROM_CATALOG",
"input": {
"prompt": "slow camera push-in",
"image_url": "spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
"duration_seconds": 8,
"resolution": "1080p",
"generate_audio": true
},
"callBackUrl": "https://your-app.example.com/hooks/spicy"
}| 欄位 | 規則 |
|---|---|
model | 必須是已認證即時目錄回傳且目前可用的精確值 |
input | 必須通過同一目錄條目的目前 inputSchema;未知欄位會被拒絕 |
callBackUrl | 選填,公網可達的 HTTP(S) 回呼網址;v1 固定使用這個大小寫 |
Canonical fields
同一概念在所有新端點只使用一個 snake_case 名稱:
| 概念 | 欄位 | 型別 |
|---|---|---|
| 正向提示詞 | prompt | string |
| 負向提示詞 | negative_prompt | string |
| 主圖 / 尾幀 | image_url / last_image_url | URI 或已提交的 spicy:// URI |
| 主影片 / 主音訊 | video_url / audio_url | URI 或已提交的 spicy:// URI |
| 參考素材 | reference_image_urls / reference_video_urls / reference_audio_urls | URI array |
| 輸出時長 | duration_seconds | number 或 integer |
| 解析度 / 畫面比例 | resolution / aspect_ratio | 文件列舉 |
| 原生音訊 | generate_audio | boolean |
| 隨機種子 | seed | integer |
| 輸出數 | num_outputs | positive integer |
| 編碼格式 | output_format | 文件列舉 |
模型專屬的進階參數可以增加,但必須代表真實能力、不能重複 canonical 概念,欄位名稱也只描述能力本身。歷史端點若仍使用舊欄位,以其即時 inputSchema 為準;新端點不得繼續複製舊拼寫。
inputSchema 與 UI 註解
每個模型的 inputSchema 使用 JSON Schema 2020-12,同時驅動伺服器端驗證、Playground 表單、範例和報價。官方 TypeScript SDK 對公共請求信封與回應型別提供靜態型別;各模型的 input 仍是 Record<string, unknown>,必須按即時 Schema 驗證,不能假設 SDK 內建了每個模型的靜態欄位型別。
本站不維護逐模型的參數列。參數只有一個來源:GET /api/v1/models?includeSchema=1 回傳的 inputSchema,Playground 渲染的也是同一份。靜態副本會隨模型更新漂移,而漂移沒有任何檢查能發現。怎麼按它自己渲染表單,見模型目錄與 Schema。
每份 Schema 必須:
- 根節點是
object,預設additionalProperties: false; - 明確
required、型別、列舉、範圍、長度、陣列上限、URI 格式、預設值和已確認的條件相依關係; - 每個欄位都有非空的英文
description; - 未確認的欄位直接省略,不猜預設值或能力;
- 條件相依關係用根節點的
anyOf/allOf表達(見下),而不是僅靠 UI 隱藏; - 不包含只影響內部處理方式的開關,也不包含內容過濾開關。
展示註解 x-ui
展示後設資料不是第二套驗證規則。它全部寫在欄位自己的 x-ui 物件裡,鍵一律 snake_case,取自一個封閉集合:
| 鍵 | 含義 |
|---|---|
widget | 控制項,取值見下 |
order | 欄位順序的唯一來源,數值大的在前,同一端點內不重複 |
label / placeholder | 覆寫預設標籤與輸入框的預留位置文字 |
rows | textarea 行數 |
step / unit | 數值步進與單位字尾(s / px) |
accept / max_size_mb | 上傳控制項的 MIME 過濾與單一檔案大小上限 |
advanced | 摺疊進「進階參數」 |
primary | 主輸入,置頂且不摺疊 |
affects_price | 改這個值需要重新報價 |
visible_when | 決定欄位顯示或隱藏的條件運算式 |
enum_labels | 列舉值 → 顯示文字 |
x-ui.widget 的取值是這 13 個:textarea、text、upload、multi-upload、select、radio、slider、number、switch、json、object-list、chat-messages、hidden。控制項與資料形狀保證一致,渲染器可以直接分派:select / radio 一定帶 enum;slider 一定同時有 minimum 與 maximum(不會只給 exclusiveMinimum / exclusiveMaximum);switch 一定是 boolean;multi-upload 是 items.type: string 的陣列;object-list 是 items.type: object 且帶 items.properties 的陣列,每個子欄位自己也有 type 與 description;chat-messages 是陣列;hidden 表示欄位照常提交但不出現在表單裡。
不會出現在任何模型 inputSchema 裡的寫法:根節點的 x-order-properties(順序只由 x-ui.order 決定)、x-ui-component、x-ui.hint、以及 camelCase 的 x-ui.affectsPrice。它們是早期寫法,模型包驗證現在直接拒絕,伺服器端不再產出。用戶端仍應忽略未知的展示用擴充鍵——新增一個可忽略的 x-ui 鍵屬於向後相容變更。
{
"type": "object",
"required": ["prompt", "duration_seconds"],
"additionalProperties": false,
"properties": {
"prompt": {
"type": "string",
"minLength": 1,
"description": "Describes the scene and its motion.",
"x-ui": { "widget": "textarea", "order": 100, "primary": true, "rows": 4 }
},
"duration_seconds": {
"type": "integer",
"enum": [5, 8, 10],
"description": "Output duration in seconds.",
"x-ui": { "widget": "radio", "order": 90, "unit": "s", "affects_price": true }
}
},
"x-pricing": { "variantFields": ["duration_seconds"], "billingMode": "variant_matrix" }
}每個欄位的 description 是英文的,可以直接當欄位說明展示。
x-ui.affects_price: true 表示修改該欄位後應重新報價。它不代表價格公式,也不能替代目錄中的 pricing 與 quantityField。根節點的 x-pricing 是同一件事的伺服器端視角:variantFields 列出決定價格級距的欄位,始終是 affects_price: true 欄位集的子集;billingMode 說明這個端點按什麼計費。兩者都是提示,報價仍以目錄與任務回傳的金額為準。
條件欄位
條件相依關係只有兩種形態,都寫在根節點:
{
"anyOf": [{ "required": ["image_url"] }, { "required": ["image_urls"] }],
"allOf": [
{ "if": { "required": ["last_image_url"] }, "then": { "required": ["image_url"] } },
{ "if": { "properties": { "resolution": { "const": "4k" } } }, "then": { "required": ["seed"] } }
]
}anyOf 的每個分支只含 required,讀作「這幾個裡至少給一個」。allOf 的每個分支是一條 if → then:if 用 required 或 properties.<欄位>.const 觸發,then 只宣告 required,讀作「給了 A 就必須給 B」。
oneOf、not、dependentSchemas、dependentRequired 不會出現在任何模型的 inputSchema 裡:它們表達得了約束,卻渲染不成表單,使用者只會在提交後拿到一個無法理解的 400。只實作上面這兩種形態就能涵蓋全部條件端點。
非同步生命週期
queued -> running -> succeeded
-> failed
-> expiredcreateTask 回傳 Spicy 任務 ID。用戶端透過 recordInfo 輪詢,或接收帶簽章的 Spicy webhook。queued、running 是非終態;其餘是終態。歷史 canceled 僅為讀取相容,新任務沒有取消操作。
模型內部出現新的未知非終態狀態時,SpicyAPI 會繼續對應為 running,直到明確終態或平台絕對截止時間。非冪等提交在回應未知時不會被盲目重放,以避免重複生成和重複計費。
統一回應與錯誤
所有 JSON API 使用同一個外殼:
{
"code": 200,
"msg": "success",
"data": {},
"request_id": "req_01k3m8x9q2z4v7n5p6r8s0t1w2"
}同步失敗時不出現 data。程式只按 code 分支,不解析會變化的 msg;回報問題時提供 request_id。任務建立後發生的失敗透過 data.state: "failed" 和穩定的 errorCode 表達,errorMessage 只提供安全且經過正規化的說明,不包含內部服務名稱、網址、任務 ID 或原始錯誤。
結果與下載
成功結果只回傳第一方物件鍵和後設資料:
{
"assets": [
{
"key": "tasks/2026/08/31/job_.../asset_....mp4",
"mime": "video/mp4",
"durationSeconds": 8,
"bytes": 1234567
}
]
}output.assets[].key 不是 URL。呼叫 /api/v1/common/download-url 取得短期簽章網址。結果轉存期間可能回傳 pending: true 且 key 為空;多次轉存失敗後會標記 unavailable: true。公開結果一律由 SpicyAPI 自己的儲存空間提供。
使用者計費
公開目錄中的 pricing、startingPrice、quantityField 和任務的 estimatedCost / cost 只描述使用者價格,金額均為 USD 十進位字串。內部經營資料不屬於公開契約,也不會出現在目錄、任務、回呼或日誌匯出裡。
相容與棄用
以下變更向後相容:增加選填欄位、增加用戶端可忽略的回應欄位、增加未知 x-ui 提示。
以下變更需要新模型版本或已公告的棄用期:新增必填欄位、刪除列舉值、改變欄位語義或型別、縮小限制、改變公開模型識別碼。已發布識別碼保持不可變;遷移時提供相容別名或明確的轉址。
用戶端應快取已認證目錄最多 60 秒,並依 version / updatedAt 讓快取失效。不要永久快照模型 Schema,也不要在目錄沒有可用條目時改用靜態頁面頂替。

