Best Practices

Recommended patterns and practices for integrating StreetVerify APIs

Follow these best practices to build robust, efficient integrations with StreetVerify APIs.

API Integration

1. Use Environment Variables

Never hardcode API keys in your source code:

// ❌ Bad
const apiKey = 'sv_live_abc123...';

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

2. Implement Proper Error Handling

Always handle both network and API errors:

class StreetVerifyService {
  async verifyAddress(address) {
    try {
      const response = await fetch(
        `${this.baseUrl}/verify-address?address=${encodeURIComponent(address)}`,
        { headers: this.headers }
      );
      
      if (!response.ok) {
        // Handle API errors
        const error = await response.json();
        throw new APIError(error.error, response.status);
      }
      
      return await response.json();
    } catch (error) {
      if (error instanceof APIError) {
        // Handle known API errors
        this.handleAPIError(error);
      } else {
        // Handle network errors
        this.handleNetworkError(error);
      }
      throw error;
    }
  }
}

3. Validate Input Data

Validate data before sending to the API:

function validateCoordinates(lat, lng) {
  const errors = [];
  
  if (typeof lat !== 'number' || lat < -90 || lat > 90) {
    errors.push('Latitude must be between -90 and 90');
  }
  
  if (typeof lng !== 'number' || lng < -180 || lng > 180) {
    errors.push('Longitude must be between -180 and 180');
  }
  
  if (errors.length > 0) {
    throw new ValidationError(errors);
  }
}

Performance Optimization

1. Implement Caching

Cache frequently accessed data to reduce API calls:

class CachedGeocoder {
  constructor(apiKey, cacheOptions = {}) {
    this.geocoder = new StreetVerifyGeocoder(apiKey);
    this.cache = new Map();
    this.maxCacheSize = cacheOptions.maxSize || 1000;
    this.ttl = cacheOptions.ttl || 3600000; // 1 hour
  }
  
  async geocode(address) {
    const cacheKey = this.getCacheKey(address);
    const cached = this.cache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.data;
    }
    
    const result = await this.geocoder.geocode(address);
    this.setCached(cacheKey, result);
    
    return result;
  }
  
  getCacheKey(address) {
    return address.toLowerCase().replace(/\s+/g, ' ').trim();
  }
  
  setCached(key, data) {
    if (this.cache.size >= this.maxCacheSize) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    
    this.cache.set(key, {
      data,
      timestamp: Date.now()
    });
  }
}

2. Batch Requests

Process multiple items efficiently:

async function batchGeocode(addresses, options = {}) {
  const {
    batchSize = 10,
    delayMs = 100,
    onProgress
  } = options;
  
  const results = [];
  const batches = chunk(addresses, batchSize);
  
  for (const [index, batch] of batches.entries()) {
    const batchPromises = batch.map(address => 
      geocoder.geocode(address).catch(err => ({
        address,
        error: err.message
      }))
    );
    
    const batchResults = await Promise.all(batchPromises);
    results.push(...batchResults);
    
    if (onProgress) {
      onProgress({
        completed: results.length,
        total: addresses.length,
        percentage: (results.length / addresses.length) * 100
      });
    }
    
    // Rate limiting delay between batches
    if (index < batches.length - 1) {
      await delay(delayMs);
    }
  }
  
  return results;
}

3. Use Connection Pooling

For server applications, reuse HTTP connections:

const https = require('https');
const agent = new https.Agent({
  keepAlive: true,
  maxSockets: 10
});

const fetchWithPool = (url, options) => {
  return fetch(url, {
    ...options,
    agent
  });
};

Data Quality

1. Standardize Input

Normalize addresses before verification:

function normalizeAddress(address) {
  return address
    .trim()
    .replace(/\s+/g, ' ')           // Multiple spaces to single
    .replace(/[^\w\s,.-]/g, '')     // Remove special characters
    .replace(/\b(\w)/g, m => m.toUpperCase()); // Title case
}

2. Handle Ambiguous Results

When addresses can’t be fully verified:

async function verifyWithFallback(address) {
  const result = await verifier.verify(address);
  
  if (!result.data.verified) {
    // Check confidence score
    if (result.data.confidence > 0.8) {
      // High confidence - suggest correction
      return {
        status: 'needs_correction',
        suggested: result.data.formatted_address,
        confidence: result.data.confidence
      };
    } else if (result.data.confidence > 0.5) {
      // Medium confidence - require confirmation
      return {
        status: 'needs_confirmation',
        options: [result.data.formatted_address],
        original: address
      };
    } else {
      // Low confidence - manual entry required
      return {
        status: 'manual_review',
        original: address,
        message: 'Address could not be verified'
      };
    }
  }
  
  return {
    status: 'valid',
    standardized: result.data.formatted_address
  };
}

3. Validate Business Rules

Implement domain-specific validation:

function validateShippingAddress(verificationResult) {
  const { data } = verificationResult;
  const errors = [];
  
  // Check if address is verified
  if (!data.verified) {
    errors.push('Address could not be verified');
  }
  
  // Check validation details
  const details = data.validation_details || {};
  
  // Check if residential (when required)
  if (details.residential_delivery_indicator === 'Y' && config.commercialOnly) {
    errors.push('Commercial addresses only');
  }
  
  // Check if business (when required)
  if (details.business_address && config.residentialOnly) {
    errors.push('Residential addresses only');
  }
  
  return {
    valid: errors.length === 0,
    errors
  };
}

