エラーハンドリング

TCG Price Lookup APIのHTTPステータスコード、エラーレスポンスの形式、一般的なエラー、ベストプラクティス。


エラーレスポンスの形式

すべてのエラーは一貫した構造に従います:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description.",
    "status": 400
  }
}

code フィールドは安定していて機械可読です — switch 文や条件ロジックで安全に使用できます。message フィールドは人間が読めるもので、リリース間で変更される可能性があります。常に message ではなく code で分岐してください。

HTTPステータスコード

ステータス意味発生するタイミング
200OKリクエストが成功した
400Bad Request無効なパラメータまたは不正なリクエストボディ
401UnauthorizedAPIキーが未指定または無効
403ForbiddenAPIキーは有効だがプランがこのリソースを含まない
404Not Foundカード、セット、またはリソースが存在しない
429Too Many Requests日次レートリミットまたはバーストリミットを超えた
500Internal Server Errorサーバー側で問題が発生した — 再試行は安全

429 以外の 4xx エラーは再試行しないでください。これらはサーバー側の一時的な問題ではなく、リクエストの問題を示しています。

一般的なエラー

APIキーが未指定 — 401

{
  "error": {
    "code": "MISSING_API_KEY",
    "message": "The X-API-Key header is required.",
    "status": 401
  }
}

対処法: すべてのリクエストの X-API-Key ヘッダーにAPIキーを含めてください。認証をご覧ください。

APIキーが無効 — 401

{
  "error": {
    "code": "INVALID_API_KEY",
    "message": "The provided API key is invalid or expired.",
    "status": 401
  }
}

対処法: ダッシュボードでAPIキーを確認してください。期限切れの場合は再生成してください。

プランの制限 — 403

{
  "error": {
    "code": "PLAN_RESTRICTION",
    "message": "Price history requires a Trader plan or above.",
    "status": 403
  }
}

対処法: プランをアップグレードしてください。価格履歴にはTrader(月額$14.99)またはBusiness(月額$89.99)が必要です。

カードが見つからない — 404

{
  "error": {
    "code": "CARD_NOT_FOUND",
    "message": "No card found with id 'invalid-id'.",
    "status": 404
  }
}

対処法: カードIDを確認してください。IDは検索結果から取得するもので、手動で作成しないでください。検索エンドポイントを使用して有効なIDを見つけてください。

無効なgameパラメータ — 400

{
  "error": {
    "code": "INVALID_GAME",
    "message": "Invalid game 'digimon'. Supported: pokemon, mtg, yugioh, lorcana, onepiece, swu, fab, pokemonjp.",
    "status": 400
  }
}

対処法: メッセージに記載されているサポートされているゲーム識別子のいずれかを使用してください。

レートリミットを超えた — 429

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "You have exceeded your rate limit. Try again in 60 seconds.",
    "status": 429
  }
}

レスポンスには待機秒数を示す Retry-After ヘッダーが含まれています。詳細はレートリミットをご覧ください。

SDKのエラーハンドリング

すべての公式SDKは、APIエラーレスポンスをラップした型付き例外を投げます:

// JavaScript / TypeScript
import { TCGLookup, TCGError, RateLimitError, NotFoundError } from 'tcglookup';

const tcg = new TCGLookup({ apiKey: process.env.TCG_API_KEY });

try {
  const card = await tcg.getCard('invalid-id');
} catch (err) {
  if (err instanceof RateLimitError) {
    console.log(`Rate limited. Retry after ${err.retryAfter}s`);
  } else if (err instanceof NotFoundError) {
    console.log('Card does not exist');
  } else if (err instanceof TCGError) {
    console.log(`API error ${err.code}: ${err.message}`);
  } else {
    throw err; // Re-throw unexpected errors
  }
}
# Python
from tcglookup import TCGLookup, TCGError, RateLimitError, NotFoundError
import os

tcg = TCGLookup(api_key=os.environ["TCG_API_KEY"])

