# Plane API Reference

Base URL: `https://api.plane.so/api/v1/`

Auth: `X-API-Key: <token>` header; alternatively `Authorization: Bearer <oauth-token>` for OAuth apps.

Rate limit: 60 req/min per API key; check `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers.

---

## Authentication

```
X-API-Key: plane_api_<token>
```

## Pagination

Cursor-based; format is `value:offset:is_prev`.

| Param | Default | Description |
|-------|---------|-------------|
| `per_page` | 100 | Items per page (max 100) |
| `cursor` | — | Cursor string for navigation |

Response includes: `next_cursor`, `prev_cursor`, `next_page_results`, `prev_page_results`, `count`, `total_pages`, `total_results`, `results`.

## Query Parameters (all list endpoints)

| Param | Description |
|-------|-------------|
| `fields` | Comma-separated field names to return; invalid names produce an error |
| `expand` | Comma-separated relations to expand inline |

---

## Project

```
GET    /workspaces/{slug}/projects/
POST   /workspaces/{slug}/projects/
GET    /workspaces/{slug}/projects/{project_id}/
PATCH  /workspaces/{slug}/projects/{project_id}/
DELETE /workspaces/{slug}/projects/{project_id}/
```

### Schema

| Field | Type | Writable | Description |
|-------|------|----------|-------------|
| `id` | uuid | no | Unique identifier |
| `name` | string | yes | Project name |
| `identifier` | string | yes | Short code (e.g. "VEN") |
| `description` | string | yes | Project description |
| `description_text` | string | no | Plain text description |
| `description_html` | string | yes | HTML description |
| `total_members` | int | no | Member count |
| `total_cycles` | int | no | Cycle count |
| `total_modules` | int | no | Module count |
| `is_member` | bool | no | Whether the requesting user is a member |
| `member_role` | int | no | User's role level |
| `is_deployed` | int | no | Publicly visible flag |
| `network` | int | yes | 0=Secret, 2=Public |
| `emoji` | string | yes | HTML emoji DEX code |
| `icon_prop` | json | yes | Icon data |
| `module_view` | bool | yes | Modules enabled in UI |
| `cycle_view` | bool | yes | Cycles enabled in UI |
| `inbox_view` | bool | yes | Intake enabled in UI |
| `page_view` | bool | yes | Pages enabled in UI |
| `issue_views_view` | bool | yes | Views enabled in UI |
| `cover_image` | url | yes | Cover image URL |
| `archive_in` | int | yes | Auto-archive after N months (0-12) |
| `close_in` | int | yes | Auto-close after N months (0-12) |
| `created_at` | timestamp | no | Creation time |
| `updated_at` | timestamp | no | Last update time |
| `created_by` | uuid | no | Creator |
| `updated_by` | uuid | no | Last updater |
| `workspace` | uuid | no | Parent workspace |
| `default_assignee` | uuid | yes | Default assignee for new items |
| `project_lead` | uuid | yes | Project lead |
| `estimate` | uuid | yes | Associated estimate |
| `default_state` | uuid | yes | Default state for auto-closed items |

---

## State

```
GET    /workspaces/{slug}/projects/{project_id}/states/
POST   /workspaces/{slug}/projects/{project_id}/states/
GET    /workspaces/{slug}/projects/{project_id}/states/{state_id}/
PATCH  /workspaces/{slug}/projects/{project_id}/states/{state_id}/
DELETE /workspaces/{slug}/projects/{project_id}/states/{state_id}/
```

### Schema

| Field | Type | Writable | Description |
|-------|------|----------|-------------|
| `id` | uuid | no | Unique identifier |
| `name` | string | yes | State name |
| `description` | string | yes | State description |
| `color` | string | yes | Hex color code |
| `workspace_slug` | string | no | Auto-generated slug |
| `sequence` | float | no | Ordering position |
| `group` | enum | yes | `backlog`, `unstarted`, `started`, `completed`, `cancelled` |
| `default` | bool | yes | Default state for unassigned items |
| `created_at` | timestamp | no | Creation time |
| `updated_at` | timestamp | no | Last update time |
| `created_by` | uuid | no | Creator |
| `updated_by` | uuid | no | Last updater |
| `project` | uuid | no | Parent project |
| `workspace` | uuid | no | Parent workspace |

---

## Label

```
GET    /workspaces/{slug}/projects/{project_id}/labels/
POST   /workspaces/{slug}/projects/{project_id}/labels/
GET    /workspaces/{slug}/projects/{project_id}/labels/{label_id}/
PATCH  /workspaces/{slug}/projects/{project_id}/labels/{label_id}/
DELETE /workspaces/{slug}/projects/{project_id}/labels/{label_id}/
```

### Schema

| Field | Type | Writable | Description |
|-------|------|----------|-------------|
| `id` | uuid | no | Unique identifier |
| `name` | string | yes | Label name |
| `description` | string | yes | Label description |
| `color` | string | yes | Hex color code |
| `sort_order` | float | no | Ordering position |
| `parent` | uuid | yes | Parent label (for nesting) |
| `created_at` | timestamp | no | Creation time |
| `updated_at` | timestamp | no | Last update time |
| `created_by` | uuid | no | Creator |
| `updated_by` | uuid | no | Last updater |
| `project` | uuid | no | Parent project |
| `workspace` | uuid | no | Parent workspace |

---

## Work Item (Issue)

```
GET    /workspaces/{slug}/projects/{project_id}/work-items/
POST   /workspaces/{slug}/projects/{project_id}/work-items/
GET    /workspaces/{slug}/projects/{project_id}/work-items/{item_id}/
PATCH  /workspaces/{slug}/projects/{project_id}/work-items/{item_id}/
DELETE /workspaces/{slug}/projects/{project_id}/work-items/{item_id}/
```

List supports query params: `?assignee={user_id}&state={state_id}&expand=assignees,state,module,labels,type,project`

### Schema

| Field | Type | Writable | Description |
|-------|------|----------|-------------|
| `id` | uuid | no | Unique identifier |
| `name` | string | yes | Work item title |
| `description_html` | string | yes | HTML description |
| `description_stripped` | string | no | Plain text (auto-generated) |
| `description_binary` | string | no | Binary format |
| `priority` | enum | yes | `none`, `urgent`, `high`, `medium`, `low` |
| `start_date` | date | yes | Start date (YYYY-MM-DD) |
| `target_date` | date | yes | Target date (YYYY-MM-DD) |
| `sequence_id` | int | no | Auto-generated sequence number |
| `sort_order` | decimal | no | Ordering position |
| `estimate_point` | int | yes | Estimate points (0-7); nullable |
| `completed_at` | timestamp | no | When moved to completed state; nullable |
| `archived_at` | timestamp | no | When archived; nullable |
| `is_draft` | bool | yes | Draft status |
| `state` | uuid | yes | State ID |
| `type` | uuid | yes | Work item type ID |
| `module` | uuid | yes | Module ID |
| `parent` | uuid | yes | Parent work item ID |
| `assignees` | uuid[] | yes | Assigned user IDs |
| `labels` | uuid[] | yes | Label IDs |
| `created_at` | timestamp | no | Creation time |
| `updated_at` | timestamp | no | Last update time |
| `created_by` | uuid | no | Creator |
| `updated_by` | uuid | no | Last updater |
| `project` | uuid | no | Parent project |
| `workspace` | uuid | no | Parent workspace |

Expandable fields: `type`, `module`, `labels`, `assignees`, `state`, `project`

---

## Module

```
GET    /workspaces/{slug}/projects/{project_id}/modules/
POST   /workspaces/{slug}/projects/{project_id}/modules/
GET    /workspaces/{slug}/projects/{project_id}/modules/{module_id}/
PATCH  /workspaces/{slug}/projects/{project_id}/modules/{module_id}/
DELETE /workspaces/{slug}/projects/{project_id}/modules/{module_id}/
```

Module issues (add/remove work items from a module):
```
GET    /workspaces/{slug}/projects/{project_id}/modules/{module_id}/module-issues/
POST   /workspaces/{slug}/projects/{project_id}/modules/{module_id}/module-issues/
DELETE /workspaces/{slug}/projects/{project_id}/modules/{module_id}/module-issues/{item_id}/
```

### Schema

| Field | Type | Writable | Description |
|-------|------|----------|-------------|
| `id` | uuid | no | Unique identifier |
| `name` | string | yes | Module name |
| `description` | string | yes | Module description |
| `description_html` | string | yes | HTML description |
| `description_text` | string | no | Plain text description |
| `start_date` | date | yes | Start date (YYYY-MM-DD) |
| `target_date` | date | yes | Target date (YYYY-MM-DD) |
| `status` | enum | yes | `backlog`, `planned`, `in-progress`, `paused`, `completed`, `cancelled` |
| `lead` | uuid | yes | Module lead user ID |
| `members` | uuid[] | yes | Member user IDs assigned to this module |
| `sort_order` | float | no | Ordering position |
| `logo_props` | json | yes | Logo configuration |
| `view_props` | json | yes | Filter/display properties |
| `archived_at` | timestamp | no | When archived; nullable |
| `created_at` | timestamp | no | Creation time |
| `updated_at` | timestamp | no | Last update time |
| `created_by` | uuid | no | Creator |
| `updated_by` | uuid | no | Last updater |
| `project` | uuid | no | Parent project |
| `workspace` | uuid | no | Parent workspace |
| `external_source` | string | yes | External source identifier |
| `external_id` | string | yes | External ID |

---

## Cycle

```
GET    /workspaces/{slug}/projects/{project_id}/cycles/
POST   /workspaces/{slug}/projects/{project_id}/cycles/
GET    /workspaces/{slug}/projects/{project_id}/cycles/{cycle_id}/
PATCH  /workspaces/{slug}/projects/{project_id}/cycles/{cycle_id}/
DELETE /workspaces/{slug}/projects/{project_id}/cycles/{cycle_id}/
```

Cycle issues (add/remove work items from a cycle):
```
GET    /workspaces/{slug}/projects/{project_id}/cycles/{cycle_id}/cycle-issues/
POST   /workspaces/{slug}/projects/{project_id}/cycles/{cycle_id}/cycle-issues/
DELETE /workspaces/{slug}/projects/{project_id}/cycles/{cycle_id}/cycle-issues/{item_id}/
```

### Schema

| Field | Type | Writable | Description |
|-------|------|----------|-------------|
| `id` | uuid | no | Unique identifier |
| `name` | string | yes | Cycle name |
| `description` | string | yes | Cycle description |
| `start_date` | date | yes | Start date (YYYY-MM-DD) |
| `end_date` | date | yes | End date (YYYY-MM-DD) |
| `owned_by` | uuid | yes | Cycle owner user ID |
| `sort_order` | float | no | Ordering position |
| `view_props` | json | yes | Filter/display properties |
| `archived_at` | timestamp | no | When archived; nullable |
| `timezone` | string | yes | Cycle timezone |
| `version` | int | no | Version number |
| `created_at` | timestamp | no | Creation time |
| `updated_at` | timestamp | no | Last update time |
| `created_by` | uuid | no | Creator |
| `updated_by` | uuid | no | Last updater |
| `project` | uuid | no | Parent project |
| `workspace` | uuid | no | Parent workspace |
| `external_source` | string | yes | External source identifier |
| `external_id` | string | yes | External ID |

---

## HTTP Status Codes

| Code | Meaning |
|------|---------|
| 200 | OK |
| 201 | Created |
| 204 | No Content (DELETE success) |
| 400 | Bad Request |
| 401 | Unauthorized / unauthenticated |
| 404 | Not Found |
| 429 | Rate Limited; retry after `X-RateLimit-Reset` |
| 500 | Internal Server Error |

---

Source: [Plane API Docs](https://developers.plane.so/api-reference)
