Skip to main content

groundtruth-company

Give it a company's website domain. Get back what is publicly known about the company, in one JSON object, with no scraping code and no dependencies.

pip install groundtruth-company
from groundtruth_company import Client

client = Client()                      # free tier, no key
info = client.company("stripe.com")
print(info["hiring"]["open_roles"], info["web"]["email_provider"])

Python 3.9+. Standard library only.

What one call returns

client.company(domain, name=None, country=None, ticker=None) runs several lookups in parallel and joins them:

Part What is in it
web Domain record: registrar, created date, email provider, DNS, DMARC
tech The website's tech stack: CMS, analytics, CDN, frameworks, payment processors, each with a confidence
hiring From the company's own job board: open roles by department and place, salary ranges where published, roles opened and closed in the last 30 days
registry The official company register entry. Add name and country (gb, no, fr, fi, cz, sk, ie, au, or global for LEI)
sec Reported revenue from SEC filings. Add ticker (US public companies)
missing Every part that had nothing to report, with the reason

Each part stands alone: if one source is down or has nothing, the rest still answer and missing says why. Only a call where every part failed is an error, and that is never charged. Every item links to its source so you can check it.

{
  "domain": "stripe.com",
  "parts_found": ["web", "tech", "hiring"],
  "web": {"registrar": "SafeNames Ltd.", "created": "1995-09-12", "email_provider": "Google Workspace", "dmarc": "reject"},
  "tech": {"count": 21, "technologies": [{"name": "Cloudflare", "categories": ["CDN"], "confidence": 100}]},
  "hiring": {"ats": "greenhouse", "board": "stripe", "open_roles": 412, "recent": {"window_days": 30, "opened": 37, "closed": 22}},
  "missing": [{"part": "registry", "reason": "give name and country to add the official register entry"}]
}

(Trimmed to the shape. Real answers carry more fields.)

client.contacts(domain) reads the company's own website (home, contact, about, team and careers pages) and returns what the company published there:

  • phone numbers
  • role inboxes such as sales@ and info@
  • the contact page and the postal address
  • for US companies only, every other address at the company's own domain, exactly as published (people, departments, locations)

Every item carries the page it came from. Emails are never guessed from a pattern, addresses at other domains are left out, and robots.txt is honored. Results are cached for 30 days, so a site is read about once a month however often it is asked for.

{
  "domain": "example-dental.com", "us_company": True,
  "contact_page": "https://example-dental.com/contact",
  "phones": [{"number": "+1 512-555-0142", "source_url": "https://example-dental.com/contact"}],
  "role_emails": [{"email": "frontdesk@example-dental.com", "source_url": "https://example-dental.com/contact"}],
  "other_emails": [{"email": "dr.lee@example-dental.com", "source_url": "https://example-dental.com/team"}],
  "address": {"street": "100 Main St", "city": "Austin", "region": "TX", "postal_code": "78701", "country": "US", "source_url": "https://example-dental.com/"},
  "pages_read": [{"url": "https://example-dental.com/", "kind": "home"}],
  "notes": [], "fetched_at": "2026-10-30T18:00:00.000Z", "cached": False
}

Prices

Free tier 10 calls a day per caller, no key, no sign-up
$10 once 2,000 calls that never expire. Buy
$49 a month 20,000 calls a month, cancel any time. Buy
Pay per call, no account x402 (USDC on Base) at /x402/v1/company and /x402/v1/company-contacts

A company lookup uses 2 of a key's calls (so $10 is about 1,000 lookups, or $0.01 each). A contacts lookup uses 4 (about $0.02 each). Free-tier calls count as one each. A call that fails is not charged: a key gets its calls back.

The key is shown on the page Stripe sends you to after paying. Use it like this:

client = Client(api_key="your-key")

Live prices: https://ats-jobs-api.groundtruthtools.workers.dev/pricing

Errors

Every error is a subclass of GroundTruthError and carries .status and .body.

Exception HTTP Meaning
BadInputError 400 Missing or malformed input, for example not a domain. Not charged
InvalidKeyError 401 The key is not active
PaymentRequiredError 402 (also 429) Free allowance used up, or the key has no calls left. The message includes the buy links; also .buy_url and .monthly_url
NotFoundError 404 Nothing found, for example an unregistered domain. Not charged
SiteBlockedError 422 The company's site blocks automated readers. Not charged
UpstreamError 5xx, or no connection An upstream source failed. Not charged, safe to retry
from groundtruth_company import Client, PaymentRequiredError, NotFoundError

try:
    info = Client().company("stripe.com")
except PaymentRequiredError as e:
    print(e)            # says what happened and where to buy a key
except NotFoundError:
    print("no such company")

Examples

The tool functions below return the error message as text instead of raising, so an agent can read it and tell the user (including the buy link). Each example cites the framework docs it was written against.

Plain Python

import json
from groundtruth_company import Client

client = Client()  # or Client(api_key="...")

company = client.company("stripe.com", name="Stripe")
print(json.dumps(company["hiring"], indent=2))

reach = client.contacts("example-dental.com")
for phone in reach["phones"]:
    print(phone["number"], "from", phone["source_url"])

LangChain

A @tool wrapper:

# Docs: https://docs.langchain.com/oss/python/langchain/tools
import json
from langchain.agents import create_agent
from langchain.tools import tool
from groundtruth_company import Client, GroundTruthError

gt = Client()  # or Client(api_key="...")


@tool
def company_lookup(domain: str) -> str:
    """Everything public about a company from its website domain: registrar, DNS and email
    provider, tech stack, and hiring (open roles, salary ranges, recent openings and closings)."""
    try:
        return json.dumps(gt.company(domain))
    except GroundTruthError as e:
        return str(e)


@tool
def company_contacts(domain: str) -> str:
    """How to reach a company, from its own website: phones, role inboxes such as sales@,
    postal address, and the contact page. Every item names its source page."""
    try:
        return json.dumps(gt.contacts(domain))
    except GroundTruthError as e:
        return str(e)


agent = create_agent(model="openai:gpt-5.5", tools=[company_lookup, company_contacts])
result = agent.invoke({"messages": [{"role": "user", "content": "Is stripe.com hiring? What do they run on?"}]})

The remote MCP endpoint (company_lookup is in it; company-contacts is REST and x402 only for now). Current LangChain moved MCP into langchain.mcp:

# Docs: https://docs.langchain.com/oss/python/langchain/mcp
#       https://docs.langchain.com/oss/python/langchain/mcp/auth.md  (bearer token via a FastMCP client)
# pip install "langchain[mcp]"
from fastmcp.client import Client
from langchain.agents import create_agent
from langchain.mcp import MCPAdapter

URL = "https://ats-jobs-api.groundtruthtools.workers.dev/mcp"


async def main():
    # Free tier, no key:
    async with MCPAdapter(URL) as adapter:
        tools = await adapter.list_tools()
        agent = create_agent("openai:gpt-5.5", tools)
        return await agent.ainvoke({"messages": [{"role": "user", "content": "Look up stripe.com"}]})


# With a key, pass a FastMCP client that carries it:
#   async with MCPAdapter(Client(URL, auth="your-key")) as adapter: ...

With the older langchain-mcp-adapters package (its repository is archived, and its README points to langchain.mcp above, but it still works):

# Docs: https://github.com/langchain-ai/langchain-mcp-adapters
# pip install langchain-mcp-adapters
from langchain_mcp_adapters.client import MultiServerMCPClient

client = MultiServerMCPClient({
    "groundtruth": {
        "transport": "http",
        "url": "https://ats-jobs-api.groundtruthtools.workers.dev/mcp",
        # "headers": {"Authorization": "Bearer your-key"},   # optional, lifts the free tier
    }
})
tools = await client.get_tools()

CrewAI

A custom BaseTool:

# Docs: https://docs.crewai.com/en/learn/create-custom-tools
import json
from typing import Type

from crewai import Agent, Crew, Task
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
from groundtruth_company import Client, GroundTruthError

gt = Client()  # or Client(api_key="...")


class DomainInput(BaseModel):
    domain: str = Field(..., description="The company's website domain, for example stripe.com")


class CompanyLookupTool(BaseTool):
    name: str = "Company lookup"
    description: str = (
        "Everything public about a company from its website domain: registrar, DNS and email "
        "provider, tech stack, and hiring (open roles, salary ranges, recent openings and closings)."
    )
    args_schema: Type[BaseModel] = DomainInput

    def _run(self, domain: str) -> str:
        try:
            return json.dumps(gt.company(domain))
        except GroundTruthError as e:
            return str(e)


class CompanyContactsTool(BaseTool):
    name: str = "Company contacts"
    description: str = (
        "How to reach a company, from its own website: phones, role inboxes such as sales@, "
        "postal address and contact page. Every item names its source page."
    )
    args_schema: Type[BaseModel] = DomainInput

    def _run(self, domain: str) -> str:
        try:
            return json.dumps(gt.contacts(domain))
        except GroundTruthError as e:
            return str(e)


researcher = Agent(
    role="Company researcher",
    goal="Find out what a company does, what it runs on, whether it is hiring, and how to reach it",
    backstory="You check public facts and cite where they came from.",
    tools=[CompanyLookupTool(), CompanyContactsTool()],
)
task = Task(
    description="Look up stripe.com and summarise hiring and tech stack.",
    expected_output="A short summary with the numbers.",
    agent=researcher,
)
print(Crew(agents=[researcher], tasks=[task]).kickoff())

The remote MCP endpoint through CrewAI's MCPServerAdapter:

# Docs: https://docs.crewai.com/en/mcp/overview
#       https://docs.crewai.com/en/mcp/streamable-http
# pip install 'crewai-tools[mcp]'
from crewai import Agent
from crewai_tools import MCPServerAdapter

server_params = {
    "url": "https://ats-jobs-api.groundtruthtools.workers.dev/mcp",
    "transport": "streamable-http",
}

with MCPServerAdapter(server_params, "company_lookup", connect_timeout=60) as tools:
    researcher = Agent(
        role="Company researcher",
        goal="Look up companies by domain",
        backstory="You check public facts.",
        tools=tools,
    )

With a key, CrewAI's documented way to send a header is the mcps field with MCPServerHTTP:

# Docs: https://docs.crewai.com/en/mcp/overview  (structured configuration)
from crewai import Agent
from crewai.mcp import MCPServerHTTP

researcher = Agent(
    role="Company researcher",
    goal="Look up companies by domain",
    backstory="You check public facts.",
    mcps=[
        MCPServerHTTP(
            url="https://ats-jobs-api.groundtruthtools.workers.dev/mcp",
            headers={"Authorization": "Bearer your-key"},
            streamable=True,
        ),
    ],
)

LlamaIndex

A FunctionTool:

# Docs: https://developers.llamaindex.ai/python/framework/module_guides/deploying/agents/tools/
import json
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.core.tools import FunctionTool
from groundtruth_company import Client, GroundTruthError

gt = Client()  # or Client(api_key="...")


def company_lookup(domain: str) -> str:
    """Everything public about a company from its website domain: registrar, DNS and email
    provider, tech stack, and hiring (open roles, salary ranges, recent openings and closings)."""
    try:
        return json.dumps(gt.company(domain))
    except GroundTruthError as e:
        return str(e)


def company_contacts(domain: str) -> str:
    """How to reach a company, from its own website: phones, role inboxes such as sales@,
    postal address and contact page. Every item names its source page."""
    try:
        return json.dumps(gt.contacts(domain))
    except GroundTruthError as e:
        return str(e)


tools = [FunctionTool.from_defaults(company_lookup), FunctionTool.from_defaults(company_contacts)]
agent = FunctionAgent(llm=llm, tools=tools)   # llm: any LlamaIndex LLM you have configured
response = await agent.run("Is stripe.com hiring? What do they run on?")

The remote MCP endpoint with McpToolSpec:

# Docs: https://developers.llamaindex.ai/python/framework-api-reference/tools/mcp/
# pip install llama-index-tools-mcp
from llama_index.core.agent.workflow import FunctionAgent
from llama_index.tools.mcp import BasicMCPClient, McpToolSpec

client = BasicMCPClient(
    "https://ats-jobs-api.groundtruthtools.workers.dev/mcp",
    # headers={"Authorization": "Bearer your-key"},   # optional, lifts the free tier
)
tool_spec = McpToolSpec(client=client, allowed_tools=["company_lookup"])
tools = await tool_spec.to_tool_list_async()
agent = FunctionAgent(llm=llm, tools=tools)

How these examples were checked

Import paths were read from the framework docs linked above on 2026-10-30. The four MCP connection snippets without a key (langchain.mcp, langchain-mcp-adapters, CrewAI MCPServerAdapter, LlamaIndex McpToolSpec) were each run against the live endpoint and listed the company_lookup tool. The key variants (headers, auth=, MCPServerHTTP) follow the docs but were not run with a real key. The agent wiring (model names, llm) is yours to fill in.

Other things this API does

The same host serves job-board data (open jobs at thousands of companies), web page reading, email checks, domain records and more. The full list: https://ats-jobs-api.groundtruthtools.workers.dev/llms.txt

MIT licensed.

Metadata

Release files for groundtruth-company 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for groundtruth-company 0.1.0
File Size Uploaded Provenance Actions
groundtruth_company-0.1.0.tar.gz 12.9 kB Without provenance Details for groundtruth_company-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for groundtruth-company 0.1.0
File Interpreter ABI Platform Actions
groundtruth_company-0.1.0-py3-none-any.whl Python 3 none any Details for groundtruth_company-0.1.0-py3-none-any.whl

Total release size: 23.7 kB

Release files / groundtruth_company-0.1.0.tar.gz

Download URL groundtruth_company-0.1.0.tar.gz
Size 12.9 kB
Tags Source
SHA-256 checksum
How to use checksums
749b7f66d5f17d0d8483a8cd563216acf33e987c423495c6991c3f22c2de6465
BLAKE2b-256 checksum
How to use checksums
dfa218285796f282aecd0e1b57650aa41ad5e719cdea3d7bbb9bce16c107f3d8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Release files / groundtruth_company-0.1.0-py3-none-any.whl

Download URL groundtruth_company-0.1.0-py3-none-any.whl
Size 10.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
24324e4cb3355c348e4169017e1b8291445bc09ac916a7cfae1747485ea18ffd
BLAKE2b-256 checksum
How to use checksums
eb2bb31a1143ae5e1935b3577aa9ad174cf8fa40c6e2868b870806a75c13aa07
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.10

Security for groundtruth-company 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page