Skip to main content

Overview

This guide walks you through integrating Pixel Patrol’s API into your application, from initial setup to advanced usage patterns.

Getting Started

1. Obtain Your API Credentials

After creating a site in your Pixel Patrol dashboard:
  1. Navigate to your site settings
  2. Copy your site API key (format: site_xxxxxxxxxxxxxxxxxxxx)
  3. Store it securely in your environment variables
# .env file
PIXELPATROL_API_KEY=site_xxxxxxxxxxxxxxxxxxxx
PIXELPATROL_API_URL=https://your-supabase-url.supabase.co/functions/v1

2. Basic API Client Setup

class PixelPatrolClient {
  constructor(apiKey, baseUrl) {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl || process.env.PIXELPATROL_API_URL;
  }

  async submitMedia(data) {
    const response = await fetch(`${this.baseUrl}/submit-media`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        api_key: this.apiKey,
        ...data
      })
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error || `HTTP ${response.status}`);
    }

    return response.json();
  }

  async getMediaStatus(mediaId) {
    const response = await fetch(`${this.baseUrl}/get-media-status`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        api_key: this.apiKey,
        media_id: mediaId
      })
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error || `HTTP ${response.status}`);
    }

    return response.json();
  }
}

// Initialize client
const pixelPatrol = new PixelPatrolClient(process.env.PIXELPATROL_API_KEY);

Common Integration Patterns

1. User-Generated Content (UGC)

For platforms with user uploads:
// When user uploads content
async function handleUserUpload(userId, imageUrl, context) {
  try {
    // Submit for moderation
    const result = await pixelPatrol.submitMedia({
      content_url: imageUrl,
      app_media_id: `user-${userId}-${Date.now()}`,
      submitted_by: userId,
      metadata: {
        context: context, // 'profile_photo', 'post_image', etc.
        upload_timestamp: new Date().toISOString()
      }
    });

    // Store media ID for tracking
    await database.save({
      userId,
      mediaId: result.id,
      pixelPatrolId: result.id,
      status: 'pending_moderation'
    });

    return {
      success: true,
      message: 'Content submitted for review',
      mediaId: result.id
    };
  } catch (error) {
    console.error('Moderation submission failed:', error);
    // Decide whether to block upload or allow with manual review
    return {
      success: false,
      error: 'Moderation service unavailable'
    };
  }
}

2. Text Content Moderation

For comments, posts, or messages:
async function moderateTextContent(text, userId, context) {
  try {
    const result = await pixelPatrol.submitMedia({
      body: text,
      app_media_id: `text-${context}-${Date.now()}`,
      submitted_by: userId,
      metadata: {
        content_type: 'text',
        context: context, // 'comment', 'post', 'message'
        character_count: text.length
      }
    });

    // For text, you might want to wait for quick moderation
    if (context === 'comment') {
      // Poll for quick result (with timeout)
      const status = await waitForModeration(result.id, 5000); // 5 second timeout
      
      if (status.action === 'block') {
        return {
          allowed: false,
          reason: 'Content violates community guidelines'
        };
      }
    }

    return {
      allowed: true,
      mediaId: result.id
    };
  } catch (error) {
    console.error('Text moderation failed:', error);
    // Fallback strategy
    return {
      allowed: true, // or false, depending on your policy
      requiresReview: true
    };
  }
}

3. Batch Processing

For bulk content imports or migrations:
async function processBatchContent(items) {
  const results = [];
  const batchSize = 10; // Process in batches to avoid rate limits
  
  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    
    // Submit batch concurrently
    const promises = batch.map(item => 
      pixelPatrol.submitMedia({
        content_url: item.url,
        app_media_id: item.id,
        metadata: item.metadata
      }).catch(error => ({
        error: error.message,
        item: item
      }))
    );
    
    const batchResults = await Promise.all(promises);
    results.push(...batchResults);
    
    // Rate limiting - wait between batches
    if (i + batchSize < items.length) {
      await new Promise(resolve => setTimeout(resolve, 1000)); // 1 second delay
    }
  }
  
  return results;
}

Webhook Integration

Setting Up Webhook Handler

import crypto from 'crypto';

