spicyapiドキュメント
リファレンス
本文

エラー

すべての業務コード、HTTP ステータス、対処方法と、再試行が有効な条件を説明します。

呼び出しが失敗すると、レスポンスは問題の種類を示す数値(code)と、人が読むための説明文(msg)を返します。このページではすべての数値、意味、再試行の有効性を説明します。症状への対処をすぐ知りたい場合は トラブルシューティングから始めてください。

レスポンスの共通形式

ネイティブのタスク API の JSON レスポンスは、どの回答も同じ 4 フィールドで包みます。/v1 互換レスポンスと SSE は、それぞれのプロトコルに従って解析してください。

{ "code": 200, "msg": "success", "data": { }, "request_id": "req_…" }
  • code は業務コードで、HTTP ステータスではありません。 200 は成功です。
  • 失敗時は data がありません(null ではありません)。code、msg、request_id が返ります。ただし例外として、一部のアセットライブラリの失敗では data.reason が付きます。
  • msg は人向けです。文言も言語も変わることがあります(後述)。この文言で分岐せず、code を使ってください。
  • request_id は X-Request-Id ヘッダーにも入ります。問題報告時に伝えてください。

HTTP ステータスも正しく設定します。どちらで分岐しても構いませんが、片方だけを使ってください。特定のコードでは両者が異なります。例えば残高不足は code: 40201、HTTP は 402 です。混在させると、いずれケースを取りこぼします。

code での分岐を推奨します。404 は「見つからない」としか示せませんが、40201 は利用できる残高の不足を示します。

すべてのレスポンスが JSON とは限りません

エッジネットワークの平文・HTML ページなど、JSON として解析できないレスポンスには code がありません。HTTP ステータスで扱い、本文の先頭部分をログに残してください。403 を無条件に再試行しないでください。地域によるブロックなら、再試行では変わりません。

エラーメッセージの言語

人が読むエラー文は、選択した言語で返せます。デフォルトは英語です。選択した言語を使うのは次の 3 か所だけです。

  • レスポンス共通形式の msg。
  • 失敗したタスクの errorMessage。recordInfo と Webhook コールバックの両方に適用しますが、以下の例外があります。
  • OpenAI、Anthropic、Google Gemini(/v1beta)形式の互換エンドポイントのエラーオブジェクトにある error.message。

対応言語コード:en、ja、ko、de、fr、es、pt-BR、ru、it、pl。

例外: モデル自体が具体的な失敗理由を返した場合、例えば参照画像を読み込めなかった、生成音声に著作権上の制約があった場合などは、errorMessage にその理由を英語で載せます。サービス名、ホスト、URL、リクエスト ID などの内部情報は除去し、何が起きたかを確認できる形にします。アカウントや課金状態を含む理由はそのまま渡さず、SpicyAPI のメッセージに置き換えます。Content-Language は引き続き msg の言語を示し、分岐には常に errorCode を使います。

言語の選択

次のうち、最初に該当するものを使います。

  1. リクエストの Accept-Language ヘッダー。そのリクエストだけに適用します。HTTP 標準の重みに従い、認識できる言語のうち q が最も高いものを選びます。地域別表記は基本言語にまとめ、de-DE は de、pt-PT は pt-BR と扱います。中国語には対応していないため、中国語タグは一致せず、次の候補を確認します。* は指定なしです。
  2. アカウント設定。コンソールの 設定 → API エラーの言語 で設定します。アカウントの API キーを使い、認識できる Accept-Language がないすべてのリクエストに適用します。多くのサーバー側 SDK と curl はデフォルトでこのヘッダーを送らないため、大半の連携ではこの設定を使います。Webhook コールバックには元になるリクエストヘッダーがないため、常にアカウント設定に従います。
  3. それ以外は英語です。

エラーレスポンスは、実際のメッセージの言語を示す Content-Language ヘッダーを返します。

変わるのは人が読む説明文だけです

