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 | MCP | |
|---|---|---|
| Who calls it | Your code, at a moment you choose | An AI agent, when it judges the tool useful |
| Typical trigger | Signup webhook, CRM sync, nightly batch | A question in Claude, Cursor or your own agent |
| Endpoint | /api/hub/v1/companies/{domain} | https://businessmcp.com/api/hub/mcp |
| Discovery | OpenAPI document at /api/hub/v1/openapi.json | The client lists tools at connect time |
| Best at | Deterministic pipelines, caching, bulk | Ad-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:
| Tool | Route | Charged |
|---|---|---|
| company_lookup | GET /companies/{domain} | 1 credit when a company is found |
| company_bulk_lookup | POST /companies/bulk (up to 100 domains) | 1 credit per company found; misses refunded |
| company_search | POST /companies/search (up to 25 results) | 2 credits per company returned |
| company_website_status | GET /companies/{domain}/status | 1 credit when we hold the domain |
| email_pattern | GET /companies/{domain}/email-pattern | 2 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:
| Returned | Not returned |
|---|---|
| Company name, industry and category | People, named contacts or personal email addresses |
| Employee range and founding year | Revenue, funding or valuation |
| Country (ISO-2), region and city | Technographics — the tech_stack field is empty; do not build on it |
| Company phone and LinkedIn company page | Logos (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 measured | A 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.
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.
Sources
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