How to Use the Plane API: A Complete Guide to REST Integration

To use the Plane API, generate an API token from your instance and include it in the X-Api-Key header for all requests to /api/v1/ endpoints.

Plane (from the open-source repository makeplane/plane) exposes a Django REST Framework-powered external API that enables programmatic access to projects, issues, cycles, and modules. This guide covers the authentication flow, core endpoints, and practical implementation patterns derived directly from the source code.

Understanding Plane's API Architecture

Plane maintains two distinct REST API layers to separate internal web application traffic from third-party integrations.

External API vs Web-App API

The External API (/api/v1/) is the stable, public-facing interface designed for automation scripts and external tools. It requires API-Key authentication via the X-Api-Key header and is implemented in plane/app/middleware/api_authentication.py【/cache/repos/github.com/makeplane/plane/preview/apps/api/plane/app/middleware/api_authentication.py】.

The Web-App API (/api/) serves the Plane web UI exclusively and relies on session cookies or tokens handled by the frontend. This internal layer is subject to breaking changes and should not be used for external integrations.

Both layers are documented via an OpenAPI schema available at /api/schema/ when the ENABLE_DRF_SPECTACULAR setting is enabled.

Authentication and API Keys

The APIKeyAuthentication class validates tokens against the APIToken model, checking expiry dates, active status, and user associations before allowing access to protected resources.

Creating and Using API Tokens

  1. Generate a token via the Plane UI (User → API Tokens) or programmatically via POST /api/users/api-tokens/. The ApiTokenEndpoint in plane/app/views/api.py handles this request【/cache/repos/github.com/makeplane/plane/preview/apps/api/plane/app/views/api.py】.
  2. Store the token securely – the plaintext value is returned only once upon creation.
  3. Include the header in every request: X-Api-Key: <your-token>.

If authentication fails, the API returns a 401 Unauthorized error.

curl -X POST https://your-plane-instance.com/api/users/api-tokens/ \
     -H "Content-Type: application/json" \
     -d '{"label":"ci-cd-token","description":"Automation token"}'

The response contains the token value required for subsequent requests:

{
  "token": "a1b2c3d4e5f6...",
  "label": "ci-cd-token",
  "created_at": "2024-06-20T12:34:56Z"
}

Core Endpoints and Resources

The external API routes are defined in plane/api/urls.py and aggregated in plane/app/urls/__init__.py【/cache/repos/github.com/makeplane/plane/preview/apps/api/plane/app/urls/init.py】. All external endpoints share the /api/v1/ prefix and require workspace slugs in the URL path.

Issues and Project Management

The issue endpoints are routed through plane/app/urls/issue.py【/cache/repos/github.com/makeplane/plane/preview/apps/api/plane/app/urls/issue.py】 and implemented in plane/app/views/issue/base.py:

  • List issues: GET /api/v1/workspaces/{slug}/projects/{project_id}/issues/list/
  • Create issue: POST /api/v1/workspaces/{slug}/projects/{project_id}/issues/
  • Retrieve issue: GET /api/v1/workspaces/{slug}/projects/{project_id}/issues/{issue_id}/
  • Update issue: PATCH /api/v1/workspaces/{slug}/projects/{project_id}/issues/{issue_id}/
  • Delete issue: DELETE /api/v1/workspaces/{slug}/projects/{project_id}/issues/{issue_id}/

Comments, Attachments, and Reactions

  • Comments: GET|POST /api/v1/workspaces/{slug}/projects/{project_id}/issues/{issue_id}/comments/
  • Attachments: GET|POST .../issues/{issue_id}/attachments/
  • Reactions: POST .../issues/{issue_id}/reactions/ (accepts emoji codes)

Workspaces and Higher-Level Resources

  • Projects: GET /api/v1/workspaces/{slug}/projects/
  • Cycles: GET /api/v1/workspaces/{slug}/cycles/
  • Modules: GET /api/v1/workspaces/{slug}/modules/
  • Pages: GET /api/v1/workspaces/{slug}/pages/

Pagination and Filtering

List endpoints support cursor-based pagination using cursor and page_size query parameters. Filtering is handled via query parameters that map to Django filtersets defined in plane/utils/filters/filterset.py【/cache/repos/github.com/makeplane/plane/preview/apps/api/plane/utils/filters/filterset.py】, allowing you to filter issues by state, assignee, label, and other attributes.

Practical Code Examples

Listing Issues with Python

Use the requests library to fetch issues with pagination:

import requests

API_URL = "https://your-plane-instance.com/api/v1"
API_KEY = "a1b2c3d4e5f6..."

