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: | Always | See Authentication. |
Accept | No | Leave it out, or include application or *. Anything else, application included, is 406. |
Content-Type: | 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. |
If-None-Match | No | Makes a read conditional: a match answers 304 with no body. |
Idempotency-Key | On a few POSTs | Makes a POST safe to retry. At most 255 characters. See 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, , unless the operation declares a cache lifetime of its own. Responses are never publicly cacheable. A few configuration reads declare private, , and discovery declares private, . |
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. |
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. |
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, or application 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. |
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: | 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. The seconds to wait. |
Allow | On a 405: the methods the path accepts. |
WWW-Authenticate | On a 401: Bearer . |
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.
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:
- 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.
- 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, code prohibited, and an unknown query parameter with 400 invalid-query-parameter. Neither is ignored. A write that silently drops a field would be worse than one that is refused.
Generating a client 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, 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, in OpenAPI 3.1. The 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.