code、errorCode、OpenAI 互換の type と code、Gemini 互換の code と status、すべてのフィールド名は、どの言語でも同じです。文言を解析せずコードで分岐し、ログやアラートに code と request_id を記録してください。

例

同じ 40201(残高不足)は、デフォルトでは英語で返ります。

Default
HTTP/1.1 402 Payment Required
Content-Language: en

{ "code": 40201, "msg": "Insufficient balance", "request_id": "req_…" }

Accept-Language: ja ヘッダーを 1 つ追加します。それ以外は クイックスタートと同じタスク作成呼び出しです。

curl -X POST https://api.spicyapi.ai/api/v1/jobs/createTask \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
  -H "Accept-Language: ja" \
  --data "$TASK_PAYLOAD"

msg は日本語になりますが、code は 40201 のままです。

Accept-Language: ja
HTTP/1.1 402 Payment Required
Content-Language: ja

{ "code": 40201, "msg": "残高が不足しています", "request_id": "req_…" }

共通コード

codeHTTP意味再試行
200200成功—
400400リクエストの形式やパラメーターが不正不可。 修正して再送
40003400アップロードファイルのサイズ・タイプ・内容が URL 取得時の宣言と違う不可。 正しい contentType とサイズで新しい URL を取得し、再アップロードして確定
40004400リクエストは有効だが、このモデルは現在この設定の組み合わせを処理できない不可。 msg が示す解像度・比率などの設定を変えるか、別モデルを選ぶ。後述
401401認証失敗。3 種類のどれかは msg が示す不可。 ヘッダーかキーを修正
403403許可なし。有効なキーを使う API 呼び出しではアカウントが制限中。後述。メール未確認は別の 40310不可
404404存在しない、または自分のものではない不可
409409現在の状態と競合場合による。 後述
40901409見積もり期限切れ、リクエストまたは価格の変更再見積もり・再確認する。受理済みリクエストは元の冪等性キーで復旧
413413リクエスト本文がサーバーのサイズ上限を超える不可。 本文を小さくするか、メディアのアップロード手順を使う
429429リクエスト過多通常は可。 Retry-After に従い待つ。ただし速度が原因でない 429 もある。レート制限
500500SpicyAPI 側で問題が発生可。 待って再試行
503503見積もり、インライン画像保存、メディアアップロードなどの補助サービスが一時停止可。 Retry-After があればその時間待って再試行。createTask では同じ Idempotency-Key を使う。Retry-After なしで続く場合は msg に従う。例えばインライン画像の代わりに公開 HTTPS URL を使う

503 と 50301 は HTTP ステータスは同じですが、別の業務コードです。503 は補助サービス、50301 はモデル自体の問題です。

3 種類の 401 を見分ける

異なる 3 つの原因で認証結果が 401 になります。対処はそれぞれ異なりますが、code はどれも 401 で、違いは msg が示します。

msg(英語)実際の原因変更するもの
Missing credentials: send your API key in the Authorization header as "Bearer sk-spicy-..."(互換 /v1・/v1beta では …as "Authorization: Bearer sk-spicy-...", x-api-key, or x-goog-api-key)認証情報がまったく届いていないクライアントがヘッダーを落としている。インターセプター、プロキシ、実行中プロセスへの環境変数の読み込みを確認
API key format is not recognized: keys begin with sk-spicy-認証情報は届いたが SpicyAPI のキーではないコンソールのセッション値や他社のキーを貼っている。SpicyAPI キーは sk-spicy- で始まり、echo "${SPICY_API_KEY:0:9}" はその文字列を正確に表示する必要がある
Credentials are invalid or expired形式は合うが、存在しない、撤回済み、または期限切れAPI キーページで確認し、撤回・期限切れなら新しいキーを作る。再試行しても 200 にはならない

2 番目はデータベース照会なしで判定するため、3 番目とは異なり、その文字列では最初から動作し得ないことを示せます。存在しないキー、撤回済みキー、期限切れキーに 3 番目の同じメッセージを返すのは、どのキーが存在するかを探れないようにするためです。

