Company research API: research a company by name or website

A company research API researches a company when you ask and returns the fields you define, with the sources behind them. How we call Sixtyfour's Company Intelligence API one company at a time, asynchronously and in bulk, take results by webhook, design fields worth asking for and handle the answers that come back unsure.

Company ResearchResearch APICompany IntelligenceCompany SearchDeveloper ToolsWebhooks
Company research API: research a company by name or website

A company research API takes what you already know about a company, usually its name or website, researches it when you ask and returns the fields you define, with the sources behind them. A company information API that looks a record up in a stored database answers a different question, and both have their place. Sixtyfour is a company research API: you send a company name or website and a struct describing the fields you want, and its Company Intelligence endpoint returns those fields with the pages it read them on and, if you ask, a 0 to 100 confidence score and a justification for each one.

Below is how we build on it: one company synchronously, then asynchronously, then a list in one bulk job, then webhooks, followed by how to design fields and what to do with unsure answers. Request and response shapes follow our API docs, checked on 13 November 2026, and the companies in the examples are invented.

What is a company research API?

Searches for a company information API, a company search API and a company research API usually describe three different jobs:

  • Finding companies that match a description, such as laboratory equipment makers in the UK with 50 to 500 staff. That is search, and it returns a list.
  • Looking up the record a provider already holds for a company you name. That is a lookup against a stored database, usually quick and predictable in shape, and as current and complete as the record.
  • Researching a company you already have, to answer questions specific to your workflow: who owns it, what it sells, who runs it and what it announced this year. That is research, and the work happens when you ask.

A lookup fits when the fields you need are the ones the database stores and you need them for a lot of rows quickly. Research fits when the fields are particular to your question, when the company is small or new and a stored record is thin, or when someone downstream needs to open the page a fact came from. Plenty of teams run both: a lookup for standard firmographics, and research for the questions a record does not answer.

Sixtyfour does the research job through Company Intelligence and builds lists through its search endpoints, which we come to below. A matching People Intelligence endpoint researches a person, so one API covers researching people and companies.

How to research a company with Sixtyfour

Every request goes to https://api.sixtyfour.ai with an API key in the x-api-key header, and keys are created in the web app under Settings, then API Keys. A Company Intelligence request has two required parts. target_company is what you already know: any of company_name, domain, website, linkedin, location and other keys. struct is what you want back: each key becomes a field in the response, and its value tells the research agent what to find.

Our running example is Wraxholm Instruments, an invented maker of laboratory calibration equipment at wraxholm.example (the .example name is reserved for documentation). We want six fields: what it sells, its headquarters, a headcount range, its parent company, its current leadership and its news from the past year.

python
import requests
 
API = "https://api.sixtyfour.ai"
HEADERS = {"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}
 
body = {
"target_company": {
"company_name": "Wraxholm Instruments",
"website": "https://wraxholm.example",
},
"struct": {
"what_they_sell": "What the company sells and to which customers, in one sentence",
"headquarters": "City and country of the headquarters",
"headcount_range": {
"description": "Number of employees",
"type": "enum",
"options": ["1-10", "11-50", "51-200", "201-1000", "1001+"],
},
"parent_company": "The company that owns this one, if any",
"leadership": {
"description": "Current executives named on the company's own site or in its filings",
"type": "object_list",
"subfields": [
{"key": "name", "type": "string", "description": "Full name"},
{"key": "title", "type": "string", "description": "Current title"},
],
},
"recent_news": {
"description": "News about the company from the last 12 months, one line each with its month",
"type": "list[str]",
},
},
"tier": "medium",
"field_confidence": True,
}
 
response = requests.post(f"{API}/company-intelligence", headers=HEADERS, json=body, timeout=900)
response.raise_for_status()
result = response.json()

The timeout is 900 seconds because a Company Intelligence run takes up to 10 minutes, and our docs ask for sync client timeouts of at least 15 minutes. The response below is illustrative. It follows the documented shape, trimmed to four of the six fields, without org_chart and the deprecated findings, and with confidence shown for three fields:

json
{
"structured_data": {
"what_they_sell": "Calibration instruments for laboratory balances, sold to university and hospital laboratories",
"headquarters": "Leeds, United Kingdom",
"headcount_range": "51-200",
"parent_company": "Welbrae Holdings",
"company_name": "Wraxholm Instruments",
"website": "https://wraxholm.example"
},
"notes": "Found the company site, a registry filing and a 2024 press release. Headcount rests on one aggregator range.",
"references": {
"https://wraxholm.example/about": "Products and customers",
"https://wraxholm.example/contact": "Headquarters address",
"https://registry.example/companies/wraxholm-instruments": "Registered address and corporate shareholder",
"https://press.example/2024/welbrae-acquires-wraxholm": "Acquisition by Welbrae Holdings"
},
"field_confidence": {
"headquarters": {
"confidence": 94,
"justification": "The contact page and the registry filing give the same city.",
"sources": ["https://wraxholm.example/contact", "https://registry.example/companies/wraxholm-instruments"]
},
"headcount_range": {
"confidence": 46,
"justification": "A data aggregator's range, not confirmed elsewhere.",
"sources": []
},
"parent_company": {
"confidence": 78,
"justification": "A 2024 press release names Welbrae Holdings as the buyer, and the registry lists it as shareholder.",
"sources": ["https://press.example/2024/welbrae-acquires-wraxholm", "https://registry.example/companies/wraxholm-instruments"]
}
}
}

Four parts matter. structured_data holds one key per struct field and echoes the keys you sent in target_company. references maps each source URL to what was found there, and notes is a short summary of the research. field_confidence mirrors the shape of your struct: each value gets a score from 0 to 100, a sentence of justification and the URLs it was read on, and each item in an object list is scored subfield by subfield. Without field confidence, the response carries one confidence_score from 0 to 10 for the whole record instead, and the two never appear together.

To get people as well, set find_people to true with a people_focus_prompt such as "The founder and the head of operations". The people found come back in structured_data.leads, and lead_struct chooses their fields in the same format as struct.

Tiers and cost

tier sets research depth. List prices as of November 2026, in credits per company, from our credits guide:

TierCreditsWhat our docs say it is for
micro0.3A lightweight lookup for very high volume, up to 10 struct fields
low (default)1Fast, low-cost firmographic data
medium5Deeper research: more sources, more fields filled
high15Deep, recursive research built for high-stakes accounts and diligence, enabled on request

Field confidence, find_people and full_org_chart cost nothing extra, and a single-company job that fails is not charged. Company Intelligence has no deeper tier, and for an in-depth investigation of a company our docs point to Atlas, which is enabled on request. We start new integrations at medium.

How do you run research that takes several minutes?

A sync call holds a connection open for up to 10 minutes, which is fine in a script and fragile in a service. Our docs are direct about what happens when it drops: "A sync call keeps running after your client times out or disconnects, and it is charged when it finishes." A retry then starts and charges a second run. When Sixtyfour is at capacity, a sync call returns 503 while an async job waits its turn. So in production we start the job and poll for it.

POST /company-intelligence-async takes the same body and answers at once with a task_id and a status of RUNNING. If any key in the body was not recognised, the start response lists it in unrecognized_fields, which catches a misspelt field_confidence before you wonder why no scores came back.

GET /job-status/{task_id} reports running until the job reaches a final status: completed, failed, cancelled, terminated or timed_out. This is the polling helper from our async jobs page, with its two-hour timeout written out in seconds:

python
import time
 
def wait_for_job(task_id, interval=10, timeout=7200):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
response = requests.get(f"{API}/job-status/{task_id}", headers=HEADERS, timeout=30)
if response.status_code in (429, 503):
# Over the polling budget, or status briefly unavailable: the job keeps running.
time.sleep(float(response.headers.get("Retry-After", interval)))
continue
response.raise_for_status()
job = response.json()
if job["status"] != "running":
return job
time.sleep(interval)
raise TimeoutError(f"Job {task_id} still running after {timeout} seconds")
 
 
start = requests.post(f"{API}/company-intelligence-async", headers=HEADERS, json=body, timeout=60)
start.raise_for_status()
task_id = start.json()["task_id"] # store it before anything else
job = wait_for_job(task_id)
if job["status"] != "completed":
raise RuntimeError(f"Job {job['status']}: {job.get('error', 'no reason given')}")
result = job["result"]
print(job.get("charge_credits"))

Three rules come with it:

  • Store the task_id before anything else. Company Intelligence takes no dispatch_id, so in our docs' words "a repeated async start also runs and charges again".
  • Poll each job every 10 seconds or slower. Job status shares one budget per organisation, 300 reads a minute with a burst of 15, which our rate limits page says covers 50 jobs in flight at that pace. A 429 on a status read does not affect the job: wait Retry-After seconds and read again.
  • Expect a final status other than completed. A Company Intelligence job that does not start within 24 hours ends failed, and a failed single-company job is not charged.

How do you find the companies to research?

Sixtyfour's search endpoints build the list. Filter Search, POST /search/query with "mode": "company", searches companies by structured fields such as location, headcount and industry, and takes a natural language query as an alternative to filters. Deep Search, POST /search/start-deep-search with "mode": "company" (its default is people), turns a description of up to 10,000 characters into a list as an async task, with up to five running per organisation and each running for at most 15 minutes. Lookalike Search finds people only, so for companies we start from one of the other two.

python
import csv
 
payload = {
"mode": "company",
"query": "Laboratory calibration equipment makers in the United Kingdom with 50 to 500 employees",
"output_shape": "workflow",
"page_size": 100,
"max_results": 200,
}
rows = []
while True:
response = requests.post(f"{API}/search/query", headers=HEADERS, json=payload, timeout=60)
response.raise_for_status()
page = response.json()
rows += [(r["name"], r["website"]) for r in page["results"]]
if not page["has_more"]:
break
payload = {"cursor": page["next_cursor"]}
 
with open("companies.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["company_name", "website"])
writer.writerows(rows)

Read two fields on the first page before you trust the list. parsed_query is the structured query your sentence was turned into, and unmet_constraints, when present, lists the parts of the query the search did not apply, so results can include rows outside them. With "output_shape": "workflow", each company row is flat, with columns such as name, website, domain and employeeCount. Filter Search costs 0.1 credits per result returned, as of November 2026.

How do you research a list of companies with an API?

For more than a handful of companies, we upload them as one Bulk Intelligence job instead of starting one async job per row. POST /bulk-intelligence/company takes multipart/form-data with two parts: file, a CSV, JSON, JSONL or NDJSON file of up to 100 MB and 100,000 rows, and config, a JSON string with the same options as the single-company request (struct, tier, field_confidence, research_plan, find_people and the rest) plus three bulk-only ones. Each row is the input. The docs do not fix the column names for a company file: the one sample, on the Start Bulk Company reference, has company_name and domain columns. We name ours after two target_company keys, company_name and website, and give full URLs, as the Company Intelligence example does. The file for our example:

csv
company_name,website
Wraxholm Instruments,https://wraxholm.example
Wraybourne Logistics,https://wraybourne.example
Warrowby Print,https://warrowby.example
Wixendale Ceramics,https://wixendale.example
python
import json
 
config = {
"struct": body["struct"],
"tier": "medium",
"field_confidence": True,
"result_formats": ["csv", "ndjson"],
"webhook_url": "https://your-server.example/webhooks/sixtyfour",
}
 
with open("companies.csv", "rb") as f:
submit = requests.post(
f"{API}/bulk-intelligence/company",
headers={"x-api-key": "YOUR_API_KEY", "Idempotency-Key": "wraxholm-list-2026-11-12"},
files={"file": ("companies.csv", f, "text/csv")},
data={"config": json.dumps(config)},
timeout=120,
)
submit.raise_for_status()
task_id = submit.json()["task_id"]
print(submit.json().get("estimated_cost_credits"))

The response carries the task_id, a row_count and estimated_cost_credits. A submission your balance does not cover is refused with 402, and a bulk job costs the same per row as single requests. The Idempotency-Key header, up to 255 characters, makes the submission safe to retry: resubmitting with the same key within 24 hours returns the original job without the row count or cost estimate, hence the .get in the code. Submissions are limited to 10 a minute per organisation. Sixtyfour checks the webhook URL when you submit, so use your own receiver's.

While the job runs, job status carries progress with completed and total row counts. A completed bulk job has no inline result. It carries results, signed links to the output files, and the final file is the entry with the highest block_number. Each link lasts 15 minutes and needs your x-api-key header, so poll again for fresh ones.

python
job = wait_for_job(task_id, interval=30)
if job["status"] != "completed":
raise RuntimeError(f"Job {job['status']}")
final_block = max(f["block_number"] or 0 for f in job["results"])
final = next(
f for f in job["results"]
if (f["block_number"] or 0) == final_block and f["filename"].endswith(".ndjson")
)
download = requests.get(
final["download_url"],
headers={"x-api-key": "YOUR_API_KEY"},
allow_redirects=False,
timeout=120,
)
if download.status_code != 200:
raise RuntimeError(f"Download failed: {download.status_code}")
companies = [json.loads(line) for line in download.text.splitlines() if line.strip()]

We ask for NDJSON alongside the CSV because NDJSON keeps numbers, booleans and nested objects typed, which matters for the leadership list. A row that fails comes back without research and does not fail the job, and in bulk results field confidence arrives as one column holding a JSON string for each row. To keep these fields current after the first run, a monitor re-checks chosen fields on a schedule and records each change with its sources, and monitors are turned on per organisation on request.

How do you get results without polling?

Pass webhook_url, an HTTPS URL on a public host, and Sixtyfour POSTs the result to it when the job completes. It works on /company-intelligence-async, on the sync /company-intelligence call and in a bulk job's config. A single-company callback carries task_id, status, task_type and the same result that job status returns. A bulk callback is a workflow run envelope whose run_id equals the job's task_id, with a results_download_url that works for 15 minutes.

Four details from our outgoing webhooks reference shape the receiver:

  • Only completed jobs send a callback. Failed, cancelled and timed-out jobs send none, so polling stays as the fallback for any job whose callback is late.
  • A job can send more than one callback, each with a new event_id, so deduplicate on task_id, or on run_id with previous_block_id for a bulk job.
  • A Company Intelligence callback can arrive before job status reads completed, and it carries no charge. Read charge_credits from job status once it does.
  • Delivery makes 5 attempts, 1, 2, 4 and 8 seconds apart, with 10 seconds per attempt for an async job, and any 2xx response counts as success. Redirects are not followed, so a 3xx counts as a failed attempt.

Once your organisation has a signing secret, created by an admin or owner under Settings, then Webhooks, every delivery carries a Sixtyfour-Signature header with a timestamp t and a v1 value: a hex HMAC-SHA256 of the timestamp, a full stop and the raw body, keyed with the secret. This receiver uses the verify function from that page unchanged, and a shortened version of the handler in our webhooks guide:

python
import hashlib
import hmac
import time
 
from flask import Flask, request
 
TOLERANCE_SECONDS = 300
 
def verify(raw_body: bytes, header: str, secret: str) -> bool:
timestamp, signatures = None, []
for part in header.split(","):
key, _, value = part.strip().partition("=")
if key == "t":
timestamp = value
elif key == "v1":
signatures.append(value)
if not timestamp or not timestamp.isdigit() or not signatures:
return False
if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
return False
expected = hmac.new(
secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return any(hmac.compare_digest(expected, sig) for sig in signatures)
 
 
app = Flask(__name__)
seen_deliveries = set() # use a persistent store in production
 
 
@app.post("/webhooks/sixtyfour")
def sixtyfour_webhook():
raw_body = request.get_data()
if not verify(raw_body, request.headers.get("Sixtyfour-Signature", ""), SECRET):
return "", 401
event = request.get_json()
key = event.get("task_id") or f"{event['run_id']}:{event['previous_block_id']}"
if key in seen_deliveries:
return "", 200
enqueue(event) # process outside the request
seen_deliveries.add(key) # only after the event is queued
return "", 200

SECRET comes from your secret manager, and enqueue hands the event to your own queue so the handler returns 2xx within the 10 seconds a delivery allows. The handler passes verify the raw body before any parsing, and verify refuses a timestamp more than five minutes from your clock. Before a real job runs, send a sample delivery to the receiver:

bash
curl -X POST "https://api.sixtyfour.ai/webhooks/test-events" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": "company_enrichment",
"url": "https://your-server.example/webhooks/sixtyfour",
"signing": "required"
}'

That event is the sample of a Company Intelligence callback, signed as a real delivery would be. A test delivery runs no job and uses no credits, and each call sends a new event, so do not retry it automatically.

How should you design the fields?

The struct is the research brief. Our struct guide puts it directly: "The description is what the agent researches, so say exactly what you want". The habits we follow:

  • Ask for something a person could check. A parent company has an answer and a page that states it, while "is this company trustworthy" is a judgement the research can only answer with other people's opinions. Ask the research for the facts a judgement rests on, and keep the judgement for your own logic.
  • Make each description specific. The Company Intelligence page notes that "Specific descriptions return better results", and a description that names the evidence you want, such as "named on the company's own site or in its filings", tells the agent what counts.
  • Bound anything that changes over time. News from the last 12 months, each with its month, gives a list you can compare between runs, and a field asking for recent news does not.
  • Type every field that is not a string. Only a typed object returns the same type on every request, enum holds the value to your options, and object_list returns a list of objects with the subfields you name. Field names must be letters, numbers and underscores.
  • Put method in research_plan, up to 100,000 characters of plain-language guidance on where and how to look, such as "Check the About page and the LinkedIn company page". Put who to find in people_focus_prompt when find_people is on.

Two behaviours make typed fields safe to feed into code. A value is converted to a field's type only when nothing is lost in the conversion, so a researched value of about 50 never becomes the integer 50. And every field you ask for is present in the response: one the agent could not establish is null, or an empty list for a Company Intelligence list field. In the struct guide's words, "Values are never invented to satisfy a type."

What should you do with low-confidence or empty fields?

Field confidence is how we decide which values flow straight into a system and which wait for a person. Each score, our confidence scoring page says, "reflects how much assumption is needed to tie the value back to the target person or company", and the docs map the scale to five levels:

ScoreLevelWhat it means
85 to 100ConfirmedNear-zero assumption: unique identifiers, direct assertions from authoritative sources
65 to 84EstablishedMinimal assumption; a careful person would rely on it
40 to 64SupportedModerate assumption; the link is reasonable but not tight
15 to 39SpeculativeSignificant assumption; treat it as a hypothesis
0 to 14UnreliableMajor assumption or conflicting evidence; do not rely on it

Pick a threshold for each use. Our docs suggest 65 or higher for cold outreach, and a field that feeds an onboarding decision deserves a higher bar. Then:

  1. Below your threshold, route the field to a review queue with its value, its justification and its sources, so the reviewer starts where the research stopped. In our example the headcount range at 46 goes to review, and the headquarters at 94 does not.
  2. A confidence of null means the value rests only on what you sent, so it adds nothing you did not already know. An entry that is null as a whole means the field was not scored, such as a field with no value.
  3. An empty sources list means the value came from a source with no public page, so a reviewer cannot open it. We treat it like a low score.
  4. An empty field means the research did not establish it. Leave it empty, and never fill it from a language model's own knowledge or a guess.
  5. Expect a company with a common name to score lower across the board, because "The weakest link dominates". Send the website or LinkedIn page with the name: better starting context, our Research API page notes, helps distinguish the intended subject from similarly named companies.

What happens to a field the research left open is your team's call, and a person makes it.

Reading the results fairly

  • A source says what it said when it was written. A headcount from an old directory page can be accurate and out of date at once, so a field with a time window is easier to rely on than one without.
  • Small and new companies leave less to find. An empty field or a low score is a lead to follow, and it says little about whether the company is real.
  • A researched field and a stored record can disagree. The researched one carries its sources, so a reviewer can open them and see which is more recent.
  • This post cites no accuracy figure for company research. Measure it on 10 to 20 companies your team already knows well before a full list, as our credits guide suggests for any new workflow.
  • Sixtyfour returns what the research found and the pages it found it on. It does not check bank accounts, run consumer background checks or credit reports, or verify identity documents, and anything it could not establish comes back open for a person to decide.

For onboarding a business customer or a supplier, our guides to KYB verification and verifying a vendor cover the checks around the research. If the caller is an AI agent, Company Intelligence is also a tool on our MCP server, sixtyfour_company_intelligence_start, and our post on adding people and company research to an agent covers the setup and what the tools return.

The short version

Send target_company with the name and website, a struct of specific, typed fields and field_confidence set to true. Use the async endpoint in production, store the task_id at once because a repeated start charges again, poll every 10 seconds or slower, and move to signed, deduplicated webhooks once jobs pile up. Upload lists as one bulk job with an Idempotency-Key, and read the NDJSON file for typed values. Route anything under your confidence threshold, anything without sources and anything empty to a person. Sixtyfour researches each company from its name or website and returns the fields you define with the pages behind them, so your team can check what it relies on.

“Ask the research for the facts a judgement rests on, and keep the judgement for your own logic.”
Hashim Khawaja Founding Engineer, Sixtyfour
1200 × 630 — ready to share

Frequently asked

A company research API takes what you know about a company, such as its name or website, researches it when you ask and returns the fields you define, with the sources they came from. A lookup against a stored company database returns the record a provider already holds instead. Research suits fields specific to your workflow, small or new companies, and facts someone needs to check at the source.

A company information API usually looks a company up in a stored database and returns the record it holds, which is quick and consistent in shape for standard fields at volume. A company research API does the work when you ask, reading sources across the web and returning the fields you define with the pages they came from. Many teams use a lookup for standard firmographics and research for questions a record does not answer, such as who owns a company or what it announced this year.

Yes. Sixtyfour's Company Intelligence endpoint takes a company name, website, domain or LinkedIn page and returns the fields you describe in a struct, with the pages they were read on and, if you ask, a 0 to 100 confidence score and a justification for each field. Its People Intelligence endpoint does the same for a person, and both have sync, async and bulk versions. Anything the research could not establish comes back empty for a person on your team to decide.

With Sixtyfour, upload the list as one Bulk Intelligence job to the company endpoint: a CSV, JSON, JSONL or NDJSON file of up to 100 MB and 100,000 rows, plus a config that names the fields and the tier. The submission returns a task ID and a credit estimate, job status reports progress while it runs, and a completed job carries signed links to the result files or calls your webhook. An Idempotency-Key header makes the submission safe to retry.

A Sixtyfour Company Intelligence run takes up to 10 minutes, so a synchronous call needs a client timeout of at least 15 minutes. In production, start the job at the async endpoint, which returns a task ID at once, and poll job status every 10 seconds or slower, or pass a webhook URL to receive the result when the job completes. A sync call that times out keeps running and is charged, and Company Intelligence has no retry key, so store the task ID and never start the same company twice by accident.

Ask for field confidence. Each field then comes back with a 0 to 100 score, a short justification and the URLs it was read on, and the named levels run from Confirmed at 85 and above to Unreliable below 15. Send anything under your threshold, anything with no sources and any empty field to a person, who can start from the pages the research found.

As of November 2026, Company Intelligence costs 0.3 credits a company at the micro tier, 1 at low (the default), 5 at medium and 15 at high, which is enabled on request. Field confidence, finding people at the company and the org chart cost nothing extra, a failed single-company job is not charged, and bulk jobs cost the same per row. Filter Search, for building the list, costs 0.1 credits per result returned.

Get started

Create an account, get an API key and research your first company from its name or website, with the fields you define returned alongside the sources the research found.

Sign up
  1. What is a company research API?
  2. How to research a company with Sixtyfour
  3. Tiers and cost
  4. How do you run research that takes several minutes?
  5. How do you find the companies to research?
  6. How do you research a list of companies with an API?
  7. How do you get results without polling?
  8. How should you design the fields?
  9. What should you do with low-confidence or empty fields?
  10. Reading the results fairly
  11. The short version