メディア
参照メディアを署名付き URL で直接アップロードする方法と、生成ファイルの有効期限付きダウンロードリンクを説明します。
多くのモデルは画像・動画・音声を入力(参照素材)として受け取り、メディアモデルはファイル(生成ファイル)を返します。このページでは両方を扱います。
- 参照素材の入力:公開 HTTPS URL、リクエスト内に直接記述した小さな画像、またはアップロードしたファイルを指定します。アップロードではチケットを取得し、オブジェクトストレージにファイルを直接
PUTしてから確定します。確定時に返るspicy://URI をタスクの入力に指定します。 - 生成ファイルのダウンロード:結果に含まれる署名付き
output.assets[].urlを取得します。すべてのモデルがファイルを返すわけではありません。結果がテキストのエンドポイントはoutput.textに回答を返し、assets自体がありません。ダウンロードするファイルも期限切れになるリンクもありません。出力の形式を参照してください。
アップロードチケットと生成ファイルのダウンロードは /api/v1/common/、アップロードの確定は /api/v1/files/{fileId}/commit を使います。アップロードは無料で、料金が発生するのはファイルを使用するタスクだけです。
既存の HTTPS メディア
公開アクセスできる画像・動画・音声の HTTPS URL は、モデルが対応するフィールド(例:image_url)に直接指定できます。プラットフォームへのアップロードや spicy:// への変換は不要です。タスクの実行中は、独自の認証ヘッダーなしで URL にアクセスできる必要があります。大きなローカルファイルは以下のアップロード手順を使います。
URL はファイルそのものを返す必要があります。クラウドドライブ、写真アルバム、SNS 投稿などの共有ページのリンクは画像ではなくウェブページを返すため、タスクは invalid_asset で失敗します。その場合はファイルをダウンロードしてからアップロードしてください。
Base64 とローカルファイル
小さな画像は、スキーマで宣言された画像フィールドに直接指定できます。例:image_url: "data:image/png;base64,..."。MIME タイプを含め、標準の Base64 を使ってください。この経路は、MIME タイプのない Base64、URL セーフ形式、空白、SVG、インラインの音声・動画に対応していません。
- JPEG、PNG、WebP、GIF:画像 1 枚あたりデコード後 1 MiB 以下、各辺 8192 ピクセル以下、総画素数 8,388,608 以下。
- リクエストあたり画像 16 枚以下、合計 16,777,216 ピクセル以下。Base64 による約 3 分の 1 のサイズ増加を含め、JSON 本文全体が 2 MiB 以内である必要があります。
- モデル固有のスキーマとメディア制限も適用されます。見積もりではアップロードや資金の仮押さえをせずに検証します。作成時には画像をアカウントに紐づくアップロードとして保存し、ほかのアップロードと同じく 1 日間保持します。タスクの入力には Base64 のバイト列をそのまま返さず、ファイル URI が入ります。
より大きいファイルは、SDK がチケット取得、PUT、確定を処理します。
import { SpicyClient } from '@spicyapi/sdk';
const client = new SpicyClient();
const file = await client.uploadFile('./reference.png');
// With existing Base64, use uploadBase64(dataUri),
// or uploadBase64(rawBase64, { contentType: 'image/png' }).
console.log(file.uri);uploadBase64 は同じアップロード API を使うファイルアップロード用のヘルパーで、以下の制限に従います。小さなインライン画像の 1 MiB 制限はこのヘルパーには適用されません。ただし、選択したメディアタイプにモデルが対応している必要があります。既存の公開 HTTPS メディアにはアップロード用ヘルパーは不要です。
1. 参照素材の直接アップロード
公開 HTTPS URL でホストされていないローカルの参照ファイルは、4 段階でアップロードします。以下の curl コマンドは、同じターミナルウィンドウで順番に実行してください(macOS または Linux、jq をインストール済み)。各段階で、次の処理に必要な値をシェル変数に保存します。Windows では SDK またはこの節の末尾にある Python の例を使ってください。
チケットを取得する
POST /api/v1/common/upload-urlプロパティ
型
ファイル名のフィールドを設けていない理由
オブジェクトキーは必ずサーバー側で生成します。呼び出し側がパスを選べると、ほかの人のオブジェクトを指すアップロード URL を発行し、上書きできてしまうためです。
アップロード制限
contentType | 拡張子 | 最大ファイルサイズ | 最大時間 |
|---|---|---|---|
image/jpeg | .jpg | 10 MiB | — |
image/png | .png | 10 MiB | — |
image/webp | .webp | 10 MiB | — |
image/gif | .gif | 10 MiB | — |
audio/wav | .wav | 90 MiB | 600 秒 |
audio/mpeg | .mp3 | 90 MiB | 600 秒 |
video/webm | .webm | 90 MiB | 600 秒 |
video/mp4 | .mp4 | 90 MiB | 600 秒 |
video/quicktime | .mov | 90 MiB | 600 秒 |
application/octet-stream | .safetensors(LoRA の重み) | 2 GiB | — |
.mov(iPhone カメラのデフォルト形式)も受け付け、取り込み時に MP4 に変換するため、モデルに渡るファイルは常に MP4 です。ウェブのアップローダーとアップロード API は対応しています。CLI、SDK、MCP サーバーは拡張子からタイプを推定しますが、まだこの形式を一覧に含めていないため、そちらでは contentType: video/quicktime を明示してください。
LoRA の重みは application/octet-stream としてアップロードします。上限 2 GiB の .safetensors ファイルです(ウェブのアップローダーは最大 90 MiB)。チケットの有効期間は 20 分ではなく 2 時間で、コミット時に完全な safetensors ファイルであるかを確認します。返ってきた URI を loras[].path に入れてください。重みファイルはほかのフィールドでは使えません。サインインが必要なファイル(非公開の Hugging Face リポジトリにあるものなど)は、アップロードする代わりにリンクからインポートできます。アダプターの重みを参照してください。
モデル側の上限はこれより小さいことがあります。スキーマの x-ui.max_size_mb に従い、このアップロードの確実な上限にはチケットの maxBytes を使ってください。
FILE=reference.png # the file to upload
CONTENT_TYPE=image/png # must match the file type in the table above
BYTES=$(wc -c < "$FILE" | tr -d ' ')
TICKET=$(curl -s -X POST https://api.spicyapi.ai/api/v1/common/upload-url \
-H "Authorization: Bearer $SPICY_API_KEY" \
-H "Content-Type: application/json" \
--data "$(jq -n --arg type "$CONTENT_TYPE" --argjson bytes "$BYTES" '{contentType: $type, bytes: $bytes}')")
echo "$TICKET" | jq .
UPLOAD_URL=$(echo "$TICKET" | jq -r .data.uploadUrl)
FILE_ID=$(echo "$TICKET" | jq -r .data.fileId)次のようなレスポンスが返ります。UPLOAD_URL と FILE_ID には、それぞれ uploadUrl と fileId が保存されています。
{
"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
}
}expiresAt は約 20 分後です。それまでに PUT と確定処理の両方を完了してください。期限を過ぎると確定は 409 を返すため、新しいチケットが必要です。事前に用意せず、アップロードの直前にチケットを取得してください。
ファイルを PUT する
uploadUrl にファイルのバイト列を PUT します。curl、Node.js、Python などブラウザー以外のクライアントでは、headers のすべてのヘッダーを指定されたとおりに送信してください。ブラウザークライアントの Content-Length は以下の説明に従って扱います。
ヘッダーが 1 つ欠けると署名が一致しません
Content-Type と Content-Length はどちらも署名の対象です。どちらかを 1 バイトでも変更すると、オブジェクトストレージがアップロードを拒否します。この 403 は SpicyAPI ではなくストレージ層から返るため、原因が分かりにくくなります。
ブラウザーでは Content-Length は設定を禁止されたリクエストヘッダーなので、アプリのコードから fetch や XMLHttpRequest で指定できません。ブラウザーではチケットの bytes とサイズが完全に一致する File または Blob を送信し、本文から Content-Length を作る処理はブラウザーに任せてください。ほかのチケットヘッダーだけを自分で設定します。レスポンスに Content-Length が載っているのは、ブラウザー以外のクライアントで使うためと、ブラウザー側でも送信前にサイズを確認できるようにするためです。
このリクエストに Authorization ヘッダーを付けないでください。 署名付き URL 自体が認証情報です。認証ヘッダーを追加すると、ストレージの実装によっては拒否されます。
# Prints only the HTTP status. Expect 200.
curl -s -o /dev/null -w "%{http_code}\n" -X PUT "$UPLOAD_URL" \
-H "Content-Type: $CONTENT_TYPE" \
-H "Content-Length: $BYTES" \
--data-binary @"$FILE"const uploadHeaders = new Headers(ticket.data.headers);
uploadHeaders.delete('Content-Length'); // the browser derives it from File/Blob
if (file.size !== Number(ticket.data.headers['Content-Length'])) {
throw new Error('file size does not match the upload ticket');
}
await fetch(ticket.data.uploadUrl, {
method: ticket.data.method,
headers: uploadHeaders,
body: file, // File or Blob; do not turn it into text, Base64 or FormData
});アップロードを確定する
PUT の成功は一時オブジェクトが書き込まれたことだけを意味します。最初の段階で取得した fileId を使って処理を続けます。
curl -s -X POST "https://api.spicyapi.ai/api/v1/files/$FILE_ID/commit" \
-H "Authorization: Bearer $SPICY_API_KEY" | jq ."status": "ready" と、spicy:// で始まる uri が返ります。
{
"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"
}確定処理は保存サイズとメディアタイプをチケットと照合し、メディアのシグネチャを検証して SHA-256 を計算します。その後、元のチケットで上書きできない非公開オブジェクトにバイト列をコピーします。音声・動画では、小数を表す文字列の durationSeconds と、数値の width、height も返ることがあります。成功済みの確定を繰り返しても結果は同じで、冪等です。
| レスポンス | 意味 | 対応 |
|---|---|---|
400, code: 40003 | 保存ファイルのサイズ・タイプ・形式が宣言と一致しない | 正しい contentType と正確な bytes で新しいチケットを取得し、再アップロードする |
400 | ファイルがまだない:PUT が未完了か失敗した | PUT が 200 を返したことを確認してから再度確定する |
404 | このアカウントにその fileId がない | 自分のチケットに含まれる fileId を使う |
409 | チケットまたはアップロードファイルが期限切れ、または前の確定処理でこのファイルが拒否された | 手順 1 からやり直す |
409 / 429 | このファイルの検証がすでに実行中、または検証処理が混雑している | 1 秒待ってから再度確定する |
PUT の後は確定処理が必要です
未確定のファイルではタスクを作成できません。チケットの一時ストレージパスを使わず、確定が成功するまでは互換用の key フィールドも使わないでください。
入力に spicy:// URI を指定する
参照フィールドには、確定レスポンスの data.uri だけを指定してください。uploadUrl、一時オブジェクトキー、単体の fileId は使いません。
ファイルを受け付けるフィールドはモデルによって異なります。モデルの inputSchema では、ファイル用フィールドの x-ui.widget が upload(1 ファイル)または multi-upload(リスト。URI を配列に入れる)になっています。x-ui.accept はファイルタイプ、x-ui.max_size_mb はモデルの受付サイズを示します。モデルカタログとスキーマを参照してください。
以下の MODEL_ID_FROM_CATALOG とその他の入力フィールドは、構造を示すための仮の値です。同じ認証済みライブカタログの項目から、正確な model とスキーマに適合する入力を選んでください。タスクは model 自体で決まるため、input にエンドポイントのフィールドはありません。そのモデルの inputSchema に掲載された項目だけを受け付け、余分なフィールドが 1 つでもあると 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 を解決します。タスクがキューで待機している間も、一時ダウンロード URL を自分で管理せずにアップロード済みファイルを利用できます。確定時に返された 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. Get a ticket
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. Upload directly. No Authorization here; pass the headers through as-is
const put = await fetch(ticket.data.uploadUrl, {
method: ticket.data.method,
headers: ticket.data.headers,
body: file,
});
if (!put.ok) throw new Error(`upload failed: ${put.status}`);
// 3. Commit so the server verifies and freezes the uploaded bytes
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. Task input uses only the spicy:// URI returned by commit
return committed.data.uri;
}
console.log(await uploadReference('./reference.png', 'image/png'));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. Get a ticket
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. Upload directly. No Authorization; pass the headers through as-is
put = requests.put(ticket["data"]["uploadUrl"], headers=ticket["data"]["headers"], data=data)
put.raise_for_status()
# 3. Commit so the server verifies and freezes the uploaded bytes
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. Task input uses only the spicy:// URI returned by commit
return committed["data"]["uri"]
print(upload_reference("reference.png", "image/png"))追加のレート制限
通常の公開 API のリクエスト枠に加えて、upload-url には 10 秒あたり 1,000 リクエストの上位の保護制限があります。オブジェクトストレージへの書き込み認可が乱用や暴走によって大量発行されるのを防ぎます。
ここでは負担が対称ではありません。チケットの署名は SpicyAPI 内の計算だけですが、各チケットはその後 SpicyAPI を経由しない書き込み権限になります。人がファイルを選ぶ速度でこの上限に達することはなく、数十点の参照素材をアップロードするバッチでも十分に枠内です。
2. 結果をダウンロードする
準備済みの結果では、ポーリングと v2 コールバックに output.assets[].url と expiresAt が含まれます。API キーなしで URL を直接取得できます。以下の download-url は任意です。リンクの有効期間は通常 20 分で、タスクの保持期限を超えることはありません。保持期間の上限は約 14 日で、設定で短くした場合はそれに従います。
結果を残すには、リンクが有効な間に自分のストレージへ保存してください。例:
curl -L -o result.png "PASTE_THE_OUTPUT_URL_HERE"このリクエストには Authorization ヘッダーを付けないでください。期限切れでストレージが 403 を返す場合は、recordInfo でタスクを再取得して新しいリンクを取得します。
ダウンロードリンクの更新(任意)
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 ごとに 1 回呼び出します。recordInfo と同じく、タスクを作成した API キーでのみ使えます。
有効期間は 20 分、発行後の取り消しは不可
署名付き URL は文字列自体で権限を証明します。オブジェクトストレージは、持っている人が誰かを改めて確認しません。そのため、次を守ってください。
- データベースに保存せず、ブラウザーへ提供する長期キャッシュにも入れないでください。必要な時に署名を発行します。署名はローカルの計算で、実質的な費用はかかりません。
- 残したいファイルはダウンロードしてください。 SpicyAPI 側での生成ファイルの保持は最長でも約 14 日で、設定で短くした場合はさらに短くなります。保持と破棄を参照してください。
リンクがまだない場合、またはもうない場合
| 状況 | 表示・レスポンス | 対応 |
|---|---|---|
| SpicyAPI のストレージにコピー中 | recordInfo のアセットに pending: true が表示される。key なしの download-url は 409、明示的な key ありではコピー完了まで 404 | 数秒待って再取得する |
| コピーが回復不能な形で失敗した | アセットに unavailable: true が表示され、key がない | 待っても復旧しないため、再生成する |
| タスクがまだ成功していない | 404, "This task has no downloadable output" | state: "succeeded" を待つ |
| 保持期限を過ぎた、または破棄した | HTTP 410 と code: 404, "This output has expired or was deleted" | 完全に失われているため、再生成する |
自分のものでないタスクや、タスクに属さない key はいずれも 404 になります。「存在しない」と「別の人のもの」を区別すると、他人のタスク ID を列挙する手掛かりになるため、区別して返しません。
アセットライブラリ
キャラクターのポートレート、スタイルの参照画像、商品写真、音声サンプルなど、何度も使うファイルがあります。アセットライブラリはそれらを削除するまで保管し、1 件ごとに固定の参照 spicy://a/<id> を発行します。この参照は、任意のタスク入力のメディアフィールドにそのまま指定できます。spicy://f/ のアップロードと同じ使い方ですが、1 日で期限切れになることはありません。
- ワークスペース内で非公開。
spicy://a/の参照は、同じワークスペースのキーによるリクエストでのみ使えます。個人キーは個人ライブラリに、チームワークスペースで作成したキーはそのチームの共有ライブラリにつながり、両者が交わることはありません(後述)。 - 削除するまで保管。 生成結果は約 14 日後、アップロードは 1 日後に削除されますが、保存したアセットは削除するまで残ります。個人ライブラリはアカウントの閉鎖とともに、チームライブラリはチームとともに削除されます。
- ファイルホスティングではありません。 API が返すのは
spicy://a/<id>の参照と、ご本人宛てに署名された最長 20 分間有効なpreviewUrlだけです。公開リンクや恒久リンクはなく、参照をリンクに変換することもできません。保存しておくのはuriで、previewUrlではありません。
個人ライブラリとチームライブラリ
どのライブラリを使うかは、キーで決まります。個人ワークスペースで作成したキーは個人ライブラリに、チームワークスペースで作成したキー(作成したメンバーは誰でも)はそのチームの共有ライブラリにつながります。チームキーが誰かの個人ライブラリに届くことも、個人キーがチームライブラリに届くこともなく、もう一方のライブラリの spicy://a/ 参照は、存在しない ID と同じように拒否されます。
チームキーで変更できる内容は、そのキーを作成したメンバーの現在のロールに従います。
| ロール | 保存 | 名前変更・削除 | 一覧・取得・利用 |
|---|---|---|---|
owner, admin | 可 | すべてのアセット | 可 |
developer | 可 | 自分が保存したもののみ | 可 |
billing, readonly | 不可 | 不可 | 可 |
作成したメンバーがチームを離れた場合、そのキーは 403(data.reason は asset_workspace_forbidden)になります。アセットに保存者は表示されず、変更できるかどうかだけが分かります。
ファイルを保存する
POST /api/v1/assets はファイルをライブラリにコピーします。Idempotency-Key ヘッダーが必須です。保存元は次の 3 種類から 1 つ選びます。
source | フィールド | 補足 |
|---|---|---|
task_output | taskId、および key または index | ご自身の成功済みタスク。index は 0 始まりで、recordInfo の output.assets と同じ順序です。 |
upload | fileId | 確定済みのアップロード(fil_… または spicy://f/fil_…)。 |
url | url | ポート 80 または 443 の公開 http / https アドレス(ユーザー名とパスワードなし)。ファイルを 1 回ダウンロードし、種類はファイルの内容から判定します。受け付けるのは画像、動画、音声のみです。 |
name は省略可能です(最大 80 文字、既定値は Saved asset)。同じ保存元をもう一度保存すると、既存のアセットがそのまま返り、容量は増えません。サイズ、解像度、長さは当社側で計測します。
curl -X POST https://api.spicyapi.ai/api/v1/assets \
-H "Authorization: Bearer $SPICY_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"source": "task_output",
"taskId": "job_01k3m8x9q2z4v7n5p6r8s0t1w3",
"index": 0,
"name": "Hero portrait"
}'{
"code": 200,
"msg": "success",
"data": {
"id": "ast_01k3m9c4w8r2n6t5v1x7y0z3ab",
"uri": "spicy://a/ast_01k3m9c4w8r2n6t5v1x7y0z3ab",
"name": "Hero portrait",
"kind": "image",
"contentType": "image/png",
"bytes": 1843201,
"width": 1536,
"height": 1024,
"source": "task_output",
"sourceId": "job_01k3m8x9q2z4v7n5p6r8s0t1w3",
"createdAt": "2026-10-11T08:20:11Z",
"updatedAt": "2026-10-11T08:20:11Z",
"previewUrl": "https://…",
"previewUrlExpiresAt": "2026-10-11T08:40:11Z"
}
}タスクで使う
モデルの inputSchema で x-ui.widget が upload または multi-upload になっているファイルフィールドに、data.uri を指定します。
{
"model": "MODEL_ID_FROM_CATALOG",
"input": {
"prompt": "slow dolly in, rain on the window",
"image_url": "spicy://a/ast_01k3m9c4w8r2n6t5v1x7y0z3ab",
"duration_seconds": 5
}
}削除済みの参照や、このキーが届かないライブラリ(他のアカウントや別のワークスペース)の参照は、存在しない ID と同様に、タスク作成時に拒否されます。
一覧・名前変更・削除
GET /api/v1/assets は、アセットを items として返します。最後のページ以外は nextCursor も含まれます。絞り込みには kind(image、video、audio)、source(カンマ区切り: task_output,upload,url)、q(名前の部分一致検索、大文字小文字は区別しません)、favorite(true ならお気に入りのみ、false ならそれ以外)、collectionId(フォルダー 1 つ、後述)を使います。並び順は sort で指定します。recent(既定、新しい順)、oldest、name(A から Z、大文字小文字は区別しません)、size(大きい順)です。limit は 1〜100 で、既定値は 100 です。次のページは、前のレスポンスの nextCursor を cursor に渡して取得します。カーソルは発行時と同じ sort でのみ使え、別の sort のカーソルは 400 で拒否されます。
curl "https://api.spicyapi.ai/api/v1/assets?kind=image&q=portrait&sort=name&limit=20" \
-H "Authorization: Bearer $SPICY_API_KEY"
curl -X PATCH https://api.spicyapi.ai/api/v1/assets/ast_01k3m9c4w8r2n6t5v1x7y0z3ab \
-H "Authorization: Bearer $SPICY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Hero portrait v2"}'
curl -X DELETE https://api.spicyapi.ai/api/v1/assets/ast_01k3m9c4w8r2n6t5v1x7y0z3ab \
-H "Authorization: Bearer $SPICY_API_KEY"GET /api/v1/assets/{id} は 1 件のアセットを、新しい previewUrl 付きで返します。PATCH で変更できるのは名前、favorite、またはその両方です(変えたい項目だけを送ります)。uri は変わりません。DELETE するとアセットはすぐに見えなくなり、uri も新しいリクエストでは使えなくなります。どちらも繰り返し実行しても安全で、Idempotency-Key は不要です。存在しないアセットと他のアカウントのアセットは、同じ 404 になります。
お気に入り、フォルダー、生成したモデル
すべてのアセットに favorite フラグがあります。PATCH で {"favorite": true} を送ると設定できます。チームライブラリでは印がライブラリに付くため、全員に同じお気に入りが表示されます。フォルダー(API ではコレクションと呼びます)は、アセットをコピーせずにまとめる仕組みで、1 つのアセットを複数のフォルダーに入れられます。フォルダーの作成と管理は Web アプリの Assets ページで行います。API からは作成も編集もできませんが、GET /api/v1/assets の collectionId で 1 つのフォルダーのアセットを一覧できます(そのフォルダーがキーの届くライブラリにない場合は 404)。
すべてのアセットには、生成したモデルの公開名 model と表示名 modelName も含まれます。アップロードやリンクから保存したアセットでは、どちらも空文字列です。
キャラクター
キャラクターは、名前と、同じライブラリ内の画像アセットを順番に並べたセットです。画像は最大 100 枚、任意のメモは 280 文字までです。画像はアセットそのものなので、何もコピーされません。キャラクターを削除しても画像は残り、画像を削除するとすべてのキャラクターから外れます。キャラクターの作成と編集は、Web アプリの Assets ページか Studio で行います。専用の容量は不要で、ライブラリのプランだけが対象です。
API でできるのはキャラクターの読み取りだけです。
GET /api/v1/assets/entitiesは新しい順に一覧を返します。assetIdを指定すると、そのアセットを含むキャラクターだけに絞れます。limitは 1〜100(既定値は 50)で、cursorには前回のnextCursorを渡します。GET /api/v1/assets/entities/{id}は 1 人分を返します。存在しないキャラクター、他のアカウントのキャラクター、キーの届かないライブラリのキャラクターは、すべて同じ404になります。
各キャラクターには id、kind(常に character)、name、note、cover(カバー画像のアセット。画像がない間は null)、assets(順番どおりの画像。それぞれ uri と非公開の previewUrl 付き)、count、createdAt、updatedAt が含まれます。どのライブラリを見るかはアセットと同じくキーで決まり、読み取りの上限(1 分あたり 60 リクエスト)もアセットの読み取りと共通です。
キャラクターを使うには、その画像を順番どおりに spicy://a/ 参照としてモデルの参照画像フィールドに入れ、プロンプトではそのモデルのページに書かれた方法で参照します。キャラクターは以前「ペルソナ」と呼ばれ、別のライブラリにありましたが、現在はアセットライブラリの一部です。既存の spicy://p/ 参照も引き続き使えます(個人ライブラリのみ)。
自分でアップロードした画像(生成結果ではないもの)をキャラクターに追加するときは、その画像の権利を持っていること、実在の人物が写っている場合はその本人の同意があることを確認します。
ストレージプラン
ストレージは累計チャージ額に応じて自動的に解放され、下がることはありません。表が変わっても、すでに保存したアセットが削除されることはありません。ご自身のアカウントについては GET /api/v1/assets/usage が正です。現在のプラン、保存量、上限、次のプランとそこまでに必要な追加チャージ額、本日の新規作成数を返します。下の表は現時点の既定値です。
ストレージはオーナーに紐づきます。プランはオーナーの累計チャージ額で決まり、オーナーの個人ライブラリと、オーナーが持つすべてのチームライブラリが 1 つの枠を共有します。usage はこの枠全体の数値を返し、どのライブラリにつながっていて何ができるかを示す scope オブジェクト(workspaceId、personal、role、canWrite、canTopUp)を含みます。canTopUp が false(チームオーナー以外が作成したチームキー)の場合、lifetimeTopupUsd と topupToNextTierUsd は省略され、ライブラリが満杯のときも金額ではなくチームオーナーへの連絡を促すメッセージになります。
| プラン | 累計チャージ額 | 容量 | 件数 | 1 ファイル上限 |
|---|---|---|---|---|
| Free | なし | なし(新規保存不可) | 0 | なし |
| Plus | $10 以上 | 500 MB | 200 | 100 MB |
| Pro | $100 以上 | 1 GB | 400 | 100 MB |
| Studio | $250 以上 | 2 GB | 800 | 100 MB |
| Max | $1,000 以上 | 10 GB | 4,000 | 100 MB |
チャージがない場合は新しく保存できませんが、保存済みのアセットは引き続き使えます。1 ファイル 100 MB の上限が適用されるのは、タスクの出力とリンクからの保存です。アップロード経由のファイルには、アップロード自体の上限(画像 10 MiB、音声・動画 90 MiB)が引き続き適用されます。
レート制限とエラー
- 読み取り(一覧・取得・usage)は毎分 60 回、保存は毎分 30 回、新規作成は UTC 1 日あたり 200 件までです。アセットを削除しても、その日の枠は戻りません。上限に達すると
Retry-Afterヘッダー付きで429が返ります。 - ライブラリが満杯の場合、ストレージが未解放の場合、1 ファイルの上限を超える場合は、
invalid_requestの400になります。メッセージには現在のプランと、次のプランの解放に必要な追加チャージ額が示されます。 - キーがそのライブラリに届かない場合は
403(data.reasonはasset_workspace_forbidden)、チームのロールでその操作が許可されない場合は403(asset_permission_denied)です。プラットフォームのストレージが一時的に満杯のときは503が返りますので、しばらくしてからやり直してください。
これらのエンドポイントの失敗には、機械可読の data.reason(asset_library_locked、asset_library_full、asset_file_too_large、asset_daily_limit、asset_storage_full、asset_permission_denied、asset_workspace_forbidden、asset_entity_name_taken)が付くことがあります。code、HTTP ステータス、errorCode は付く場合も付かない場合も変わりません。原因コードが付くのはアセットライブラリのエンドポイントだけで、OpenAI 互換などの互換レイヤーのエラー形式は従来どおりです。未知の値は、原因コードがない場合と同じに扱ってください。詳しくはアセットライブラリの原因コードをご覧ください。
Web アプリにはライブラリの自動保存スイッチもあります。対象は Web アプリで開始した生成だけで、API から開始した生成は自動保存されません。残したい場合は POST /api/v1/assets を呼び出してください。
アップロードの保持
アップロードした参照 URI は、確定が成功した時点から 1 日間保持されます。これはプラットフォームの上限で、保持と破棄では短縮したり、完了時に破棄する設定に置き換えたりできます。使用できるのは確定レスポンスの expiresAt までです。期限切れ後は URI を使えず、それを参照したタスクの 再試行でも同様です。その後のオブジェクトストレージのライフサイクル削除には最大 24 時間の遅れがありますが、この削除待ち期間によって URI の使用期限が延びることはありません。同じ参照素材を再び使う場合は、自分のコピーを保持して再アップロードしてください。

