Skip to main content
AdCP uses a consistent error handling approach across all operations. Understanding error categories and implementing proper recovery strategies is essential for building robust integrations.

Error Categories

1. Protocol Errors

Transport/connection issues not related to AdCP business logic:
  • Network timeouts
  • Connection refused
  • TLS/SSL errors
  • JSON parsing errors
Handling: Retry with exponential backoff.

2. Task Errors

Business logic failures returned as status: "failed":
  • Insufficient inventory
  • Invalid targeting
  • Budget validation failures
  • Resource not found
Handling: Display error to user, may require different request.

3. Validation Errors

Malformed requests that fail schema validation:
  • Missing required fields
  • Invalid field types
  • Out-of-range values
Handling: Fix request format and retry. Usually development-time issues.

Error Response Format

Failed operations return status failed with error details at the top level:
{
  "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"
  ]
}

Error Response Fields

FieldDescription
statusAlways "failed" for errors
messageHuman-readable error description
error_codeMachine-readable error code
suggestionsOptional recovery suggestions
fieldField path for validation errors
retry_afterSeconds to wait before retry (rate limits)
detailsAdditional context-specific information

Standard Error Codes

Authentication Errors

CodeDescriptionResolution
INVALID_CREDENTIALSInvalid or malformed authentication credentialsVerify API key is correct and active
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();
}

Error Handling Patterns

Basic Error Handler

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;
  }
}

User-Friendly Messages

Convert technical errors to user-friendly messages:
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.';
}

Structured Error Logging

Log errors with context for debugging:
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 Error Handling

Failed Webhook Delivery

When webhook delivery fails, fall back to polling:
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 Handler Errors

Handle errors in your webhook endpoint gracefully:
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);
  }
});

Recovery Strategies

Context Recovery

If context expires, start a new conversation:
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;
  }
}

Partial Success Handling

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"
    }
  ]
}
Handle partial success:
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;
}

Best Practices

  1. Categorize errors - Different errors need different handling
  2. Implement retries - Use exponential backoff for transient errors
  3. Respect rate limits - Honor retry_after values
  4. Log with context - Include relevant IDs for debugging
  5. User-friendly messages - Convert technical errors for users
  6. Fallback strategies - Always have a backup (e.g., polling for webhooks)
  7. Don’t retry permanent errors - Validation errors need code fixes
  8. Handle partial success - Process warnings in successful responses

Next Steps

  • Task Lifecycle: See Task Lifecycle for status handling
  • Webhooks: See Webhooks for webhook error handling
  • Security: See Security for authentication errors