BusinessMCP

Developers

Company Enrichment API: Add It to Your App or Agent

A company enrichment API turns a domain into a name, industry, size and location. Here is how to wire one into a backend over REST and into an AI agent over MCP, what it should cost, how to cache it, and what to do when the answer is “we don’t know”.

By the BusinessMCP team11 min readSeptember 28, 2026
Company Enrichment API: Add It to Your App or Agent — illustrated overview

Key takeaways

  • Use REST when your code decides when to enrich (a signup hook, a CRM sync); use MCP when an AI agent decides. The same key, tools and prices serve both.
  • Pay per hit, not per request: a lookup that finds nothing should cost nothing, and bulk calls should refund their misses.
  • Filter personal mail domains before you call, cache hits for weeks and misses for days, and send an Idempotency-Key so a retry is never billed twice.
  • Know what the data is not: company-level firmographics, not people, not revenue, not technographics — and a miss is an honest answer, never a prompt to guess.

What a company enrichment API does

A company enrichment API takes something you already have — a domain, a work email, a URL — and returns what is known about the company behind it: name, industry, headcount band, founding year, location, company phone, LinkedIn page and a short description. It is the step that turns “jane@acme-logistics.com signed up” into “a 200-person freight company in Dallas signed up”, before the lead reaches a human or an agent.

This guide wires it up two ways against the BusinessMCP Data API: as a REST endpoint your code calls, and as an MCP tool an AI agent calls on its own. Both read the same company store — about 34M companies compiled from public registries, open company datasets, public business listings and company websites, refreshed by our own crawler — and both are billed the same way.

REST or MCP: pick by who decides when to enrich
RESTMCP
Who calls itYour code, at a moment you chooseAn AI agent, when it judges the tool useful
Typical triggerSignup webhook, CRM sync, nightly batchA question in Claude, Cursor or your own agent
Endpoint/api/hub/v1/companies/{domain}https://businessmcp.com/api/hub/mcp
DiscoveryOpenAPI document at /api/hub/v1/openapi.jsonThe client lists tools at connect time
Best atDeterministic pipelines, caching, bulkAd-hoc research, multi-step reasoning

Most teams end up with both: a backend hook that enriches every signup, and an agent that can look a company up mid-conversation. For the general trade-off between the two interfaces, see MCP vs API.

A working REST call

Create a key (they start with mcph_) in the developer portal — sign up here — and export it as BUSINESSMCP_API_KEY. The first 1,000 first-party credits each month are free, so this costs nothing to try:

curl https://businessmcp.com/api/hub/v1/companies/stripe.com \
  -H "Authorization: Bearer $BUSINESSMCP_API_KEY"

The response is the company plus a meta envelope saying what the call cost:

{
  "ok": true,
  "data": {
    "found": true,
    "company": {
      "domain": "stripe.com",
      "name": "Stripe",
      "industry": "financial services",
      "category": null,
      "employee_range": "5001-10000",
      "employee_count": null,
      "founded_year": 2010,
      "country": "US",
      "region": "california",
      "city": "south san francisco",
      "phone": null,
      "linkedin_url": "linkedin.com/company/stripe",
      "description": "Financial infrastructure for the internet.",
      "tech_stack": null,
      "brand_only": false,
      "website_status": "live",
      "source": "store"
    }
  },
  "meta": {
    "request_id": "…",
    "tool": "fp.company.lookup",
    "provider": "first_party",
    "trust_tier": "first_party",
    "credits_charged": 1,
    "replayed": false
  }
}

Three things worth wiring from day one. found is the only field to branch on — a miss returns found: false and company: null, never an empty shell. The x-credits-charged response header (and meta.credits_charged) tells you what the call cost, so you can meter your own usage. And an Idempotency-Key request header becomes the call’s request id: send the same key again and you get the stored answer back with replayed: true instead of a second charge.

Give an AI agent the same lookup over MCP