ここでは英語の文字列を示しています。msg は上記の言語選択に従い、文言は固定契約ではありません。分岐には code を使い、メッセージは読む・表示する・ログに残すものとして扱い、文字列一致に使わないでください。

アカウントが API を呼び出せない

次のどちらもキー自体は有効ですが、アカウントがまだ API を呼び出せません。

40310 はメールアドレスが未確認という意味です。メッセージは Verify your account email to run models and call the API… です。

  1. コンソールを開きます。上部のバナーが未確認を示し、確認メールを再送できます。
  2. 確認メールのリンクを開きます。迷惑メールフォルダーも確認してください。
  3. 同じ呼び出しを再試行します。新しいキーは不要です。

API がブロックされていてもコンソールは使えるため、履歴は確認できます。

通常の 403 と This account cannot call the API right now はアカウントの制限を意味します。request_id を添えてサポートへ連絡してください。

残高と上限

codeHTTP意味対応
40201402残高が見積もり額に足りないコンソールの「請求」で入金。アカウント設定を参照。GET /api/v1/chat/credit も確認
40202402支出上限に達したmsg で対象を確認。キー上限(This key has reached its …)は API キーページで引き上げるかリセットを待つ。チーム上限(Workspace … budget has been reached)はオーナー・管理者・予算管理者に「チーム」ページでの引き上げを依頼。プラットフォーム上限(Platform quota for today is exhausted…)は変更できず、UTC 00:00 以降に再試行。日次は UTC 00:00、月次は 1 日の UTC 00:00 にリセット。累計はリセットしない

再送だけでは残高や上限は変わらないため、どちらも再試行には意味がありません。支出上限を参照してください。

権限

codeHTTP意味対応
40301403このキーにモデルの呼び出し権限がないコンソールでキーのモデル許可リストを広げる
40302403送信元アドレスがキーの IP 許可リストにない追加するか、そのキーの IP 制限を解除
40303403この地域ではサービスを提供していない回避できない。提供地域を参照
40310403アカウントのメールが未確認のため、モデルを実行できない登録時の確認メールのリンクを開くか、コンソールから再送。確認すればキーはそのまま動作し、ユーザー側の変更は不要

いずれも再試行では解決しません。

アセットライブラリの原因コード

アセットライブラリのエンドポイント(/api/v1/assets…)の失敗では、通常のエンベロープに機械可読のフィールド data.reason が 1 つ加わることがあります。

{ "code": 400, "msg": "…", "data": { "reason": "asset_library_locked" }, "request_id": "req_…" }

code、HTTP ステータス、msg、タスクの errorCode は、原因コードの有無にかかわらず同じなので、既存の処理はそのまま動きます。原因コードが付くのはアセットライブラリのエンドポイントだけで、他のネイティブエンドポイントには付きません。OpenAI 互換などの互換レイヤーのエラー形式も変わりません。値は今後増える可能性があるため、未知の値は原因コードがない場合と同じに扱ってください。

data.reasonHTTP意味対処
asset_library_locked400アセットライブラリは最初のチャージ後に利用できますチャージしてください。保存済みのアセットは引き続き使えます
asset_library_full400保存枠(件数またはバイト数)がいっぱいですアセットを削除するか、上のプランに上げてください。次のプランは GET /api/v1/assets/usage で確認できます
asset_file_too_large400ファイルがプランの 1 ファイル上限を超えていますより小さいファイルを使うか、上のプランに上げてください
asset_daily_limit4291 日の新規作成数の上限に達しましたUTC 0 時まで待ち、Retry-After に従ってください
asset_storage_full503プラットフォーム全体のストレージがいっぱいですしばらくしてからやり直してください
asset_permission_denied403チームでのロールでは、この変更は許可されていませんチームのオーナーか管理者に依頼するか、許可されたロールのメンバーが作成したキーを使ってください
asset_workspace_forbidden403このキーはそのライブラリにつながりません(例: チームキーを作成したメンバーがチームを離れた)必要なワークスペースで新しいキーを作成してください
asset_entity_name_taken409ライブラリに同じ名前(大文字と小文字を区別しない)のキャラクターがすでにあります。キャラクターの名前は Web アプリの Assets ページで付けるもので、読み取り専用の API は今のところこれを返しません別の名前を選んでください

