Documentation Index
Fetch the complete documentation index at: https://docs.pixelpatrol.net/llms.txt
Use this file to discover all available pages before exploring further.
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:- Navigate to your site settings
- Copy your site API key (format:
site_xxxxxxxxxxxxxxxxxxxx) - 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;
}
}