Skip to content

Plane API v2 ​

API v2 is the current, recommended REST interface to Plane. It is resource-oriented, JSON in and JSON out, and described by a machine-readable OpenAPI document you can generate clients from.

Everything here is stable and actively developed. API v1 remains available and supported for existing integrations, but it is in maintenance — build new integrations on v2.

Already on v1?

The two versions run side by side, so you can migrate one resource at a time. See Migrating from v1 for the field-by-field differences.

Base URL ​

DeploymentBase URL
Plane Cloudhttps://api.plane.so
Self-managedYour instance URL, for example https://plane.example.com

Every v2 path lives under /api/v2/, and every URL ends with a trailing slash. Dropping it is the single most common first-request mistake — /api/v2/users/me is not the same URL as /api/v2/users/me/.

The OpenAPI document advertises one servers entry that matches your deployment, so a generated client points at the right host without further configuration.

Quickstart ​

GET /api/v2/users/me/ needs no workspace or project id, which makes it the fastest way to confirm your credentials work.

Verify your credentials
bash
curl "https://api.plane.so/api/v2/users/me/" \
  -H "X-Api-Key: $PLANE_API_KEY"
Response200
json
{
  "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
  "email": "priya@my-team.io",
  "display_name": "priya",
  "principal_kind": "api_key",
  "scopes": []
}

If that returns 200, your key is valid. If it returns 401, the header name or the key itself is wrong — see Authentication.

Requests ​

  • JSON only. Send Content-Type: application/json on requests with a body; responses are JSON.
  • POST creates, PATCH updates. There is no PUT — sending one returns 405 method_not_allowed.
  • PATCH is partial. Only the fields you send are changed. Omitting a field leaves it untouched, which is not the same as sending null to clear it.
  • DELETE returns 204 with an empty body. Deletes are soft.
  • Writes take ids, not nested objects. Set a relation with its *_id field (state_id, parent_id, lead_id) or its *_ids array (assignee_ids, label_ids).
  • Audit fields are read-only. created_at, created_by_id, and their siblings are set server-side; sending them has no effect.
bash
# Create
curl -X POST \
  "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/states/" \
  -H "X-Api-Key: $PLANE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "In Review", "color": "#3f76ff", "group": "started"}'

# Partial update — color changes, nothing else does
curl -X PATCH \
  "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/states/f960d3c2-8524-4a41-b8eb-055ce4be2a7f/" \
  -H "X-Api-Key: $PLANE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"color": "#ffffff"}'

Design principles ​

These five rules hold across every v2 endpoint. Learning them once means you rarely have to re-read a reference page.

Reads are sparse ​

A read returns foreign keys as *_id fields and many-to-many relations as *_ids arrays. No related objects are embedded by default, so response size stays predictable no matter how many relations a resource has.

json
{
  "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13",
  "name": "Fix login redirect",
  "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f",
  "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]
}

Expansion is separate-key ​

?expand= adds an object beside the id — it never replaces it. ?expand=state gives you state_id and a state object, so code that reads state_id keeps working whether or not the caller asked for the expansion.

Only work items and members support ?expand=. See Expanding relations for the allowed values.

Errors are RFC 9457 ​

Every failure is application/problem+json with a stable machine-readable code. Branch on code, never on the HTTP status or the human-readable detail. See Errors.

Cross-tenant ids return 404 ​

A workspace, project, or record id that belongs to someone else returns 404 resource_not_found, never 403. The API does not distinguish "does not exist" from "exists but is not yours", so it never leaks the existence of a resource you cannot see.

A 404 is not always a typo

If a 404 surprises you on an id you are confident is real, check which workspace or project the id belongs to before checking your spelling.

v2 evolves additively ​

Inside v2 the contract only grows: new optional fields, new enum values, new endpoints. Removals and renames are reserved for a future major version. Write clients that ignore unknown response fields and tolerate unrecognized enum values, and upgrades stay uneventful.

What's in v2 ​

88 operations across these resources.

ResourceBase pathReference
Work items/api/v2/workspaces/{slug}/projects/{project_id}/work-items/Overview
Work item comments…/work-items/{work_item_id}/comments/Overview
States…/projects/{project_id}/states/Overview
Labels…/projects/{project_id}/labels/Overview
Cycles…/projects/{project_id}/cycles/Overview
Modules…/projects/{project_id}/modules/Overview
Work item types & properties, project…/projects/{project_id}/work-item-types/, …/work-item-properties/Overview
Work item types & properties, workspace/api/v2/workspaces/{slug}/work-item-types/, …/work-item-properties/Overview
Work item property contexts/api/v2/workspaces/{slug}/work-item-properties/{property_id}/contexts/Overview
Members/api/v2/workspaces/{slug}/members/, …/projects/{project_id}/members/Overview
Workspace features/api/v2/workspaces/{slug}/features/Overview
Audit logs/api/v2/workspaces/{slug}/audit-logs/Overview
Users/api/v2/users/me/Overview

Two surfaces for work item types

A workspace manages work item types either at the project level or at the workspace level — never both at once. Writing to the wrong surface returns a 409. See Work item type modes.

Machine-readable schema ​

The full OpenAPI document is served live at:

GET /api/v2/schema/

Point a generator at it to produce a typed client, or diff it between releases to see exactly what changed. It is the authority for every field, enum, and scope in this reference.

Where to go next ​