メインコンテンツへスキップ
Pro プラン15 minutes中級

APIエラー処理

Zenovay APIのエラーを適切に処理する方法 — エラーコード、リトライ戦略、デバッグテクニックをご説明します。このAPI統合ガイドでエラー処理について学びます。

apierrorsdebuggingtroubleshootingbest-practices
最終更新日:

適切なエラーコード、リトライ戦略、デバッグテクニックを使って、Zenovay APIのエラーを適切に処理する方法をご説明します。

Zenovay REST APIはPro、Scale、Enterpriseプランで利用可能です。フリープランのキーで認証された要求は、403API_PAID_PLAN_REQUIREDコードで拒否されます。

エラーレスポンスの形式

すべてのAPIレスポンスはsuccessフラグでラップされています。エラーはsuccess: falseerrorオブジェクトを返します。

{
  "success": false,
  "error": {
    "message": "Rate limit exceeded (30 requests/minute). Try again in 42 seconds",
    "code": "RATE_LIMIT_EXCEEDED",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

HTTPステータスコードはレスポンス自体(例:429)にあり、ボディ内にはありません。

レスポンスフィールド

フィールド説明
successエラー時はfalse、成功時はtrue
error.code機械可読なエラーコード(大文字、例:RATE_LIMIT_EXCEEDED
error.message人間が読めるエラーの説明
error.timestampエラーが生成された時刻のISO 8601タイムスタンプ

また、すべてのレスポンスにはx-request-idヘッダ(UUID)が含まれます。サポートに問い合わせる際はこれを含めてください — 単一のリクエストを追跡できます。

HTTPステータスコード

成功コード(2xx)

コード説明
200リクエスト成功

成功レスポンスはsuccess: trueラッパーを使用します:

{
  "success": true,
  "data": { /* ... */ },
  "timestamp": "2026-06-13T10:30:00.000Z"
}

クライアントエラー(4xx)

コードエラーコード説明
400VALIDATION_ERROR / MISSING_SITE_ID / GENERIC_ERROR無効または不足しているパラメータ
401UNAUTHORIZED無効または不足しているAPIキー
403FORBIDDENAPIキーが要求されたリソースにアクセスできない
403API_PAID_PLAN_REQUIREDAPIは有料プランが必要(フリーキーはブロック)
404NOT_FOUNDリソースが見つからない
409CONFLICTリソースの競合(重複)
429RATE_LIMIT_EXCEEDEDこの分間のリクエストが多すぎる
429MONTHLY_LIMIT_EXCEEDED月次リクエストクォータに達した

サーバーエラー(5xx)

コードエラーコード説明
500INTERNAL_ERROR / GENERIC_ERRORサーバーエラー

よくあるエラーコード

認証エラー

不足しているキー、形式が悪いキー(Zenovayキーはzv_で始まります)、または取り消されたキーはすべて401UNAUTHORIZEDコードを返します。メッセージがどれかを示します:

// APIキーが不足している
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Missing API key. Use Authorization: Bearer <key> or X-API-Key header",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

// 無効または取り消されたキー
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid API key",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

どちらかのヘッダで認証します:

Authorization: Bearer zv_YOUR_API_KEY
X-API-Key: zv_YOUR_API_KEY

バリデーションエラー

不足しているまたは形式が悪いパラメータは、問題を説明するプレーンテキストメッセージ付きで400を返します:

// 必須クエリパラメータが不足している
{
  "success": false,
  "error": {
    "code": "MISSING_SITE_ID",
    "message": "site_id parameter is required",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

// 無効な入力
{
  "success": false,
  "error": {
    "code": "GENERIC_ERROR",
    "message": "Query must be 500 characters or less",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

レート制限エラー

1分あたりのレート制限を超えると、RATE_LIMIT_EXCEEDED429を取得します。Retry-Afterレスポンスヘッダ(秒数)を読んで、どのくらい待つ必要があるかを確認します:

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded (30 requests/minute). Try again in 45 seconds",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

レスポンスには次のヘッダが含まれます:

ヘッダ説明
Retry-Afterリトライ前に待機する秒数
X-RateLimit-Limit1分あたりのリクエスト制限
X-RateLimit-ResetウィンドウがリセットされるISO 8601タイムスタンプ

月次クォータを使い切ると、MONTHLY_LIMIT_EXCEEDEDX-Usage-*ヘッダ(X-Usage-MonthlyX-Usage-LimitX-Usage-Reset)で429が返されます。

リソースエラー

アクセス権のないウェブサイト、または存在しないウェブサイトをリクエストすると、404 NOT_FOUNDまたは403 FORBIDDENが返されます:

// ウェブサイトが見つからない
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Website not found",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

// キーはこのウェブサイトにアクセスできない
{
  "success": false,
  "error": {
    "code": "FORBIDDEN",
    "message": "API key does not have access to this website",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

プラン制限エラー

一部のエンドポイントは現在のプランより高いプランが必要です。フリーキーは完全にブロックされており、ProキーはScale専用エンドポイントからブロックされています(例:自然言語クエリAPI):

// 任意のAPIエンドポイントを呼び出すフリーキー
{
  "success": false,
  "error": {
    "code": "API_PAID_PLAN_REQUIRED",
    "message": "The Zenovay API requires a paid plan. Upgrade to Pro or higher to use API keys.",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

// Scale専用エンドポイントを呼び出すProキー
{
  "success": false,
  "error": {
    "code": "FORBIDDEN",
    "message": "This endpoint requires a Scale plan or higher. Your current plan: Pro",
    "timestamp": "2026-06-13T10:30:00.000Z"
  }
}

エラー処理戦略

基本的なエラー処理

async function callZenovayAPI(endpoint) {
  try {
    const response = await fetch(`https://api.zenovay.com/api/external/v1${endpoint}`, {
      headers: {
        'X-API-Key': API_KEY,
      }
    });

    const body = await response.json();

    if (!response.ok || body.success === false) {
      throw new ZenovayAPIError(body.error, response.status, response.headers.get('x-request-id'));
    }

    return body.data;
  } catch (error) {
    if (error instanceof ZenovayAPIError) {
      handleAPIError(error);
    } else {
      // ネットワークエラー
      console.error('Network error:', error);
    }
    throw error;
  }
}

class ZenovayAPIError extends Error {
  constructor(error, status, requestId) {
    super(error?.message);
    this.code = error?.code;
    this.status = status;
    this.requestId = requestId;
  }
}

function handleAPIError(error) {
  switch (error.code) {
    case 'UNAUTHORIZED':
      console.error('Invalid or missing API key');
      break;
    case 'API_PAID_PLAN_REQUIRED':
      console.error('This API requires a paid plan');
      break;
    case 'RATE_LIMIT_EXCEEDED':
      console.error('Rate limited - back off and retry');
      break;
    default:
      console.error(`API error: ${error.message}`);
  }
}

指数バックオフによるリトライ

4295xxレスポンスをリトライします。429の場合、推測する代わりにRetry-Afterヘッダを尊重します:

async function fetchWithRetry(url, options, maxRetries = 3) {
  const retryableStatus = [429, 500, 502, 503, 504];

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      const body = await response.json();

      if (response.ok && body.success !== false) {
        return body.data;
      }

      // リトライ不可能なクライアントエラーはリトライしない
      if (!retryableStatus.includes(response.status) || attempt === maxRetries) {
        throw new ZenovayAPIError(body.error, response.status, response.headers.get('x-request-id'));
      }

      // 429の場合はサーバーのRetry-After(秒)を優先
      let delay;
      const retryAfter = response.headers.get('Retry-After');
      if (response.status === 429 && retryAfter) {
        delay = parseInt(retryAfter, 10) * 1000;
      } else {
        delay = Math.min(1000 * Math.pow(2, attempt), 30000); // 最大30秒
      }

      console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
      await sleep(delay);

    } catch (error) {
      if (error instanceof ZenovayAPIError) {
        throw error;
      }

      // ネットワークエラー - リトライ
      if (attempt === maxRetries) {
        throw error;
      }

      await sleep(1000 * Math.pow(2, attempt));
    }
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

class ZenovayAPIError extends Error {
  constructor(error, status, requestId) {
    super(error?.message);
    this.code = error?.code;
    this.status = status;
    this.requestId = requestId;
  }
}

レート制限処理

レート制限ヘッダを使用してリクエストをペースし、429の前にバックオフします:

class RateLimitedClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.queue = [];
    this.processing = false;
    this.rateLimitReset = null;
  }

  async request(endpoint) {
    return new Promise((resolve, reject) => {
      this.queue.push({ endpoint, resolve, reject });
      this.processQueue();
    });
  }

  async processQueue() {
    if (this.processing || this.queue.length === 0) return;

    // レート制限中は待機
    if (this.rateLimitReset && Date.now() < this.rateLimitReset) {
      const waitTime = this.rateLimitReset - Date.now();
      setTimeout(() => this.processQueue(), waitTime);
      return;
    }

    this.processing = true;
    const { endpoint, resolve, reject } = this.queue.shift();

    try {
      const response = await fetch(`https://api.zenovay.com/api/external/v1${endpoint}`, {
        headers: {
          'X-API-Key': this.apiKey,
        }
      });

      // レート制限ヘッダを確認
      const remaining = response.headers.get('X-RateLimit-Remaining');
      const reset = response.headers.get('X-RateLimit-Reset');

      if (remaining === '0' && reset) {
        this.rateLimitReset = new Date(reset).getTime();
      }

      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After');
        this.rateLimitReset = Date.now() + ((parseInt(retryAfter, 10) || 60) * 1000);

        // リクエストを再度キューに入れ
        this.queue.unshift({ endpoint, resolve, reject });
      } else {
        const body = await response.json();
        if (response.ok && body.success !== false) {
          resolve(body.data);
        } else {
          reject(body.error);
        }
      }

    } catch (error) {
      reject(error);
    } finally {
      this.processing = false;
      if (this.queue.length > 0) {
        setImmediate(() => this.processQueue());
      }
    }
  }
}

サーキットブレーカーパターン

class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.resetTimeout = options.resetTimeout || 30000;
    this.failures = 0;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    this.nextAttempt = null;
  }

  async execute(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker is OPEN');
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failures = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failures++;
    if (this.failures >= this.failureThreshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.resetTimeout;
    }
  }
}

// 使用例
const breaker = new CircuitBreaker();

async function getAnalytics(websiteId) {
  return breaker.execute(() =>
    fetch(`https://api.zenovay.com/api/external/v1/analytics/${websiteId}?range=7d`, {
      headers: {
        'X-API-Key': API_KEY,
      }
    })
  );
}

デバッグテクニック

デバッグモードの有効化

// すべてのAPIリクエストをログ記録
const DEBUG = process.env.ZENOVAY_DEBUG === 'true';

async function apiRequest(endpoint) {
  if (DEBUG) {
    console.log(`[Zenovay] Request: ${endpoint}`);
  }

  const response = await fetch(`https://api.zenovay.com/api/external/v1${endpoint}`, {
    headers: { 'X-API-Key': API_KEY },
  });
  const result = await response.json();

  if (DEBUG) {
    console.log(`[Zenovay] Response: ${response.status} [${response.headers.get('x-request-id')}]`, JSON.stringify(result, null, 2));
  }

  return result;
}

リクエストIDのトラッキング

リクエストが失敗したときは必ずx-request-idレスポンスヘッダをログに記録してください — サポートはそれを使用して正確なリクエストを検索できます:

function handleError(error) {
  console.error(`API Error [${error.requestId}]:`, error.message);

  // エラーレポートに含める
  if (typeof Sentry !== 'undefined') {
    Sentry.captureException(error, {
      extra: {
        requestId: error.requestId,
        code: error.code,
        status: error.status
      }
    });
  }
}

送信前に検証

function validateQuery(params) {
  const errors = [];

  if (!params.site_id) {
    errors.push('site_id is required');
  }

  if (params.range && !['24h', '7d', '30d', '90d', '1y'].includes(params.range)) {
    errors.push("range must be one of 24h, 7d, 30d, 90d, 1y");
  }

  if (errors.length > 0) {
    throw new Error(`Validation failed: ${errors.join(', ')}`);
  }

  return true;
}

言語別の例

Python

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

class ZenovayClient:
    def __init__(self, api_key):
        self.api_key = api_key
        self.base_url = 'https://api.zenovay.com/api/external/v1'

        # リトライ戦略を構成
        self.session = requests.Session()
        retry = Retry(
            total=3,
            backoff_factor=1,
            status_forcelist=[429, 500, 502, 503, 504]
        )
        adapter = HTTPAdapter(max_retries=retry)
        self.session.mount('https://', adapter)

    def get_analytics(self, website_id, time_range='7d'):
        response = self.session.get(
            f'{self.base_url}/analytics/{website_id}',
            params={'range': time_range},
            headers={
                'X-API-Key': self.api_key,
            },
            timeout=10
        )

        body = response.json()
        if not response.ok or body.get('success') is False:
            error = body.get('error', {})
            raise ZenovayAPIError(
                code=error.get('code'),
                message=error.get('message'),
                status=response.status_code,
                request_id=response.headers.get('x-request-id')
            )
        return body['data']

class ZenovayAPIError(Exception):
    def __init__(self, code, message, status, request_id=None):
        self.code = code
        self.message = message
        self.status = status
        self.request_id = request_id
        super().__init__(f'[{code}] {message}')

Ruby

require 'net/http'
require 'json'

class ZenovayClient
  class APIError < StandardError
    attr_reader :code, :status, :request_id

    def initialize(code:, message:, status:, request_id: nil)
      @code = code
      @status = status
      @request_id = request_id
      super(message)
    end
  end

  def initialize(api_key)
    @api_key = api_key
    @base_url = 'https://api.zenovay.com/api/external/v1'
  end

  def get_analytics(website_id, time_range = '7d')
    request("/analytics/#{website_id}?range=#{time_range}")
  end

  private

  def request(endpoint, retries: 3)
    uri = URI("#{@base_url}#{endpoint}")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true

    req = Net::HTTP::Get.new(uri)
    req['X-API-Key'] = @api_key

    response = http.request(req)
    result = JSON.parse(response.body)

    if response.is_a?(Net::HTTPSuccess) && result['success'] != false
      result['data']
    else
      error = result['error'] || {}
      raise APIError.new(
        code: error['code'],
        message: error['message'],
        status: response.code.to_i,
        request_id: response['x-request-id']
      )
    end
  rescue APIError
    raise
  rescue StandardError => e
    retries -= 1
    retry if retries > 0
    raise e
  end
end

エラー監視

エラー追跡との統合

// Sentry統合
import * as Sentry from '@sentry/node';

async function fetchAnalytics(websiteId) {
  try {
    return await apiRequest(`/analytics/${websiteId}`);
  } catch (error) {
    Sentry.captureException(error, {
      tags: {
        api_error_code: error.code,
        endpoint: '/analytics'
      },
      extra: {
        request_id: error.requestId,
        website_id: websiteId
      }
    });
    throw error;
  }
}

重大エラーのアラート

function handleError(error) {
  // 認証/プランの問題についてアラート
  if (error.code === 'UNAUTHORIZED' || error.code === 'API_PAID_PLAN_REQUIRED') {
    sendAlert('API authentication failed', error);
  }

  // クォータ枯渇についてアラート
  if (error.code === 'MONTHLY_LIMIT_EXCEEDED') {
    sendAlert('Monthly API quota reached', error);
  }

  // すべてのエラーをログ記録
  console.error(`[${error.code}] ${error.message}`, {
    requestId: error.requestId,
    status: error.status
  });
}

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

リクエストが失敗する

  1. APIキーが有効でzv_で始まることを確認する
  2. プランにAPI アクセスが含まれていることを確認する(Pro以上)
  3. エンドポイントURLとベースパス(/api/external/v1)を確認する
  4. クエリパラメータを検証する(例:site_idrange
  5. ネットワーク接続を確認する

レート制限に達した

  1. リトライ前にRetry-Afterヘッダを尊重する
  2. X-RateLimit-*ヘッダを使用してリクエストをペースする
  3. 5xxレスポンスに対して指数バックオフを実装する
  4. 継続的に制限に達する場合はプランアップグレードを検討する

バリデーションエラーが発生する

  1. error.messageを読む — 問題のパラメータが示されている
  2. 必須パラメータが存在することを確認する
  3. サポートされているrange値を使用する(24h7d30d90d1y
  4. クエリ文字列を文書化された長さ制限内に保つ

断続的な障害

  1. バックオフ付きのリトライロジックを実装する
  2. サーキットブレーカーパターンを使用する
  3. ネットワークの問題を確認する
  4. Zenovayステータスページを監視する

次のステップ

この記事は役に立ちましたか?