Skip to main content
AdCP は全オペレーションで一貫したエラーハンドリングを行います。エラー分類を理解し、適切な復旧戦略を実装することが堅牢な統合には不可欠です。

エラーの分類

1. プロトコルエラー

AdCP ビジネスロジック外の通信・接続問題:
  • ネットワークタイムアウト
  • 接続拒否
  • TLS/SSL エラー
  • JSON パースエラー
対応: 指数バックオフでリトライ。

2. タスクエラー

status: "failed" で返るビジネスロジックの失敗:
  • 在庫不足
  • 無効なターゲティング
  • 予算バリデーション失敗
  • リソース未検出
対応: ユーザーに表示し、リクエスト内容の見直しを検討。

3. バリデーションエラー

スキーマ検証に失敗する不正リクエスト:
  • 必須項目の欠落
  • 無効な型
  • 範囲外の値
対応: リクエスト形式を修正して再送(多くは開発時の問題)。

エラーレスポンス形式

失敗した処理はステータス failed とエラー詳細をトップレベルで返します:
{
  "status": "failed",
  "message": "Unable to create media buy: Insufficient inventory available for your targeting criteria",
  "context_id": "ctx-123",
  "error_code": "insufficient_inventory",
  "requested_impressions": 10000000,
  "available_impressions": 2500000,
  "suggestions": [
    "Expand geographic targeting",
    "Increase CPM bid",
    "Adjust date range"
  ]
}

エラーレスポンスのフィールド

FieldDescription
statusエラー時は常に "failed"
message人向けのエラー説明
error_code機械判読用コード
suggestions任意の復旧提案
fieldバリデーションエラーのフィールドパス
retry_afterリトライまでの秒数(レート制限など)
details追加のコンテキスト情報

標準エラーコード

認証エラー

CodeDescriptionResolution
INVALID_CREDENTIALS無効/不正な認証情報API キーが正しく有効か確認
TOKEN_EXPIREDAuthentication token has expiredRefresh OAuth token or re-authenticate
INSUFFICIENT_PERMISSIONSAccount lacks required permissionsContact administrator to upgrade permissions

Validation Errors

CodeDescriptionResolution
MISSING_REQUIRED_FIELDRequired parameter is missingInclude all required fields
INVALID_FIELD_VALUEField value doesn’t meet requirementsProvide valid values per specification
INVALID_FIELD_FORMATField format is incorrectUse correct format as specified

Resource Errors

CodeDescriptionResolution
RESOURCE_NOT_FOUNDRequested resource doesn’t existVerify ID is correct and current
PRODUCT_NOT_FOUNDProduct ID doesn’t existUse get_products to find valid IDs
CREATIVE_NOT_FOUNDCreative ID doesn’t existUse list_creatives to find valid IDs

Operation Errors

CodeDescriptionResolution
INSUFFICIENT_INVENTORYNot enough inventory for requestExpand targeting or reduce impressions
BUDGET_EXCEEDEDRequest exceeds budget limitsReduce budget or request limit increase
INVALID_TARGETINGTargeting criteria is invalidAdjust targeting parameters
CREATIVE_REJECTEDCreative failed policy validationReview and fix creative content

Rate Limiting Errors

CodeDescriptionResolution
RATE_LIMIT_EXCEEDEDToo many requestsWait for retry_after seconds

System Errors

CodeDescriptionResolution
INTERNAL_SERVER_ERRORUnexpected server errorRetry request, contact support if persistent
SERVICE_UNAVAILABLEExternal service temporarily downWait and retry
TIMEOUTRequest exceeded processing timeRetry with smaller request or contact support

Retry Logic

Exponential Backoff

Implement exponential backoff for retryable errors:
async function retryWithBackoff(fn, options = {}) {
  const {
    maxRetries = 3,
    baseDelay = 1000,
    maxDelay = 60000
  } = options;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (!isRetryable(error) || attempt === maxRetries) {
        throw error;
      }

      // Check for retry_after header/field
      const retryAfter = error.retry_after ||
        Math.min(baseDelay * Math.pow(2, attempt), maxDelay);

      // Add jitter to prevent thundering herd
      const jitter = retryAfter * (0.75 + Math.random() * 0.5);
      await sleep(jitter);
    }
  }
}

Error Categorization

Group errors by whether they’re retryable:
const RETRYABLE_ERRORS = [
  'RATE_LIMIT_EXCEEDED',
  'TIMEOUT',
  'SERVICE_UNAVAILABLE',
  'INTERNAL_SERVER_ERROR'
];

const PERMANENT_ERRORS = [
  'INVALID_CREDENTIALS',
  'INSUFFICIENT_PERMISSIONS',
  'RESOURCE_NOT_FOUND',
  'MISSING_REQUIRED_FIELD',
  'INVALID_FIELD_VALUE'
];

function isRetryable(error) {
  // Network errors are retryable
  if (error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') {
    return true;
  }

  // Check error code
  return RETRYABLE_ERRORS.includes(error.error_code);
}

Rate Limit Handling

async function handleRateLimit(error, retryFn) {
  if (error.error_code !== 'RATE_LIMIT_EXCEEDED') {
    throw error;
  }

  const retryAfter = error.retry_after || 60;
  console.log(`Rate limited. Waiting ${retryAfter} seconds...`);

  await sleep(retryAfter * 1000);
  return retryFn();
}

