# Domain lookup

`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](/docs/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](#when-the-domain-doesn-t-exist).

Input that can't be a website is rejected with [`invalid_domain`](/docs/errors#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`](/docs/company)).

## Example

```bash tab="cURL"
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"}'
```

```javascript tab="JavaScript"
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);
```

```python tab="Python"
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`](/docs/company#response) 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:

```json title="200 OK"
{
  "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`](/docs/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`](/docs/errors#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`:

```json title="404 Not Found"
{
  "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](/docs/caching).

## 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](/docs/billing).
