> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pinakelai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Caching

> ETags and conditional requests on retrieve, Cache-Control on every response, and compression.

## What each response allows

| Response | `Cache-Control` | What it means for you |
| - | - | - |
| `GET /v1/trademarks/{id}` | `private, no-cache` | You may store it, but must revalidate it with the `ETag` before reusing it. |
| `GET /v1/jurisdictions`, `/v1/jurisdictions/{code}` | `private, max-age=60` | Reuse it for up to 60 seconds. |
| `GET /v1/openapi.json` | `public, max-age=300` | Reuse it for up to 5 minutes. |
| Everything else, including search, batch, the feed and errors | `no-store` | Do not cache the response. |

`private` means shared caches, such as a corporate proxy or a CDN, must not store the response: it was produced for your key.

For records, the most reliable cache is your own store, updated from the [changes feed](https://docs.pinakelai.com/guides/syncing.md) or by [polling ids in batches](https://docs.pinakelai.com/use-cases/docketing-sync.md). HTTP caching helps when you re-read individual records.

## Conditional requests

Retrieve returns a weak `ETag`, a hash of the response body:

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json
ETag: W/"5b0c6e2f9a41d8e37c2a"
Cache-Control: private, no-cache
```

Send it back in `If-None-Match`. If the record has not changed, the answer is `304 Not Modified` with no body, repeating `ETag`, `Cache-Control` and `Vary`:

```bash cURL theme={null}
curl -i "https://api.pinakelai.com/v1/trademarks/tm_AD_2376e41e-3d24-57e3-af6f-6a176f9985ba" \
  -H "Authorization: Bearer $PINAKEL_API_KEY" \
  -H 'If-None-Match: W/"5b0c6e2f9a41d8e37c2a"'
```

```python Python theme={null}
cache: dict[str, tuple[str, dict]] = {}   # id -> (etag, record)


def get_record(tm_id: str) -> dict:
    headers = {"If-None-Match": cache[tm_id][0]} if tm_id in cache else {}
    resp = session.get(f"{API}/trademarks/{tm_id}", headers=headers, timeout=30)
    if resp.status_code == 304:
        return cache[tm_id][1]
    resp.raise_for_status()
    cache[tm_id] = (resp.headers["ETag"], resp.json())
    return cache[tm_id][1]
```

```javascript JavaScript theme={null}
const cache = new Map(); // id -> { etag, record }

async function getRecord(id) {
  const cached = cache.get(id);
  const res = await fetch(`${API}/trademarks/${id}`, {
    headers: { ...headers, ...(cached ? { "If-None-Match": cached.etag } : {}) },
  });
  if (res.status === 304) return cached.record;
  if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
  const record = await res.json();
  cache.set(id, { etag: res.headers.get("ETag"), record });
  return record;
}
```

A 304 is still a counted call, but it carries no body, so it is faster and lighter for both sides. The ETag changes whenever any part of the response changes, including `updated_at`, so a [register reload](https://docs.pinakelai.com/data/how-records-change.md#reloads) invalidates every ETag in that register even when nothing visible changed.

ETags depend on the exact request. A retrieve with `include=goods_services` has a different ETag from the same record without it; keep one cache entry per URL.

## Compression

Responses of 1 KB or more are compressed with gzip when the request allows it. Send `Accept-Encoding: gzip`: most HTTP libraries do this by default, and with cURL use `--compressed`. Search and batch responses compress well, often to a fifth of their size.

## Don't cache errors

Error responses are `no-store`. A 503 or 429 says something about this moment; retry it as described in [Handling errors](https://docs.pinakelai.com/guides/errors.md), and never serve a cached error to your own users.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.