エラーハンドリングパターン

基本的なエラーハンドラー

async function handleAdcpError(error) {
  switch (error.error_code) {
    case 'INVALID_CREDENTIALS':
    case 'TOKEN_EXPIRED':
      // Re-authenticate and retry
      await refreshCredentials();
      return retry();

    case 'RATE_LIMIT_EXCEEDED':
      // Wait and retry
      await sleep(error.retry_after * 1000);
      return retry();

    case 'INSUFFICIENT_INVENTORY':
      // Show suggestions to user
      return showSuggestions(error.suggestions);

    case 'MISSING_REQUIRED_FIELD':
    case 'INVALID_FIELD_VALUE':
      // Developer error - fix the request
      console.error('Validation error:', error);
      throw error;

    default:
      // Log and show generic error
      console.error('AdCP error:', error);
      throw error;
  }
}

ユーザーフレンドリーなメッセージ

技術的なエラーをユーザー向けメッセージに変換します:
const USER_MESSAGES = {
  'INSUFFICIENT_INVENTORY': 'There isn\'t enough inventory for your targeting. Try expanding your audience or dates.',
  'RATE_LIMIT_EXCEEDED': 'Too many requests. Please wait a moment and try again.',
  'INSUFFICIENT_PERMISSIONS': 'Your account doesn\'t have permission for this. Contact your administrator.',
  'RESOURCE_NOT_FOUND': 'This item no longer exists or has been removed.',
  'BUDGET_EXCEEDED': 'This exceeds your budget limit. Request a limit increase or reduce the budget.',
  'SERVICE_UNAVAILABLE': 'The service is temporarily unavailable. Please try again in a few minutes.'
};

function getUserMessage(errorCode, fallbackMessage) {
  return USER_MESSAGES[errorCode] || fallbackMessage || 'An unexpected error occurred. Please try again.';
}

構造化されたエラーログ

デバッグのためにコンテキスト付きでエラーを記録します:
function logError(error, context = {}) {
  console.error('AdCP Error:', {
    error_code: error.error_code,
    message: error.message,
    timestamp: new Date().toISOString(),
    context_id: error.context_id,
    task_id: error.task_id,
    ...context,
    // Don't log sensitive data
    // NO: credentials, briefs, PII
  });
}

Webhook のエラーハンドリング

Webhook 配信失敗

Webhook 配信に失敗した場合、ポーリングにフォールバックします:
class WebhookErrorHandler {
  async onDeliveryFailure(taskId, error) {
    console.warn(`Webhook delivery failed for ${taskId}:`, error);

    // Start polling as fallback
    this.startPolling(taskId);

    // Track failure for monitoring
    this.metrics.incrementCounter('webhook_failures');
  }

  async startPolling(taskId) {
    const response = await adcp.call('tasks/get', {
      task_id: taskId,
      include_result: true
    });

    if (['completed', 'failed', 'canceled'].includes(response.status)) {
      await this.processResult(taskId, response);
    } else {
      // Schedule next poll
      setTimeout(() => this.startPolling(taskId), 30000);
    }
  }
}

Webhook ハンドラーのエラー

Webhook エンドポイント内のエラーを丁寧に扱います:
app.post('/webhooks/adcp', async (req, res) => {
  try {
    // Always respond quickly
    res.status(200).json({ status: 'received' });

    // Process asynchronously
    await processWebhookAsync(req.body);
  } catch (error) {
    // Log error but don't fail the response
    console.error('Webhook processing error:', error);

    // Move to dead letter queue for investigation
    await deadLetterQueue.add(req.body, error);
  }
});

復旧戦略

コンテキストの復旧

コンテキストが期限切れの場合は新しい会話を開始します:
async function callWithContextRecovery(request) {
  try {
    return await adcp.call(request);
  } catch (error) {
    if (error.error_code === 'CONTEXT_EXPIRED' ||
        error.message?.includes('context not found')) {
      // Clear stale context and retry
      delete request.context_id;
      return await adcp.call(request);
    }
    throw error;
  }
}

部分的成功の扱い

Some operations may partially succeed:
{
  "status": "completed",
  "message": "Created media buy with warnings",
  "media_buy_id": "mb_123",
  "errors": [
    {
      "code": "CREATIVE_SIZE_MISMATCH",
      "message": "Creative dimensions don't match all placements",
      "field": "creatives[0]",
      "suggestion": "Upload additional sizes for full coverage"
    }
  ]
}
部分成功を処理します:
function handlePartialSuccess(response) {
  if (response.status === 'completed' && response.errors?.length) {
    // Show warnings to user
    for (const warning of response.errors) {
      showWarning(warning.message, warning.suggestion);
    }
  }

  // Continue with successful result
  return response;
}

ベストプラクティス

  1. エラーを分類 - 種類に応じて対応を変える
  2. リトライを実装 - 一時的なエラーは指数バックオフで再試行
  3. レート制限を尊重 - retry_after を順守
  4. コンテキスト付きログ - デバッグ用に関連 ID を記録
  5. ユーザー向け文言に変換 - 技術的な内容をわかりやすく
  6. フォールバックを用意 - 常に代替策(例: Webhook 失敗時のポーリング)
  7. 恒久エラーは再試行しない - バリデーションエラーはコード修正
  8. 部分成功に対応 - 成功レスポンスの警告も処理

次のステップ