Skip to content

Plane API Documentation ​

The Plane API is a REST API. It has predictable, resource-oriented URLs, accepts application/json request bodies, returns JSON responses, and uses standard HTTP response codes, authentication, and verbs.

Base URL ​

Send all Plane Cloud API requests to this base URL:

https://api.plane.so/

Prefix every endpoint path with this URL.

For example, to retrieve all projects in a workspace:

GET https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/

TIP

On a self-hosted instance of Plane, the API base URL depends on your custom domain and setup.

Authentication ​

The API authenticates requests with an API key. Send the key in the X-API-Key header of every request. Plane uses it to verify the client's identity and permissions.

INFO

To generate a key, you need a Plane account or an account on your instance.

Generating an API key ​

  1. Log into your Plane account and go to Profile Settings.
  2. Open the Personal Access Tokens tab.
  3. Click Add personal access token.
  4. Enter a title and description that record why you're creating the token and where you'll use it.
  5. Optionally, set an expiry so the token stops working after a set date.

Using the API key ​

To authenticate an API request, include your API key in the request header:

X-API-Key: <Your-API-Key>

Keep your API key confidential to prevent unauthorized access to your account.

Using an OAuth token ​

If your application uses OAuth to obtain user authorization (for example, a Plane app you've built), you can authenticate API requests with the OAuth access token. Include the token in the Authorization header as a Bearer token:

Authorization: Bearer <your-oauth-access-token>

The access token is scoped to the permissions (scopes) the user granted when authorizing your app. See OAuth scopes for the full list of available scopes.

Using a token from your identity provider ​

If your workspace uses OAuth Bridge, your apps can authenticate with access tokens from your own identity provider, such as Microsoft Entra ID, ADFS, or Okta. Send the token as a Bearer token:

Authorization: Bearer <your-idp-access-token>

Plane matches the token to a Plane user by email, and the request runs with that user's permissions.

Example of an authenticated API request ​

Using an API key:

GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/
Headers:
  X-API-Key: plane_api_<token>

Using an OAuth token:

GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/
Headers:
  Authorization: Bearer <your-oauth-access-token>

Response:

json
{
  [ ... ]
}

Error handling ​

If the X-API-Key header is missing, the API returns an error saying that authentication is required. If the API key is invalid or expired, the API returns an authentication failure error.

Security recommendations ​

Treat your API key like a password. Do not share it or expose it in client-side code. If you suspect that your API key has been compromised, generate a new one immediately and update your applications.


HTTP methods ​

HTTP request methods, also called verbs, indicate the action to perform on a resource. The API uses these:

VerbDescriptionExample
GETRequests a representation of the specified resourceFetch all issues from a project
POSTSubmits an entity to the specified resourceCreate a project
DELETEDeletes the specified resourceDelete a module-issue
PATCHApplies partial modifications to a resourceEdit a module

Status codes ​

Success responses ​

Status CodeDescription
200 OKThe request succeeded and a new resource was created. Generally returned for GET or PATCH requests.
201 CreatedThe request succeeded and a new resource was created. Generally returned for POST or PATCH requests.
204 No ContentThe request succeeded and the response has no body. Generally returned for DELETE requests.

Error responses ​

Status CodeDescription
400 Bad RequestThe server cannot or will not process the request because of what it considers a client error.
401 UnauthorizedThe request is not authenticated. The HTTP standard calls this "unauthorized", but it means the client must authenticate to get the requested response.
404 Not FoundThe server cannot find the requested resource. The URL is not recognized.
429 Throttling ErrorThe server is handling too many requests and cannot process yours. Retry the request later.
500 Internal Server ErrorThe server hit a condition it could not handle.
502 Bad GatewayThe server, acting as a gateway, got an invalid response while fetching what it needed to handle the request.
503 Service UnavailableThe server is not ready to handle the request. Common causes are a server that is down for maintenance or is overloaded.
504 Gateway TimeoutThe server, acting as a gateway, did not get a response in time.

Pagination ​

Overview ​

The API uses cursor-based pagination to page through large result sets. A cursor parameter tracks the position and direction of pagination.

Cursor format ​

The cursor is a string formatted as value:offset:is_prev, where:

  • value is the page size (number of items per page).
  • offset is the current page number (starting from 0).
  • is_prev indicates whether the cursor is moving to the previous page (1) or to the next page (0).

Request parameters ​

  • per_page (optional): Number of items per page. Defaults to 100, maximum 100.
  • cursor (optional): Cursor string for the page to fetch. If omitted, pagination starts from the first page.

Response fields ​

The paginated response includes the following fields:

FieldDescription
next_cursorCursor string for the next page.
prev_cursorCursor string for the previous page.
next_page_resultsBoolean indicating if there are more results after the current page.
prev_page_resultsBoolean indicating if there are results before the current page.
countTotal number of items on the current page.
total_pagesEstimated total number of pages.
total_resultsTotal number of items across all pages.
extra_statsAdditional statistics, if any.
resultsArray of items for the current page.

Example: fetching the first page ​

Request:

GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/?per_page=20

Response:

json
{
  "next_cursor": "20:1:0",
  "prev_cursor": "",
  "next_page_results": true,
  "prev_page_results": false,
  "count": 20,
  "total_pages": 50,
  "total_results": 1000,
  "extra_stats": {},
  "results": [ ... ]
}

Example: fetching the next page ​

Request:

GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items?per_page=20&cursor=20:1:0

Response:

json
{
  "next_cursor": "20:2:0",
  "prev_cursor": "20:0:1",
  "next_page_results": true,
  "prev_page_results": true,
  "count": 20,
  "total_pages": 50,
  "total_results": 1000,
  "extra_stats": {},
  "results": [ ... ]
}

Rate limiting ​

Overview ​

The API limits the number of requests a client can make within a time window. This keeps usage fair and service quality steady for all users.

Rate limit details ​

Each client can make 60 requests per minute. The counter resets every minute, and the limit applies to all requests made with a given API key.

Identifying your rate limit status ​

Every API response reports your rate limit status in these headers:

  • X-RateLimit-Remaining: The number of requests remaining in the current rate limit window.
  • X-RateLimit-Reset: When the current rate limit window resets, in UTC epoch seconds.
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1700327957

Fields and expand query parameters ​

Two query parameters, fields and expand, control what a response contains. Use them to reduce payload size or to include related data.

Fields parameter ​

The fields parameter returns only a subset of a resource's fields. Use it to reduce response size and bandwidth when you need only specific data.

Pass a comma-separated list of the field names to include in the response:

GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/?fields=id,name,description

This request returns only the id, name, and description fields of the resource.

Expand parameter ​

The expand parameter adds related information to the response, so you can get details of nested resources without making separate API calls.

Pass the related resources or nested objects to include:

GET /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/?expand=assignees,state

This request returns the resource data with expanded details for assignees and state.

Error handling ​

  • Invalid or unrecognized field names in fields return an error response that lists the invalid fields.
  • Passing a field that cannot be expanded in expand also returns an error.