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);
});