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
- Log into your Plane account and go to Profile Settings.
- Open the Personal Access Tokens tab.
- Click
Add personal access token. - Enter a title and description that record why you're creating the token and where you'll use it.
- 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:
{
[ ... ]
}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:
| Verb | Description | Example |
|---|---|---|
| GET | Requests a representation of the specified resource | Fetch all issues from a project |
| POST | Submits an entity to the specified resource | Create a project |
| DELETE | Deletes the specified resource | Delete a module-issue |
| PATCH | Applies partial modifications to a resource | Edit a module |
Status codes
Success responses
| Status Code | Description |
|---|---|
| 200 OK | The request succeeded and a new resource was created. Generally returned for GET or PATCH requests. |
| 201 Created | The request succeeded and a new resource was created. Generally returned for POST or PATCH requests. |
| 204 No Content | The request succeeded and the response has no body. Generally returned for DELETE requests. |
Error responses
| Status Code | Description |
|---|---|
| 400 Bad Request | The server cannot or will not process the request because of what it considers a client error. |
| 401 Unauthorized | The request is not authenticated. The HTTP standard calls this "unauthorized", but it means the client must authenticate to get the requested response. |
| 404 Not Found | The server cannot find the requested resource. The URL is not recognized. |
| 429 Throttling Error | The server is handling too many requests and cannot process yours. Retry the request later. |
| 500 Internal Server Error | The server hit a condition it could not handle. |
| 502 Bad Gateway | The server, acting as a gateway, got an invalid response while fetching what it needed to handle the request. |
| 503 Service Unavailable | The server is not ready to handle the request. Common causes are a server that is down for maintenance or is overloaded. |
| 504 Gateway Timeout | The 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:
valueis the page size (number of items per page).offsetis the current page number (starting from 0).is_previndicates 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:
| Field | Description |
|---|---|
next_cursor | Cursor string for the next page. |
prev_cursor | Cursor string for the previous page. |
next_page_results | Boolean indicating if there are more results after the current page. |
prev_page_results | Boolean indicating if there are results before the current page. |
count | Total number of items on the current page. |
total_pages | Estimated total number of pages. |
total_results | Total number of items across all pages. |
extra_stats | Additional statistics, if any. |
results | Array 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=20Response:
{
"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:0Response:
{
"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: 1700327957Fields 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,descriptionThis 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,stateThis request returns the resource data with expanded details for assignees and state.
Error handling
- Invalid or unrecognized field names in
fieldsreturn an error response that lists the invalid fields. - Passing a field that cannot be expanded in
expandalso returns an error.

