Skip to main content

Authentication & Security

// ✅ Good
const apiKey = process.env.INSTAVIEW_API_KEY;

// ❌ Bad
const apiKey = 'sk_live_abc123'; // Hardcoded!
  • Production: sk_live_xxx
  • Staging: sk_test_xxx
  • Development: sk_test_yyy
// ✅ Good: Only what's needed
scopes: ['read:jobs', 'read:candidates']

// ❌ Bad: Unnecessary permissions
scopes: ['read:*', 'write:*', 'delete:*']
  • Rotate high-privilege keys every 90 days
  • Implement graceful key rotation with fallback
  • Monitor key usage after rotation

Error Handling

Comprehensive Error Handler

class InstaViewAPI {
  async request(method, path, data) {
    try {
      const response = await fetch(`${this.baseURL}${path}`, {
        method,
        headers: this.headers,
        body: data ? JSON.stringify(data) : null
      });
      
      const result = await response.json();
      
      if (!result.success) {
        throw this.createError(result.error, response.status);
      }
      
      return result.data;
      
    } catch (error) {
      if (error.name === 'FetchError') {
        // Network error - retry
        return this.retryRequest(method, path, data);
      }
      throw error;
    }
  }
  
  createError(errorData, statusCode) {
    const error = new Error(errorData.message);
    error.code = errorData.code;
    error.details = errorData.details;
    error.statusCode = statusCode;
    return error;
  }
}

Rate Limiting

Request Queue

class RateLimitedQueue {
  constructor(requestsPerSecond = 5) {
    this.queue = [];
    this.processing = false;
    this.interval = 1000 / requestsPerSecond;
  }
  
  async add(fn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ fn, resolve, reject });
      if (!this.processing) {
        this.process();
      }
    });
  }
  
  async process() {
    this.processing = true;
    
    while (this.queue.length > 0) {
      const { fn, resolve, reject } = this.queue.shift();
      
      try {
        const result = await fn();
        resolve(result);
      } catch (error) {
        reject(error);
      }
      
      if (this.queue.length > 0) {
        await this.sleep(this.interval);
      }
    }
    
    this.processing = false;
  }
  
  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Usage
const queue = new RateLimitedQueue(5); // 5 requests/second

await queue.add(() => createJob(jobData));
await queue.add(() => createCandidate(candidateData));

Pagination

Efficient Iteration

async function* paginateAll(endpoint) {
  let page = 1;
  let hasMore = true;
  
  while (hasMore) {
    const response = await fetch(
      `${endpoint}?page=${page}&limit=100`,
      { headers: { 'Authorization': `Bearer ${apiKey}` } }
    );
    
    const result = await response.json();
    yield* result.data.items;
    
    hasMore = result.data.pagination.hasNextPage;
    page++;
  }
}

// Process items as they arrive
for await (const candidate of paginateAll('/candidates')) {
  await processCandidate(candidate);
}

Data Validation

Pre-Request Validation

function validateCandidateData(data) {
  const errors = [];
  
  if (!data.firstName || data.firstName.length < 1) {
    errors.push('firstName is required');
  }
  
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
    errors.push('Invalid email format');
  }
  
  if (!/^\+\d{10,15}$/.test(data.phoneNumber)) {
    errors.push('Phone must be in E.164 format (+1234567890)');
  }
  
  if (errors.length > 0) {
    throw new ValidationError(errors);
  }
}

// Use before API call
try {
  validateCandidateData(candidateData);
  await createCandidate(candidateData);
} catch (error) {
  if (error instanceof ValidationError) {
    showUserErrors(error.errors);
  }
}

Caching

Smart Caching Strategy

class CachedAPI {
  constructor(apiKey) {
    this.cache = new Map();
    this.ttl = {
      jobs: 300000,      // 5 minutes
      candidates: 60000,  // 1 minute
      interviews: 30000   // 30 seconds
    };
  }
  
  async get(resource, id) {
    const cacheKey = `${resource}:${id}`;
    const cached = this.cache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < this.ttl[resource]) {
      return cached.data;
    }
    
    const data = await this.fetchFromAPI(resource, id);
    this.cache.set(cacheKey, {
      data,
      timestamp: Date.now()
    });
    
    return data;
  }
  
  invalidate(resource, id) {
    this.cache.delete(`${resource}:${id}`);
  }
}

Monitoring & Logging

Structured Logging

class APILogger {
  static log(level, message, context = {}) {
    const log = {
      timestamp: new Date().toISOString(),
      level,
      message,
      ...context,
      service: 'instaview-api'
    };
    
    console.log(JSON.stringify(log));
    
    // Send to monitoring service
    if (process.env.NODE_ENV === 'production') {
      monitoring.track(log);
    }
  }
  
  static info(message, context) {
    this.log('info', message, context);
  }
  
  static error(message, error, context) {
    this.log('error', message, {
      ...context,
      error: {
        message: error.message,
        code: error.code,
        stack: error.stack
      }
    });
  }
}

// Usage
APILogger.info('Creating candidate', {
  jobId: 'job-uuid',
  candidateEmail: '[email protected]'
});

Testing

Integration Tests

describe('InstaView API Integration', () => {
  let testJobId;
  let testCandidateId;
  
  beforeAll(async () => {
    // Create test job
    const job = await createJob({
      title: 'Test Job',
      status: 'OPEN'
    });
    testJobId = job.id;
  });
  
  afterAll(async () => {
    // Cleanup
    await deleteJob(testJobId);
  });
  
  test('should create and retrieve candidate', async () => {
    // Create candidate
    const candidate = await createCandidate({
      jobId: testJobId,
      firstName: 'Test',
      lastName: 'User',
      email: '[email protected]',
      phoneNumber: '+1234567890'
    });
    
    testCandidateId = candidate.id;
    expect(candidate.firstName).toBe('Test');
    
    // Retrieve candidate
    const retrieved = await getCandidate(testCandidateId);
    expect(retrieved.id).toBe(testCandidateId);
    expect(retrieved.email).toBe('[email protected]');
  });
});

Performance

Batch Operations

Group related API calls together

Parallel Requests

Use Promise.all() for independent calls

Connection Pooling

Reuse HTTP connections

Compression

Enable gzip compression

Deployment Checklist

1

Environment Variables

✓ API keys in environment variables
✓ Different keys for each environment
✓ Secrets not in version control
2

Error Handling

✓ Comprehensive error handling
✓ Retry logic with exponential backoff
✓ Circuit breakers for failures
3

Monitoring

✓ Logging for all API calls
✓ Error tracking (Sentry, etc.)
✓ Performance monitoring
✓ Alert on rate limit hits
4

Security

✓ API key rotation schedule
✓ IP allowlists configured
✓ HTTPS only
✓ Webhook signature verification

Next Steps