Every request carries a bearer token in the Authorization header, and nothing else proves who it is for. There is no key pair, no session cookie and no request signing.
curl "https://<your-url>/api/v2/projects" \
-H "Authorization: Bearer <token>"<your-url> is the address you sign in to plnnrly on, such as acme.plnnrly.com, or your organisation's own domain.
There are two kinds of token:
| Token | For | How you get one |
|---|---|---|
| Personal access token | Scripts, servers and integrations you run yourself. | You create it in plnnrly. See API tokens. |
| OAuth access token | Apps that other people install and that act for whoever signs in. | The app signs the person in. See Sign in with OAuth. |
Both carry the same read and write abilities. Three differences: an OAuth token never carries the destroy class, it cannot upload file contents (see Files: upload and download), and its user's Manage API tokens permission is checked on every request. Everything else on this page applies to both.
The host is part of the credential
The API is served only on your organisation's own address: https://<your-url>/api/v2.
- There is no shared API host, and no organisation id in the path or in a header. The host is what selects your organisation.
- A token works only on the organisation it was created in. On another organisation's host it answers
401. - If your organisation has its own domain, only that exact domain serves the API. Any other host, including the
wwwor bare variant of that domain, answers404with "No API is served on this host." - The API never redirects, so following redirects gains a client nothing. Use the address shown under How to connect on the API tokens page.
Responses carry a Vary header that includes Authorization, so a cache between you and plnnrly cannot serve one caller's rows to another.
A token acts as a user
A token belongs to one user: the person who created it, or the person who signed in to the app. Every request runs as that user, so:
- The token sees what the user sees, and no more. Project membership, row visibility and the rules on each record apply exactly as they do in plnnrly.
- A role change takes effect at once. Permissions are read again on every request. Taking a permission away from a role narrows every token its users hold, without touching the tokens.
- Everything runs as that user.
POST /worklogslogs time for the token's owner, and filters that acceptme, such asfilter[user_id]=me, resolve to them. GET/me returns that user to any API token and needs no permission, so a client can show who it is connected as.
Choose whose token an integration uses with care. It can do exactly what that person can, and it changes when their roles change.
Sign in with OAuth
An app that other people install should not ask them to paste a token. Instead it signs them in with OAuth 2.0 (the authorization code flow with PKCE) and receives an access token that it renews itself. The app needs no secret and no approval from plnnrly in advance: it registers itself on the organisation's host. Sign in with OAuth walks through every step and ends with a complete, runnable example.
OAuth tokens cannot delete
OAuth never offers a destroy-class scope: neither api:destroy nor any api:destroy:{prefix}. Every DELETE on /api/v2 needs that class, so an app signed in with OAuth cannot delete anything through the REST API, not even a soft delete. Deleting needs a personal access token with Delete.
AI assistants are the exception. Over MCP, an assistant can delete through its api_write tool when the organisation allows destructive actions and the user confirms the delete. That does not apply when the same sign-in also asked for api: scopes. See Permissions and safety.
The order of checks
Every request passes these checks in this order. The first one that fails decides the answer.
- The REST API is switched off for the organisation:
404endpoint-not-found. - The bearer token is missing or not valid:
401unauthenticated. - OAuth tokens only: the user lacks Manage API tokens:
403insufficient-permission. - The token's scope does not cover the operation:
403insufficient-token-scope, withrequired_abilitylisting what would have worked. - The operation belongs to a feature your organisation does not have, such as billing:
404endpoint-not-found. - Writes only, the body: an unknown or invalid field answers
422validation-failed. A field that needs a permission of its own answers403insufficient-permission naming that field. - The role lacks the operation's permission:
403insufficient-permission, withrequired_permission. On a route with a record in it, this is404resource-not-found instead when the role cannot read that kind of record at all, so the answer does not reveal whether the record exists. - The record is not visible to the user:
404resource-not-found. - The record's own rules refuse the user:
403forbidden-by-policy, or404under the same rule as step 7. - The record's state does not allow the change:
409state-conflict.
Steps 4 and 7 are separate fixes. A scope failure needs a different token; a permission failure needs a role change. Every operation page states the scope and the permission it needs, and Scopes and permissions explains both.
Other answers can come before or in between: 404 on a host that serves no API, 503 tenant-unavailable while the organisation is being set up, suspended or archived, 429 once you exceed a rate limit, 400 for an unknown query parameter or a missing Idempotency-Key, and 404 resource-not-found right after step 4 when the record id does not exist at all. Errors and retries lists them all.
When authentication itself fails
Every authentication failure answers 401 unauthenticated, whatever the cause:
- no
Authorizationheader, or a malformed one - a token that is unknown or revoked
- a token that has expired
- a token from another organisation
Every 401 carries the header WWW-Authenticate: Bearer realm="plnnrly-api".
An expired token answers unauthenticated too. The error catalogue also lists token-expired, but it is not sent today, so treat the two alike.
A personal access token expires within 12 months, so plan for it. Watch the Expires column on the API tokens page, and roll or replace a token before that date. An OAuth app renews its own access token with its refresh token.
When the API is switched off
An administrator can switch the REST API off for the whole organisation, under Settings › Administration › MCP & API. See For administrators for who may do that. While it is off:
- Every
/api/v2request answers404endpoint-not-found with the detail "The REST API is not enabled for this organisation.", whatever the token. - The OAuth endpoints answer
404, so apps cannot sign anyone in. - API access disappears from the user menu.
- MCP is switched off too. Switching the API back on does not switch MCP back on.
- Tokens are kept. They work again once the API is back on, until they expire.
MCP access is a different family
AI assistants connect to the MCP server through the same sign-in, with the mcp:use scope. Older personal MCP tokens keep working there.
- A token that holds only
mcp:usecannot call/api/v2: it answers403insufficient-token-scope. - A token without
mcp:usecannot open an MCP session. - An app that asks for
mcp:useandapi:scopes in one sign-in is held to thoseapi:scopes over MCP as well, and its user's Manage API tokens permission is checked on every call. Because OAuth offers no destroy class, such an app cannot delete over MCP either.
The consent screen lists every scope an app asks for, so the user sees what they approve.
Browsers and CORS
/api/v2 accepts browser requests only from this documentation site, for the Try it panel on operation pages. A single-page app on any other origin cannot call the API from the browser, and neither can it call the OAuth token endpoint. Call the API from your own server instead, and keep the token there.
Related
- API tokens: creating, narrowing, rolling and revoking personal access tokens.
- Sign in with OAuth: letting people sign in to your app.
- Scopes and permissions: what each scope and permission covers.
- Getting started: your first requests, step by step.