REST API for reading and writing workspace data from external tools and integrations.
https://plate.to/api
Two methods are supported. API keys are for server-to-server integrations. OAuth Bearer tokens are issued by the MCP OAuth flow and represent an authenticated user.
API key — pass in the X-API-Key header. Generate keys in Workspace Settings → API. Always has full read/write access.
# API key curl https://plate.to/api/v1/workspace \ -H "X-API-Key: plt_your_key_here"
Bearer token — pass a JWT from the MCP OAuth flow in the Authorization header, plus the target workspace ID in X-Workspace-Id. Scope is read (GET only) or write (all methods).
# Bearer token curl https://plate.to/api/v1/workspace \ -H "Authorization: Bearer <token>" \ -H "X-Workspace-Id: <workspaceId>"
| Status | Meaning |
|---|---|
400 | Missing or invalid fields |
401 | Missing or invalid API key / Bearer token |
403 | Action not allowed (e.g. free plan limit, read-only scope, not a workspace member) |
404 | Resource not found |
500 | Internal server error |
{ "error": "Invalid API key" }
Returns info about the workspace linked to your API key.
{
"id": "ws_abc",
"name": "Acme Corp",
"urlId": "acme",
"avatarUrl": null,
"taskPrefix": "SCA"
}
Returns all workspace members. Use userId as assigneeId when creating or updating tasks.
[{
"userId": "user_abc",
"name": "Jane Smith",
"email": "jane@acme.com",
"role": "member",
"avatarUrl": null
}]
role is "owner", "member", or "guest".
Returns all task statuses in the workspace, ordered by position.
[{
"id": "status_abc",
"name": "In Review",
"color": "#FFFABB",
"systemType": null,
"order": 2
}]
systemType is "todo" for the default start status, "done" for the completion status, or null for custom statuses.
Returns all projects in the workspace.
[{ "id": "proj_abc", "name": "Website Redesign", "description": null, "taskPrefix": "SCA" }]
Creates a new project with a default "To Do" section. Returns 403 if the workspace is on the free plan and has reached the 3-project limit.
| Field | Type | Description |
|---|---|---|
name* | string | Project name |
descriptionoptional | string | Project description |
# Returns 201 { "id": "proj_xyz", "defaultListId": "sec_abc" }
Updates a project's name or description. At least one field required.
| Field | Type | Description |
|---|---|---|
nameoptional | string | New project name |
descriptionoptional | string | null | New description, or null to clear |
{ "id": "proj_abc" }
Returns all sections in a project, ordered by position.
[{ "id": "sec_abc", "name": "To Do", "order": 0, "projectId": "proj_abc" }]
Creates a new section at the end of a project.
| Field | Type | Description |
|---|---|---|
projectId* | string | Target project |
name* | string | Section name |
# Returns 201 { "id": "sec_xyz" }
Renames a section.
| Field | Type | Description |
|---|---|---|
name* | string | New name |
Each task has a number field. Combine it with taskPrefix from GET /v1/projects and urlId from GET /v1/workspace to build the task URL: https://plate.to/{urlId}/task/{taskPrefix}-{number}
Returns all tasks in a project.
[{
"id": "task_abc",
"number": 42,
"name": "Design homepage hero",
"isCompleted": false,
"description": null,
"listId": "sec_abc",
"assigneeId": null,
"statusId": null,
"order": 3,
"createdAt": "2026-06-01T10:00:00.000Z"
}]
order is the task's display position within its section (lower = higher in the list). description is null when empty, or a rich text node array when set.
Returns a single task with full details, including description, labels, and projectId.
{
"id": "task_abc",
"number": 42,
"name": "Design homepage hero",
"isCompleted": false,
"description": null,
"listId": "sec_abc",
"projectId": "proj_abc",
"assigneeId": null,
"statusId": null,
"labels": [],
"createdAt": "2026-06-01T10:00:00.000Z"
}
description is null when empty, or a rich text node array when set. To extract plain text, concatenate the text fields from all leaf nodes. Labels are read-only via the API.
Creates a new task in a section. Returns 400 if listId does not belong to the specified projectId. Returns 403 if the workspace is on the free plan and has reached the 300-task limit. To set a description, call PATCH /v1/tasks/:id after creation.
| Field | Type | Description |
|---|---|---|
projectId* | string | Target project |
listId* | string | Target section (must belong to projectId) |
name* | string | Task name |
assigneeIdoptional | string | Assignee user ID from GET /v1/members |
# Returns 201 { "id": "task_xyz", "number": 42 }
Updates a task. Only the fields you send are changed. At least one field is required (otherwise 400).
| Field | Type | Description |
|---|---|---|
nameoptional | string | New name |
listIdoptional | string | Move task to a different section. Must belong to the same project. Returns 400 if the section belongs to a different project. |
isCompletedoptional | boolean | Completion state. Also auto-sets statusId to the matching system status. |
statusIdoptional | string | Status ID from GET /v1/statuses. Also updates isCompleted based on the status systemType. |
assigneeIdoptional | string | null | Assignee user ID from GET /v1/members, or null to unassign. |
descriptionoptional | string | JSON | null | Rich text body. Pass a markdown string, a Plate.js node array, or null to clear. |
description format
Pass a markdown string (recommended) or a raw Plate.js node array.
Supported markdown syntax: # / ## / ### headings, - bullet lists, 1. numbered lists, **bold**, *italic*, ***bold italic***, ~~strikethrough~~, `inline code`, > blockquote.
# Markdown string (recommended) { "description": "## Steps to reproduce\n\n- Open settings\n- Click Profile\n\n**Expected:** profile page opens" } # Raw Plate node array (advanced) { "description": [{ "type": "p", "children": [{ "text": "Your text here" }] }] } # Clear the description { "description": null }
description cannot be set in POST /v1/tasks — use PATCH after creation.description from PATCH leaves existing content unchanged.[] (empty array) stores an empty node — use null to clear.Deletes a task and all its comments. Returns 204 on success.
Adds a comment to a task. When using an API key the comment appears under the key name; when using a Bearer token it appears as the authenticated user. Returns 201 on success.
| Field | Type | Description |
|---|---|---|
text* | string | Comment text. Supports markdown: # headings, - bullets, 1. numbered lists, **bold**, *italic*, `code`, > blockquote. Blank lines separate blocks. |
# Plain text { "text": "Fixed the issue in projectsSaga.ts:287." } # With markdown formatting { "text": "## Root cause\n\nThe worker was missing an `await`.\n\n- Fixed in projectsSaga.ts:287\n- Added unit test" } # Returns 201 { "id": "comment_xyz" }
Deletes a comment. Returns 204 on success.
Uploading a file is a two-step process: get a signed URL, upload the file directly to Firebase Storage, then register the attachment on the task.
Returns a resumable upload URL for uploading a file directly to Firebase Storage (valid for 7 days). PUT the file to this URL with the exact Content-Type and Content-Length headers.
| Field | Type | Description |
|---|---|---|
contentType* | string | MIME type of the file (e.g. image/png) |
size* | number | File size in bytes. Rejected if over plan limit (25 MB free / 100 MB pro). |
# Step 1: get upload URL { "uploadUrl": "https://storage.googleapis.com/...", "attachmentId": "att_abc" } # Step 2: upload the file directly to GCS curl -X PUT "<uploadUrl>" \ -H "Content-Type: image/png" \ -H "Content-Length: <size in bytes>" \ --data-binary @file.png
Registers an already-uploaded file as an attachment on a task. Call this after a successful PUT to the signed URL. Returns the permanent download URL.
| Field | Type | Description |
|---|---|---|
attachmentId* | string | ID returned by POST /v1/attachments/upload |
name* | string | File name (e.g. screenshot.png) |
contentType* | string | MIME type |
size* | number | File size in bytes |
{ "id": "att_abc", "url": "https://firebasestorage.googleapis.com/..." }
Webhooks let Plate push real-time notifications to your server whenever tasks are created, updated, or completed — no polling required. See the Webhooks reference for payload format, signature verification, and retry behavior.
Registers a new webhook endpoint. Plate will send a signed HTTP POST to url for each matching event. Returns 201 on success.
| Field | Type | Description |
|---|---|---|
url* | string | HTTPS endpoint that will receive event payloads |
secret* | string | Shared secret used to sign payloads. Keep it private — Plate never returns it again. |
eventsoptional | string[] | Which events to deliver: task.created, task.updated, task.completed. Omit or send [] to subscribe to all three. |
{ "id": "wh_abc", "url": "https://example.com/hooks/plate", "events": ["task.created", "task.updated", "task.completed"] }
Returns all registered webhook endpoints for the workspace. Secrets are never included in the response.
[
{
"id": "wh_abc",
"url": "https://example.com/hooks/plate",
"events": ["task.created", "task.updated", "task.completed"],
"createdAt": "2024-01-15T10:00:00.000Z"
}
]
Deletes a webhook endpoint. Returns 204 on success.
Questions? Write to us at hello@plate.to