The same tools are served as an MCP server, so an agent in Claude Desktop, Cursor, Claude Code or VS Code can call company_lookup, company_bulk_lookup, company_search and the rest with no glue code. Claude Desktop and Cursor read stdio configs, so they reach the remote endpoint through mcp-remote with the key as a header:

{
  "mcpServers": {
    "businessmcp": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://businessmcp.com/api/hub/mcp",
        "--header",
        "Authorization: Bearer mcph_YOUR_KEY_HERE"
      ]
    }
  }
}

Claude Code takes the URL and header directly:

claude mcp add --transport http businessmcp https://businessmcp.com/api/hub/mcp \
  --header "Authorization: Bearer mcph_YOUR_KEY_HERE"

Restart the client and ask something only a lookup can answer: “What does the company behind acme-logistics.com do, and how big is it?” The agent picks company_lookup from its description, which states that it is charged only when a company is found. The server also carries hub_search_tools and hub_call_tool, so an agent can find a tool it was not told about.

Pricing: pay per company found

Every tool is priced in credits. After the free 1,000 a month, credits are billed on a graduated scale: $2.00 per 1,000 up to 100,000, $1.20 per 1,000 up to 1,000,000, $0.80 per 1,000 above 1,000,000. The company tools only charge when they return something:

Company tools and what they charge (from the live registry)
ToolRouteCharged
company_lookupGET /companies/{domain}1 credit when a company is found
company_bulk_lookupPOST /companies/bulk (up to 100 domains)1 credit per company found; misses refunded
company_searchPOST /companies/search (up to 25 results)2 credits per company returned
company_website_statusGET /companies/{domain}/status1 credit when we hold the domain
email_patternGET /companies/{domain}/email-pattern2 credits when a pattern is known

Bulk calls reserve before they refund. A 100-domain bulk call reserves 100 credits, runs, and refunds every domain that missed. That matters near a limit: a call that would cost 40 once the misses are refunded is still refused if 100 credits of headroom are not there. Size batches to your remaining allowance.

Rate limits are 600 requests a minute per key and 3,000 per workspace, with a tighter 60 a minute for bulk and other per-unit tools — so bulk is the fast path: 60 calls of 100 domains is 6,000 domains a minute. New paying accounts also carry a default monthly cap of 50,000 credits ($100.00 at the first tier) that you can raise, so a runaway loop cannot run up an open-ended bill.

curl -X POST https://businessmcp.com/api/hub/v1/companies/bulk \
  -H "Authorization: Bearer $BUSINESSMCP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domains":["stripe.com","notarealcompany-xyz.com"]}'

Caching: what to store and for how long

Firmographics change slowly, so the cheapest enrichment is the one you do not repeat. A sensible default:

  • Filter first. gmail.com, outlook.com and other personal mail domains are not companies. Drop them before the call rather than paying to learn that.
  • Key the cache on the normalised domain (lowercase, no www., no path) — the API normalises a URL or a work email the same way, so https://www.Acme.com/about and jane@acme.com are one entry.
  • Cache hits for weeks. Name, industry and size rarely move month to month. Thirty days is a reasonable ceiling for a CRM field.
  • Cache misses for days, not forever. The store grows as the crawler finds new companies, so a domain that missed this week may resolve next month.
  • Treat website status as dated. It carries a checked_at timestamp from our crawler; re-ask when you are about to spend a sequence on the account.
// Enrich a signup domain: skip personal mail, cache hits and misses, never invent a company.
const BASE = "https://businessmcp.com/api/hub/v1"
const PERSONAL = new Set(["gmail.com", "outlook.com", "yahoo.com", "icloud.com", "proton.me"])

type Cached = { company: unknown | null; at: number }
const cache = new Map<string, Cached>() // use Redis or a table in production
const HIT_TTL = 30 * 86_400_000  // firmographics change slowly
const MISS_TTL = 7 * 86_400_000  // the store grows, so re-ask about misses

