Claude Code Plugins

Community-maintained marketplace

Feedback

api-integration-debugging-skill

@Eibon7/roastr-ai
0
0

Use when debugging social media API integrations - captures request/response cycles, identifies rate limit vs auth vs data errors, applies platform-specific quirks from integration patterns

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name api-integration-debugging-skill
description Use when debugging social media API integrations - captures request/response cycles, identifies rate limit vs auth vs data errors, applies platform-specific quirks from integration patterns
triggers API error, rate limit, authentication failed, invalid token, unexpected response, integration test, 429, 401, 403
used_by test-engineer, back-end-dev, integration-specialist, orchestrator
steps [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object], [object Object]
output - Error classified with platform context - Defensive code added (retry logic, better error messages) - Integration test covering edge case - Pattern documented if recurring

API Integration Debugging Skill

Purpose

Systematic debugging for social media API integrations (10+ platforms). Reduces debugging time by 80% through structured error classification and platform-specific quirk application.

When to Use

Triggers:

  • Any API error from social platforms
  • Rate limit errors (429)
  • Authentication failures (401, 403)
  • Unexpected/empty responses
  • Integration test failures
  • Timeout errors

Platforms: Twitter, YouTube, Instagram, Facebook, Discord, Twitch, Reddit, TikTok, Bluesky

Debugging Process

Step 1: Identify Platform & Endpoint

Identify:

// Example error location
src/integrations/twitter/twitterService.js:145
  → Method: fetchMentions()
  → Endpoint: GET /2/users/:id/mentions
  → Platform: Twitter API v2

Check integration docs:

Read: docs/INTEGRATIONS.md
# Find platform section for rate limits, auth requirements

Step 2: Check Auth State

Validation checklist:

  • Token present in environment variables?
  • Token not expired? (check expiry timestamp)
  • OAuth flow completed successfully?
  • Correct scopes/permissions granted?
  • Token format correct for platform?

Commands:

# Check env vars (DO NOT log actual values)
echo "TWITTER_BEARER_TOKEN present: $([ -n "$TWITTER_BEARER_TOKEN" ] && echo 'YES' || echo 'NO')"
echo "YOUTUBE_API_KEY present: $([ -n "$YOUTUBE_API_KEY" ] && echo 'YES' || echo 'NO')"

# Check token expiry (if stored in DB)
node scripts/check-token-expiry.js --platform=twitter --org-id=123

Step 3: Capture Full Request

Add diagnostic logging:

// BEFORE the API call
logger.debug('API Request', {
  platform: 'twitter',
  endpoint: '/2/users/:id/mentions',
  method: 'GET',
  headers: {
    'Authorization': 'Bearer [REDACTED]',  // NEVER log full token
    'Content-Type': req.headers['content-type']
  },
  params: req.params,
  body: req.body,
  timestamp: new Date().toISOString()
});

⚠️ Security:

  • NEVER log full API keys/tokens
  • Log only token prefix: Bearer sk-...abc123Bearer sk-...REDACTED
  • Redact sensitive fields: email, password, phone

Step 4: Capture Full Response

Add response logging:

try {
  const response = await apiCall();

  logger.debug('API Response', {
    platform: 'twitter',
    endpoint: '/2/users/:id/mentions',
    statusCode: response.status,
    headers: {
      'x-rate-limit-remaining': response.headers['x-rate-limit-remaining'],
      'x-rate-limit-reset': response.headers['x-rate-limit-reset'],
      'x-rate-limit-limit': response.headers['x-rate-limit-limit']
    },
    bodySize: JSON.stringify(response.data).length,
    bodyPreview: JSON.stringify(response.data).substring(0, 200),  // First 200 chars
    timestamp: new Date().toISOString()
  });

  return response.data;
} catch (error) {
  logger.error('API Error', {
    platform: 'twitter',
    endpoint: '/2/users/:id/mentions',
    statusCode: error.response?.status,
    errorCode: error.code,
    errorMessage: error.message,
    headers: error.response?.headers,
    body: error.response?.data,
    timestamp: new Date().toISOString()
  });
  throw error;
}

Step 5: Classify Error Type

