Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Look Up Companies by Domain (in TypeScript)

About 40 lines of TypeScript: read domains, call the API, retry on 429, write one JSON line per company.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

You send a domain to POST /v1/companies/enrich and get one company record back. This page gives you a TypeScript script that does that for a list of domains.

TL;DR

  • It takes about 10 minutes and needs no packages beyond Node's built-in fetch.
  • It spends one LeadOcean record per company returned. A 404 (domain not in our data) is free, so the Free plan's 1,000 records cover 1,000 hits.
  • It retries on 429 using the Retry-After header and writes one JSON line per company.

Prerequisites

  • Node 20 or newer, and tsx to run TypeScript directly (npm i -D tsx typescript @types/node).
  • A LeadOcean API key, exported as $LEADOCEAN_API_KEY. The Free plan gives one key, no card.
  • A file domains.txt with one domain per line, for example acme.com.

Step 1: Send one request

The endpoint takes a flat JSON body with domain or linkedin_url. Auth is the x-api-key header. Test it with curl before writing code.

bash
curl -s https://api.leadocean.io/v1/companies/enrich \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"acme.com"}'

A hit returns 200 with a data object. A miss returns 404.

Step 2: Write the script

Save this as lookup.ts. It reads domains.txt, calls the API once per domain and prints JSON Lines to stdout.

ts
import { readFileSync } from "node:fs";

const API = "https://api.leadocean.io/v1/companies/enrich";
const KEY = process.env.LEADOCEAN_API_KEY;
if (!KEY) throw new Error("Set LEADOCEAN_API_KEY");

const sleep = (s: number) => new Promise((r) => setTimeout(r, s * 1000));

async function lookup(domain: string): Promise<unknown | null> {
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch(API, {
      method: "POST",
      headers: { "x-api-key": KEY!, "Content-Type": "application/json" },
      body: JSON.stringify({ domain }),
    });
    if (res.status === 200) return res.json();
    if (res.status === 404) return null;
    if (res.status === 429) {
      await sleep(Math.max(Number(res.headers.get("retry-after")) || 1, 1));
      continue;
    }
    throw new Error(`${domain}: HTTP ${res.status} ${await res.text()}`);
  }
  throw new Error(`${domain}: still rate limited`);
}

const domains = readFileSync("domains.txt", "utf8")
  .split("\n")
  .map((d) => d.trim())
  .filter(Boolean);

async function main() {
  for (const domain of domains) {
    const company = await lookup(domain);
    if (company) console.log(JSON.stringify(company));
  }
}

main().catch((e) => { console.error(e); process.exit(1); });

The 429 branch waits for the number of seconds in Retry-After, then tries again, up to five times. A 429 costs no records. The loop sits in main(), so the script runs the same as CommonJS or as an ES module.

Step 3: Run it and save the results

bash
printf 'acme.com\nexample.org\n' > domains.txt
npx tsx lookup.ts > companies.jsonl
wc -l companies.jsonl

Each line is one company. Misses are skipped, so the line count can be lower than the domain count. The loop is sequential on purpose: it stays under the Free plan's 5 requests a second without a rate limiter.

Step 4: Page through a search when you do not have the domains yet

Enrich returns one company per call, so there is nothing to page. If you need to build the domain list first, call POST /v1/companies/search. The body is flat JSON and filters are arrays (hqCountry is the head office country). limit runs 1 to 100, and you pass the cursor from the previous page to get the next one.

ts
const body = { hqCountry: ["US"], limit: 100, cursor: lastCursor }; // lastCursor = meta.nextCursor from the previous page

One search pages to 10,000 rows. The last page has meta.depthCapped true and no nextCursor. Narrow the filters instead of walking further. To size the list first, call GET /v1/companies/count, which is free. Search spends one record per company returned, so check the count before you page.

What you get

This is the response shape from the API reference, with placeholder values. A hit is wrapped as success, data and meta, and data holds these groups.

json
{
  "success": true,
  "data": {
    "company_data": { "...": "Acme" },
    "company_detected_technologies": { "...": "..." },
    "company_metrics": { "...": "..." },
    "meta": { "...": "..." }
  },
  "meta": { "...": "..." }
}

Source: api.leadocean.io/openapi.json, September 2026. Parse company_data for name, domain and firmographics, and company_detected_technologies for the stack. Check the live reference for the field names inside each group, then type only the fields you use.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-keyCheck that $LEADOCEAN_API_KEY is exported in the same shell as npx tsx
429Requests above the plan's rate (5 a second on Free, 200 on Agency)Keep the Retry-After sleep, or upgrade the plan
402The 1,000 Free records are spentAdd a paid plan at leadocean.io/pricing. Solo is $97 a month
400A body field the API does not know, such as country on company searchRead error.details, which names the field. Company search uses hqCountry
404We hold nothing for that domainExpected and free. The script skips it
Empty companies.jsonldomains.txt has the wrong path or only missesRun from the folder that holds domains.txt. Try a bare domain, not a URL
Search stops at 10,000 rowsThe depth cap on one searchSplit the query with narrower filters

FAQ

Do I need an SDK?

No. The API is plain HTTPS and JSON, so built-in fetch is enough.

Can I look up by LinkedIn URL instead?

Yes. Send linkedin_url in place of domain in the same body.

How do I go faster than one request at a time?

Run small batches with Promise.all, capped at your plan's rate: 5 a second on Free, 50 on Solo, 200 on Agency. Keep the Retry-After handling.

Does a miss cost a record?

No. A 404 is free. A record is spent only for a company you receive.

Where do I see the other languages?

Same recipe in Go, C# and Rust. More guides are in the blog. Plan details are on pricing.

Run your first TypeScript lookup on the free plan

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →