媒體
參考圖透過預先簽章的網址直傳,以及生成結果下載網址的簽發。
媒體輸入可直接使用公開 HTTPS 網址、小圖 Data URI 或檔案上傳。檔案直傳與結果下載直接走物件儲存:
- 參考圖上傳 —— 申請直傳票據,用
PUT把檔案直接推進物件儲存,再提交commit;該上傳檔案用 commit 回傳的spicy://URI 引用。 - 結果下載 —— 直接使用任務結果中的短期
output.assets[].url。
直傳票據與結果下載位於 /api/v1/common/,上傳提交位於 /api/v1/files/{fileId}/commit。
已有公開 HTTPS 素材
把可公開存取的 HTTPS 圖片、影片或音訊網址直接填入模型支援的欄位(例如 image_url),無需先上傳,也無需轉換成 spicy://。網址應在任務執行期間保持可存取,且不依賴自訂的身分驗證請求標頭。較大的本機檔案使用下方直傳流程。
輪詢和 v2 回呼中就緒的輸出檔案已包含 output.assets[].url 與 expiresAt,直接 GET 該網址即可,不攜帶 API Key,無需先呼叫 download-url。連結通常20分鐘有效,且不超過14天結果留存期。
Base64 與本機檔案
小圖片可以直接傳入 Schema 已宣告的圖片欄位,例如 image_url: "data:image/png;base64,..."。使用包含 MIME 的標準 Base64 Data URI;裸 Base64、URL-safe 編碼、空白、SVG、內嵌音訊和影片不適用於此方式。
- 支援 JPEG、PNG、WebP、GIF;每張解碼後不超過 1 MiB,寬高各不超過 8192,總畫素不超過 8,388,608。
- 每請求最多 16 張、總畫素不超過 16,777,216;整個 JSON 請求不超過 2 MiB,需計入 Base64 約三分之一的體積膨脹。
- 模型 Schema 的數量、參數和媒體約束仍然生效。報價只驗證輸入,不上傳、不預扣;建立時自動儲存為該帳戶的上傳檔案,查詢輸入顯示檔案 URI,不回傳 Base64 原文。
較大檔案可由 SDK 完成上傳,無需手寫票據、PUT 和 commit:
import { SpicyClient } from '@spicyapi/sdk';
const client = new SpicyClient();
const file = await client.uploadFile('./reference.png');
// 已有 Base64 時,也可用 uploadBase64(dataUri),
// 或 uploadBase64(rawBase64, { contentType: 'image/png' })。
console.log(file.uri);uploadBase64 是檔案上傳輔助方法,支援上傳介面允許的 MIME;圖片上限 10 MiB,音影片上限 100 MiB,仍以票據和所選模型的較低限制為準。它不受小圖直傳的 1 MiB 限制,也不意味著所有模型都支援音影片。上傳 URI 有效期一天;已有公開 HTTPS 素材無需呼叫此方法。
一、參考圖直傳
參考素材尚未託管在可公開存取的 HTTPS 網址時,本機檔案上傳分四步。
申請票據
POST /api/v1/common/upload-url欄位
型別
沒有檔名欄位,這是刻意的
物件鍵一律由伺服器端產生。接受呼叫方指定路徑,等於允許任何人簽出一個指向別人物件的上傳網址把它覆蓋掉。
允許的媒體型別:
contentType | 副檔名 |
|---|---|
image/jpeg | .jpg |
image/png | .png |
image/webp | .webp |
image/gif | .gif |
audio/wav | .wav |
audio/mpeg | .mp3 |
video/webm | .webm |
video/mp4 | .mp4 |
curl -X POST https://api.spicyapi.ai/api/v1/common/upload-url \
-H "Authorization: Bearer $SPICY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contentType": "image/png", "bytes": 402118}'{
"code": 200,
"msg": "success",
"data": {
"fileId": "fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
"key": "spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
"uploadUrl": "https://<account-id>.r2.cloudflarestorage.com/spicy-production-inputs/tmp/…?X-Amz-Signature=…",
"method": "PUT",
"headers": {
"Content-Type": "image/png",
"Content-Length": "402118"
},
"expiresAt": "2026-08-28T09:32:11Z",
"maxBytes": 10485760
}
}PUT 上去
把檔案直接 PUT 到 uploadUrl。curl、Node.js、Python 等非瀏覽器用戶端應把 headers 裡的每一項原樣帶上;瀏覽器用戶端按下方說明處理 Content-Length。
少一個標頭,簽章就對不上
Content-Type 與 Content-Length 都參與了簽章。改一個位元組,物件儲存就會拒絕這次上傳(回傳 403,而且那個錯誤來自物件儲存不是來自我們,看起來會很莫名其妙)。
瀏覽器把 Content-Length 定義為禁止由應用程式碼設定的請求標頭,fetch 與 XMLHttpRequest 都不能手動寫入。瀏覽器上傳時應傳送大小與申請票據時 bytes 完全一致的 File 或 Blob,並讓瀏覽器根據請求本文自動產生 Content-Length;只手動設定票據中其餘的標頭。回應仍列出 Content-Length,供 curl、Node.js、Python 等用戶端原樣使用,也方便瀏覽器核對檔案大小。
這一步不要帶 Authorization 標頭。 預先簽章的網址本身就是憑證,多帶一個認證標頭反而會讓某些物件儲存實作拒絕請求。
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: image/png" \
-H "Content-Length: 402118" \
--data-binary @reference.pngconst uploadHeaders = new Headers(ticket.data.headers);
uploadHeaders.delete('Content-Length'); // 瀏覽器會根據 File/Blob 自動產生
if (file.size !== Number(ticket.data.headers['Content-Length'])) {
throw new Error('檔案大小與上傳票據不一致');
}
await fetch(ticket.data.uploadUrl, {
method: ticket.data.method,
headers: uploadHeaders,
body: file, // File 或 Blob;不要先轉成字串、Base64 或 FormData
});提交上傳
PUT 成功只表示臨時物件已寫入。繼續用第一步的 fileId 提交 commit:
curl -X POST \
"https://api.spicyapi.ai/api/v1/files/fil_01k3m8x9q2z4v7n5p6r8s0t1w3/commit" \
-H "Authorization: Bearer $SPICY_API_KEY"{
"code": 200,
"msg": "success",
"data": {
"fileId": "fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
"status": "ready",
"bytes": 402118,
"contentType": "image/png",
"sha256": "a91f4c2e5b7d16809e3f2a4c8d0b61e7c4a890e5d2f71b3c609ae8427d15f0c6",
"uri": "spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
"expiresAt": "2026-08-29T09:12:11Z"
},
"request_id": "req_01k3m8x9q2z4v7n5p6r8s0t1w2"
}commit 會核對實際位元組數與媒體型別、檢查媒體特徵並計算 SHA-256,再把內容複製到不可被舊票據覆蓋的私有物件。成功的 commit 可以冪等重放。
PUT 後必須 commit
未 commit 的檔案不能建立任務。不要使用票據裡的臨時儲存路徑,也不要在 commit 成功前使用相容欄位 key。
把 spicy:// URI 寫進 input
只用 commit 回應的 data.uri(不是 uploadUrl、臨時物件鍵或 fileId)作為參考圖的值。MODEL_ID_FROM_CATALOG 與其餘輸入欄位是結構預留位置;請從同一條已認證即時目錄記錄中取得精確 model 與符合 Schema 的輸入。任務類型由 model 本身決定,input 裡沒有指定端點的欄位;input 只收該模型 inputSchema 列出的欄位,多傳一個就會 400:
{
"model": "MODEL_ID_FROM_CATALOG",
"input": {
"prompt": "slow dolly in, rain on the window",
"image_url": "spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
"duration_seconds": 5
}
}平台在任務執行時解析已提交的 spicy:// URI。任務排隊期間,你無需自行維護臨時下載網址;直接使用 commit 回傳的 URI 即可。
URI 未就緒、已過期或不屬於你時,會在建任務階段就回傳 400「參考素材不可用」。
完整範例
import { readFile } from 'node:fs/promises';
const BASE = 'https://api.spicyapi.ai/api/v1';
const AUTH = { Authorization: `Bearer ${process.env.SPICY_API_KEY}` };
async function uploadReference(path, contentType) {
const file = await readFile(path);
// 1. 拿票據
const ticketRes = await fetch(`${BASE}/common/upload-url`, {
method: 'POST',
headers: { ...AUTH, 'Content-Type': 'application/json' },
body: JSON.stringify({ contentType, bytes: file.byteLength }),
});
const ticket = await ticketRes.json();
if (ticket.code !== 200) throw new Error(`${ticket.code} ${ticket.msg}`);
// 2. 直傳。注意這裡不帶 Authorization,且 headers 原樣帶上
const put = await fetch(ticket.data.uploadUrl, {
method: ticket.data.method,
headers: ticket.data.headers,
body: file,
});
if (!put.ok) throw new Error(`上傳失敗:${put.status}`);
// 3. 提交上傳,讓伺服器端驗證並固定保存檔案
const commitRes = await fetch(
`${BASE}/files/${encodeURIComponent(ticket.data.fileId)}/commit`,
{ method: 'POST', headers: AUTH },
);
const committed = await commitRes.json();
if (committed.code !== 200) throw new Error(`${committed.code} ${committed.msg}`);
// 4. 任務 input 只使用 commit 回傳的 spicy:// URI
return committed.data.uri;
}import os, requests
BASE = "https://api.spicyapi.ai/api/v1"
AUTH = {"Authorization": f"Bearer {os.environ['SPICY_API_KEY']}"}
def upload_reference(path: str, content_type: str) -> str:
with open(path, "rb") as f:
data = f.read()
# 1. 拿票據
ticket = requests.post(
f"{BASE}/common/upload-url",
headers={**AUTH, "Content-Type": "application/json"},
json={"contentType": content_type, "bytes": len(data)},
).json()
if ticket["code"] != 200:
raise RuntimeError(f'{ticket["code"]} {ticket["msg"]}')
# 2. 直傳。不帶 Authorization,headers 原樣帶上
put = requests.put(ticket["data"]["uploadUrl"], headers=ticket["data"]["headers"], data=data)
put.raise_for_status()
# 3. 提交上傳,讓伺服器端驗證並固定保存檔案
committed = requests.post(
f'{BASE}/files/{ticket["data"]["fileId"]}/commit',
headers=AUTH,
).json()
if committed["code"] != 200:
raise RuntimeError(f'{committed["code"]} {committed["msg"]}')
# 4. 任務 input 只使用 commit 回傳的 spicy:// URI
return committed["data"]["uri"]額外的速率限制
upload-url 除了開放 API 面的通用速率限制之外,還有一道 1000 次 / 10 秒的獨立高位保險絲,用來阻止惡意或失控的物件儲存寫入授權。
理由是這個介面的成本不對稱:對我們,簽一張票是純本機計算;對濫用者,每一張票都是一次不再經過我們的寫入授權。人手動選圖不可能這麼快,批次指令碼傳幾十張參考圖也夠用。
二、結果下載
POST /api/v1/common/download-url欄位
型別
curl -X POST https://api.spicyapi.ai/api/v1/common/download-url \
-H "Authorization: Bearer $SPICY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"taskId": "job_01k3m8x9q2z4v7n5p6r8s0t1w3",
"key": "tasks/2026/08/28/job_01k3m8x9q2z4v7n5p6r8s0t1w3/9f3c1d0a7b4e2f68c5a1d3e9b0472fa1.png"
}'{
"code": 200,
"msg": "success",
"data": {
"key": "tasks/2026/08/28/job_01k3m8x9q2z4v7n5p6r8s0t1w3/9f3c1d0a7b4e2f68c5a1d3e9b0472fa1.png",
"url": "https://<account-id>.r2.cloudflarestorage.com/spicy-production-results/tasks/…?X-Amz-Signature=…",
"expiresAt": "2026-08-28T09:32:11Z"
}
}一個任務有多個輸出檔案時,每個 key 各呼叫一次。
有效期 20 分鐘,且簽出之後無法吊銷
已簽章的網址是一段自證有效的字串:物件儲存那邊不會再問一次持有者是誰。所以
- 別把它存進資料庫,也別寫進你回傳給前端的長期快取。要用的時候再當場簽發,簽章是本機計算,成本可以忽略。
- 要長期儲存請自己下載歸檔。 生成結果在我們這裡也只留約 14 天,見資料留存。
兩種「暫時拿不到」
| 情況 | 表現 | 該怎麼辦 |
|---|---|---|
| 輸出檔案還在轉存到我們的儲存空間 | 409,訊息為「結果仍在轉存中;請稍後重試」;recordInfo 裡對應資產是 pending: true | 等幾秒再取 |
| 轉存徹底失敗 | 資產是 unavailable: true,取不到 key | 再等沒有意義,重新生成 |
| 任務還沒成功 | 404「此任務沒有可下載的結果」 | 先等任務進 succeeded |
任務不屬於你、或 key 不屬於這個任務,都回傳 404——不區分「不存在」與「是別人的」,否則可以靠錯誤差異列舉出別人的任務 ID。
上傳素材的留存
參考圖 URI 從 commit 成功起保留 1 天,並且只可使用到 commit 回應中的 expiresAt。到期後 URI 會立即失效;物件儲存的生命週期清理可能再滯後最多 24 小時,但這段清理時間窗不會延長 URI 的可用期。需要反覆使用同一張參考圖時,請在你自己這邊留底並重新上傳。
圖片上限為 10 MiB,音影片上限為 100 MiB;若模型 Schema 的 max_size_mb 更低,則按更低值處理,上傳票據 maxBytes 為最終上限。音影片最長 600 秒。commit 會驗證真實格式,並視情況回傳 durationSeconds(十進位字串)、width、height。只有 commit 成功回傳的 spicy:// URI 能作為已上傳素材使用,有效期為 1 天。
image/jpeg · image/png · image/webp · image/gif · video/mp4 · video/webm · audio/mpeg · audio/wav

