# Generating a client

Generate a client or import the API into an API tool from /openapi.json, and avoid the traps that break generated clients.

plnnrly publishes no SDK, but the whole API is described in one OpenAPI document. Generate a client from it, or import it into an API tool. This page lists the places where a generated client tends to break, and how to avoid them.

In the examples, `<your-url>` is the address you sign in to plnnrly on, such as `acme.plnnrly.com`, or your organisation's own domain.

## The document

[/openapi.json](https://developers.plnnrly.com/openapi.json) is an OpenAPI 3.1 document of about 5.2 MB, generated from the API's own code. It describes every operation in the reference.

- **Server.** The server URL is `https://{tenant_host}/api/v2`. Set the variable `tenant_host` to `<your-url>`.
- **Authentication.** Every operation takes a bearer token: a personal access token or an OAuth access token. Each operation's `security` names the scopes that cover it, under the schemes `bearerToken` and `oauth2`. A `DELETE` lists `bearerToken` only, because no OAuth scope covers deletes. See [Authentication](https://developers.plnnrly.com/en/docs/api/v2/guides/authentication/).
- **Operation ids.** Every operation has an id, such as `api.v2.work-items.links.store`, which a generator can use to name its methods.
- **Nullable fields** use the OpenAPI 3.1 form, such as `"type": ["string", "null"]`. Use a generator that supports OpenAPI 3.1.

## Accept fields you do not know

New fields can appear in any response within v2. Do not let your client reject a response because it has a key it does not know.

Some response objects, such as `links` and `meta`, are declared with `additionalProperties: false`. That describes today's keys. It is not a promise that no key will be added, so turn off strict response validation in your client. See [Headers and versioning](https://developers.plnnrly.com/en/docs/api/v2/guides/headers-and-versioning/).

## Accept enum values you do not know

Enum fields and parameters marked `x-extensible-enum: true` may gain new values. The document carries 200 of these marks. This is an OpenAPI extension, so check whether your generator reads it. If it does not, make the generated enum types accept unknown values, or type those fields as plain strings. Treat an unknown value as unhandled, for example by falling back to a default, never as an error.

## Path parameters that take a key

Some path parameters accept either the numeric id or a readable key, declared as `oneOf` an integer and a string with a pattern:

| Parameter         | Accepts                                                    |
| ----------------- | ---------------------------------------------------------- |
| `{work_item}`     | The id, such as `812`, or the issue key, such as `ACM-12`. |
| `{sales_invoice}` | The id, or the invoice number.                             |

If your generator struggles with `oneOf` in a path, type the parameter as a string. Both forms are valid path segments.

## Filters

- **Each filter is its own query parameter**, with the brackets in its name, such as `filter[due_date][gte]`. An `[in]` filter takes a comma-separated list.
- **User filters accept a word as well as a number.** Filters such as `filter[user_id]`, `filter[assignee_id]` and `filter[project_manager_id]` accept the literal `me`, even where their schema says `integer`. The parameter's description says so. A strictly typed client needs a way to pass that string.

See [Filtering and sorting](https://developers.plnnrly.com/en/docs/api/v2/guides/filtering-and-sorting/).

## Request bodies are closed

Every request body is declared with `additionalProperties: false`, and the API enforces it: an unknown key is refused with `422` [validation-failed](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#validation-failed), code `prohibited`.

So do not send back a record you have read. Its `id`, `created_at` and other read-only fields are not part of any request body. Build a `PATCH` body from only the fields you change.

A single related record in a response, such as a project's `customer`, is `oneOf` a bare reference like `{ "id": 12 }` and a summary object with more fields. Both have `id`, so read the id from either.

## Decimals and date-times

- **Decimals are strings**, such as `"1250.00"`, declared with `"format": "decimal"` and a pattern. `decimal` is not a standard OpenAPI format, so a generator that does not know it keeps the value as a string. Keep it as a string, or parse it into a decimal type. Never parse it into a floating-point number.
- **Date-times are UTC, to the second**, such as `2026-10-15T09:00:00Z`. When you send one, leave out fractions of a second. In JavaScript, `toISOString()` adds milliseconds (`2026-10-15T09:00:00.000Z`), which the API refuses: `422` in a body, `400` in a filter.

See [Data formats](https://developers.plnnrly.com/en/docs/api/v2/guides/data-formats/).

## Idempotency keys

Send an `Idempotency-Key` on every `POST`. Every `POST` accepts one, and a few require it. Do not rely on the generated client to know which ones: the parameter description says so. See [Idempotency](https://developers.plnnrly.com/en/docs/api/v2/guides/idempotency/).

## API tools

Postman, Insomnia and Bruno can each import an OpenAPI document from a file or a URL. Import `https://developers.plnnrly.com/openapi.json`, then:

1. Set the base URL to `https://<your-url>/api/v2`.
2. Set authentication to a bearer token, with your token as the value.

Start with a read-only token while you explore. Writes from an API tool change real data. See [API tokens](https://developers.plnnrly.com/en/docs/api/v2/guides/tokens/).

## Related

- [Headers and versioning](https://developers.plnnrly.com/en/docs/api/v2/guides/headers-and-versioning/)
- [Data formats](https://developers.plnnrly.com/en/docs/api/v2/guides/data-formats/)
- [What the API cannot do](https://developers.plnnrly.com/en/docs/api/v2/guides/known-limitations/)
