# Authentication

Every request carries a bearer token, sent to your organisation's own address and acting as one user. Here is how it works and how it fails.

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.

```bash
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](https://developers.plnnrly.com/en/docs/api/v2/guides/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](https://developers.plnnrly.com/en/docs/api/v2/guides/oauth-apps/). |

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](https://developers.plnnrly.com/en/docs/api/v2/guides/uploads/)), 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 `www` or bare variant of that domain, answers `404` with "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:

1. **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.
2. **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.
3. **Everything runs as that user.** `POST /worklogs` logs time for the token's owner, and filters that accept `me`, such as `filter[user_id]=me`, resolve to them. [GET /me](https://developers.plnnrly.com/en/docs/api/v2/me/get/) 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](https://developers.plnnrly.com/en/docs/api/v2/guides/oauth-apps/) 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](https://developers.plnnrly.com/en/docs/mcp/permissions-and-safety/).

## The order of checks

Every request passes these checks in this order. The first one that fails decides the answer.

1. **The REST API is switched off** for the organisation: `404` [endpoint-not-found](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#endpoint-not-found).
2. **The bearer token** is missing or not valid: `401` [unauthenticated](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#unauthenticated).
3. **OAuth tokens only:** the user lacks **Manage API tokens**: `403` [insufficient-permission](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#insufficient-permission).
4. **The token's scope** does not cover the operation: `403` [insufficient-token-scope](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#insufficient-token-scope), with `required_ability` listing what would have worked.
5. **The operation belongs to a feature** your organisation does not have, such as billing: `404` [endpoint-not-found](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#endpoint-not-found).
6. **Writes only, the body:** an unknown or invalid field answers `422` [validation-failed](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#validation-failed). A field that needs a permission of its own answers `403` [insufficient-permission](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#insufficient-permission) naming that field.
7. **The role lacks the operation's permission:** `403` [insufficient-permission](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#insufficient-permission), with `required_permission`. On a route with a record in it, this is `404` [resource-not-found](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#resource-not-found) instead when the role cannot read that kind of record at all, so the answer does not reveal whether the record exists.
8. **The record is not visible** to the user: `404` [resource-not-found](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#resource-not-found).
9. **The record's own rules** refuse the user: `403` [forbidden-by-policy](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#forbidden-by-policy), or `404` under the same rule as step 7.
10. **The record's state** does not allow the change: `409` [state-conflict](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#state-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](https://developers.plnnrly.com/en/docs/api/v2/guides/scopes/) explains both.

Other answers can come before or in between: `404` on a host that serves no API, `503` [tenant-unavailable](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#tenant-unavailable) while the organisation is being set up, suspended or archived, `429` once you exceed a [rate limit](https://developers.plnnrly.com/en/docs/api/v2/guides/rate-limits/), `400` for an unknown query parameter or a missing `Idempotency-Key`, and `404` [resource-not-found](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#resource-not-found) right after step 4 when the record id does not exist at all. [Errors and retries](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/) lists them all.

## When authentication itself fails

Every authentication failure answers `401` [unauthenticated](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#unauthenticated), whatever the cause:

- no `Authorization` header, 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](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#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](https://developers.plnnrly.com/en/docs/api/v2/guides/tokens/#roll-a-token) 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](https://developers.plnnrly.com/en/docs/administration/) for who may do that. While it is off:

- Every `/api/v2` request answers `404` [endpoint-not-found](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#endpoint-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](https://developers.plnnrly.com/en/docs/mcp/) through the same sign-in, with the `mcp:use` scope. Older personal MCP tokens keep working there.

- A token that holds only `mcp:use` cannot call `/api/v2`: it answers `403` [insufficient-token-scope](https://developers.plnnrly.com/en/docs/api/v2/guides/errors/#insufficient-token-scope).
- A token without `mcp:use` cannot open an MCP session.
- An app that asks for `mcp:use` and `api:` scopes in one sign-in is held to those `api:` 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](https://developers.plnnrly.com/en/docs/api/v2/guides/tokens/): creating, narrowing, rolling and revoking personal access tokens.
- [Sign in with OAuth](https://developers.plnnrly.com/en/docs/api/v2/guides/oauth-apps/): letting people sign in to your app.
- [Scopes and permissions](https://developers.plnnrly.com/en/docs/api/v2/guides/scopes/): what each scope and permission covers.
- [Getting started](https://developers.plnnrly.com/en/docs/api/v2/guides/getting-started/): your first requests, step by step.
