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 (PHP)

One cURL call, a retry on 429 and a file of results. About 40 lines of PHP 8, no Composer packages.

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

TL;DR

  • Send POST /v1/companies/enrich with {"domain": "acme.com"} and the x-api-key header. You get one company back.
  • Setup takes about 10 minutes. A run of 100 domains spends at most 100 of your 1,000 free records.
  • The script retries on 429 using Retry-After, reads domains from a file in batches and writes one JSON line per company.

The lookup is a single call. The rest of this guide is the loop around it: pacing, retries and output.

Prerequisites

  • PHP 8.0 or newer with the cURL extension (php -m | grep curl).
  • A LeadOcean API key from app.leadocean.io. Free needs no card.
  • The key in your shell as $LEADOCEAN_API_KEY.
  • A file domains.txt with one domain per line, such as acme.com.

Step 1: Set up the files

bash
export LEADOCEAN_API_KEY="your key from app.leadocean.io"
printf 'acme.com\nexample.org\n' > domains.txt

No Composer packages are needed. The cURL extension and json_encode ship with PHP.

Step 2: Write the lookup, retry and output loop

Save this as lookup.php. It sends one request per domain, waits out a 429, skips a 404 and appends each hit to companies.jsonl.

php
<?php
$key = getenv('LEADOCEAN_API_KEY') ?: exit("Set LEADOCEAN_API_KEY\n");
$domains = array_filter(array_map('trim', file('domains.txt')));
$out = fopen('companies.jsonl', 'a');

function lookup(string $domain, string $key): ?string {
    for ($attempt = 0; $attempt < 5; $attempt++) {
        $retryAfter = null;
        $ch = curl_init('https://api.leadocean.io/v1/companies/enrich');
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode(['domain' => $domain]),
            CURLOPT_HTTPHEADER => ['x-api-key: ' . $key, 'Content-Type: application/json'],
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADERFUNCTION => function ($c, $h) use (&$retryAfter) {
                if (stripos($h, 'retry-after:') === 0) $retryAfter = (int) trim(substr($h, 12));
                return strlen($h);
            },
        ]);
        $body = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        curl_close($ch);

        if ($status === 429) { sleep($retryAfter ?: 1 << $attempt); continue; }
        if ($status === 404) return null;
        if ($status !== 200) throw new RuntimeException("$domain: HTTP $status");
        return $body;
    }
    throw new RuntimeException("$domain: still rate limited after 5 tries");
}

foreach (array_chunk($domains, 50) as $batch) {
    foreach ($batch as $domain) {
        $json = lookup($domain, $key);
        if ($json === null) { echo "$domain: not found\n"; }
        else { fwrite($out, trim($json) . "\n"); echo "$domain: ok\n"; }
        usleep(220000);
    }
    fflush($out);
}

Run it with php lookup.php. Each batch of 50 is flushed to disk, so a crash loses at most one batch.

Step 3: Match the pace to your plan

The 220 ms pause keeps you near 4.5 requests a second, under the 5 a second limit on Free. Rate limits are per key and per plan:

PlanRequests a second
Free5
Solo50
Agency200
Platform10,000

On a paid plan, lower the usleep value or run several processes over different slices of the file. The retry branch still protects you if you overshoot. See pricing for what each plan includes.

Step 4: Read the output

Each line in companies.jsonl is one JSON response. Decode it line by line:

php
foreach (file('companies.jsonl') as $line) {
    $row = json_decode($line, true);
    print_r($row['data']['company_data']);
}

The company fields sit under company_data. Detected technologies and metrics have their own objects.

What you get

Each response has this shape, from the API reference (placeholder values, not a real record):

json
{
  "success": true,
  "data": {
    "company_data": {
      "company_name": "Acme",
      "company_domain": "acme.com",
      "company_type": "Privately Held",
      "company_industries": [{ "name2": "Software Development", "priority": 1 }],
      "company_employees": { "number_of_employees": 120, "number_of_employees_code": "51-200" },
      "company_primary_address": { "city": "San Francisco", "country_code": "US" },
      "company_number_of_detected_technologies": 2
    },
    "company_detected_technologies": [
      { "technology_product": { "name": "Example CDN" }, "technology_category": { "name2": "Content Delivery Network" } }
    ],
    "company_metrics": { "completion_score": 90, "marketability_score": 80 },
    "meta": { "sources": ["own"], "schema": "leadocean.company.v1" }
  },
  "meta": { "source": "leadocean" }
}

Response shape from the company lookup docs (October 2026), placeholder values. Fields we hold nothing for come back as null. company_data also carries funding, revenue, NAICS and SIC codes, and company_number_of_profiles when we hold them.

The console prints one line per domain: acme.com: ok or example.org: not found.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-keyCheck $LEADOCEAN_API_KEY is exported in the same shell as php lookup.php.
429Requests above your plan's rate limit (5 a second on Free, 200 on Agency)The loop waits Retry-After. Raise the pause or move up a plan.
402Records spentFree is 1,000 records for life. Move to a paid plan or trim the list.
404No company held for that domainCosts nothing. Check the spelling and use the bare domain, such as acme.com.
Empty companies.jsonlEvery domain returned 404, or the file had blank lines onlyRead the console output and test one known domain by hand.

FAQ

Can I use GET instead of POST?

Yes. GET /v1/companies/enrich?domain=acme.com returns the same record and costs the same one record.

Can I look up by LinkedIn URL instead?

Yes. The body takes domain or linkedin_url.

How do I page through results?

Enrichment returns one company per call, so you page your own domain list, as array_chunk($domains, 50) does. To find companies you do not know yet, use /v1/companies/search. It takes a flat JSON body, limit from 1 to 100 and a cursor.

Does a failed lookup cost a record?

No. A 404 is free. Only a company you receive counts as one record.

Where are the same recipes in other languages?

See Go, C# and TypeScript.

Look up your first 100 companies by domain, free

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

Get your free API key →