POST /v1/companies/enrich takes one domain or LinkedIn company URL and returns the company record, including every technology detected on its website. It costs 1 record per company found. A 404 costs nothing.
There is no separate tech endpoint. The stack is the company_detected_technologies block of the record. This page is the developer reference. For what the data is and where it stops, read Company tech stack lookup. The same lookup is the leadocean_get_company tool over MCP.
Quick start (curl)
Create a free account, copy a key with the enrich scope and export it as LEADOCEAN_API_KEY.
curl -s -X POST "https://api.leadocean.io/v1/companies/enrich" \
-H "x-api-key: $LEADOCEAN_API_KEY" \
-H "content-type: application/json" \
-d '{"domain":"acme.com"}'No domain? Send the LinkedIn company page instead. The endpoint also answers GET with the same fields as query parameters.
curl -s -G "https://api.leadocean.io/v1/companies/enrich" \
--data-urlencode "linkedin_url=https://www.linkedin.com/company/acme" \
-H "x-api-key: $LEADOCEAN_API_KEY"Node.js (fetch)
const res = await fetch("https://api.leadocean.io/v1/companies/enrich", {
method: "POST",
headers: {
"x-api-key": process.env.LEADOCEAN_API_KEY,
"content-type": "application/json",
},
body: JSON.stringify({ domain: "acme.com" }),
});
if (res.status === 404) {
console.log("No record for this company. Not billed.");
} else if (!res.ok) {
throw new Error(`${res.status} ${(await res.json()).error?.message}`);
} else {
const { data } = await res.json();
const stack = data.company_detected_technologies ?? [];
for (const t of stack) {
console.log(t.technology_product?.name, t.technology_category?.name2);
}
if (stack.length === 0) console.log("Company found, no technologies held");
}Python (requests)
import os
import requests
r = requests.post(
"https://api.leadocean.io/v1/companies/enrich",
json={"domain": "acme.com"},
headers={"x-api-key": os.environ["LEADOCEAN_API_KEY"]},
timeout=10,
)
if r.status_code == 404:
print("No record for this company. Not billed.")
else:
r.raise_for_status()
stack = r.json()["data"].get("company_detected_technologies") or []
for t in stack:
print((t.get("technology_product") or {}).get("name"))
if not stack:
print("Company found, no technologies held")Request
Send one identifier. The key needs the enrich scope.
| Parameter | Type | Required | Meaning |
|---|---|---|---|
domain | string | One of the two | Website domain, for example acme.com. Preferred key. |
linkedin_url | string (URL) | One of the two | LinkedIn company page URL. |
There is no lookup by company name. With only a name, call POST /v1/companies/search first, then send the domain you get back. The older POST /v1/companies/enrich still works as a deprecated alias.
Response
Response shape from the API reference. Every value below is a placeholder, and the real record carries many more fields.
{
"success": true,
"data": {
"company_data": {
"company_name": "Acme",
"company_domain": "acme.com",
"company_number_of_detected_technologies": 47
},
"company_detected_technologies": [
{
"technology_product": { "name": "Cloudflare" },
"technology_category": { "name2": "Content Delivery Network" },
"technology_last_detected_date": "2026-08-21"
}
],
"company_metrics": { "completion_score": 94, "marketability_score": 81 },
"meta": { "sources": ["own"], "schema": "leadocean.company.v1" }
},
"meta": { "credits": 1, "source": "own" }
}| Field | Meaning |
|---|---|
company_detected_technologies[] | One entry per technology found on the company website. Empty when we hold none. |
technology_product | The product detected. The name is at technology_product.name. |
technology_vendor | The company that sells it. |
technology_category | What it is for. The label is at technology_category.name2. |
tags | Extra labels on the technology. |
technology_last_detected_date | When we last saw it. The docs also list a first-detected date. Read both to tell a live tool from a retired one. |
company_data.company_number_of_detected_technologies | How many technologies the record lists. Check it before you read the array. |
meta.credits | Records charged for this call. |
Unknown values are null, never guessed. Arrays that hold nothing come back empty, not missing.
Errors
Every failure is { "success": false, "error": { "message": "...", "details": ... } }. Status codes are from the limits docs, September 2026.
| Status | Meaning | What to do |
|---|---|---|
| 400 | Validation failed. details names the fields. | Send domain or linkedin_url, as a string. Not billed. |
| 401 | Missing, wrong or revoked key. | Check the x-api-key header. |
| 402 | Free plan: the 1,000 records are spent. They do not reset. | Upgrade. Not billed. |
| 403 | The key lacks the enrich scope, or the account is suspended. | Create a key with the scope. |
| 404 | We hold nothing for this company. | Stop. It costs nothing. Try the other identifier. |
| 429 | Over 100 requests a second, or 1 a minute on a paid account past its records. | Wait for Retry-After, then retry. Not billed. |
| 503 | The store behind the lookup is unreachable (code: no_source). | Retry with backoff. Never billed. |
Rate limits and cost
One company found is 1 record, by domain or by LinkedIn URL. A company we hold but have not crawled still comes back, with an empty technology list, and costs 1 record. A 404, 400, 401, 402, 403, 429 or 503 costs nothing.
The limit is 100 requests per second per key, on every plan. Free is 1,000 records, one-off, no card. Pro is $499 a month, flat, with no per-record price inside fair use. See pricing.
Technology data comes from website crawls and covers about 7.8M of the 63M companies, so expect empty lists. Test your hit rate on a few hundred domains before a large run. GET /v1/account is free and shows records left.
Bulk
For a list of domains, loop this endpoint. At 100 requests a second, 10,000 domains is under two minutes of calls, and only the companies you receive are billed.
To go the other way, filter by what companies run. POST /v1/companies/search takes technologies (names) and technologyCategories (for example CRM, Analytics or CDN). It costs 1 record per company returned. Add count=true as a query parameter with limit 1 to size it for free.
POST /v1/exports is the bulk route for people, not companies. It takes the same technologies and technologyCategories filters, but its columns carry no technology list. Use it for the contacts at companies that run a tool, with numbered columns such as email_N_address. They come back as email_1_address, email_2_address and so on, and caps sets how many.
curl -X POST https://api.leadocean.io/v1/exports \
-H "x-api-key: $LEADOCEAN_API_KEY" -H "content-type: application/json" \
-d '{
"name": "VP Sales at CRM users",
"filters": { "technologyCategories": ["CRM"], "jobLevel": ["VP"], "country": ["US"] },
"limit": 2000,
"columns": ["person_id", "profile_full_name", "current_company_domain", "email_N_address"],
"caps": { "emails": 1 }
}'The reply is a 202 with a job id. Poll GET /v1/exports/{id}, then fetch /v1/exports/{id}/download. An export takes up to 50,000 rows and bills 1 record per row.
FAQ
Does a company with an empty tech list cost a record?
Yes. A 200 with an empty company_detected_technologies costs 1 record, because the company came back. A 404, where we hold nothing for the company, costs nothing. To avoid paying for empty answers, test a few hundred domains first and count how many come back with technologies.
Can I look up a tech stack by company name?
No. Send a domain or a LinkedIn company URL. With a name, search companies first, then send the domain from the result. The deeper explanation is on Company tech stack lookup, and the wider endpoint family is in the company enrichment API guide.
Can an agent call it without an API key?
Yes. The MCP server at https://api.leadocean.io/mcp signs in with OAuth 2.1, so no key is pasted. The tool is leadocean_get_company, 1 record. Setup is on Company Tech Stack over MCP.
Get your company tech stack API key free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →