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