Classification matrix:

Status Code Type Typical Cause Action
401 AUTH Invalid/expired token Refresh token, check credentials
403 AUTH Insufficient permissions Check OAuth scopes, request access
429 RATE_LIMIT Too many requests Implement backoff, check quota
400 DATA Invalid request format Validate input, check API docs
404 DATA Resource not found Check resource ID, handle gracefully
422 DATA Validation error Fix request payload
500 SERVER Platform issue Retry with backoff
502/503/504 SERVER Platform down/slow Retry with backoff, alert if persistent

Classification function:

function classifyAPIError(error) {
  const status = error.response?.status;

  if ([401, 403].includes(status)) {
    return { type: 'AUTH', retryable: false, severity: 'high' };
  }

  if (status === 429) {
    const resetTime = error.response.headers['x-rate-limit-reset'];
    return {
      type: 'RATE_LIMIT',
      retryable: true,
      retryAfter: resetTime,
      severity: 'medium'
    };
  }

  if (status >= 400 && status < 500) {
    return { type: 'DATA', retryable: false, severity: 'medium' };
  }

  if (status >= 500) {
    return { type: 'SERVER', retryable: true, severity: 'high' };
  }

  return { type: 'UNKNOWN', retryable: false, severity: 'high' };
}

Step 6: Apply Platform-Specific Quirks

Read quirks documentation:

Read: docs/INTEGRATIONS.md  # Platform-specific details
Read: docs/patterns/api-quirks.md  # Known edge cases

Common quirks by platform:

Twitter / X

  • Rate limits: Per user vs app-level (check x-rate-limit-reset)
  • Character limit: 280 chars (enforce before sending)
  • Media uploads: Separate endpoint, chunked for >5MB
  • Error format: data.errors[0].message (NOT top-level)

YouTube

  • Quota units: NOT requests (CommentThreads.list = 1 unit, Videos.list = 1 unit)
  • Daily limit: 10,000 units default (monitor with quotaUser param)
  • OAuth scopes: youtube.force-ssl required for comments

Instagram

  • Long-lived tokens: Expire every 60 days (auto-refresh at 30 days)
  • Graph API: Needs Business Account (NOT personal)
  • Comments: May be empty if user privacy settings hide them
  • Webhooks: Must verify callback URL

Facebook

  • Graph API versions: Change quarterly (pin version in URL)
  • Permissions: pages_read_engagement + pages_manage_posts required
  • Rate limit: No explicit header, uses scoring system

Discord

  • WebSocket: Required for real-time events (NOT REST only)
  • Reconnect logic: Implement resume with sequence number
  • Rate limit: Per-route buckets (different endpoints = different limits)

Twitch

  • Chat vs API: Separate rate limits (IRC for chat, REST for API)
  • OAuth refresh: Tokens expire every 60 days
  • Webhooks: EventSub requires HTTPS endpoint

Reddit

  • User-Agent: REQUIRED or instant 429 (format: platform:appname:version (by /u/username))
  • OAuth: Requires Authorization: bearer + User-Agent
  • Rate limit: 60 requests/minute per OAuth client

Apply quirk example:

// Reddit-specific: User-Agent is MANDATORY
async function redditAPICall(endpoint, options = {}) {
  if (!options.headers) options.headers = {};

  // QUIRK: Reddit returns 429 without User-Agent
  options.headers['User-Agent'] = 'roastr:toxicity-bot:v1.0.0 (by /u/roastr_dev)';

  return await fetch(`https://oauth.reddit.com${endpoint}`, options);
}

Step 7: Implement Defensive Retry Logic

Exponential backoff template:

async function apiCallWithRetry(fn, options = {}) {
  const {
    maxRetries = 3,
    initialDelay = 1000,  // 1 second
    maxDelay = 30000,     // 30 seconds
    backoffFactor = 2,
    retryableStatuses = [429, 500, 502, 503, 504]
  } = options;

  let lastError;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      const status = error.response?.status;

      // Don't retry if not retryable
      if (!retryableStatuses.includes(status)) {
        throw error;
      }

      // Last attempt - don't wait
      if (attempt === maxRetries) {
        break;
      }

      // Calculate delay
      let delay = initialDelay * Math.pow(backoffFactor, attempt - 1);

      // Respect platform's rate limit reset time
      if (status === 429) {
        const resetTime = error.response.headers['x-rate-limit-reset'];
        if (resetTime) {
          delay = Math.max(delay, (resetTime * 1000) - Date.now());
        }
      }

      // Cap at max delay
      delay = Math.min(delay, maxDelay);

      logger.warn(`API call failed, retrying (${attempt}/${maxRetries})`, {
        error: error.message,
        status,
        retryAfter: delay,
        endpoint: error.config?.url
      });

      await sleep(delay);
    }
  }

  throw lastError;
}

// Usage
const mentions = await apiCallWithRetry(() => twitterService.fetchMentions(userId));

Platform-specific backoff:

// Twitter: Respect X-Rate-Limit-Reset header
if (error.response.headers['x-rate-limit-reset']) {
  const resetTimestamp = parseInt(error.response.headers['x-rate-limit-reset']);
  const waitTime = (resetTimestamp * 1000) - Date.now();
  logger.warn(`Twitter rate limit hit, waiting ${waitTime}ms until reset`);
  await sleep(waitTime);
}

// YouTube: Monitor quota usage, stop if exceeded
const quotaUsed = parseInt(error.response.headers['x-goog-api-client']);
if (quotaUsed > 9500) {  // Near daily limit
  logger.error('YouTube quota nearly exhausted, stopping requests');
  throw new Error('QUOTA_EXCEEDED: Stop to preserve quota');
}

Step 8: Add Integration Test

Test template:

// tests/integration/api-errors.test.js
describe('API Error Handling - Twitter', () => {
  it('should retry on 429 rate limit with exponential backoff', async () => {
    // Mock API to return 429 twice, then 200
    let attempts = 0;
    nock('https://api.twitter.com')
      .get('/2/users/123/mentions')
      .times(2)
      .reply(429, { errors: [{ message: 'Rate limit exceeded' }] }, {
        'x-rate-limit-reset': Math.floor(Date.now() / 1000) + 60
      })
      .get('/2/users/123/mentions')
      .reply(200, { data: [{ id: '1', text: 'Test mention' }] });

    const startTime = Date.now();
    const result = await twitterService.fetchMentions('123');
    const elapsed = Date.now() - startTime;

    // Verify retried
    expect(result.data).toHaveLength(1);

    // Verify exponential backoff happened (should take ~3 seconds for 2 retries)
    expect(elapsed).toBeGreaterThan(2000);
  });

  it('should not retry on 401 auth error', async () => {
    nock('https://api.twitter.com')
      .get('/2/users/123/mentions')
      .reply(401, { errors: [{ message: 'Invalid authentication credentials' }] });

    await expect(twitterService.fetchMentions('123'))
      .rejects.toThrow('Invalid authentication credentials');

    // Verify only 1 attempt (no retries)
    expect(nock.isDone()).toBe(true);
  });

  it('should handle empty response gracefully', async () => {
    nock('https://api.twitter.com')
      .get('/2/users/123/mentions')
      .reply(200, { data: [] });  // No mentions

    const result = await twitterService.fetchMentions('123');

    expect(result.data).toEqual([]);
    // Should not throw error
  });
});

Step 9: Document Pattern (If Recurring)

When to document:

  • Same error ≥2 times across different issues
  • Platform-specific quirk not in docs
  • Non-obvious fix that took >30 minutes

Add to docs/patterns/api-quirks.md:

### Reddit - User-Agent Required

**Pattern:** Reddit API returns 429 even with valid credentials

**Root cause:** Missing User-Agent header (mandatory for Reddit)

**Error:**
```json
{ "message": "Too Many Requests", "error": 429 }

Fix:

headers: {
  'User-Agent': 'platform:appname:version (by /u/username)'
}

Occurrences: Issue #680, Issue #712 Last seen: 2025-11-02 Platform: Reddit OAuth API


## Success Criteria

✅ Error classified correctly (AUTH/RATE_LIMIT/DATA/SERVER)
✅ Platform-specific quirks applied
✅ Retry logic implemented with exponential backoff
✅ Integration test added for edge case
✅ Pattern documented if recurring (≥2 occurrences)
✅ Defensive code prevents future occurrences

## Examples

### Example 1: Twitter Rate Limit (429)

**Error:**
```javascript
TwitterAPIError: Rate limit exceeded
  Status: 429
  Headers: { 'x-rate-limit-reset': '1698765432' }

