Developer Guide

Node.js integration guide

A minimal Telebase client in Node.js needs nothing beyond the built-in fetch: URL-encode the phone number, send it with a Bearer token, and parse the JSON response. This page covers that function, the three error responses worth handling explicitly, and a simple retry pattern for transient network failures.

A minimal client

Use encodeURIComponent to URL-encode the phone number before building the request URL. The + in an E.164 number must become %2B; passing a raw + in a query string is interpreted as a space and will produce a failed or incorrect lookup. encodeURIComponent handles this correctly.

JavaScript
const API_KEY = "tb_live_xxxxxxxxxxxxxxxxxxxxxxxx";
const BASE_URL = "https://telebase.fatcatremote.com/api/lookup";

async function lookup(phoneNumber) {
  const encoded = encodeURIComponent(phoneNumber);
  const response = await fetch(`${BASE_URL}?phone=${encoded}`, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  if (!response.ok) {
    throw new Error(`Lookup failed with status ${response.status}`);
  }
  return response.json();
}

const result = await lookup("+447700900000");
console.log(result.carrier, result.numberType, result.simSwap);

Handling errors explicitly

fetch does not throw on a non-200 response, it resolves normally, so checking response.ok and branching on response.status is the right place to distinguish the errors your onboarding or authentication flow needs to react to differently.

JavaScript
async function lookup(phoneNumber) {
  const encoded = encodeURIComponent(phoneNumber);
  const response = await fetch(`${BASE_URL}?phone=${encoded}`, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });

  if (response.status === 401) {
    throw new Error("Missing or malformed API key");
  }
  if (response.status === 400) {
    throw new Error(`Invalid phone number: ${phoneNumber}`);
  }
  if (response.status === 402) {
    throw new Error("Account balance is at zero, top up via the dashboard");
  }
  if (!response.ok) {
    throw new Error(`Unexpected status ${response.status}`);
  }
  return response.json();
}

What each status means

A retry pattern for transient failures

401, 400 and 402 will not resolve themselves on retry, since each reflects something about the request or account that needs fixing first. Reserve retries for genuinely transient failures: network errors and 5xx responses from the server. A short exponential backoff with a small number of attempts is enough for most integrations.

JavaScript
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function lookupWithRetry(phoneNumber, maxAttempts = 3) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await lookup(phoneNumber);
    } catch (err) {
      const isLastAttempt = attempt === maxAttempts - 1;
      const isTransient = err.message.includes("Unexpected status 5") || err.name === "TypeError";
      if (!isTransient || isLastAttempt) throw err;
      await sleep(2 ** attempt * 1000); // 1s, 2s, 4s
    }
  }
}

Do not retry on 401, 400 or 402: fix the API key, the phone number format, or the account balance instead.

Signals available today

Live in every response

SIM swap detection is launching. Early access is open now.

The simSwap field is present in every response today and returns UNKNOWN in GB, DE, NL and FR while carrier registration completes. The Node.js client above needs no changes when it goes live for your market. See the SIM swap detection API.

New to the API entirely? Start with the quickstart guide for the first request end to end.

$0.03 per query. No contract. No minimum spend. Billed via Paddle.
Request early access