# Keys and limits

> How to send the key, plan limits, balance headers and how to save requests.

> Documentation index for agents: https://kinodata.space/en/llms.txt — the whole documentation in one file: https://kinodata.space/en/llms-full.txt

Every request carries your API key in the `X-API-Key` header. Two limits apply: requests per second and requests per day. This page covers how to send the key, how to watch what you have left, and how to avoid wasting requests.

## Send the key

Put the key in the `X-API-Key` header of every request. Every example in these docs reads it from the `KINODATA_KEY` environment variable:

```bash
curl -H "X-API-Key: $KINODATA_KEY" \
  "https://api.kinodata.space/v1.3/ids/kp/447301"
```

Response `200`:

```json
{
  "imdb_id": "tt1375666",
  "kp_id": "447301",
  "tmdb_id": "27205",
  "tmdb_tv_id": null,
  "kinorium_id": "472809"
}
```

Without a key, or with a wrong one, the API responds with `401`. The same request without the header:

```bash
curl -s "https://api.kinodata.space/v1.3/ids/kp/447301"
```

Response `401`:

```json
{
  "error": {
    "code": "unauthorized",
    "message": "missing api key"
  }
}
```

The only address that works without a key is `https://api.kinodata.space/health`, a check that the API is up.

## Plan limits

| plan | requests per second | requests per day |
|---|---|---|
| Free | 10 | 500 |
| Basic | 20 | 5,000 |
| Business | 40 | unlimited under fair use |

Basic and Business let you issue two keys. The limits stay per account, not per key: two Basic keys share one 20 requests per second and 5,000 a day rather than getting that much each.

Days are counted in UTC: the daily quota resets at midnight UTC, not 24 hours after your first request. Plans and prices are on the [Pricing](https://kinodata.space/en/pricing) page.

## Watch what's left

Every response carries headers with your current balance:

| header | what it is |
|---|---|
| `X-RateLimit-Limit` | Requests allowed per second. |
| `X-RateLimit-Remaining` | Requests left in the current second. |
| `X-RateLimit-Reset` | When the next second starts, as Unix time. |
| `X-Quota-Limit` | Requests allowed per day. |
| `X-Quota-Remaining` | Requests left today. |
| `X-Quota-Reset` | When the quota resets, as Unix time: the next midnight UTC. |
| `Retry-After` | Only on `429`: how many seconds to wait before retrying. |

This is what the headers look like for a Basic key:

```text
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 19
X-RateLimit-Reset: 1791566201
X-Quota-Limit: 5000
X-Quota-Remaining: 4812
X-Quota-Reset: 1791590400
```

A key without a limit doesn't get the matching headers. Browsers can read them too: the API lists them in `Access-Control-Expose-Headers`.

## When you hit a limit

The API responds with `429`. The message says which limit you hit:

```json
{ "error": { "code": "rate_limited", "message": "quota limit exceeded" } }
```

- `rate limit exceeded`: too many requests in this second. Wait `Retry-After` seconds and spread your requests out; a queue or a pause between requests works better than retries.
- `quota limit exceeded`: the daily quota is used up. `Retry-After` shows the seconds until midnight UTC. If you run out regularly, have a look at the [plans](https://kinodata.space/en/pricing).

A request rejected by the per-second limit doesn't count against the daily quota.

## Save requests

### Ask only for the blocks you need

A full title card is about 26 KB. If you need the title and the ratings, ask for just those: `fields=title,ratings`. The response gets smaller, and so does the load on your network. More in [Lists and pagination](https://kinodata.space/en/docs/lists.md#only-the-fields-you-need).

### Cache responses

You can keep a response for five minutes: the API sends `Cache-Control: private, max-age=300`. Your server or the browser may store it, but a shared cache such as a CDN may not: the response belongs to the key.

To check whether the data has changed without downloading it again, use a conditional request.

1. Save the `ETag` header from the response:

```bash
ETAG=$(curl -sI -H "X-API-Key: $KINODATA_KEY" "https://api.kinodata.space/v1.3/titles/tmdb/27205" | grep -i '^etag:' | cut -d' ' -f2 | tr -d '\r')
```

2. On the next request to the same URL, send it back in `If-None-Match` as is, quotes included:

```bash
curl -s -o /dev/null -w '%{http_code}\n' -H "X-API-Key: $KINODATA_KEY" -H "If-None-Match: $ETAG" "https://api.kinodata.space/v1.3/titles/tmdb/27205"
```

If the data hasn't changed, you get `304` with no body, which means your saved response is still valid. A `304` doesn't count against the quota, so you can check for fresh data as often as you like.

### Turn on compression

With the `Accept-Encoding: gzip` header the *Inception* card is 8 KB instead of 26 KB. Most HTTP clients ask for compression on their own; curl does it only with the `--compressed` flag.

## Keep the key on the server

The API answers browser requests from any site, but a key in website or mobile app code is visible to anyone who opens the developer tools. Send requests from your own server and hand the browser ready data.

## When something goes wrong

Every response carries two service headers:

| header | what it is |
|---|---|
| `X-Request-Id` | The request ID. Include it when you contact us and we'll find your request. |
| `X-API-Version` | The API version that answered: `1.3`. |

## Next

- [Errors](https://kinodata.space/en/docs/errors.md): every code and what to do about it.
- [Lists and pagination](https://kinodata.space/en/docs/lists.md): how to page through long responses.
