# Person lookup

`POST https://api.happyfetch.net/v1/person`

A person's profile summary: their name, location, the start of their About text, employers, schools and
follower count.

## Request

| Field | Type | Required | Meaning |
|---|---|---|---|
| `url` | string | yes | The person's LinkedIn profile, in any of the forms below. |
| `max_age` | integer, seconds | no | The oldest cached answer you accept. `0` forces a fresh lookup. Defaults to 24 hours. See [Caching](/docs/caching). |

Every form of the same profile is the same lookup and the same cache entry:

| You send | Read as |
|---|---|
| `https://www.linkedin.com/in/jane-example` | the name `jane-example`. Any LinkedIn host, with or without the scheme, and the query is ignored. |
| `in/jane-example` or `jane-example` | the name `jane-example` |
| `j%C3%BCrgen-m%C3%BCller` | `jürgen-müller`: percent-encoded names are decoded |

Anything else is rejected with [`invalid_person_reference`](/docs/errors#invalid_person_reference) and isn't
charged. That includes company pages (the error points you to [`POST /v1/company`](/docs/company)) and member
ids (`ACoAA…`), which aren't accepted.

## Example

```bash tab="cURL"
curl https://api.happyfetch.net/v1/person --max-time 120 \
  -H "Authorization: Bearer $HAPPYFETCH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://www.linkedin.com/in/jane-example"}'
```

```javascript tab="JavaScript"
const res = await fetch("https://api.happyfetch.net/v1/person", {
  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({
    "url": "https://www.linkedin.com/in/jane-example"
  }),
  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/person",
    headers={
        "Authorization": f"Bearer {os.environ['HAPPYFETCH_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),  # send the same key when you retry this call
    },
    json={
      "url": "https://www.linkedin.com/in/jane-example"
    },
    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

The complete response for a test profile, exactly as the API returns it:

```json title="200 OK"
{
  "data": {
    "vanityName": "jane-example",
    "url": "https://www.linkedin.com/in/jane-example",
    "name": "Jane Example",
    "location": {
      "locality": "Amsterdam",
      "country": "NL"
    },
    "about": "Building the web at Vercel",
    "currentEmployer": {
      "name": "Vercel",
      "slug": "vercel",
      "linkedInUrl": "https://www.linkedin.com/company/vercel"
    },
    "employers": [
      {
        "name": "Vercel",
        "slug": "vercel",
        "linkedInUrl": "https://www.linkedin.com/company/vercel"
      },
      {
        "name": "Stripe",
        "slug": "stripe",
        "linkedInUrl": "https://www.linkedin.com/company/stripe"
      }
    ],
    "employerCount": 3,
    "education": [
      {
        "name": "Example University",
        "slug": "example-university",
        "linkedInUrl": "https://nl.linkedin.com/school/example-university/",
        "startYear": 2012,
        "endYear": 2016
      }
    ],
    "schoolCount": 2,
    "followers": 1234
  },
  "meta": {
    "request_id": "b7e2c915-0f4a-4d38-9a6e-3c81d5f02e94",
    "endpoint": "person",
    "credits_charged": 1,
    "credits_remaining": 199,
    "cache": {
      "status": "miss",
      "age_seconds": 0,
      "fetched_at": "2026-09-25T10:04:18.000Z",
      "max_age_seconds": 86400
    },
    "content_hash": "sha256:3f9a1c7e5b2d8046e1a9c3b7d5f2e8a4c6b1d9e3f7a5c2b8d4e6f1a3c9b7d5e2"
  }
}
```

| Field | Meaning |
|---|---|
| `vanityName`, `url` | The profile's name and its canonical URL. |
| `name` | The person's name. |
| `location` | `locality` and `country`, or `null`. |
| `about` | The start of the About text, or `null`. The headline isn't included. |
| `currentEmployer` | The first listed employer: the current job, or the latest one when none is current. `null` when there's none, or when the profile doesn't show it. |
| `employers` | The employers the profile shows, in its order, each with `name`, `slug` and `linkedInUrl` (`null` without a LinkedIn page). |
| `employerCount` | How many employers the profile lists, hidden ones included. |
| `education` | The schools the profile shows, each with its page and `startYear` and `endYear` when given. |
| `schoolCount` | How many schools the profile lists, hidden ones included. |
| `followers` | The follower count, or `null`. |

Job titles and dates aren't included, and some employers and schools may be missing. The counts tell you
how many there are.

## The content hash

`meta.content_hash` covers `data`, so two answers with the same hash carry the same profile.

## Caching

A person is cached for 24 hours. A name without a profile is remembered too, and asking again within
that time is answered from the cache for free. People are personal data, so we keep them only for a limited
time after the last lookup, and delete them on request.

## Price

A person lookup costs 1 credit fetched now and 1 credit from cache. An answer that there's no profile at that name costs 1 credit, and nothing when it comes from the cache. Lookups that fail on our side are refunded automatically, and input that isn't a profile is
never charged. See [Credits, billing and refunds](/docs/billing).