モデルの利用可否

codeHTTP意味再試行
50301503モデルが現在利用できない可。 後で再試行するか別モデルを選ぶ
50302503ストリーミングかどうかを問わず、回答が届く前にテキストリクエストがモデル側で失敗し、仮押さえは返却された可。 新しい Idempotency-Key で再送
40004400モデルは稼働中だが、この設定では使えない不可。 msg が示す設定を変更

50301 は送信時点でモデルが受け付けられなかったことを意味します。非掲載、有効な価格なし、現在の生成容量なしなどです。一時的な容量問題は回数を限定した再試行で解消することがありますが、利用不可モデルや価格の欠落は再送では直りません。カタログを更新し、モデルを切り替える前にユーザーの確認を得てください。

40004 と 50301 は意図的に区別しています。50301 は今はどのタスクも受け付けられず、待つと改善する可能性があります。40004 はモデルは動くもののこの組み合わせには対応せず、msg の括弧内に設定を示します。例:This model cannot serve this parameter combination right now (…)。待っても変わらず、値を変えると改善します。どちらもタスクを作らず、資金を仮押さえしません。

受理後の生成失敗は state: failed で返ります

createTask がタスク ID を返した後の失敗は、recordInfo の state: "failed" と errorCode / errorMessage に現れます。通常、仮押さえは全額解放しますが、モデルページで課金対象と明示した一部のコンテンツ拒否は例外です。40004 と同じ状況が受理後に分かった場合は errorCode: "unsupported_combination" になります。このページにない同期の業務コード分岐を追加しないでください。

タスクの失敗コード

受理後の失敗は recordInfo の errorCode に入ります。コードの集合は固定で、内部診断値は公開しません。一覧にない値は upstream_failed として扱ってください。

errorCode意味再試行
invalid_requestパラメーターが受け付けられなかった不可。 修正して再送
unsupported_combination現在この組み合わせには対応していない不可。 別の設定を選ぶ
content_rejected選択したモデルが内容を拒否。SpicyAPI 自体はリクエストを審査しない。コンテンツと利用の責任を参照不可。 プロンプトを変えるか別モデルを選ぶ
rate_limited生成サービスが混雑可。 待って再試行
upstream_unavailable生成が一時的に利用できない可。 少し後に再試行
invalid_asset参照素材がもう利用できない元タスクの再試行は不可。 retry は元入力を再利用するため、素材を再アップロードし、新 URI で新規タスクを作る
generation_failed生成は始まったが出力がなかった場合による。 同じ入力では通常同じ失敗になる
timeout未完了のまま期限を超えた場合による。 短い時間や低い解像度を試す
upstream_failedその他の失敗可。 待って再試行

失敗・期限切れは通常、仮押さえを全額解放します。一部のコンテンツ拒否は、モデルページで例外を明示している場合に限り、受理時の見積もり額を課金します。最終請求は settled と cost を確認してください。errorMessage は人向けの説明文です。モデルの理由を英語で載せる場合があり(上記)、文言も変わるため、分岐には errorCode だけを使います。ここでの「再試行」は新しいタスクの送信で、成功すれば再び課金されます。

料金がかかるエラー

タスクの状態と接続の状態は別の事実です

明示的に拒否された作成、期限切れタスク、過去のキャンセル記録には生成料金がかかりません。失敗タスクも通常は無料ですが、モデルページで課金対象と明示した一部のコンテンツ拒否は例外です。クライアントのタイムアウトや切断はタスク失敗ではありません。受理済みタスクはキャンセルできないため、元タスクを復旧して最終状態と精算を確認してください。有効な使用量がすでに記録されたテキストストリームは、切断しても課金される場合があります。

