Irmu
Free tool

EIN extractor

Paste a company page, an SEC filing or a Form 990 and pull out every US Employer Identification Number. Prefix-checked, deduplicated and scored by whether a tax-ID label sits next to it.

0 matches
Paste page HTML or text
Results

Nothing yet. Paste content on the left — every nine-digit number in NN-NNNNNNN shape is checked against the IRS prefix list and scored by whether a tax-ID label sits next to it.

Runs entirely in your browser. Nothing you paste is uploaded, stored or logged.

How it works

Shape, prefix, label — in that order

An EIN has no check digit, so matching the shape alone is not enough. Three passes get you to a list you can trust.

  • Shape: nine digits written as NN-NNNNNNN, with or without the hyphen or a space.
  • Prefix: the first two digits must fall in a range the IRS issues — 01-06, 10-16, 20-27, 30-35, 37-39, 41-48, 50-68, 71-77, 80-88, 90-95, 98-99.
  • Label: wording such as EIN, FEIN, Federal Tax ID or 'I.R.S. Employer Identification No.' within about 60 characters of the number.
  • Everything else — phone numbers, DUNS, order references, zip+4 — is what the label filter is there to remove.
  • Duplicates collapse into one row with an occurrence count, and each row keeps the surrounding text so you can sanity-check it.
With Irmu

When you also need to fetch the page

A browser cannot fetch arbitrary URLs. Irmu returns the HTML — rendering, proxies and retries included — and you run the same regex over it.

crawl_and_find_ein.py
# pip install requests
# Fetch the page with Irmu — rendering, proxies and retries handled — then run the regex above.

import os, requests
from bs4 import BeautifulSoup

resp = requests.get(
    "https://app.irmu.com/api/crawl",
    headers={"Authorization": f"Bearer {os.environ['IRMU_API_KEY']}"},
    params={"url": "https://example.com/terms", "js": "true"},
    timeout=60,
)
resp.raise_for_status()

text = BeautifulSoup(resp.text, "html.parser").get_text(" ", strip=True)
for hit in extract_eins(text, require_label=True):
    print(hit["ein"], "—", hit["context"][:80])
Code examples

Do the same thing in your own pipeline

Prefix validation plus label proximity, in four languages.

import re

# Prefixes the IRS actually issues. Everything else is not an EIN.
VALID_PREFIXES = set()
for lo, hi in [(1, 6), (10, 16), (20, 27), (30, 35), (37, 39), (41, 48),
               (50, 68), (71, 77), (80, 88), (90, 95), (98, 99)]:
    VALID_PREFIXES.update(f"{n:02d}" for n in range(lo, hi + 1))

EIN = re.compile(r"\b(\d{2})[-\s]?(\d{7})\b")
LABEL = re.compile(
    r"(ein|fein|employer\s+identification|federal\s+tax\s*id|i\.?r\.?s\.?\s+no)",
    re.I,
)

def extract_eins(text: str, require_label: bool = False):
    found = {}
    for m in EIN.finditer(text):
        prefix, rest = m.group(1), m.group(2)
        if prefix not in VALID_PREFIXES:
            continue
        window = text[max(0, m.start() - 60): m.end() + 20]
        labelled = bool(LABEL.search(window))
        if require_label and not labelled:
            continue
        ein = f"{prefix}-{rest}"
        found.setdefault(ein, {"ein": ein, "labelled": labelled, "context": window.strip()})
    return list(found.values())

sample = "Acme Robotics, Inc. Federal Tax ID (EIN): 84-3719205. Order 12-3456789."
print(extract_eins(sample, require_label=True))
# [{'ein': '84-3719205', 'labelled': True, 'context': '...Federal Tax ID (EIN): 84-3719205...'}]
Authoritative sources

Public companies: ask EDGAR, don't scrape

The SEC publishes the EIN of every registrant as structured data. Use it before you crawl anything.

edgar_ein.sh
# Public companies print their EIN on the cover page of every filing.
# EDGAR's company facts endpoint returns it directly — no scraping needed.

curl -s -H "User-Agent: your-name your@email.com" \
  "https://data.sec.gov/submissions/CIK0000320193.json" \
  | jq '{name: .name, ein: .ein, tickers: .tickers}'

# {
#   "name": "Apple Inc.",
#   "ein": "942404110",
#   "tickers": ["AAPL"]
# }
At scale

Sweeping a list of domains

EINs cluster on a handful of predictable pages. Check those, stop at the first labelled hit, and you spend a few credits per company instead of crawling a whole site.

sweep.mjs
// Node 18+. Walk a list of company sites, check the pages EINs usually live on,
// and stop as soon as a labelled match is found.

const PATHS = ["/", "/terms", "/privacy", "/about", "/contact", "/imprint", "/legal"];

async function findEin(domain) {
  for (const path of PATHS) {
    const target = new URL(path, `https://${domain}`).href;
    const res = await fetch(
      `https://app.irmu.com/api/crawl?url=${encodeURIComponent(target)}&js=true`,
      { headers: { Authorization: `Bearer ${process.env.IRMU_API_KEY}` } },
    );
    if (!res.ok) continue;

    const html = await res.text();
    const [hit] = extractEins(html.replace(/<[^>]+>/g, " "), { requireLabel: true });
    if (hit) return { domain, ein: hit.ein, source: target };
  }
  return { domain, ein: null, source: null };
}

for (const domain of ["example.com", "example.org"]) {
  console.log(await findEin(domain));
}
FAQ

Questions about EIN extraction

Fetch the pages, extract the fields

One API for rendering, proxies and retries. 200 free credits every month, no card required.