# Headers and versioning

The request headers you may send, the response headers you get back, and how the API changes within v2.

This page lists the request headers the API reads, the response headers it sends, and what may change inside version 2. The reference repeats the header names on every operation page; their meaning is explained here, once.

## Request headers

| Header                           | Required                 | Notes                                                                                                                                                           |
| -------------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Authorization: Bearer <token>`  | Always                   | See [Authentication](https://developers.plnnrly.com/en/docs/api/v2/guides/authentication/).                                                                     |
| `Accept`                         | No                       | Leave it out, or include `application/json` or `*/*`. Anything else, `application/*` included, is `406`.                                                        |
| `Content-Type: application/json` | On a request with a body | Anything else is `415`. A `charset` parameter is fine. There is no form encoding and no multipart.                                                              |
| `If-Match`                       | No, never required       | Protects a write against overwriting a newer version. See [Concurrency and ETags](https://developers.plnnrly.com/en/docs/api/v2/guides/concurrency-and-etags/). |
| `If-None-Match`                  | No                       | Makes a read conditional: a match answers `304` with no body.                                                                                                   |
| `Idempotency-Key`                | On a few `POST`s         | Makes a `POST` safe to retry. At most 255 characters. See [Idempotency](https://developers.plnnrly.com/en/docs/api/v2/guides/idempotency/).                     |
| `X-Request-Id`                   | No                       | Your own correlation id. See below.                                                                                                                             |

92 operations accept `If-Match` and 58 accept `Idempotency-Key`, of which 3 require it. Each operation page lists the headers it accepts.

### Your own request id

Send `X-Request-Id` with 1 to 64 characters, using only the letters `A` to `Z` and `a` to `z`, the digits `0` to `9`, `_` and `-`. It is recorded with the request in our logs. Support can then find the request by your id as well as ours.

Nothing echoes it back. The `X-Request-Id` response header always carries our own id, and your id never appears in a response body. A value outside the pattern is dropped silently, and the request runs as normal.

## Response headers

### On every response

| Header                   | Meaning                                                                                                                                                                                                                         |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Cache-Control`          | `private, no-store`, unless the operation declares a cache lifetime of its own. Responses are never publicly cacheable. A few configuration reads declare `private, max-age=60`, and discovery declares `private, max-age=300`. |
| `Vary`                   | The request headers the response depends on. Always includes `Authorization`, so a cache cannot serve one caller's rows to another.                                                                                             |
| `RateLimit`              | What is left in the tightest rate bucket. See [Rate limits](https://developers.plnnrly.com/en/docs/api/v2/guides/rate-limits/).                                                                                                 |
| `RateLimit-Policy`       | That bucket's ceiling and window, as `<limit>;w=<seconds>`.                                                                                                                                                                     |
| `X-Plnnrly-Api-Version`  | The major version that answered. Always `2` on `/api/v2`.                                                                                                                                                                       |
| `X-Request-Id`           | Our id for this request, a ULID. Send it when you contact support.                                                                                                                                                              |
| `X-Content-Type-Options` | `nosniff`, problem documents included.                                                                                                                                                                                          |

### Only on some responses

| Header                       | When                                                                                                                                                                                                                                                                                            |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`               | On every response with a body: `application/json`, or `application/problem+json` on an error.                                                                                                                                                                                                   |
| `ETag`                       | On a `200` or `201` with a record (strong) or a page of records (weak, `W/"…"`), on a `304`, and the record's current tag on a `412`. Never on a `204`, and not on other errors. See [Concurrency and ETags](https://developers.plnnrly.com/en/docs/api/v2/guides/concurrency-and-etags/#etag). |
| `Location`                   | On a `201`: the absolute URL of the new record. The two file registrations, on attachments and on Drive files, do not send it.                                                                                                                                                                  |
| `Idempotency-Replayed: true` | On a `POST` whose response was replayed from an earlier request with the same `Idempotency-Key`. Absent otherwise.                                                                                                                                                                              |

### On errors

| Header             | When                                                                                                                                                                       |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Retry-After`      | On a `429`, a `503`, and a `409` [idempotency-key-in-flight](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#idempotency-key-in-flight). The seconds to wait. |
| `Allow`            | On a `405`: the methods the path accepts.                                                                                                                                  |
| `WWW-Authenticate` | On a `401`: `Bearer realm="plnnrly-api"`.                                                                                                                                  |
| `ETag`             | On a `412`: the record's current tag.                                                                                                                                      |

### Deprecated

| Header                  | Sent until | Use instead |
| ----------------------- | ---------- | ----------- |
| `X-RateLimit-Limit`     | 2027-09-01 | `RateLimit` |
| `X-RateLimit-Remaining` | 2027-09-01 | `RateLimit` |
| `X-RateLimit-Reset`     | 2027-09-01 | `RateLimit` |

`X-RateLimit-Reset` is a Unix timestamp, while the `reset` in `RateLimit` counts seconds from now. The reference strikes these headers through on every operation page.

## Versioning

The version is in the path, and only in the path: `/api/v2`. There is no version header to send and no version parameter. `?version=` is an unknown query parameter, so it answers `400` [invalid-query-parameter](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#invalid-query-parameter).

`X-Plnnrly-Api-Version` on a response only tells you which major version answered. It is useful in a log, not for choosing a version.

### What can change within v2

Changes within v2 are additive: a new field, a new optional parameter, a new endpoint, or a new value in a response enum. A change that would break a client does not ship in v2. Two requirements follow for your client:

1. **Accept fields you do not know.** Do not reject a response because it has an extra key. A field added next month must not break you.
2. **Accept enum values you do not know.** Enum fields and parameters marked as extensible may gain new values. The reference says so on the field, and the document carries 200 of these marks. Treat an unknown value as unhandled, for example by falling back to a default, never as an error.

Requests are closed. An unknown body key is refused with `422` [validation-failed](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#validation-failed), code `prohibited`, and an unknown query parameter with `400` [invalid-query-parameter](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#invalid-query-parameter). Neither is ignored. A write that silently drops a field would be worse than one that is refused.

[Generating a client](https://developers.plnnrly.com/en/docs/api/v2/guides/client-generation/) shows how to set up a generated client for these rules.

### Deprecation

When something is deprecated, the API tells you in the response:

| Deprecated                 | What you see                                                                                                                                                                                                                      |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| An endpoint                | A `Deprecation` header with the date it was deprecated (`@` and a Unix timestamp), and a `Sunset` header with the date it stops. When it has a replacement, a `Link` header with `rel="deprecation"` points to its documentation. |
| A field in a response      | A `deprecated_field` entry in `meta.warnings`, with `sunset` (the date it stops) and `replacement` (what to use instead).                                                                                                         |
| Anything, in the reference | It is struck through, with its sunset date beside it.                                                                                                                                                                             |

After its sunset date, a retired endpoint answers `410` [endpoint-retired](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#endpoint-retired), with `sunset` and `replaced_by`.

The warning codes `deprecated_parameter` and `deprecated_endpoint` are declared in the contract but not sent yet. Today nothing is deprecated except the three `X-RateLimit-*` headers above.

### Tracking changes

The whole contract is published as [/openapi.json](https://developers.plnnrly.com/openapi.json), in OpenAPI 3.1. The [changelog](https://developers.plnnrly.com/en/docs/changelog/) records what changed in each release. The `info.version` field in the document does not change with each release yet, so do not use it to detect a new release. To see exactly what changed, compare two copies of the document.

## Related

- [Errors and retries](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/)
- [Rate limits](https://developers.plnnrly.com/en/docs/api/v2/guides/rate-limits/)
- [Generating a client](https://developers.plnnrly.com/en/docs/api/v2/guides/client-generation/)