状況課金
API がタスクを作らず createTask を明示的に拒否(例:40201、40202、40004、50301)なし。 作成と仮押さえは同一トランザクション。ゲートウェイエラーや応答欠落だけでは拒否と判断できない
送信のタイムアウト、タブの閉鎖、接続切断まだ不明。 元の冪等性キーで受理状況を復旧し、無料の再試行だと扱わない
createTask が 202 を返し、後で failed / expired通常は課金なし。モデルページで明示した一部の failed / content_rejected は受理時の見積もり額を課金。settled と cost を読む
過去のタスクが canceledなし。 互換用の状態であり、現在使える操作ではない
createTask が 202 を返し、タスクが succeededあり。 実際の使用量で精算し、受理時の仮押さえ額を上限とする。未使用分を解放し、後で超過徴収しない
recordInfo、chat/credit、common/* のエラーなし。 これらのエンドポイントは課金しない
レート制限(429)、または冪等性キーによる重複排除なし。 前者は未受理、後者は元タスクを返し、課金は 1 回だけ

精算・解放はプロセス中断後も自動で完了します。通常の失敗は全額解放し、モデルページに明記した一部のコンテンツ拒否は受理時の見積もり額を課金します。タスクの精算と残高で結果を確認してください。課金を参照してください。

409 の再試行が有効な場合

状況再試行
同じキーと変更していないペイロードで同時に createTask を呼び、まれに競合した可。 数百ミリ秒待って同じキーで再送。冪等性を参照
同じキーをすでに別ペイロードや別 API キーで使っている不可。 処理を止めキーの所属を修正。再試行では競合は変わらない
インライン画像やアップロード確定の処理中(Retry-After が付く場合あり)可。 Retry-After または数秒待ち、同じ Idempotency-Key で再送
処理中にアカウントの価格や設定が変更された可。 見積もりを使う場合は取り直してから再送
生成ファイルのコピー中可。 数秒待ってダウンロードリンクを再取得
未完了または未精算タスクへの jobs/retry後で可。 元タスクが最終状態かつ settled が true になるまで待つ
成功タスク、または保持処理で入力削除済みのタスクへの jobs/retry不可。 createTask で新しいタスクを作る
アップロード URL またはアップロードファイルの期限切れ不可。 ファイルを再アップロードする

再試行の実装

無条件に再試行しないでください

400、401、40004、40201 などの入力・認証エラーは何度送っても同じです。上の「不可」の再送は無駄になり、毎回自分のレート制限枠を消費します。

指数バックオフに揺らぎを加え、まとめて失敗したリクエストが同時に殺到しないようにします。

JavaScript
// 409 is operation-specific and must be handled at the call site.
const RETRYABLE = new Set([429, 500, 503, 50301]);

async function callWithRetry(path, init, { attempts = 4 } = {}) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(`https://api.spicyapi.ai/api/v1${path}`, init);

    // A page from the edge network is not our JSON envelope
    if (!res.headers.get('content-type')?.includes('json')) {
      const text = await res.text();
      throw new Error(`HTTP ${res.status}, not JSON: ${text.slice(0, 200)}`);
    }
    const body = await res.json();

    if (body.code === 200) return body.data;
    if (!RETRYABLE.has(body.code)) {
      throw new Error(`${body.code} ${body.msg} (request_id=${body.request_id})`);
    }

    // When the server says how long to wait, trust it
    const retryAfter = Number(res.headers.get('retry-after'));
    const backoff = Number.isFinite(retryAfter) && retryAfter > 0
      ? retryAfter * 1000
      : 2 ** i * 1000 + Math.random() * 500;

    await new Promise((r) => setTimeout(r, backoff));
  }
  throw new Error('out of retries');
}
Python
import random, time, requests

# 409 is operation-specific and must be handled at the call site.
RETRYABLE = {429, 500, 503, 50301}
BASE = "https://api.spicyapi.ai/api/v1"


def call_with_retry(method: str, path: str, attempts: int = 4, **kwargs) -> dict:
    for i in range(attempts):
        res = requests.request(method, f"{BASE}{path}", **kwargs)

        # A page from the edge network is not our JSON envelope
        try:
            body = res.json()
        except ValueError:
            raise RuntimeError(f"HTTP {res.status_code}, not JSON: {res.text[:200]}")

        if body["code"] == 200:
            return body["data"]
        if body["code"] not in RETRYABLE:
            raise RuntimeError(f'{body["code"]} {body["msg"]} (request_id={body.get("request_id")})')

        # When the server says how long to wait, trust it
        retry_after = res.headers.get("retry-after")
        backoff = float(retry_after) if retry_after else 2**i + random.random() * 0.5
        time.sleep(backoff)

    raise RuntimeError("out of retries")
Go
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"math/rand"
	"net/http"
	"strconv"
	"time"
)

// retryable lists the codes worth another attempt. Anything else returns
// the same answer however many times you ask — and each ask eats your own
// rate limit.
// Whether the generation itself failed is read from recordInfo's state.
var retryable = map[int]bool{429: true, 500: true, 503: true, 50301: true}

type envelope struct {
	Code      int             `json:"code"`
	Msg       string          `json:"msg"`
	Data      json.RawMessage `json:"data"`
	RequestID string          `json:"request_id"`
}

func callWithRetry(newReq func() (*http.Request, error), attempts int, out any) error {
	for i := 0; i < attempts; i++ {
		req, err := newReq()
		if err != nil {
			return err
		}
		res, err := http.DefaultClient.Do(req)
		if err != nil {
			// Transport-level failure: the request may well have arrived.
			// On createTask, send the same Idempotency-Key or this retry
			// turns into a second generation and a second charge.
			time.Sleep(backoff(i, ""))
			continue
		}

		var env envelope
		decErr := json.NewDecoder(res.Body).Decode(&env)
		io.Copy(io.Discard, res.Body)
		res.Body.Close()
		if decErr != nil {
			// Not our JSON envelope, for example a page from the edge network.
			return fmt.Errorf("HTTP %d, response is not JSON: %w", res.StatusCode, decErr)
		}

		if env.Code == 200 {
			return json.Unmarshal(env.Data, out)
		}
		if !retryable[env.Code] {
			return fmt.Errorf("%d %s (request_id=%s)", env.Code, env.Msg, env.RequestID)
		}
		// When the server says how long to wait, trust it
		time.Sleep(backoff(i, res.Header.Get("Retry-After")))
	}
	return fmt.Errorf("out of retries")
}

// backoff trusts Retry-After when present, otherwise backs off
// exponentially with jitter so a batch of failures does not come back
// as one thundering herd.
func backoff(attempt int, retryAfter string) time.Duration {
	if s, err := strconv.Atoi(retryAfter); err == nil && s > 0 {
		return time.Duration(s) * time.Second
	}
	base := time.Duration(1<<attempt) * time.Second
	return base + time.Duration(rand.Intn(500))*time.Millisecond
}

createTask の再試行では、必ず同じ Idempotency-Key を送ってください。別のキーにすると「実際は成功していたが応答を失った」が二重生成・二重課金になります。

原因の調査

エラーの理由が分からないときは、次の順で確認してください。

  1. request_id:これを伝えれば SpicyAPI が該当リクエストを特定できます。
  2. code:上の表を確認します。
  3. コンソールのログ:ログページの検索欄にタスク ID または request_id を貼り、タスクを開きます。状態、費用、遅延、送信したキー、エラーコード、理由を確認できます。パラメーターと結果は保持中だけ表示し、チームでは内容の閲覧権限があるロールだけに表示します。

500 の msg は常に内部情報を含まない汎用の「サービスが一時的に利用できません」という説明です。内部構造、コンポーネント名、スタックトレースをレスポンスに出すことはありません。調査は request_id から行います。

問い合わせはコンソールの サポートから会話を開始するか、support@spicyapi.ai にメールしてください。request_id またはタスク ID、時刻(UTC)、code を添え、API キーは絶対に送らないでください。

このページの内容