Best Practices
Tips for building robust integrations with the telco.dev API
Follow these best practices to build reliable, efficient integrations with the telco.dev API.
Authentication
Store API Keys Securely
Never hardcode API keys in your source code. Use environment variables or a secrets manager.
// Good
const apiKey = process.env.TELCO_API_KEY;
// Bad - never do this
const apiKey = "tk_live_abc123...";
Use Separate Keys for Environments
Create different API keys for development, staging, and production. This helps with:
- Tracking usage per environment
- Revoking compromised keys without affecting production
- Different rate limits if needed
Performance
Cache Responses
Phone number data changes infrequently. Cache lookups to reduce API calls:
const cache = new Map();
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
async function lookupWithCache(tn) {
const cached = cache.get(tn);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const data = await lookupNumber(tn);
cache.set(tn, { data, timestamp: Date.now() });
return data;
}
Batch Lookups When Possible
If you need to look up multiple numbers, consider spacing requests to stay within rate limits:
async function lookupBatch(numbers) {
const results = [];
for (const tn of numbers) {
const result = await lookupNumber(tn);
results.push(result);
// Small delay to respect rate limits
await new Promise(r => setTimeout(r, 200));
}
return results;
}
Use Appropriate Page Sizes
For paginated endpoints, use larger page sizes (up to 100) to reduce the number of requests:
// Fewer API calls
const results = await fetch(url + "?limit=100");
// More API calls (avoid)
const results = await fetch(url + "?limit=10");
Error Handling
Always Handle Errors
Check response status and handle errors appropriately:
async function safeLookup(tn) {
try {
const response = await fetch(`${API_URL}/v1/lookup/${tn}`, {
headers: { "X-API-Key": apiKey }
});
if (!response.ok) {
const error = await response.json();
if (response.status === 404) {
return { found: false, data: null };
}
throw new Error(error.message);
}
return { found: true, data: await response.json() };
} catch (error) {
console.error("Lookup failed:", error);
throw error;
}
}
Implement Retry Logic
Add retries for transient failures:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
// Rate limited - wait and retry
const retryAfter = parseInt(response.headers.get("Retry-After") || "60");
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
if (response.status >= 500) {
// Server error - retry with backoff
await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
continue;
}
return response;
} catch (error) {
if (attempt === maxRetries) throw error;
await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
}
}
}
Rate Limiting
Monitor Your Usage
Check rate limit headers on every response:
function checkRateLimits(response) {
const remaining = parseInt(response.headers.get("X-RateLimit-Remaining"));
const minuteRemaining = parseInt(response.headers.get("X-RateLimit-Minute-Remaining"));
if (remaining < 100) {
console.warn(`Low daily quota: ${remaining} requests remaining`);
}
if (minuteRemaining < 2) {
console.warn(`Near minute limit: ${minuteRemaining} remaining`);
}
}
Implement Backoff
When you hit rate limits, wait before retrying:
async function handleRateLimit(response) {
const data = await response.json();
const resetTime = data.details?.reset || (Date.now() / 1000 + 60);
const waitMs = Math.max(0, resetTime * 1000 - Date.now());
console.log(`Rate limited. Waiting ${Math.ceil(waitMs / 1000)}s...`);
await new Promise(r => setTimeout(r, waitMs));
}
Data Quality
Validate Input
Always validate phone numbers before making API calls:
function isValidTN(tn) {
// Remove non-digits
const digits = tn.replace(/\D/g, "");
// Must be exactly 10 digits
if (digits.length !== 10) return false;
// NPA can't start with 0 or 1
if (digits[0] === "0" || digits[0] === "1") return false;
// NXX can't start with 0 or 1
if (digits[3] === "0" || digits[3] === "1") return false;
return true;
}
Handle Missing Data
Some fields may be null. Always check before using:
const result = await lookupNumber(tn);
const carrierName = result.carrier?.name || "Unknown Carrier";
const carrierType = result.carrier?.type || "Unknown";
const rateCenter = result.location?.rate_center || "Unknown";
Security
Don't Expose API Keys
Never include API keys in client-side code, URLs, or logs:
// Good - use server-side proxy
app.get("/api/lookup/:tn", async (req, res) => {
const result = await lookupNumber(req.params.tn);
res.json(result);
});
// Bad - exposed in browser
fetch(`https://api.telco.dev/v1/lookup/${tn}?api_key=${key}`);
Validate User Input
Sanitize any user-provided data before using it in API calls:
app.get("/api/lookup/:tn", async (req, res) => {
const tn = req.params.tn.replace(/\D/g, "");
if (!isValidTN(tn)) {
return res.status(400).json({ error: "Invalid phone number" });
}
const result = await lookupNumber(tn);
res.json(result);
});