headers = {"X-Api-Key": API_KEY}
workspace = "engineering"
project_id = "e3b0c442-98fc-1c14-9af4-123456789abc"

response = requests.get(
    f"{API_URL}/workspaces/{workspace}/projects/{project_id}/issues/list/",
    headers=headers,
    params={"page_size": 20, "cursor": "eyJpZCI6MTB9"}
)

data = response.json()
for issue in data["results"]:
    print(f"{issue['id']}: {issue['name']}")

Creating an Issue

curl -X POST https://your-plane-instance.com/api/v1/workspaces/engineering/projects/e3b0c442-98fc-1c14-9af4-123456789abc/issues/ \
     -H "X-Api-Key: a1b2c3d4e5f6..." \
     -H "Content-Type: application/json" \
     -d '{
           "name": "Fix authentication middleware",
           "description": "Update token validation logic",
           "state_id": "d8f9e2c3-...",
           "assignee_ids": ["user-uuid-1"]
         }'

Adding a Comment

import requests

issue_id = "f2a1b3c4-d5e6-7f89-0a1b-2c3d4e5f6g7h"
url = f"{API_URL}/workspaces/{workspace}/projects/{project_id}/issues/{issue_id}/comments/"

payload = {"comment": "Investigating the API authentication flow."}
response = requests.post(url, json=payload, headers=headers)
print(response.status_code)  # 201 Created

Retrieving the OpenAPI Schema

When ENABLE_DRF_SPECTACULAR is enabled, fetch the complete schema for auto-generating clients:

curl -H "X-Api-Key: a1b2c3d4e5f6..." \
     https://your-plane-instance.com/api/schema/

Key Source Files and Implementation Details

The Plane API follows a standard Django REST Framework pipeline: router → view → serializer → model.

File Purpose
plane/app/middleware/api_authentication.py Implements APIKeyAuthentication for validating X-Api-Key headers【/cache/repos/github.com/makeplane/plane/preview/apps/api/plane/app/middleware/api_authentication.py】
plane/app/views/api.py Contains ApiTokenEndpoint for token CRUD operations【/cache/repos/github.com/makeplane/plane/preview/apps/api/plane/app/views/api.py】
plane/api/urls.py Root router for external API routes (/api/v1/)
plane/app/urls/__init__.py Aggregates all application URL patterns【/cache/repos/github.com/makeplane/plane/preview/apps/api/plane/app/urls/init.py】
plane/app/urls/issue.py URL patterns for issue-related endpoints (CRUD, comments, attachments)【/cache/repos/github.com/makeplane/plane/preview/apps/api/plane/app/urls/issue.py】
plane/app/views/issue/base.py Viewset implementations including IssueViewSet
plane/utils/filters/filterset.py Filter definitions for query parameter parsing【/cache/repos/github.com/makeplane/plane/preview/apps/api/plane/utils/filters/filterset.py】

Summary

  • Plane exposes two API layers: use the External API (/api/v1/) for integrations, not the internal Web-App API.
  • Authentication requires an API key passed via the X-Api-Key header, validated by middleware in plane/app/middleware/api_authentication.py.
  • Core resources include issues, projects, cycles, and modules, accessible through RESTful endpoints under /api/v1/workspaces/{slug}/.
  • Pagination uses cursor-based cursor and page_size parameters, while filtering leverages Django filtersets.
  • Source code references: Token management lives in plane/app/views/api.py, and issue routing is defined in plane/app/urls/issue.py.

Frequently Asked Questions

How do I authenticate with the Plane API?

Include a valid API token in the X-Api-Key header for every request to /api/v1/ endpoints. Generate tokens via the Plane UI under User → API Tokens or programmatically via POST /api/users/api-tokens/. The APIKeyAuthentication class in plane/app/middleware/api_authentication.py validates these tokens against the database.

What is the difference between /api/ and /api/v1/?

The /api/v1/ prefix denotes the stable External API designed for third-party integrations and requires API-Key authentication. The /api/ prefix serves the internal web application and uses session-based authentication. External integrations should only use /api/v1/ endpoints as defined in plane/api/urls.py.

Does Plane support OpenAPI or Swagger documentation?

Yes, when the ENABLE_DRF_SPECTACULAR environment variable is set to true, Plane serves an OpenAPI schema at /api/schema/. This schema describes all available endpoints, request bodies, and response formats, allowing you to import the specification into tools like Postman or Swagger UI.

How does pagination work in the Plane API?

List endpoints use cursor-based pagination rather than offset-based. Pass cursor and page_size as query parameters to navigate through large result sets. The next and previous fields in the response provide cursors for subsequent requests, implemented via Django filtersets in plane/utils/filters/filterset.py.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →