Domain lookup
POST /v1/domain finds the company behind a website domain and returns its structured company profile.
POST https://api.happyfetch.net/v1/domain
Request#
| Field | Type | Required | Meaning |
|---|---|---|---|
domain |
string | yes | A website: a bare domain or any URL on it. |
max_age |
integer, seconds | no | The oldest cached answer you accept. 0 forces a fresh lookup. Defaults to 7 days. See Caching. |
The lookup is for the domain you could register, so every address on a site leads to the same company and the same cache entry:
| You send | Looked up as |
|---|---|
stripe.com or STRIPE.COM |
stripe.com |
https://www.stripe.com/pricing?x=1 |
stripe.com. The scheme, www., port, path and query don't matter. |
http://careers.booking.com |
booking.com |
https://www.bbc.co.uk/news |
bbc.co.uk |
A domain with an ending that doesn't exist, like stripe.cmo, is still a domain: the lookup runs and
answers that it doesn't exist.
Input that can't be a website is rejected with invalid_domain and isn't
charged: a single word, an IP address, localhost, or a LinkedIn company URL or slug (the error points
you to POST /v1/company).
Example#
curl https://api.happyfetch.net/v1/domain --max-time 120 \
-H "Authorization: Bearer $HAPPYFETCH_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"domain":"stripe.com"}'const res = await fetch("https://api.happyfetch.net/v1/domain", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HAPPYFETCH_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(), // send the same key when you retry this call
},
body: JSON.stringify({
"domain": "stripe.com"
}),
signal: AbortSignal.timeout(120_000),
});
const { data, meta, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
console.log(meta.request_id, meta.cache.status, data);import os, uuid, requests
res = requests.post(
"https://api.happyfetch.net/v1/domain",
headers={
"Authorization": f"Bearer {os.environ['HAPPYFETCH_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()), # send the same key when you retry this call
},
json={
"domain": "stripe.com"
},
timeout=120,
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["meta"]["request_id"], body["meta"]["cache"]["status"])Response#
data is the same company profile that POST /v1/company returns, with the
same 18 fields. meta has one addition, resolved:
| Field | Meaning |
|---|---|
resolved.domain |
The domain that was looked up, such as booking.com for http://careers.booking.com. |
resolved.verified |
true when the website on the company's profile is on this domain, or on the domain the site redirects to. false when the company was matched to this domain but its profile lists another website, such as a careers site or a parent company's. Check these before you rely on them. |
The complete response for stripe.com, exactly as the API returns it:
{
"data": {
"name": "Stripe",
"tagline": "Help increase the GDP of the internet.",
"description": "Stripe builds programmable financial services. Millions of companies—from the world’s largest enterprises to the most ambitious startups—use Stripe to accept payments, grow their revenue, and accelerate new business opportunities. Headquartered in San Francisco and Dublin, the company aims to increase the GDP of the internet.",
"industry": "Technology, Information and Internet",
"websiteUrl": "https://stripe.com",
"phone": null,
"logo": "https://media.licdn.com/dms/image/v2/D560BAQE2ZfJyfn-VCg/company-logo_200_200/B56ZlyKwpUKIAI-/0/1758557047806/stripe_logo?e=2147483647&v=beta&t=En4Hm6NDGbDTYCoOQ0Ko5Ne2gylx0WRb0yL2GlOeXBQ",
"backgroundUrl": "https://media.licdn.com/dms/image/v2/D563DAQFt98MykXcktg/image-scale_191_1128/B56Z352EMMKIAY-/0/1778013193531/stripe_cover?e=2147483647&v=beta&t=UwiZWbPrYkD77DpQmZIjVuuqKMCfyZ-e-YcHE336MlY",
"followerCount": 1721407,
"employeeCount": 16594,
"employeeCountRange": {
"start": 5001,
"end": 10000
},
"foundedOn": {
"year": 2010
},
"headquarter": {
"city": "South San Francisco",
"country": "US",
"geographicArea": "California",
"postalCode": "94080",
"street1": "354 Oyster Point Blvd",
"street2": null
},
"locations": [
{
"city": "South San Francisco",
"country": "US",
"postalCode": "94080",
"state": "California",
"street1": "354 Oyster Point Blvd",
"street2": null
}
],
"specialities": [],
"universalName": "stripe",
"linkedInUrl": "https://www.linkedin.com/company/stripe",
"linkedInId": "2135371"
},
"meta": {
"request_id": "c41d7e02-5b8a-4f3c-9e61-7a2d0b95f4e8",
"endpoint": "domain",
"credits_charged": 4,
"credits_remaining": 196,
"cache": {
"status": "miss",
"age_seconds": 0,
"fetched_at": "2026-09-22T17:40:00.000Z",
"max_age_seconds": 604800
},
"content_hash": "sha256:5da96e23d8f77cc3b21e33ef47ed3f0086328e61fdf427f2941e351f56af9554",
"canonical": {
"slug": "stripe",
"id": "2135371"
},
"resolved": {
"domain": "stripe.com",
"verified": true
}
}
}meta.canonical holds the company's canonical slug and numeric id, the same keys
POST /v1/company uses, so you can store companies found either way under one key.
When the domain doesn't exist#
A lookup that finds no company is an answer too: HTTP 404 with not_found,
charged 4 credits, and nothing when the answer comes from our cache. error.message says
plainly what was found, and error.reason says
which answer it is:
reason |
Meaning |
|---|---|
no_website |
Nothing answers at the domain, and no company page lists it. Often a typo. |
no_linkedin_page |
The website exists, but it has no company page. |
The complete response for stripe.cmo:
{
"error": {
"type": "not_found_error",
"code": "not_found",
"message": "stripe.cmo doesn't exist: no website answers there, and no LinkedIn company page lists it.",
"retryable": false,
"doc_url": "https://happyfetch.net/docs/errors#not_found",
"reason": "no_website"
},
"meta": {
"request_id": "e8b3f190-2c6d-4a57-b0d4-51f9a6c3e27b",
"credits_charged": 4,
"credits_remaining": 196,
"cache": {
"status": "miss",
"age_seconds": 0,
"fetched_at": "2026-09-23T09:12:40.118Z",
"max_age_seconds": 604800
}
}
}The answer is cached too, for a shorter time than a found company. Asking again soon gets it from the cache, for free.
Caching#
A domain lookup and a company lookup share the company's cache entry. After a lookup of stripe.com,
POST /v1/company for stripe is a cache hit, and so is https://stripe.com/pricing here. max_age
works as on every endpoint; see Caching and max_age.
Price#
A lookup by website domain costs 4 credits fetched now and 2 credits from our cache. A domain with no company behind it (no website, or no LinkedIn company page) costs 4 credits, and nothing from our cache. Lookups that fail on our side are refunded automatically, and input that isn't a domain is never charged. See Credits, billing and refunds.