export async function enrich(emailOrDomain: string) {
  const domain = emailOrDomain.split("@").pop()!.trim().toLowerCase()
  if (PERSONAL.has(domain)) return null // not a company: do not spend a credit

  const hit = cache.get(domain)
  if (hit && Date.now() - hit.at < (hit.company ? HIT_TTL : MISS_TTL)) return hit.company

  const res = await fetch(`${BASE}/companies/${encodeURIComponent(domain)}`, {
    headers: {
      Authorization: `Bearer ${process.env.BUSINESSMCP_API_KEY}`,
      "Idempotency-Key": `enrich:${domain}:${new Date().toISOString().slice(0, 10)}`,
    },
  })
  const body = await res.json()
  if (!body.ok) {
    // 402 = free credits or cap used up, 429 = slow down, 503 = retry later.
    if (body.error?.retryable) throw new Error(`retry later: ${body.error.code}`)
    return null
  }
  const company = body.data.found ? body.data.company : null // found=false costs nothing
  cache.set(domain, { company, at: Date.now() })
  return company
}

The pattern above never writes a guess into your CRM: a miss is stored as a miss, and a retryable error is raised rather than swallowed as “no company”.

What the data contains — and what it does not

Being precise about the edges is what keeps enrichment from quietly corrupting your records:

Company tools: in scope and out of scope
ReturnedNot returned
Company name, industry and categoryPeople, named contacts or personal email addresses
Employee range and founding yearRevenue, funding or valuation
Country (ISO-2), region and cityTechnographics — the tech_stack field is empty; do not build on it
Company phone and LinkedIn company pageLogos (we never hotlink a favicon service)
A short description (up to 400 characters)Intent data or buying signals
Website status with the date it was measuredA guaranteed answer for every domain

Brand platforms are returned name-only. A listing hosted on google.com or facebook.com is not Google or Facebook, so for those domains brand_only is true and phone and description are withheld rather than borrowed from a tenant page. Person-level data is a separate toolset with its own terms and geographic limits — see people data — and is never mixed into a company response.

Handling misses without making things up

Every enrichment source misses: new companies, holding domains, personal sites, parked domains. The failure to avoid is not the miss — it is turning the miss into a confident wrong answer.

company_lookup returns found: false (no charge)
Store the miss with a short TTL
Optionally check company_website_status or email_pattern for partial facts
Show “unknown” in the UI and let a human or a later sync fill it

For an agent, say so in its instructions: “If the lookup finds nothing, say the company is unknown. Do not infer one from the domain name.”

Errors come back as ok: false with a typed error.code and a retryable flag. Retry only when retryable is true (a 503 or a 429, which also sends retry-after: 60). A 402 means the free credits or your monthly cap are used up and carries a link to billing; a 400 means the input was not a domain. None of these should ever be written into a record as “no company”.

Frequently asked questions

Should I use the REST API or the MCP server for enrichment?

Use REST when your own code decides when to enrich, such as a signup webhook or a nightly CRM sync, because it is deterministic, cacheable and supports bulk calls. Use MCP when an AI agent should decide on its own to look a company up. The key, the tools and the prices are the same for both.

Am I charged when a company is not found?

No. company_lookup, company_bulk_lookup, company_search, company_website_status and email_pattern are charged only for results. A bulk call reserves credits for every domain first and refunds the misses when it finishes.

Can I pass an email address instead of a domain?

Yes. The lookup accepts a domain, a URL or a work email and normalises all three to the registrable domain. Filter out personal mail providers such as gmail.com first, because they are not companies.

How long should I cache enrichment results?

Cache found companies for up to about thirty days and misses for about a week. Firmographics change slowly, while the store keeps growing, so a domain that misses today may resolve later. Re-check website status before any outreach, because it is a dated measurement.

Does the company data include contacts or technology stack?

No. The company tools return company-level firmographics only. Named people are a separate toolset with its own terms, and the API is not a technographics source.

BM

BusinessMCP Team

Every guide is written from running BusinessMCP on its own platform — the match rates, reply rates, and deliverability lessons are from our own data, not recycled blog folklore. About BusinessMCP

Turn your business into one AI-ready MCP server

Connect your tools, install one tracking script, and expose your unified data to any AI agent through a single secure endpoint.

Get started free