Security

1. Secure API Keys

// Server-side: Use environment variables
const apiKey = process.env.STREETVERIFY_API_KEY;

// Client-side: Use proxy endpoint
// Never expose API keys in client-side code
async function verifyAddress(address) {
  // Call your backend proxy
  const response = await fetch('/api/proxy/verify-address', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${userToken}` // User auth, not API key
    },
    body: JSON.stringify({ address })
  });
  
  return response.json();
}

2. Implement Request Signing

For additional security, implement request signing:

const crypto = require('crypto');

function signRequest(payload, secret) {
  const timestamp = Date.now();
  const message = `${timestamp}.${JSON.stringify(payload)}`;
  const signature = crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');
    
  return {
    'X-Signature': signature,
    'X-Timestamp': timestamp
  };
}

3. Rate Limit Your Own Users

Implement application-level rate limiting:

const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 1 * 60 * 1000, // 1 minute
  max: 30, // Limit each user to 30 requests per minute
  message: 'Too many requests from this user',
  standardHeaders: true,
  legacyHeaders: false,
});

app.use('/api/address-verification', apiLimiter);

Monitoring & Logging

1. Log API Interactions

class APILogger {
  logRequest(endpoint, params, apiKey) {
    console.log({
      timestamp: new Date().toISOString(),
      type: 'api_request',
      endpoint,
      params,
      apiKey: this.maskApiKey(apiKey)
    });
  }
  
  logResponse(endpoint, status, responseTime) {
    console.log({
      timestamp: new Date().toISOString(),
      type: 'api_response',
      endpoint,
      status,
      responseTime,
      success: status === 200
    });
  }
  
  logError(endpoint, error) {
    console.error({
      timestamp: new Date().toISOString(),
      type: 'api_error',
      endpoint,
      error: error.message,
      stack: error.stack
    });
  }
  
  maskApiKey(apiKey) {
    return apiKey.substring(0, 8) + '...' + apiKey.substring(apiKey.length - 4);
  }
}

2. Track Performance Metrics

class PerformanceMonitor {
  constructor() {
    this.metrics = [];
  }
  
  async trackAPICall(fn, endpoint) {
    const start = Date.now();
    
    try {
      const result = await fn();
      const duration = Date.now() - start;
      
      this.recordMetric(endpoint, duration, true);
      
      return result;
    } catch (error) {
      const duration = Date.now() - start;
      this.recordMetric(endpoint, duration, false);
      throw error;
    }
  }
  
  recordMetric(endpoint, duration, success) {
    this.metrics.push({
      endpoint,
      duration,
      success,
      timestamp: Date.now()
    });
    
    // Alert on slow responses
    if (duration > 1000) {
      console.warn(`Slow API response: ${endpoint} took ${duration}ms`);
    }
  }
  
  getStats(endpoint) {
    const endpointMetrics = this.metrics.filter(m => m.endpoint === endpoint);
    const successful = endpointMetrics.filter(m => m.success);
    
    return {
      totalCalls: endpointMetrics.length,
      successRate: (successful.length / endpointMetrics.length) * 100,
      avgResponseTime: endpointMetrics.reduce((sum, m) => sum + m.duration, 0) / endpointMetrics.length,
      p95ResponseTime: this.percentile(endpointMetrics.map(m => m.duration), 0.95)
    };
  }
}

Testing

1. Use Test API Keys

const client = new StreetVerifyClient({
  apiKey: process.env.STREETVERIFY_TEST_KEY
});

// Use realistic test addresses
const testAddresses = {
  valid: '1600 Amphitheatre Parkway, Mountain View, CA',
  invalid: '999 Fake Street, Nowhere, XX 00000',
  needsCorrection: '123 Mian St, Anytown, USA' // Typo in "Main"
};

2. Mock API Responses

// For unit tests
class MockStreetVerifyClient {
  constructor() {
    this.responses = new Map();
  }
  
  setMockResponse(address, response) {
    this.responses.set(this.normalizeAddress(address), response);
  }
  
  async verify(address) {
    const normalized = this.normalizeAddress(address);
    const response = this.responses.get(normalized);
    
    if (!response) {
      throw new Error(`No mock response for: ${address}`);
    }
    
    return response;
  }
  
  normalizeAddress(address) {
    return address.toLowerCase().trim();
  }
}

Integration Patterns

For different use cases:

// E-commerce checkout validation
async function validateCheckoutAddress(address) {
  try {
    const result = await streetverify.verify(address);
    
    if (result.data.verified) {
      // Use the standardized address
      return {
        success: true,
        address: result.data.formatted_address,
        components: result.data.components
      };
    } else {
      // Address couldn't be verified
      return {
        success: false,
        message: 'Please check your address and try again'
      };
    }
  } catch (error) {
    console.error('Address verification error:', error);
    // Fallback - allow order to proceed but flag for review
    return {
      success: true,
      needsReview: true,
      address: address
    };
  }
}

Summary

Following these best practices will help you:

  • Build reliable, performant integrations
  • Handle errors gracefully
  • Optimize API usage and costs
  • Maintain data quality
  • Ensure security and compliance

For more specific guidance, refer to our API Reference or contact support@streetverify.com.