Debugging:

  1. Identify: Twitter API v2, fetchMentions endpoint
  2. Auth: Token valid ✅
  3. Request: GET /2/users/123/mentions
  4. Response: 429 with x-rate-limit-reset
  5. Classify: RATE_LIMIT (retryable)
  6. Quirks: Twitter uses x-rate-limit-reset (Unix timestamp)
  7. Retry: Wait until reset time + exponential backoff
  8. Test: Mock 429 → verify retry → verify success

Result: API call succeeds after waiting for rate limit reset

Example 2: Instagram Empty Data (Not Error)

Symptom:

response.data.comments = undefined  // Expected array

Debugging:

  1. Identify: Instagram Graph API, getComments endpoint
  2. Auth: Token valid ✅
  3. Request: GET /{post_id}/comments
  4. Response: 200 OK but no data.comments field
  5. Classify: DATA (but not error - privacy settings)
  6. Quirks: Instagram hides comments if user privacy settings enabled
  7. Defensive: Add null check, return empty array
  8. Test: Mock empty response → verify graceful handling

Fix:

const comments = response.data?.comments || [];
if (comments.length === 0) {
  logger.info('No comments available (privacy settings or no comments)');
}
return comments;

Example 3: Reddit 429 Despite Valid Token

Error:

RedditAPIError: Too Many Requests
  Status: 429
  No rate limit headers

Debugging:

  1. Identify: Reddit OAuth API
  2. Auth: Token valid ✅
  3. Request: Missing User-Agent header ⚠️
  4. Classify: RATE_LIMIT (but actually AUTH issue)
  5. Quirks: Reddit REQUIRES User-Agent or instant 429
  6. Fix: Add User-Agent header
  7. Test: Mock with/without User-Agent → verify difference

Pattern documented: Added to docs/patterns/api-quirks.md

Error Handling Patterns

Pattern 1: Graceful Degradation

try {
  const data = await apiCall();
  return data;
} catch (error) {
  logger.warn('API failed, using cached data', { error: error.message });
  return getCachedData() || [];
}

Pattern 2: Circuit Breaker

if (consecutiveFailures >= 5) {
  logger.error('Circuit breaker opened - too many failures');
  throw new Error('SERVICE_UNAVAILABLE: Circuit breaker active');
}

Pattern 3: Fallback to Alternative API

try {
  return await perspectiveAPI.analyzeToxicity(text);
} catch (error) {
  logger.warn('Perspective API failed, falling back to OpenAI');
  return await openAI.moderateContent(text);
}

References

  • Platform docs: docs/INTEGRATIONS.md
  • Known quirks: docs/patterns/api-quirks.md
  • Base integration: src/integrations/base/BaseIntegration.js
  • Test examples: tests/integration/api-errors.test.js
  • Retry logic: src/utils/retryHelper.js (if exists)

Related Skills

  • systematic-debugging-skill - General debugging framework
  • root-cause-tracing-skill - Trace errors backward
  • test-generation-skill - Generate integration tests

Reglas de Oro

❌ NEVER

  1. Log full API keys or tokens (use [REDACTED])
  2. Retry auth errors (401, 403) - fix credentials instead
  3. Ignore platform-specific quirks (e.g., User-Agent for Reddit)
  4. Assume error type without checking status code
  5. Retry infinitely (cap at 3-5 attempts)

✅ ALWAYS

  1. Classify error type (AUTH/RATE_LIMIT/DATA/SERVER)
  2. Check platform quirks before debugging
  3. Implement exponential backoff for retries
  4. Add integration test for edge case
  5. Document recurring patterns (≥2 occurrences)
  6. Capture request/response for debugging
  7. Respect rate limit reset times
  8. Handle empty/null responses gracefully