// Webhook handler
async function handlePixelPatrolWebhook(req, res) {
  // Verify signature
  const signature = req.headers['x-webhook-signature'];
  const payload = JSON.stringify(req.body);
  
  const expectedSignature = 'sha256=' + crypto
    .createHmac('sha256', process.env.PIXELPATROL_WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');
  
  if (signature !== expectedSignature) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Process event
  const event = req.body;
  
  switch (event.event) {
    case 'media.moderated':
      await handleModerationComplete(event.data);
      break;
    
    case 'media.created':
      await handleMediaCreated(event.data);
      break;
    
    default:
      console.log('Unhandled event type:', event.event);
  }
  
  // Always respond quickly
  res.status(200).json({ received: true });
}

// Handle moderation completion
async function handleModerationComplete(data) {
  const { media_id, status, ai_results, applied_rules } = data;
  
  // Update your database
  await database.updateMedia({
    pixelPatrolId: media_id,
    moderationStatus: status,
    aiResults: ai_results,
    appliedRules: applied_rules,
    moderatedAt: new Date()
  });
  
  // Take action based on status
  if (status === 'rejected' || status === 'flagged') {
    await handleRejectedContent(media_id);
  } else if (status === 'approved') {
    await publishContent(media_id);
  }
}

Error Handling

Comprehensive Error Handling

class PixelPatrolError extends Error {
  constructor(message, code, details) {
    super(message);
    this.code = code;
    this.details = details;
  }
}

async function submitWithErrorHandling(data) {
  try {
    return await pixelPatrol.submitMedia(data);
  } catch (error) {
    // Parse error response
    if (error.message.includes('Invalid API Key')) {
      throw new PixelPatrolError(
        'Authentication failed',
        'AUTH_FAILED',
        { originalError: error.message }
      );
    }
    
    if (error.message.includes('Moderation limit exceeded')) {
      throw new PixelPatrolError(
        'Moderation limit reached',
        'LIMIT_EXCEEDED',
        { 
          type: 'moderation_limit',
          suggestion: 'Upgrade your plan or wait for limit reset'
        }
      );
    }
    
    if (error.message.includes('Team has no active subscription')) {
      throw new PixelPatrolError(
        'Subscription required',
        'NO_SUBSCRIPTION',
        { suggestion: 'Please activate a subscription' }
      );
    }
    
    // Generic error
    throw new PixelPatrolError(
      'Moderation service error',
      'SERVICE_ERROR',
      { originalError: error.message }
    );
  }
}

Retry Logic

async function submitWithRetry(data, maxRetries = 3) {
  let lastError;
  
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await pixelPatrol.submitMedia(data);
    } catch (error) {
      lastError = error;
      
      // Don't retry on client errors
      if (error.message.includes('Invalid') || 
          error.message.includes('limit exceeded')) {
        throw error;
      }
      
      // Exponential backoff
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError;
}

Performance Optimization

Caching Moderation Results

const moderationCache = new Map();

async function getCachedModeration(contentHash) {
  // Check cache first
  const cached = moderationCache.get(contentHash);
  if (cached && cached.timestamp > Date.now() - 3600000) { // 1 hour cache
    return cached.result;
  }
  
  // If not cached, submit for moderation
  const result = await pixelPatrol.submitMedia({
    content_url: contentUrl,
    app_media_id: contentHash,
    metadata: { 
      content_hash: contentHash 
    }
  });
  
  // Cache the result
  moderationCache.set(contentHash, {
    result: result,
    timestamp: Date.now()
  });
  
  return result;
}

Connection Pooling

// Use connection pooling for better performance
import { Agent } from 'https';

const httpsAgent = new Agent({
  keepAlive: true,
  maxSockets: 10
});

// Use with fetch
const response = await fetch(url, {
  agent: httpsAgent,
  // ... other options
});

Testing

Unit Testing

import { jest } from '@jest/globals';

// Mock the API client
jest.mock('./pixelPatrolClient');

describe('Content Moderation', () => {
  it('should handle approved content', async () => {
    // Mock successful moderation
    pixelPatrol.submitMedia.mockResolvedValue({
      id: 'media_123',
      status: 'pending'
    });
    
    const result = await handleUserUpload('user123', 'https://example.com/image.jpg', 'profile');
    
    expect(result.success).toBe(true);
    expect(result.mediaId).toBe('media_123');
  });
  
  it('should handle rate limits gracefully', async () => {
    // Mock rate limit error
    pixelPatrol.submitMedia.mockRejectedValue(
      new Error('Moderation limit exceeded')
    );
    
    const result = await handleUserUpload('user123', 'https://example.com/image.jpg', 'profile');
    
    expect(result.success).toBe(false);
    expect(result.error).toContain('limit');
  });
});

Integration Testing

// Test webhook handling
describe('Webhook Handler', () => {
  it('should verify webhook signatures', async () => {
    const payload = {
      event: 'media.moderated',
      data: { media_id: 'test_123' }
    };
    
    const signature = crypto
      .createHmac('sha256', 'test_secret')
      .update(JSON.stringify(payload))
      .digest('hex');
    
    const req = {
      headers: { 'x-pixelpatrol-signature': signature },
      body: payload
    };
    
    const res = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn()
    };
    
    await handlePixelPatrolWebhook(req, res);
    
    expect(res.status).toHaveBeenCalledWith(200);
  });
});

Monitoring & Logging

Structured Logging

import winston from 'winston';

const logger = winston.createLogger({
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'pixelpatrol.log' })
  ]
});

// Log all API interactions
async function submitMediaWithLogging(data) {
  const startTime = Date.now();
  
  try {
    const result = await pixelPatrol.submitMedia(data);
    
    logger.info('Media submitted successfully', {
      mediaId: result.id,
      appMediaId: data.app_media_id,
      duration: Date.now() - startTime
    });
    
    return result;
  } catch (error) {
    logger.error('Media submission failed', {
      error: error.message,
      appMediaId: data.app_media_id,
      duration: Date.now() - startTime
    });
    
    throw error;
  }
}

Next Steps