spicyapi文件
主要內容

媒體

參考圖透過預先簽章的網址直傳,以及生成結果下載網址的簽發。

媒體輸入可直接使用公開 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[].urlexpiresAt,直接 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
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 上去

把檔案直接 PUTuploadUrl。curl、Node.js、Python 等非瀏覽器用戶端應headers 裡的每一項原樣帶上;瀏覽器用戶端按下方說明處理 Content-Length

少一個標頭,簽章就對不上

Content-TypeContent-Length 都參與了簽章。改一個位元組,物件儲存就會拒絕這次上傳(回傳 403,而且那個錯誤來自物件儲存不是來自我們,看起來會很莫名其妙)。

瀏覽器把 Content-Length 定義為禁止由應用程式碼設定的請求標頭,fetchXMLHttpRequest 都不能手動寫入。瀏覽器上傳時應傳送大小與申請票據時 bytes 完全一致的 FileBlob,並讓瀏覽器根據請求本文自動產生 Content-Length;只手動設定票據中其餘的標頭。回應仍列出 Content-Length,供 curl、Node.js、Python 等用戶端原樣使用,也方便瀏覽器核對檔案大小。

這一步不要帶 Authorization 標頭。 預先簽章的網址本身就是憑證,多帶一個認證標頭反而會讓某些物件儲存實作拒絕請求。

curl
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/png" \
  -H "Content-Length: 402118" \
  --data-binary @reference.png
Browser
const 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
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「參考素材不可用」。

完整範例

JavaScript
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;
}
Python
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
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

本頁目錄