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.