try:
    card = tcg.get_card("invalid-id")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after}s")
except NotFoundError:
    print("Card does not exist")
except TCGError as e:
    print(f"API error {e.code}: {e.message}")
// Go
import (
    "errors"
    tcg "github.com/TCG-Price-Lookup/tcglookup-go"
)

card, err := client.GetCard("invalid-id")
if err != nil {
    var rateLimitErr *tcg.RateLimitError
    var notFoundErr *tcg.NotFoundError
    var apiErr *tcg.APIError

    switch {
    case errors.As(err, &rateLimitErr):
        fmt.Printf("Rate limited. Retry after %ds\n", rateLimitErr.RetryAfter)
    case errors.As(err, &notFoundErr):
        fmt.Println("Card does not exist")
    case errors.As(err, &apiErr):
        fmt.Printf("API error %s: %s\n", apiErr.Code, apiErr.Message)
    default:
        return err
    }
}
// Rust
use tcglookup::{Client, Error};

match client.get_card("invalid-id").await {
    Ok(card) => println!("{}", card.name),
    Err(Error::RateLimit { retry_after }) => {
        println!("Rate limited. Retry after {}s", retry_after);
    }
    Err(Error::NotFound) => {
        println!("Card does not exist");
    }
    Err(Error::Api { code, message, .. }) => {
        println!("API error {}: {}", code, message);
    }
    Err(e) => return Err(e.into()),
}

リトライ戦略

一時的なエラー(429、500)の場合、ジッターを加えた指数バックオフを実装してください:

async function fetchWithRetry(fn, maxRetries = 4) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      const isRetryable = err.status === 429 || err.status >= 500;
      const isLastAttempt = attempt === maxRetries - 1;

      if (!isRetryable || isLastAttempt) throw err;

      // 429の場合はRetry-Afterを尊重、それ以外は指数バックオフ
      const baseDelay = err.retryAfter
        ? err.retryAfter * 1000
        : Math.pow(2, attempt) * 1000;

      // サンダリングハードを避けるためにジッターを追加
      const jitter = Math.random() * 500;
      await new Promise(r => setTimeout(r, baseDelay + jitter));
    }
  }
}

// 使用例
const card = await fetchWithRetry(() => tcg.getCard('pokemon-sv4-charizard-ex-006'));

トラブルシューティングチェックリスト

401が返ってくる場合

  • すべてのリクエストに X-API-Key ヘッダーが含まれていますか?
  • キー全体をペーストしましたか(余分なスペースや切り捨てはありませんか)?
  • ダッシュボードにログインして、キーがアクティブかどうか確認してください

403が返ってくる場合

  • ご利用のプランに含まれていないエンドポイントを呼び出しています
  • 価格履歴にはTraderプラン以上が必要です
  • エラーの message を確認してください — 必要なプランが記載されています

404が返ってくる場合

  • カードIDが検索結果以外から取得されています
  • IDの形式を確認してください: {game}-{setCode}-{slug}
  • 検索を実行して、カードがデータベースに存在することを確認してください

429が返ってくる場合

  • 最近のレスポンスの X-RateLimit-Remaining を確認してください — 制限に近づいていました
  • バッチリクエストを使用してリクエスト量を減らしてください
  • 再試行前に Retry-After 秒待機してください

500が返ってくる場合

  • 一時的なサーバーエラー — バックオフして再試行しても安全です
  • 問題が続く場合は status.tcgpricelookup.com を確認してください

ベストプラクティス

  1. 429を明示的に処理するRetry-After を尊重し、ジッターを加えた指数バックオフを実装する
  2. error.code で分岐する — リリース間で安定しています。error.message はそうではありません
  3. 4xxエラーは再試行しない(429を除く)— リクエストの問題を示しています
  4. codestatus の両方をログに記録する — デバッグがはるかに速くなります
  5. SDKのエラー型を使用する — 解析、リトライ、ヘッダーの検査を自動的に処理します