# Cache check

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

Free. Before you look a company up, ask how old our copy is. Then decide: take it from the cache for
less, or pay for a fresh fetch with `max_age`.

## Request

Send the body you'd send to the lookup, with one of these:

| Field | Type | Meaning |
|---|---|---|
| `url` | string | A company reference, as for [`POST /v1/company`](/docs/company). |
| `domain` | string | A website, as for [`POST /v1/domain`](/docs/domain). |
| `max_age` | integer, seconds | Optional: the `max_age` you'd send with the lookup. Defaults to its cache TTL. |

Send exactly one of `url` and `domain`. Input the lookup would reject is rejected here too, with the same
error codes, and nothing is ever charged.

## Example

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

```javascript tab="JavaScript"
const res = await fetch("https://api.happyfetch.net/v1/cache", {
  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/company/stripe"
  }),
  signal: AbortSignal.timeout(120_000),
});
const { data, meta, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
console.log(data.from_cache, data.price_credits, data.age_seconds);
```

```python tab="Python"
import os, uuid, requests

res = requests.post(
    "https://api.happyfetch.net/v1/cache",
    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/company/stripe"
    },
    timeout=120,
)
body = res.json()
if not res.ok:
    raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["data"]["from_cache"], body["data"]["price_credits"], body["data"]["age_seconds"])
```

## Response

The answer for Stripe's company page, three days after we fetched it:

```json title="200 OK"
{
  "data": {
    "lookup": "company",
    "cached": true,
    "answer": "company",
    "fetched_at": "2026-09-20T09:14:02.000Z",
    "age_seconds": 259200,
    "max_age_seconds": 604800,
    "from_cache": true,
    "price_credits": 1,
    "fetch_price_credits": 2
  },
  "meta": {
    "request_id": "5d0a9c3e-71f2-4b86-a4e0-c29b8f1d6a37",
    "endpoint": "cache",
    "credits_charged": 0,
    "credits_remaining": 200
  }
}
```

| Field | Meaning |
|---|---|
| `lookup` | `company` or `domain`: the lookup the answer is about. |
| `cached` | Whether we hold an answer for it. |
| `answer` | `company`, or `not_found` when what we hold is the answer that there's no company page. `null` when nothing is cached. |
| `fetched_at`, `age_seconds` | When we got that answer, and how old it is (86400 seconds is a day). `null` when nothing is cached. |
| `max_age_seconds` | The oldest answer the lookup accepts: your `max_age`, or its default. |
| `from_cache` | Whether the lookup, sent now with this body, would be answered from our cache. |
| `price_credits` | What that lookup costs now: the cache price, `0` for a not-found from the cache, or the fetch price. |
| `fetch_price_credits` | What it costs fetched fresh, with `max_age: 0`. |

The answer says nothing about which company it is: that's what the lookup is for. `meta` holds the
request id and `credits_charged`, which is always `0`.

## Price

Free. Calls count toward your API key's [rate limit](/docs/rate-limits) like any other.
