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 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 variabletenant_hostto<your-url>. - Authentication. Every operation takes a bearer token: a personal access token or an OAuth access token. Each operation's
securitynames the scopes that cover it, under the schemesbearerTokenandoauth2. ADELETElistsbearerTokenonly, because no OAuth scope covers deletes. See 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.
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]andfilter[project_manager_id]accept the literalme, even where their schema saysinteger. The parameter's description says so. A strictly typed client needs a way to pass that string.
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, 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.decimalis 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:422in a body,400in a filter.
See 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.
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:
- Set the base URL to
https://<your-url>/api/v2. - 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.