Planitude
Remote MCP server of Planitude, a free online Gantt and project planning tool: ChatGPT, Claude, Gemini, Cursor or Claude Code read tasks, dates and critical path and update the plan. OAuth or API key.
Hosted MCP Server
npx add-mcp 'https://planitude.app/mcp'Installs into Claude Code, Codex, Cursor and more
Documentation
Build with Planitude
A REST API, signed webhooks and an MCP server: keep your systems in sync with the plan, react to every change and let an AI assistant work on your projects.
Code, field names and the technical reference are in English, like most API documentation.
-
OpenAPI 3.1: https://planitude.app/api/openapi.json
-
Base URL: https://planitude.app
-
API version: 2026-10-01
-
Sync tasks with your systems: Create projects and tasks from a CRM, an ERP or a script, find them again by your own id with
externalIdand let the scheduling engine compute the dates. -
React to every change: Receive a signed HTTPS request when a task is created, completed or moved, whether the change comes from the app, the API, an import or an AI assistant.
-
Connect an AI assistant: ChatGPT, Claude, Gemini, Cursor and VS Code read your plans and propose changes through the MCP server, with your permissions and nothing more.
Requests and responses are JSON over HTTPS. Durations and lags are in working minutes (an 8-hour day is 480), instants are ISO 8601 in UTC, dates without a time are YYYY-MM-DD.
Quickstart
From zero to your first webhook in three steps. All you need is a terminal.
1. Create an API key
Open Profile → API keys, choose Read and write and copy the key: it starts with pln_ and is shown only once. Keep it in an environment variable.
export PLANITUDE_API_KEY="pln_…"
2. Make your first call
List the projects you can see. The key acts as you: same projects, same roles.
curl https://planitude.app/api/projects \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Expected result:
{
"projects": [
{
"id": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"key": "WHX",
"keyLocked": false,
"name": "Warehouse expansion",
"description": null,
"startDate": "2026-10-05",
"calendarId": null,
"currency": "EUR",
"scheduleFrom": "forward",
"deadline": null,
"color": null,
"settings": {},
"archivedAt": null,
"createdBy": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z",
"workspaceName": "Acme Construction",
"role": "owner",
"taskCount": 12,
"finishAt": "2026-10-07T15:00:00.000Z"
}
]
}
3. Receive a webhook
Subscribe a public HTTPS endpoint to the events you need, then send a test event right away and verify its signature.
curl -X POST https://planitude.app/api/webhooks \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/planitude",
"events": [
"task.created",
"task.completed"
],
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8"
}'
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "webhook.ping",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"message": "Planitude webhook test",
"webhook": {
"id": "1747ee7f-a395-4d3c-94b0-54b513d71d88",
"name": "Warehouse sync",
"events": [
"task.created",
"task.completed"
]
}
}
}
Authentication
Authenticate every request with a personal API key in the Authorization header, as a bearer token. Keys are personal: they act as you, with your role on each project, never more. You can limit a key to one workspace, give it an expiry and revoke it at any time; every call is logged next to the key.
| Scope | Allows |
|---|---|
read (planitude:read) | Read projects, tasks, dependencies, members, comments, baselines and webhooks. |
write (planitude:write) | Everything in read, plus create and change projects, tasks, dependencies, comments, baselines and webhooks. |
Keys and OAuth apps cannot delete projects or tasks: deleting stays in the web app, where a person confirms it. A script that targets the wrong id cannot wipe a plan.
OAuth 2.1 for third-party apps
Apps that act on behalf of other people use OAuth 2.1: authorization code with PKCE (S256), refresh tokens rotated on every use, dynamic client registration (RFC 7591). Ask for the resource https://planitude.app/api and the scope planitude:read or planitude:write.
The endpoints are published in the standard discovery documents, so OAuth libraries configure themselves.
{
"issuer": "https://planitude.app",
"authorization_endpoint": "https://planitude.app/oauth/authorize",
"token_endpoint": "https://planitude.app/api/oauth/token",
"registration_endpoint": "https://planitude.app/api/oauth/register",
"revocation_endpoint": "https://planitude.app/api/oauth/revoke",
"scopes_supported": [
"planitude:read",
"planitude:write"
],
"grant_types_supported": [
"authorization_code",
"refresh_token"
],
"code_challenge_methods_supported": [
"S256"
]
}
Good practices
- Keep keys in environment variables or a secret manager, never in code or in a repository. Keys start with
pln_, so secret scanners can spot them. - Use a read-only key when you only read, and one key per integration: you can revoke one without stopping the others.
- Give keys used by scripts an expiry and rotate them.
- Call the API from your server, not from a browser: a key in front-end code is a public key.
Errors
Planitude uses standard HTTP status codes. Errors always have the same JSON shape: a stable error code to branch on and, when useful, details with the reason or the fields to fix.
| Status | error | Meaning |
|---|---|---|
| 400 | invalid_request | The request is not valid: details.fields lists each field and the problem. |
| 401 | unauthorized | The key is missing, invalid, revoked or expired. |
| 403 | forbidden | The key or your role does not allow it (details.reason), for example a write with a read-only key, or a delete. |
| 404 | not_found | Not found, or not visible to you. |
| 409 | dependency_cycle | Conflict with the plan, for example a dependency that would close a cycle (dependency_cycle). |
| 429 | rate_limited | Too many requests: wait for the seconds in Retry-After. |
| 500 | server_error | Something went wrong on our side: retry with exponential backoff. |
| Field | Type | Required | Description |
|---|---|---|---|
error | string | yes | Stable error code: invalid_request, unauthorized, forbidden, not_found, conflict, dependency_cycle, limit_exceeded, rate_limited, server_error, ... |
details | object |
HTTP 400
{
"error": "invalid_request",
"details": {
"fields": [
{
"path": "name",
"message": "Invalid input: expected string, received undefined"
},
{
"path": "durationMin",
"message": "Too small: expected number to be >=0"
}
]
}
}
HTTP 403
{
"error": "forbidden",
"details": {
"reason": "destructive"
}
}
HTTP 409
{
"error": "dependency_cycle",
"details": {
"cycles": [
{
"path": "Foundations -> Walls -> Foundations",
"taskIds": [
"4dbd104a-00cd-4035-8955-255f52ea586a",
"a22fd799-c4dc-4869-a466-0dd6e96f71ce"
],
"taskNames": [
"Foundations",
"Walls"
]
}
]
}
}
Rate limits
Limits apply per key, in a sliding window of one minute: 240 read requests and 60 write requests. Over the limit you get 429 rate_limited with a Retry-After header, in seconds.
Wait for Retry-After before retrying, spread bulk work over time and group many changes in one change set: up to 1,000 changes in one request and one recalculation.
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json
{
"error": "rate_limited",
"details": {
"retryAfterSec": 12
}
}
Pagination and search
Task lists are paginated with a cursor. Pass limit (1–500, default 100) and, for the next page, the nextCursor of the previous response as cursor. When nextCursor is null there are no more pages.
Filter with externalId (exact match on your own id), q (name contains), updatedSince and completed; sort with sort=-number (newest first) or -updatedAt. Shorter lists, such as projects, members and dependencies, come back whole in one response.
const tasks = [];
let cursor = null;
do {
const url = new URL("https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/tasks");
url.searchParams.set("limit", "500");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.PLANITUDE_API_KEY}` },
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const page = await res.json();
tasks.push(...page.tasks);
cursor = page.nextCursor;
} while (cursor);
console.log(tasks.length);
Versioning
The API and the webhook payloads are versioned by date. The current version is 2026-10-01: it is the apiVersion field of every event and the info.version of the OpenAPI document.
Within a version we only add: new endpoints, new optional fields, new event types. Fields are never removed or retyped without a new version, so ignore the fields you do not know.
API reference
Every request: Authorization: Bearer $PLANITUDE_API_KEY. Examples use the same project (WHX), task and person throughout.
Account
Who is calling, and their workspaces.
User object
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | ||
email | string (email) | ||
name | string, nullable | ||
avatarUrl | string, nullable | ||
locale | string | ||
emailVerified | boolean |
{
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"email": "anna@example.com",
"name": "Anna Rossi",
"avatarUrl": null,
"locale": "en",
"emailVerified": true
}
Workspace object
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | ||
name | string | ||
role | string | One of: owner, admin, member. |
{
"id": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"name": "Acme Construction",
"role": "owner"
}
Current user (connection test)
GET /api/auth/me (scope: read)
curl https://planitude.app/api/auth/me \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: The user
| Field | Type | Required | Description |
|---|---|---|---|
user | User |
{
"user": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"email": "anna@example.com",
"name": "Anna Rossi",
"avatarUrl": null,
"locale": "en",
"emailVerified": true
}
}
Workspaces of the user, with role
GET /api/workspaces (scope: read)
curl https://planitude.app/api/workspaces \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Workspaces
| Field | Type | Required | Description |
|---|---|---|---|
workspaces | array of Workspace |
{
"workspaces": [
{
"id": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"name": "Acme Construction",
"role": "owner"
}
]
}
Projects
Projects, templates and baselines.
Project object
A project as returned by the write endpoints and project listings.
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | ||
workspaceId | string (uuid) | ||
key | string | Project key, e.g. CRON (task keys are CRON-12). | |
keyLocked | boolean | ||
name | string | ||
description | string, nullable | ||
startDate | string (date) | ||
calendarId | string (uuid), nullable | ||
currency | string | ||
scheduleFrom | string | One of: forward, backward. | |
deadline | string (date), nullable | ||
color | string, nullable | ||
settings | object | ||
archivedAt | string (date-time), nullable | ||
createdBy | string (uuid), nullable | ||
createdAt | string (date-time) | ||
updatedAt | string (date-time) |
{
"id": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"key": "WHX",
"keyLocked": false,
"name": "Warehouse expansion",
"description": null,
"startDate": "2026-10-05",
"calendarId": null,
"currency": "EUR",
"scheduleFrom": "forward",
"deadline": null,
"color": null,
"settings": {},
"archivedAt": null,
"createdBy": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z"
}
Template object
| Field | Type | Required | Description |
|---|---|---|---|
id | string | builtin:<slug> or the id of a template saved by you. | |
kind | string | One of: builtin, user. | |
name | string | ||
description | string, nullable | ||
taskCount | integer | ||
durationDays | integer | Working days from start to finish. |
{
"id": "builtin:website-launch",
"kind": "builtin",
"name": "Website launch",
"description": "From discovery to go-live: design, content, development, testing and launch.",
"taskCount": 12,
"durationDays": 43
}
Baseline object
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | ||
name | string | ||
index | integer, nullable | Numbered MS Project baseline (1-10); null for the main baseline and the ones you name. | |
capturedAt | string (date-time) | ||
capturedBy | string (uuid), nullable | ||
taskCount | integer |
{
"id": "b5d68d05-0640-48f9-8eeb-ed7804f4a116",
"name": "Plan v1",
"index": null,
"capturedAt": "2026-10-01T08:30:12.418Z",
"capturedBy": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"taskCount": 12
}
List projects
GET /api/projects (scope: read)
All projects visible to the user (not paginated). ?archived=1 includes archived ones.
Query parameters:
| Field | Type | Required | Description |
|---|---|---|---|
archived | string | One of: 1. |
curl https://planitude.app/api/projects \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Projects
| Field | Type | Required | Description |
|---|---|---|---|
projects | array of Project |
{
"projects": [
{
"id": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"key": "WHX",
"keyLocked": false,
"name": "Warehouse expansion",
"description": null,
"startDate": "2026-10-05",
"calendarId": null,
"currency": "EUR",
"scheduleFrom": "forward",
"deadline": null,
"color": null,
"settings": {},
"archivedAt": null,
"createdBy": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z",
"workspaceName": "Acme Construction",
"role": "owner",
"taskCount": 12,
"finishAt": "2026-10-07T15:00:00.000Z"
}
]
}
Create a project
POST /api/projects (scope: write)
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | |
workspaceId | string (uuid) | Defaults to your personal workspace. | |
key | string | 2-5 letters/digits, starts with a letter. Derived from the name if omitted. | |
description | string, nullable | ||
startDate | string (date) | ||
deadline | string (date), nullable | ||
currency | string | ||
scheduleFrom | string | One of: forward, backward. | |
color | string, nullable |
curl -X POST https://planitude.app/api/projects \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Warehouse expansion","startDate":"2026-10-05","currency":"EUR"}'
Returns 201 Created: Created
| Field | Type | Required | Description |
|---|---|---|---|
project | Project |
{
"project": {
"id": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"key": "WHX",
"keyLocked": false,
"name": "Warehouse expansion",
"description": null,
"startDate": "2026-10-05",
"calendarId": null,
"currency": "EUR",
"scheduleFrom": "forward",
"deadline": null,
"color": null,
"settings": {},
"archivedAt": null,
"createdBy": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z"
}
}
Create a project from a template
POST /api/projects/from-template (scope: write)
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
templateId | string | yes | Id from GET /api/templates. |
startDate | string (date) | yes | |
name | string | ||
key | string | ||
locale | string | Language of the template texts (en, it, es, fr, de, pt, nl, pl, ja, zh, sv, da, no, fi, cs, ro, uk, tr, ar, he, hi, id, vi, ko). | |
tz | string | IANA time zone of the project calendar. | |
workspaceId | string (uuid) |
curl -X POST https://planitude.app/api/projects/from-template \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "builtin:website-launch",
"startDate": "2026-10-05",
"name": "Website relaunch",
"locale": "en"
}'
Returns 201 Created: Created
| Field | Type | Required | Description |
|---|---|---|---|
project | Project | ||
taskCount | integer | ||
conflicts | array of Conflict |
{
"project": {
"id": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"key": "WHX",
"keyLocked": false,
"name": "Warehouse expansion",
"description": null,
"startDate": "2026-10-05",
"calendarId": null,
"currency": "EUR",
"scheduleFrom": "forward",
"deadline": null,
"color": null,
"settings": {},
"archivedAt": null,
"createdBy": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z"
},
"taskCount": 12,
"conflicts": []
}
List project templates
GET /api/templates (scope: read)
Query parameters:
| Field | Type | Required | Description |
|---|---|---|---|
locale | string |
curl https://planitude.app/api/templates \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Templates
| Field | Type | Required | Description |
|---|---|---|---|
templates | array of Template |
{
"templates": [
{
"id": "builtin:website-launch",
"kind": "builtin",
"name": "Website launch",
"description": "From discovery to go-live: design, content, development, testing and launch.",
"taskCount": 12,
"durationDays": 43
}
]
}
Full project (tasks tree, dependencies, resources, members, baselines)
GET /api/projects/{projectId} (scope: read)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
curl https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8 \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Project
| Field | Type | Required | Description |
|---|---|---|---|
project | Project | ||
tasks | array of Task | ||
dependencies | array of Dependency |
{
"project": {
"id": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"key": "WHX",
"keyLocked": false,
"name": "Warehouse expansion",
"description": null,
"startDate": "2026-10-05",
"calendarId": null,
"currency": "EUR",
"scheduleFrom": "forward",
"deadline": null,
"color": null,
"settings": {},
"archivedAt": null,
"createdBy": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z"
},
"tasks": [
{
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"sortKey": "i",
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null
}
],
"dependencies": [
{
"id": "9ba5faf8-923d-4ecc-86f0-f245c5d179db",
"predecessorId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"successorId": "a22fd799-c4dc-4869-a466-0dd6e96f71ce",
"type": "FS",
"lagMin": 0
}
]
}
Update a project
PATCH /api/projects/{projectId} (scope: write)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ||
description | string, nullable | ||
startDate | string (date) | ||
deadline | string (date), nullable | ||
currency | string | ||
scheduleFrom | string | One of: forward, backward. | |
color | string, nullable | ||
archived | boolean |
curl -X PATCH https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8 \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"deadline":"2026-12-18"}'
Returns 200 OK: Updated
| Field | Type | Required | Description |
|---|---|---|---|
project | Project | ||
conflicts | array of Conflict | ||
rescheduled | boolean | The schedule was recalculated (start date, calendar or direction changed). |
{
"project": {
"id": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"key": "WHX",
"keyLocked": false,
"name": "Warehouse expansion",
"description": null,
"startDate": "2026-10-05",
"calendarId": null,
"currency": "EUR",
"scheduleFrom": "forward",
"deadline": null,
"color": null,
"settings": {},
"archivedAt": null,
"createdBy": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z"
},
"conflicts": [],
"rescheduled": false
}
Export the plan as a file (Microsoft Project XML, CSV, PNG or PDF)
GET /api/projects/{projectId}/export (scope: read)
Returns the file itself (Content-Disposition: attachment), not JSON. Any role with access to the project, read-only included.
png and pdf draw the Gantt chart on the server (title, generation date, legend; in the PDF repeated on every page) and accept the image options below; at most 20 image exports per user every 10 minutes. Over the size limits: 413 limit_exceeded with details.reason (tasks, image_too_large, pages, timeline).
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
Query parameters:
| Field | Type | Required | Description |
|---|---|---|---|
format | string | File format. mspdi = Microsoft Project XML. One of: mspdi, csv, png, pdf. Default: mspdi. | |
locale | string | Language of headings, legend and dates (e.g. en, it, ar, ja). Default: en. | |
scale | string | PNG/PDF: time unit of the columns. One of: auto, day, week, month, quarter. Default: auto. | |
from | string (date) | PNG/PDF: first day to draw (with to). Default: the whole project. | |
to | string (date) | PNG/PDF: last day to draw (with from). | |
critical | string | PNG/PDF: highlight the critical path. One of: 0, 1. Default: 1. | |
baseline | string | PNG/PDF: draw the main baseline under each bar. One of: 0, 1. Default: 1. | |
links | string | PNG/PDF: draw dependency arrows. One of: 0, 1. Default: 1. | |
paper | string | PDF: paper size. One of: a4, a3, letter. Default: a4. | |
orientation | string | PDF: page orientation. One of: landscape, portrait. Default: landscape. |
curl "https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/export?format=pdf&locale=en&paper=a4" \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-o plan.pdf
Returns 200 OK: The file: application/xml, text/csv, image/png or application/pdf.
List baselines
GET /api/projects/{projectId}/baseline (scope: read)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
curl https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/baseline \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Baselines
| Field | Type | Required | Description |
|---|---|---|---|
baselines | array of Baseline |
{
"baselines": [
{
"id": "b5d68d05-0640-48f9-8eeb-ed7804f4a116",
"name": "Plan v1",
"index": null,
"capturedAt": "2026-10-01T08:30:12.418Z",
"capturedBy": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"taskCount": 12
}
]
}
Save a baseline
POST /api/projects/{projectId}/baseline (scope: write)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string |
curl -X POST https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/baseline \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Plan v1"}'
Returns 201 Created: Saved
| Field | Type | Required | Description |
|---|---|---|---|
baseline | Baseline |
{
"baseline": {
"id": "b5d68d05-0640-48f9-8eeb-ed7804f4a116",
"name": "Plan v1",
"index": null,
"capturedAt": "2026-10-01T08:30:12.418Z",
"capturedBy": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"taskCount": 12
}
}
Tasks
Tasks, search by external id, comments.
Task object
Task representation of GET /tasks and webhook payloads: the stored task (without sortKey) plus project, assignees and link.
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | ||
number | integer | Sequential number in the project, never reused. | |
key | string | Human key, e.g. CRON-12. | |
parentId | string (uuid), nullable | ||
name | string | ||
type | string | One of: summary, task, milestone. | |
durationMin | integer | Duration in working minutes (8h day = 480). | |
workMin | integer | ||
progressPct | number | ||
constraintKind | string | One of: asap, alap, start_no_earlier_than, start_no_later_than, finish_no_earlier_than, finish_no_later_than, must_start_on, must_finish_on. | |
constraintDate | string (date-time), nullable | ||
deadline | string (date-time), nullable | ||
calendarId | string (uuid), nullable | ||
costFixed | number | ||
notes | string, nullable | ||
color | string, nullable | ||
wbs | string, nullable | ||
externalId | string, nullable | Your identifier for this task in another system. Searchable with GET /tasks?externalId=. | |
startAt | string (date-time), nullable | Computed start. | |
finishAt | string (date-time), nullable | Computed finish. | |
isCritical | boolean | On the critical path. | |
totalFloatMin | integer, nullable | Total float (slack) in working minutes. | |
earlyStartAt | string (date-time), nullable | ||
lateFinishAt | string (date-time), nullable | ||
actualStartAt | string (date-time), nullable | ||
actualFinishAt | string (date-time), nullable | ||
actualDurationMin | integer, nullable | ||
remainingDurationMin | integer, nullable | ||
manuallyScheduled | boolean | ||
levelingDelayMin | integer | ||
taskType | string | One of: fixed_units, fixed_duration, fixed_work, fixed_duration_and_units. | |
effortDriven | boolean | ||
durationOptimisticMin | integer, nullable | ||
durationPessimisticMin | integer, nullable | ||
projectId | string (uuid) | ||
completed | boolean | progressPct = 100. | |
assignees | array of Assignee | ||
url | string (uri) | Page of the task in the web app. |
{
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null,
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"completed": false,
"assignees": [
{
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"unitsPct": 100
}
],
"url": "https://planitude.app/app/t/WHX-1"
}
WriteResult object
| Field | Type | Required | Description |
|---|---|---|---|
tasks | array of Task | Created/updated tasks plus the ones the recalculation moved. | |
deletedTaskIds | array of string | ||
dependencies | array of Dependency | ||
deletedDependencyIds | array of string | ||
conflicts | array of Conflict | ||
projectStartAt | string (date-time) | ||
projectFinishAt | string (date-time) | ||
stats | object | ||
stats.taskCount | integer | ||
stats.recomputedCount | integer | Tasks the scheduling engine recalculated. | |
stats.savedCount | integer | Tasks whose stored values changed. | |
stats.durationMs | integer |
{
"tasks": [
{
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"sortKey": "i",
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null
}
],
"deletedTaskIds": [],
"dependencies": [
{
"id": "9ba5faf8-923d-4ecc-86f0-f245c5d179db",
"predecessorId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"successorId": "a22fd799-c4dc-4869-a466-0dd6e96f71ce",
"type": "FS",
"lagMin": 0
}
],
"deletedDependencyIds": [],
"conflicts": [],
"projectStartAt": "2026-10-05T06:00:00.000Z",
"projectFinishAt": "2026-10-07T15:00:00.000Z",
"stats": {
"taskCount": 12,
"recomputedCount": 3,
"savedCount": 2,
"durationMs": 39
}
}
Comment object
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | ||
taskId | string (uuid) | ||
userId | string (uuid) | ||
authorName | string | ||
authorAvatarUrl | string, nullable | ||
body | string | ||
mentions | array of string | ||
createdAt | string (date-time) | ||
editedAt | string (date-time), nullable |
{
"id": "0fc2dec5-a409-47c9-9c6e-0663e8f8bcf3",
"taskId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"authorName": "Anna Rossi",
"authorAvatarUrl": null,
"body": "Concrete delivered, we can start the walls.",
"mentions": [],
"createdAt": "2026-10-01T08:30:12.418Z",
"editedAt": null
}
List or search tasks (paginated)
GET /api/projects/{projectId}/tasks (scope: read)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
Query parameters:
| Field | Type | Required | Description |
|---|---|---|---|
limit | integer | 1-500. Default: 100. | |
cursor | string | nextCursor of the previous page. | |
sort | string | -number = newest first (polling triggers). -updatedAt returns the first page only. One of: number, -number, -updatedAt. Default: number. | |
externalId | string | Exact match on externalId. | |
q | string | Name contains (case-insensitive). | |
updatedSince | string (date-time) | ||
completed | string | One of: true, false. |
curl "https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/tasks?externalId=crm-4711" \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: A page of tasks
| Field | Type | Required | Description |
|---|---|---|---|
tasks | array of Task | ||
nextCursor | string, nullable |
{
"tasks": [
{
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null,
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"completed": false,
"assignees": [
{
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"unitsPct": 100
}
],
"url": "https://planitude.app/app/t/WHX-1"
}
],
"nextCursor": null
}
Create a task, or apply a change set
POST /api/projects/{projectId}/tasks (scope: write)
Send a TaskCreate to create one task (201), or a ChangeSet to apply several changes in one transaction (200). Dates are recalculated by the scheduling engine.
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
Request body (TaskCreate):
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | |
parentId | string (uuid), nullable | Parent (summary) task; null = top level. | |
afterTaskId | string (uuid), nullable | Insert after this sibling. | |
beforeTaskId | string (uuid), nullable | Insert before this sibling. | |
type | string | One of: summary, task, milestone. | |
durationMin | integer | Duration in working minutes (8h day = 480). | |
workMin | integer | ||
progressPct | number | ||
constraintKind | string | One of: asap, alap, start_no_earlier_than, start_no_later_than, finish_no_earlier_than, finish_no_later_than, must_start_on, must_finish_on. | |
constraintDate | string (date-time), nullable | ||
deadline | string (date-time), nullable | ||
calendarId | string (uuid), nullable | ||
costFixed | number | ||
notes | string, nullable | ||
color | string, nullable | ||
wbs | string, nullable | ||
externalId | string, nullable | Your identifier for this task in another system. Searchable with GET /tasks?externalId=. |
Request body (or ChangeSet):
| Field | Type | Required | Description |
|---|---|---|---|
createTasks | array of TaskCreate | ||
updateTasks | array of any | ||
createDependencies | array of DependencyCreate | ||
deleteDependencyIds | array of string | ||
fullReschedule | boolean |
curl -X POST https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/tasks \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Foundations","durationMin":1440,"externalId":"crm-4711"}'
Returns 201 Created: Task created
| Field | Type | Required | Description |
|---|---|---|---|
tasks | array of Task | Created/updated tasks plus the ones the recalculation moved. | |
deletedTaskIds | array of string | ||
dependencies | array of Dependency | ||
deletedDependencyIds | array of string | ||
conflicts | array of Conflict | ||
projectStartAt | string (date-time) | ||
projectFinishAt | string (date-time) | ||
stats | object | ||
stats.taskCount | integer | ||
stats.recomputedCount | integer | Tasks the scheduling engine recalculated. | |
stats.savedCount | integer | Tasks whose stored values changed. | |
stats.durationMs | integer |
{
"tasks": [
{
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"sortKey": "i",
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null
}
],
"deletedTaskIds": [],
"dependencies": [
{
"id": "9ba5faf8-923d-4ecc-86f0-f245c5d179db",
"predecessorId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"successorId": "a22fd799-c4dc-4869-a466-0dd6e96f71ce",
"type": "FS",
"lagMin": 0
}
],
"deletedDependencyIds": [],
"conflicts": [],
"projectStartAt": "2026-10-05T06:00:00.000Z",
"projectFinishAt": "2026-10-07T15:00:00.000Z",
"stats": {
"taskCount": 12,
"recomputedCount": 3,
"savedCount": 2,
"durationMs": 39
}
}
Get a task
GET /api/projects/{projectId}/tasks/{taskId} (scope: read)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
taskId | string (uuid) | yes | Task id. |
curl https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/tasks/4dbd104a-00cd-4035-8955-255f52ea586a \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: The task
| Field | Type | Required | Description |
|---|---|---|---|
task | Task |
{
"task": {
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null,
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"completed": false,
"assignees": [
{
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"unitsPct": 100
}
],
"url": "https://planitude.app/app/t/WHX-1"
}
}
Update a task
PATCH /api/projects/{projectId}/tasks/{taskId} (scope: write)
Only the fields you send change; null clears a field.
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
taskId | string (uuid) | yes | Task id. |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ||
type | string | One of: summary, task, milestone. | |
durationMin | integer | Duration in working minutes (8h day = 480). | |
workMin | integer | ||
progressPct | number | ||
constraintKind | string | One of: asap, alap, start_no_earlier_than, start_no_later_than, finish_no_earlier_than, finish_no_later_than, must_start_on, must_finish_on. | |
constraintDate | string (date-time), nullable | ||
deadline | string (date-time), nullable | ||
calendarId | string (uuid), nullable | ||
costFixed | number | ||
notes | string, nullable | ||
color | string, nullable | ||
wbs | string, nullable | ||
externalId | string, nullable | Your identifier for this task in another system. Searchable with GET /tasks?externalId=. | |
placement | object | ||
placement.parentId | string (uuid), nullable | Parent (summary) task; null = top level. | |
placement.afterTaskId | string (uuid), nullable | Insert after this sibling. | |
placement.beforeTaskId | string (uuid), nullable | Insert before this sibling. |
curl -X PATCH https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/tasks/4dbd104a-00cd-4035-8955-255f52ea586a \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"progressPct":100}'
Returns 200 OK: Updated
| Field | Type | Required | Description |
|---|---|---|---|
tasks | array of Task | Created/updated tasks plus the ones the recalculation moved. | |
deletedTaskIds | array of string | ||
dependencies | array of Dependency | ||
deletedDependencyIds | array of string | ||
conflicts | array of Conflict | ||
projectStartAt | string (date-time) | ||
projectFinishAt | string (date-time) | ||
stats | object | ||
stats.taskCount | integer | ||
stats.recomputedCount | integer | Tasks the scheduling engine recalculated. | |
stats.savedCount | integer | Tasks whose stored values changed. | |
stats.durationMs | integer |
{
"tasks": [
{
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"sortKey": "i",
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null
}
],
"deletedTaskIds": [],
"dependencies": [
{
"id": "9ba5faf8-923d-4ecc-86f0-f245c5d179db",
"predecessorId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"successorId": "a22fd799-c4dc-4869-a466-0dd6e96f71ce",
"type": "FS",
"lagMin": 0
}
],
"deletedDependencyIds": [],
"conflicts": [],
"projectStartAt": "2026-10-05T06:00:00.000Z",
"projectFinishAt": "2026-10-07T15:00:00.000Z",
"stats": {
"taskCount": 12,
"recomputedCount": 3,
"savedCount": 2,
"durationMs": 39
}
}
Comments of a task
GET /api/projects/{projectId}/tasks/{taskId}/comments (scope: read)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
taskId | string (uuid) | yes | Task id. |
curl https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/tasks/4dbd104a-00cd-4035-8955-255f52ea586a/comments \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Comments
| Field | Type | Required | Description |
|---|---|---|---|
comments | array of Comment |
{
"comments": [
{
"id": "0fc2dec5-a409-47c9-9c6e-0663e8f8bcf3",
"taskId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"authorName": "Anna Rossi",
"authorAvatarUrl": null,
"body": "Concrete delivered, we can start the walls.",
"mentions": [],
"createdAt": "2026-10-01T08:30:12.418Z",
"editedAt": null
}
]
}
Comment on a task
POST /api/projects/{projectId}/tasks/{taskId}/comments (scope: write)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
taskId | string (uuid) | yes | Task id. |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
body | string | yes | Plain text. |
mentions | array of string | User ids to notify. |
curl -X POST https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/tasks/4dbd104a-00cd-4035-8955-255f52ea586a/comments \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"body":"Concrete delivered, we can start the walls."}'
Returns 201 Created: Created
| Field | Type | Required | Description |
|---|---|---|---|
comment | Comment |
{
"comment": {
"id": "0fc2dec5-a409-47c9-9c6e-0663e8f8bcf3",
"taskId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"authorName": "Anna Rossi",
"authorAvatarUrl": null,
"body": "Concrete delivered, we can start the walls.",
"mentions": [],
"createdAt": "2026-10-01T08:30:12.418Z",
"editedAt": null
}
}
Dependencies
Links between tasks (FS, SS, FF, SF, with lag).
Dependency object
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | ||
predecessorId | string (uuid) | ||
successorId | string (uuid) | ||
type | string | One of: FS, SS, FF, SF. | |
lagMin | integer | Working minutes; negative = lead. |
{
"id": "9ba5faf8-923d-4ecc-86f0-f245c5d179db",
"predecessorId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"successorId": "a22fd799-c4dc-4869-a466-0dd6e96f71ce",
"type": "FS",
"lagMin": 0
}
List dependencies
GET /api/projects/{projectId}/dependencies (scope: read)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
curl https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/dependencies \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Dependencies
| Field | Type | Required | Description |
|---|---|---|---|
dependencies | array of Dependency |
{
"dependencies": [
{
"id": "9ba5faf8-923d-4ecc-86f0-f245c5d179db",
"predecessorId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"successorId": "a22fd799-c4dc-4869-a466-0dd6e96f71ce",
"type": "FS",
"lagMin": 0
}
]
}
Create (or update) a dependency
POST /api/projects/{projectId}/dependencies (scope: write)
A dependency that would close a cycle is refused with 409 dependency_cycle.
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
Request body (DependencyCreate):
| Field | Type | Required | Description |
|---|---|---|---|
predecessorId | string (uuid) | yes | |
successorId | string (uuid) | yes | |
type | string | One of: FS, SS, FF, SF. Default: FS. | |
lagMin | integer | Default: 0. |
Request body (or dependencies):
| Field | Type | Required | Description |
|---|---|---|---|
dependencies | array of DependencyCreate | yes |
curl -X POST https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/dependencies \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"predecessorId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"successorId": "a22fd799-c4dc-4869-a466-0dd6e96f71ce",
"type": "FS",
"lagMin": 0
}'
Returns 201 Created: Created
| Field | Type | Required | Description |
|---|---|---|---|
tasks | array of Task | Created/updated tasks plus the ones the recalculation moved. | |
deletedTaskIds | array of string | ||
dependencies | array of Dependency | ||
deletedDependencyIds | array of string | ||
conflicts | array of Conflict | ||
projectStartAt | string (date-time) | ||
projectFinishAt | string (date-time) | ||
stats | object | ||
stats.taskCount | integer | ||
stats.recomputedCount | integer | Tasks the scheduling engine recalculated. | |
stats.savedCount | integer | Tasks whose stored values changed. | |
stats.durationMs | integer |
{
"tasks": [
{
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"sortKey": "i",
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null
}
],
"deletedTaskIds": [],
"dependencies": [
{
"id": "9ba5faf8-923d-4ecc-86f0-f245c5d179db",
"predecessorId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"successorId": "a22fd799-c4dc-4869-a466-0dd6e96f71ce",
"type": "FS",
"lagMin": 0
}
],
"deletedDependencyIds": [],
"conflicts": [],
"projectStartAt": "2026-10-05T06:00:00.000Z",
"projectFinishAt": "2026-10-07T15:00:00.000Z",
"stats": {
"taskCount": 12,
"recomputedCount": 3,
"savedCount": 2,
"durationMs": 39
}
}
Remove a dependency
DELETE /api/projects/{projectId}/dependencies/{dependencyId} (scope: write)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
dependencyId | string (uuid) | yes |
curl -X DELETE https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/dependencies/9ba5faf8-923d-4ecc-86f0-f245c5d179db \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Removed
| Field | Type | Required | Description |
|---|---|---|---|
tasks | array of Task | Created/updated tasks plus the ones the recalculation moved. | |
deletedTaskIds | array of string | ||
dependencies | array of Dependency | ||
deletedDependencyIds | array of string | ||
conflicts | array of Conflict | ||
projectStartAt | string (date-time) | ||
projectFinishAt | string (date-time) | ||
stats | object | ||
stats.taskCount | integer | ||
stats.recomputedCount | integer | Tasks the scheduling engine recalculated. | |
stats.savedCount | integer | Tasks whose stored values changed. | |
stats.durationMs | integer |
{
"tasks": [
{
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"sortKey": "i",
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null
}
],
"deletedTaskIds": [],
"dependencies": [
{
"id": "9ba5faf8-923d-4ecc-86f0-f245c5d179db",
"predecessorId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"successorId": "a22fd799-c4dc-4869-a466-0dd6e96f71ce",
"type": "FS",
"lagMin": 0
}
],
"deletedDependencyIds": [],
"conflicts": [],
"projectStartAt": "2026-10-05T06:00:00.000Z",
"projectFinishAt": "2026-10-07T15:00:00.000Z",
"stats": {
"taskCount": 12,
"recomputedCount": 3,
"savedCount": 2,
"durationMs": 39
}
}
People
Project members and task assignees.
Member object
| Field | Type | Required | Description |
|---|---|---|---|
userId | string (uuid) | ||
name | string | ||
email | string | ||
role | string | Effective role on the project. One of: owner, editor, viewer. | |
directRole | string, nullable | Role granted on the project itself (null = only through the workspace). One of: owner, editor, viewer. | |
inherited | boolean | Access comes from the workspace. | |
resourceId | string (uuid), nullable | Resource used when the person is assigned to tasks. | |
addedAt | string (date-time) | ||
avatarUrl | string, nullable |
{
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"email": "anna@example.com",
"role": "owner",
"directRole": null,
"inherited": false,
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"addedAt": "2026-10-01T08:30:12.418Z",
"avatarUrl": null
}
Assignee object
| Field | Type | Required | Description |
|---|---|---|---|
resourceId | string (uuid) | ||
userId | string (uuid), nullable | ||
name | string | ||
unitsPct | integer |
{
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"unitsPct": 100
}
Replace the people assigned to a task
PUT /api/projects/{projectId}/tasks/{taskId}/assignees (scope: write)
userIds must be members of the project (GET /members). An empty list removes everyone. Optional resourceIds replaces the people imported from a file (resources without an account) assigned to the task; omit it to leave them as they are.
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
taskId | string (uuid) | yes | Task id. |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
userIds | array of string | yes | |
resourceIds | array of string |
curl -X PUT https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/tasks/4dbd104a-00cd-4035-8955-255f52ea586a/assignees \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"userIds":["b1666432-132a-4ea9-b1ae-76ea69e226c5"]}'
Returns 200 OK: Assignees saved
| Field | Type | Required | Description |
|---|---|---|---|
taskId | string (uuid) | ||
assignments | array of object | ||
assignments.id | string (uuid) | ||
assignments.taskId | string (uuid) | ||
assignments.resourceId | string (uuid) | ||
assignments.unitsPct | integer | ||
assignments.workMin | integer | ||
resources | array of object | ||
resources.id | string (uuid) | ||
resources.userId | string (uuid), nullable | ||
resources.name | string | ||
resources.kind | string | One of: work, material, cost. | |
resources.email | string, nullable | ||
resources.costPerHour | number | ||
resources.costPerUse | number | ||
resources.capacityPct | integer | ||
resources.calendarId | string (uuid), nullable | ||
resources.color | string, nullable | ||
resources.avatarUrl | string, nullable | ||
needsReschedule | boolean | ||
conflicts | array of Conflict |
{
"taskId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"assignments": [
{
"id": "e62ac96b-e68e-40d3-842e-335e27dfd099",
"taskId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"unitsPct": 100,
"workMin": 0
}
],
"resources": [
{
"id": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"kind": "builtin",
"email": "anna@example.com",
"costPerHour": 0,
"costPerUse": 0,
"capacityPct": 0,
"calendarId": null,
"color": null,
"avatarUrl": null
}
],
"needsReschedule": false,
"conflicts": []
}
People with access to the project (assignable)
GET /api/projects/{projectId}/members (scope: read)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string (uuid) | yes | Project id. |
curl https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/members \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Members
| Field | Type | Required | Description |
|---|---|---|---|
members | array of Member | ||
role | string |
{
"members": [
{
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"email": "anna@example.com",
"role": "owner",
"directRole": null,
"inherited": false,
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"addedAt": "2026-10-01T08:30:12.418Z",
"avatarUrl": null
}
],
"role": "owner"
}
Webhooks
Outgoing webhook subscriptions (REST hooks: subscribe/unsubscribe).
Webhook object
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | ||
workspaceId | string (uuid) | ||
workspaceName | string | ||
projectId | string (uuid), nullable | null = every project of the workspace. | |
projectName | string, nullable | ||
name | string | ||
url | string (uri) | ||
events | array of string | One of: project.created, project.updated, project.deleted, task.created, task.updated, task.deleted, task.completed, task.dates_changed, dependency.created, dependency.deleted, baseline.saved, member.invited, member.joined, comment.created. | |
secretPrefix | string | First characters of the signing secret (not secret). | |
active | boolean | ||
disabledReason | string, nullable | One of: failures, gone, project_deleted, user. | |
disabledAt | string (date-time), nullable | ||
consecutiveFailures | integer | ||
viaApi | boolean | Created with the API (REST hook) rather than from the profile. | |
createdBy | object, nullable | ||
createdBy.id | string (uuid) | ||
createdBy.name | string, nullable | ||
createdAt | string (date-time) | ||
updatedAt | string (date-time) | ||
lastDeliveryAt | string (date-time), nullable | ||
lastSuccessAt | string (date-time), nullable |
{
"id": "1747ee7f-a395-4d3c-94b0-54b513d71d88",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"workspaceName": "Acme Construction",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"projectName": "Warehouse expansion",
"name": "Warehouse sync",
"url": "https://example.com/hooks/planitude",
"events": [
"task.created",
"task.completed"
],
"secretPrefix": "whsec_Fdwl",
"active": true,
"disabledReason": null,
"disabledAt": null,
"consecutiveFailures": 0,
"viaApi": true,
"createdBy": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z",
"lastDeliveryAt": "2026-10-01T08:30:12.418Z",
"lastSuccessAt": "2026-10-01T08:30:12.418Z"
}
Delivery object
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | ||
eventId | string (uuid) | ||
eventType | string | ||
status | string | One of: pending, success, failed, cancelled. | |
attempts | integer | ||
nextAttemptAt | string (date-time), nullable | ||
lastAttemptAt | string (date-time), nullable | ||
responseStatus | integer, nullable | ||
responseExcerpt | string, nullable | ||
error | string, nullable | ||
durationMs | integer, nullable | ||
manual | boolean | ||
createdAt | string (date-time) |
{
"id": "4b207161-80f9-40bb-bc3f-d8f70315be00",
"eventId": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"eventType": "task.completed",
"status": "success",
"attempts": 1,
"nextAttemptAt": null,
"lastAttemptAt": "2026-10-01T08:30:12.418Z",
"responseStatus": 200,
"responseExcerpt": "{\"ok\":true}",
"error": null,
"durationMs": 39,
"manual": false,
"createdAt": "2026-10-01T08:30:12.418Z"
}
Event object
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | yes | Event id, also in the Planitude-Event-Id header: use it to deduplicate. |
type | string | yes | One of: project.created, project.updated, project.deleted, task.created, task.updated, task.deleted, task.completed, task.dates_changed, dependency.created, dependency.deleted, baseline.saved, member.invited, member.joined, comment.created, webhook.ping. |
apiVersion | string | yes | One of: 2026-10-01. |
createdAt | string (date-time) | yes | |
workspaceId | string (uuid) | yes | |
projectId | string (uuid), nullable | ||
actor | object, nullable | Who made the change (null for system changes). | |
actor.id | string (uuid) | ||
actor.name | string, nullable | ||
data | object | yes |
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "project.created",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {}
}
Project (event payload) object
Compact project representation used in webhook payloads.
| Field | Type | Required | Description |
|---|---|---|---|
id | string (uuid) | ||
workspaceId | string (uuid) | ||
key | string | ||
name | string | ||
description | string, nullable | ||
startDate | string (date) | ||
deadline | string (date), nullable | ||
currency | string | ||
scheduleFrom | string | One of: forward, backward. | |
color | string, nullable | ||
archived | boolean | ||
createdAt | string (date-time) | ||
updatedAt | string (date-time) | ||
url | string (uri) |
{
"id": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"key": "WHX",
"name": "Warehouse expansion",
"description": null,
"startDate": "2026-10-05",
"deadline": null,
"currency": "EUR",
"scheduleFrom": "forward",
"color": null,
"archived": false,
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z",
"url": "https://planitude.app/app/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8"
}
List webhook subscriptions
GET /api/webhooks (scope: read)
Query parameters:
| Field | Type | Required | Description |
|---|---|---|---|
workspaceId | string (uuid) | ||
projectId | string (uuid) |
curl https://planitude.app/api/webhooks \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Subscriptions
| Field | Type | Required | Description |
|---|---|---|---|
webhooks | array of Webhook |
{
"webhooks": [
{
"id": "1747ee7f-a395-4d3c-94b0-54b513d71d88",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"workspaceName": "Acme Construction",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"projectName": "Warehouse expansion",
"name": "Warehouse sync",
"url": "https://example.com/hooks/planitude",
"events": [
"task.created",
"task.completed"
],
"secretPrefix": "whsec_Fdwl",
"active": true,
"disabledReason": null,
"disabledAt": null,
"consecutiveFailures": 0,
"viaApi": true,
"createdBy": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z",
"lastDeliveryAt": "2026-10-01T08:30:12.418Z",
"lastSuccessAt": "2026-10-01T08:30:12.418Z"
}
]
}
Subscribe (REST hook)
POST /api/webhooks (scope: write)
Creates a subscription. The response contains the signing secret: it is shown only once. Aliases accepted for automation platforms: targetUrl for url, event for a single event.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
url | string (uri) | yes | HTTPS endpoint that receives the events. Public addresses only. |
events | array of string | yes | * = all events. One of: project.created, project.updated, project.deleted, task.created, task.updated, task.deleted, task.completed, task.dates_changed, dependency.created, dependency.deleted, baseline.saved, member.invited, member.joined, comment.created, *. |
name | string | ||
projectId | string (uuid) | Limit to one project (editor role needed). Omit for the whole workspace (owner/admin). | |
workspaceId | string (uuid) |
curl -X POST https://planitude.app/api/webhooks \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/planitude",
"events": [
"task.created",
"task.completed"
],
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8"
}'
Returns 201 Created: Created
| Field | Type | Required | Description |
|---|---|---|---|
webhook | Webhook | ||
secret | string | whsec_... |
{
"webhook": {
"id": "1747ee7f-a395-4d3c-94b0-54b513d71d88",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"workspaceName": "Acme Construction",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"projectName": "Warehouse expansion",
"name": "Warehouse sync",
"url": "https://example.com/hooks/planitude",
"events": [
"task.created",
"task.completed"
],
"secretPrefix": "whsec_Fdwl",
"active": true,
"disabledReason": null,
"disabledAt": null,
"consecutiveFailures": 0,
"viaApi": true,
"createdBy": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z",
"lastDeliveryAt": "2026-10-01T08:30:12.418Z",
"lastSuccessAt": "2026-10-01T08:30:12.418Z"
},
"secret": "whsec_3dvU0T1oaYlW4kH6m2Vb8nqXcR9eZpLsYw1QhGk"
}
Event catalog (public)
GET /api/webhooks/events (public, no key)
curl https://planitude.app/api/webhooks/events
Returns 200 OK: Events
| Field | Type | Required | Description |
|---|---|---|---|
apiVersion | string | ||
events | array of string |
{
"apiVersion": "2026-10-01",
"events": [
"project.created",
"project.updated",
"project.deleted",
"task.created",
"task.updated",
"task.deleted",
"task.completed",
"task.dates_changed",
"dependency.created",
"dependency.deleted",
"baseline.saved",
"member.invited",
"member.joined",
"comment.created"
]
}
Get a subscription
GET /api/webhooks/{webhookId} (scope: read)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
webhookId | string (uuid) | yes | Webhook id. |
curl https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88 \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Subscription
| Field | Type | Required | Description |
|---|---|---|---|
webhook | Webhook |
{
"webhook": {
"id": "1747ee7f-a395-4d3c-94b0-54b513d71d88",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"workspaceName": "Acme Construction",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"projectName": "Warehouse expansion",
"name": "Warehouse sync",
"url": "https://example.com/hooks/planitude",
"events": [
"task.created",
"task.completed"
],
"secretPrefix": "whsec_Fdwl",
"active": true,
"disabledReason": null,
"disabledAt": null,
"consecutiveFailures": 0,
"viaApi": true,
"createdBy": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z",
"lastDeliveryAt": "2026-10-01T08:30:12.418Z",
"lastSuccessAt": "2026-10-01T08:30:12.418Z"
}
}
Update, pause or resume a subscription
PATCH /api/webhooks/{webhookId} (scope: write)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
webhookId | string (uuid) | yes | Webhook id. |
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ||
url | string (uri) | ||
events | array of string | ||
active | boolean | false = pause; true = resume (also resets the failure counter). |
curl -X PATCH https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88 \
-H "Authorization: Bearer $PLANITUDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"active":false}'
Returns 200 OK: Updated
| Field | Type | Required | Description |
|---|---|---|---|
webhook | Webhook |
{
"webhook": {
"id": "1747ee7f-a395-4d3c-94b0-54b513d71d88",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"workspaceName": "Acme Construction",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"projectName": "Warehouse expansion",
"name": "Warehouse sync",
"url": "https://example.com/hooks/planitude",
"events": [
"task.created",
"task.completed"
],
"secretPrefix": "whsec_Fdwl",
"active": true,
"disabledReason": null,
"disabledAt": null,
"consecutiveFailures": 0,
"viaApi": true,
"createdBy": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z",
"lastDeliveryAt": "2026-10-01T08:30:12.418Z",
"lastSuccessAt": "2026-10-01T08:30:12.418Z"
}
}
Unsubscribe (REST hook)
DELETE /api/webhooks/{webhookId} (scope: write)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
webhookId | string (uuid) | yes | Webhook id. |
curl -X DELETE https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88 \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 204 No Content: Deleted
Delivery log (newest first, kept 30 days)
GET /api/webhooks/{webhookId}/deliveries (scope: read)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
webhookId | string (uuid) | yes | Webhook id. |
Query parameters:
| Field | Type | Required | Description |
|---|---|---|---|
limit | integer | 1-100. Default: 50. | |
before | string (date-time) | nextCursor of the previous page. |
curl https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88/deliveries \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Deliveries
| Field | Type | Required | Description |
|---|---|---|---|
deliveries | array of Delivery | ||
nextCursor | string, nullable |
{
"deliveries": [
{
"id": "4b207161-80f9-40bb-bc3f-d8f70315be00",
"eventId": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"eventType": "task.completed",
"status": "success",
"attempts": 1,
"nextAttemptAt": null,
"lastAttemptAt": "2026-10-01T08:30:12.418Z",
"responseStatus": 200,
"responseExcerpt": "{\"ok\":true}",
"error": null,
"durationMs": 39,
"manual": false,
"createdAt": "2026-10-01T08:30:12.418Z"
}
],
"nextCursor": null
}
Body sent with a delivery
GET /api/webhooks/{webhookId}/deliveries/{deliveryId} (scope: read)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
webhookId | string (uuid) | yes | Webhook id. |
deliveryId | string (uuid) | yes |
curl https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88/deliveries/4b207161-80f9-40bb-bc3f-d8f70315be00 \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: Payload
| Field | Type | Required | Description |
|---|---|---|---|
payload | Event |
{
"payload": {
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "project.created",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {}
}
}
Send the same event again
POST /api/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver (scope: write)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
webhookId | string (uuid) | yes | Webhook id. |
deliveryId | string (uuid) | yes |
curl -X POST https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88/deliveries/4b207161-80f9-40bb-bc3f-d8f70315be00/redeliver \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 202 Accepted: Queued
| Field | Type | Required | Description |
|---|---|---|---|
delivery | Delivery |
{
"delivery": {
"id": "4b207161-80f9-40bb-bc3f-d8f70315be00",
"eventId": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"eventType": "task.completed",
"status": "success",
"attempts": 1,
"nextAttemptAt": null,
"lastAttemptAt": "2026-10-01T08:30:12.418Z",
"responseStatus": 200,
"responseExcerpt": "{\"ok\":true}",
"error": null,
"durationMs": 39,
"manual": false,
"createdAt": "2026-10-01T08:30:12.418Z"
}
}
Send a webhook.ping test event
POST /api/webhooks/{webhookId}/test (scope: write)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
webhookId | string (uuid) | yes | Webhook id. |
curl -X POST https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88/test \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 202 Accepted: Queued
| Field | Type | Required | Description |
|---|---|---|---|
delivery | Delivery |
{
"delivery": {
"id": "4b207161-80f9-40bb-bc3f-d8f70315be00",
"eventId": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"eventType": "task.completed",
"status": "success",
"attempts": 1,
"nextAttemptAt": null,
"lastAttemptAt": "2026-10-01T08:30:12.418Z",
"responseStatus": 200,
"responseExcerpt": "{\"ok\":true}",
"error": null,
"durationMs": 39,
"manual": false,
"createdAt": "2026-10-01T08:30:12.418Z"
}
}
Generate a new signing secret
POST /api/webhooks/{webhookId}/rotate-secret (scope: write)
Path parameters:
| Field | Type | Required | Description |
|---|---|---|---|
webhookId | string (uuid) | yes | Webhook id. |
curl -X POST https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88/rotate-secret \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
Returns 200 OK: New secret (shown once)
| Field | Type | Required | Description |
|---|---|---|---|
webhook | Webhook | ||
secret | string |
{
"webhook": {
"id": "1747ee7f-a395-4d3c-94b0-54b513d71d88",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"workspaceName": "Acme Construction",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"projectName": "Warehouse expansion",
"name": "Warehouse sync",
"url": "https://example.com/hooks/planitude",
"events": [
"task.created",
"task.completed"
],
"secretPrefix": "whsec_Fdwl",
"active": true,
"disabledReason": null,
"disabledAt": null,
"consecutiveFailures": 0,
"viaApi": true,
"createdBy": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z",
"lastDeliveryAt": "2026-10-01T08:30:12.418Z",
"lastSuccessAt": "2026-10-01T08:30:12.418Z"
},
"secret": "whsec_3dvU0T1oaYlW4kH6m2Vb8nqXcR9eZpLsYw1QhGk"
}
Webhooks
Webhooks send an HTTPS POST with a JSON body to your endpoint as soon as something changes, whether the change comes from the web app, the API, the MCP server or an import. A webhook covers one project, or every project of a workspace (owners and admins).
Create them in Profile → Webhooks or with the API, as REST hooks: subscribe with POST /api/webhooks, unsubscribe with DELETE. The signing secret is shown only once.
| Header | Description |
|---|---|
Planitude-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256>: see Verify signatures. |
Planitude-Event | Event type, e.g. task.completed. |
Planitude-Event-Id | Event id, the same in every retry: use it to deduplicate. |
Planitude-Delivery | Id of this delivery (changes with a manual resend). |
User-Agent | Planitude-Webhooks/1.0 (+https://planitude.app/developers) |
Content-Type | application/json |
Event types
Subscribe to single events, or to * for all of them. Every event has the same envelope; data changes with the type.
| Event | Description |
|---|---|
project.created | A project was created (blank, from a template, duplicated or imported from MS Project). |
project.updated | Project fields changed (name, dates, deadline, archive...), or a saved version was restored. |
project.deleted | A project was deleted. |
task.created | A task was created. |
task.updated | Task fields or assignees changed. changes lists only the fields that changed. |
task.deleted | A task was deleted (one event per task of a deleted branch). |
task.completed | Task progress reached 100%. |
task.dates_changed | Start or finish moved, because of an edit (cause: edit) or because the schedule was recalculated (cause: reschedule). |
dependency.created | A dependency was created or changed. |
dependency.deleted | A dependency was removed. |
baseline.saved | A baseline was saved. |
member.invited | An invite link was created for the project. |
member.joined | A person joined the project. |
comment.created | A comment was added to a task. |
project.created
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "project.created",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"project": {
"id": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"key": "WHX",
"name": "Warehouse expansion",
"description": null,
"startDate": "2026-10-05",
"deadline": null,
"currency": "EUR",
"scheduleFrom": "forward",
"color": null,
"archived": false,
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z",
"url": "https://planitude.app/app/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8"
},
"source": "blank"
}
}
project.updated
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "project.updated",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"project": {
"id": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"key": "WHX",
"name": "Warehouse expansion",
"description": null,
"startDate": "2026-10-05",
"deadline": null,
"currency": "EUR",
"scheduleFrom": "forward",
"color": null,
"archived": false,
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z",
"url": "https://planitude.app/app/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8"
},
"changes": {
"deadline": {
"from": null,
"to": "2026-12-18"
}
},
"reason": "version_restored"
}
}
project.deleted
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "project.deleted",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"project": {
"id": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"key": "WHX",
"name": "Warehouse expansion",
"description": null,
"startDate": "2026-10-05",
"deadline": null,
"currency": "EUR",
"scheduleFrom": "forward",
"color": null,
"archived": false,
"createdAt": "2026-10-01T08:30:12.418Z",
"updatedAt": "2026-10-01T08:30:12.418Z",
"url": "https://planitude.app/app/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8"
}
}
}
task.created
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "task.created",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"task": {
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null,
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"completed": false,
"assignees": [
{
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"unitsPct": 100
}
],
"url": "https://planitude.app/app/t/WHX-1"
}
}
}
task.updated
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "task.updated",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"task": {
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null,
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"completed": false,
"assignees": [
{
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"unitsPct": 100
}
],
"url": "https://planitude.app/app/t/WHX-1"
},
"changes": {
"progressPct": {
"from": 40,
"to": 60
}
}
}
}
task.deleted
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "task.deleted",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"task": {
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null,
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"completed": false,
"assignees": [
{
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"unitsPct": 100
}
],
"url": "https://planitude.app/app/t/WHX-1"
}
}
}
task.completed
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "task.completed",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"task": {
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 100,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null,
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"completed": true,
"assignees": [
{
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"unitsPct": 100
}
],
"url": "https://planitude.app/app/t/WHX-1"
}
}
}
task.dates_changed
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "task.dates_changed",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"task": {
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null,
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"completed": false,
"assignees": [
{
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"unitsPct": 100
}
],
"url": "https://planitude.app/app/t/WHX-1"
},
"cause": "reschedule",
"changes": {
"startAt": {
"from": "2026-10-02T06:00:00.000Z",
"to": "2026-10-05T06:00:00.000Z"
},
"finishAt": {
"from": "2026-10-06T15:00:00.000Z",
"to": "2026-10-07T15:00:00.000Z"
}
}
}
}
dependency.created
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "dependency.created",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"dependency": {
"id": "9ba5faf8-923d-4ecc-86f0-f245c5d179db",
"predecessorId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"successorId": "a22fd799-c4dc-4869-a466-0dd6e96f71ce",
"type": "FS",
"lagMin": 0
}
}
}
dependency.deleted
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "dependency.deleted",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"dependency": {
"id": "9ba5faf8-923d-4ecc-86f0-f245c5d179db",
"predecessorId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"successorId": "a22fd799-c4dc-4869-a466-0dd6e96f71ce",
"type": "FS",
"lagMin": 0
}
}
}
baseline.saved
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "baseline.saved",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"baseline": {
"id": "b5d68d05-0640-48f9-8eeb-ed7804f4a116",
"name": "Plan v1",
"index": null,
"capturedAt": "2026-10-01T08:30:12.418Z",
"capturedBy": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"taskCount": 12
}
}
}
member.invited
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "member.invited",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"invite": {
"id": "5d0b7c11-8e2a-4f63-a1d4-93c7e2b6f804",
"role": "editor",
"expiresAt": "2026-10-31T08:30:12.418Z",
"maxUses": null
}
}
}
member.joined
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "member.joined",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"member": {
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"role": "editor"
}
}
}
comment.created
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "comment.created",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-01T08:30:12.418Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"data": {
"comment": {
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"taskId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"body": "Concrete delivered, we can start the walls.",
"author": {
"id": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi"
},
"createdAt": "2026-10-01T08:30:12.418Z"
},
"task": {
"id": "4dbd104a-00cd-4035-8955-255f52ea586a",
"number": 1,
"key": "WHX-1",
"parentId": null,
"name": "Foundations",
"type": "task",
"durationMin": 1440,
"workMin": 0,
"progressPct": 0,
"constraintKind": "asap",
"constraintDate": null,
"deadline": null,
"calendarId": null,
"costFixed": 0,
"notes": null,
"color": null,
"wbs": null,
"externalId": "crm-4711",
"startAt": "2026-10-05T06:00:00.000Z",
"finishAt": "2026-10-07T15:00:00.000Z",
"isCritical": true,
"totalFloatMin": 0,
"earlyStartAt": "2026-10-05T06:00:00.000Z",
"lateFinishAt": "2026-10-07T15:00:00.000Z",
"actualStartAt": null,
"actualFinishAt": null,
"actualDurationMin": null,
"remainingDurationMin": null,
"manuallyScheduled": false,
"levelingDelayMin": 0,
"taskType": "fixed_units",
"effortDriven": false,
"durationOptimisticMin": null,
"durationPessimisticMin": null,
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"completed": false,
"assignees": [
{
"resourceId": "0c6a8f52-3d7e-4b19-9a4e-6f1d2c8b7e30",
"userId": "b1666432-132a-4ea9-b1ae-76ea69e226c5",
"name": "Anna Rossi",
"unitsPct": 100
}
],
"url": "https://planitude.app/app/t/WHX-1"
}
}
}
Verify signatures
Every request is signed with HMAC-SHA256 and your webhook’s secret. The Planitude-Signature header holds a timestamp t and the signature v1, computed on t, a dot and the raw body.
- Use the raw body, before any JSON parsing: a re-serialized body has a different signature.
- Compute the HMAC and compare it in constant time.
- Reject requests whose timestamp is more than 5 minutes from your clock: a captured request cannot be replayed.
- Rotate the secret whenever you want: the old one stops working at once.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the request body as a string, before JSON.parse
export function verifyPlanitude(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // replay
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
const given = Buffer.from(parts.v1 ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}
// Express: app.post("/hooks/planitude", express.raw({ type: "application/json" }), (req, res) => {
// if (!verifyPlanitude(req.body.toString("utf8"), req.get("Planitude-Signature"), process.env.PLANITUDE_WEBHOOK_SECRET)) return res.sendStatus(400);
// res.sendStatus(200); // then process the event in a queue
// });
import hashlib
import hmac
import time
def verify_planitude(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs(time.time() - t) > tolerance:
return False # replay
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
# Flask: verify_planitude(request.get_data(), request.headers["Planitude-Signature"], os.environ["PLANITUDE_WEBHOOK_SECRET"])
Retries and delivery
Answer with any 2xx within 10 seconds and do the real work afterwards, in a queue. Redirects are not followed. On errors and timeouts Planitude tries up to 6 times in total: immediately, then after 1 min, 5 min, 30 min, 2 h, 12 h.
Delivery is at least once and not ordered. Store the event id (Planitude-Event-Id, also id in the body) and skip the ones you have already processed; order by createdAt.
After 5 events in a row fail every attempt, the webhook is disabled and its owner receives an email. A 410 Gone answer disables it at once. Turn it back on from the profile, or with PATCH and active: true.
The delivery log (status, HTTP code, response excerpt, payload, manual resend) is kept for 30 days. Only public addresses are accepted as destinations.
Test your endpoint
- Send a
webhook.pingevent with the test endpoint, or with the Test button in the profile. - To receive events on your computer, expose your local server with a tunnel (for example ngrok or Cloudflare Tunnel) and subscribe its public HTTPS address.
- Open the delivery log to see status, HTTP code and response, and resend a delivery once your endpoint is fixed.
curl -X POST https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88/test \
-H "Authorization: Bearer $PLANITUDE_API_KEY"
MCP server
Planitude is an MCP (Model Context Protocol) server: AI assistants and code editors read projects, analyze the critical path and the workload, and create or change tasks, with your permissions. Web apps connect with OAuth (you sign in and allow access); command-line tools and editors use an API key.
Server URL: https://planitude.app/mcp
ChatGPT (OAuth)
In ChatGPT open Settings › Security and login and turn on Developer mode. Create an app with the server URL and OAuth authentication, then sign in to Planitude and allow access.
Server URL:
https://planitude.app/mcp
Official documentation: https://developers.openai.com/api/docs/guides/developer-mode
Claude (OAuth)
Open Customize › Connectors › Add custom connector, paste the server URL and choose Connect: Planitude opens, you sign in and allow access. It works in claude.ai, Claude Desktop and the mobile app.
Server URL:
https://planitude.app/mcp
Official documentation: https://claude.com/docs/connectors/custom/add-unlisted
Gemini (OAuth)
On gemini.google.com open Settings › Connected Apps, choose Add a custom app and paste the server URL; then sign in to Planitude and allow access.
Server URL:
https://planitude.app/mcp
Official documentation: https://support.google.com/gemini/answer/17209137
Claude Code (API key)
Run the command with your key in an environment variable. Add --scope user to use it in every project and check with claude mcp list.
Terminal:
claude mcp add --transport http planitude https://planitude.app/mcp \
--header "Authorization: Bearer $PLANITUDE_API_KEY"
Official documentation: https://code.claude.com/docs/en/mcp
Codex (API key)
Put the key in PLANITUDE_API_KEY and add the block to ~/.codex/config.toml: it applies to the CLI, the app and the IDE extension.
~/.codex/config.toml:
[mcp_servers.planitude]
url = "https://planitude.app/mcp"
bearer_token_env_var = "PLANITUDE_API_KEY"
Official documentation: https://learn.chatgpt.com/docs/extend/mcp
Cursor (API key)
Add the block to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project); Cursor reads the key from the environment.
~/.cursor/mcp.json:
{
"mcpServers": {
"planitude": {
"url": "https://planitude.app/mcp",
"headers": {
"Authorization": "Bearer ${env:PLANITUDE_API_KEY}"
}
}
}
}
Official documentation: https://cursor.com/docs/context/mcp
Visual Studio Code (API key)
Add .vscode/mcp.json to the workspace: VS Code asks for the key once and stores it securely, outside the file.
.vscode/mcp.json:
{
"servers": {
"planitude": {
"type": "http",
"url": "https://planitude.app/mcp",
"headers": {
"Authorization": "Bearer ${input:planitude-key}"
}
}
},
"inputs": [
{
"id": "planitude-key",
"type": "promptString",
"description": "Planitude API key",
"password": true
}
]
}
Official documentation: https://code.visualstudio.com/docs/copilot/customization/mcp-servers
Gemini CLI (API key)
Run the command with your key in an environment variable, or add the block to ~/.gemini/settings.json.
Terminal:
gemini mcp add --transport http \
--header "Authorization: Bearer $PLANITUDE_API_KEY" planitude https://planitude.app/mcp
~/.gemini/settings.json:
{
"mcpServers": {
"planitude": {
"httpUrl": "https://planitude.app/mcp",
"headers": {
"Authorization": "Bearer $PLANITUDE_API_KEY"
}
}
}
}
Official documentation: https://geminicli.com/docs/tools/mcp-server/
Tools
The server exposes 29 tools, from reading and analysis to safe changes. None of them deletes projects or tasks.
- read:
planitude_list_projects,planitude_portfolio_overview,planitude_get_project,planitude_critical_path,planitude_delay_impact,planitude_monte_carlo,planitude_workload,planitude_list_baselines,planitude_list_versions,planitude_list_templates,planitude_list_comments,planitude_preview_import - write:
planitude_create_project,planitude_create_project_from_template,planitude_create_project_from_plan,planitude_import_file,planitude_edit_project,planitude_add_task,planitude_update_task,planitude_set_progress,planitude_indent_task,planitude_outdent_task,planitude_link_tasks,planitude_unlink_tasks,planitude_reschedule,planitude_configure_simulation,planitude_add_comment,planitude_save_baseline,planitude_save_version
Step-by-step guide for ChatGPT, Claude and Gemini
AI agents
Add up to 5 external AI agents to the workspace as members: Claude Code, Cursor, Codex, Gemini CLI, GitHub Copilot or any MCP client. Assign them tasks, mention them in comments and review their changes.
Owners and admins create agent keys in Agents, Connection tab. A key starts with pln_, is shown once and stored as a hash; each agent can have up to 2 keys, and revoking one takes effect at once.
Agent keys work only with the MCP server, which then exposes the agent tools instead of the usual ones. On the REST API they get 403 agent_credential.
claude mcp add --transport http planitude https://planitude.app/mcp \
--header "Authorization: Bearer $PLANITUDE_AGENT_KEY"
Each key either reads and comments or can edit. Claiming, completing and proposing changes need a key that can edit; otherwise the answer is 403 agent_key_read_only.
An agent sees only the projects it was added to, as viewer (reads and comments) or editor (also changes tasks), never as owner. The project also needs “Allow agents in this project”: it is off by default and the owner turns it on.
Agents can change a task’s name, notes, duration, progress and deadline, or create tasks. No tool deletes, invites people or changes roles.
Default limits per agent: 60 requests a minute and 200 writes a day, adjustable up to 240 and 2,000. At most 200 changes per project can wait for review.
Read tools put text written by people (names, notes, comments) inside planitude-data blocks with a random nonce, as JSON strings, and mark rows flagged by an automatic check as SUSPICIOUS: it is data, never instructions. Comments read with a personal key get the same blocks.
How AI agents work in Planitude
Agent tools
With an agent key, the MCP server exposes these 16 tools:
- read:
planitude_list_my_projects,planitude_list_tasks,planitude_read_comments,planitude_list_my_proposals - propose:
planitude_add_comment,planitude_propose_task_update,planitude_propose_status,planitude_propose_task_create - events:
planitude_poll_events,planitude_ack_events - runs:
planitude_list_my_runs,planitude_claim_task,planitude_extend_lease,planitude_complete_task,planitude_fail_task,planitude_release_task
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "planitude_claim_task",
"arguments": {
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"task": "4dbd104a-00cd-4035-8955-255f52ea586a",
"leaseMinutes": 30
}
}
}
During an Autonomous run, the agent’s changes to its own tasks apply right away and stay in the history, ready to restore. Otherwise, and in With approval mode, they wait in “Needs review” until someone on the project applies them.
Without a webhook, the agent reads its events with planitude_poll_events (up to 50 at a time) and confirms them with planitude_ack_events.
Run webhook
To start an agent on its own, turn on execution in Agents, Execution tab, and enter your runner’s webhook: Planitude sends a signed POST when the agent has a task to run. The secret starts with whsec_ and is shown once. The URL must be a public HTTPS address: private and local ones are refused.
Signature, headers, the 10-second timeout and retries (1 min, 5 min, 30 min, 2 h, 12 h) work as for the other webhooks. After 5 failed deliveries in a row the webhook turns off: save the URL again to turn it back on.
The notice carries only ids, never task text: the agent reads the task over MCP.
| Event | Description |
|---|---|
task.assigned | Only with the “on assignment” trigger: a person assigned the task to the agent. Once per assignment. |
task.start_due | Default trigger: the task’s start date has come (a working day of its calendar). |
task.due_today | The task is due today (deadline or planned finish). Opens a run only if the task never had one. |
task.overdue | The task is past its due date, once per due date. Opens a run only if the task never had one. |
run.retry | The lease expired without completion, or a person chose Rerun: a new attempt is queued. |
run.cancel | A run in progress was cancelled (by a person, an emergency stop or the rules): stop working on it. |
webhook.ping | Sent by “Send test event” in the agent’s Execution tab. |
{
"id": "6f1c1d1e-2f4b-4d0a-9a77-2a3c5b1e9f10",
"type": "task.start_due",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-14T07:00:00.000Z",
"workspaceId": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
"projectId": "40be00e0-7e34-4f7a-864b-5cc986f575b8",
"actor": null,
"data": {
"agentId": "b4d17e2c-93a8-4c6f-8e15-6a0f2d9c7b31",
"taskId": "4dbd104a-00cd-4035-8955-255f52ea586a",
"runId": "7c3e2a91-5d4b-4f08-b6e1-2f9a0c8d4e57",
"newRun": true,
"attempt": 1,
"leaseMinutes": 30,
"writeMode": "autonomous",
"day": "2026-10-14",
"links": {
"mcp": "https://planitude.app/mcp",
"app": "https://planitude.app/app/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8?task=4dbd104a-00cd-4035-8955-255f52ea586a"
}
}
}
A notice with newRun: true opens a run. The agent claims it with planitude_claim_task and holds a lease of 30 minutes (from 5 to 240), renewed with planitude_extend_lease. If the lease expires, a new attempt starts, up to 3 (at most 10). A run nobody claims expires after 24 hours.
planitude_complete_task sets the task to 100% and posts the report as a comment; with open subtasks it answers 409 subtasks_open. planitude_fail_task records why and notifies the team, who can rerun it; planitude_release_task gives the task back with no retry.
Each run allows 50 edits (up to 500, then 429) and each agent 5 open runs (then 409 too_many_open_runs). Autonomous, the default mode, applies changes at once and lets you restore them; With approval sends them to review. An emergency stop, per project or for the whole workspace, cancels open runs and blocks writes.
Write a runner
The runner is your small service that receives the notices and starts the agent. Any language works; in Node 20+ it needs no dependencies.
- Verify the signature on the raw body and answer 200 at once: Planitude waits 10 seconds at most.
- Skip events you have already seen (
Planitude-Event-Id): retries resend the same event. - On
newRun: true, claim the task and start the agent, for exampleclaude -pwith the Planitude MCP server and the agent key. - Renew the lease while the agent works; on
run.cancel, stop it. - Ask the agent to end with
PLANITUDE: DONEonly when the whole task is done, then complete with its report. Otherwise (PLANITUDE: NOT DONE - …or no last line) fail with what is left.
Treat task text as data: tell the agent never to follow instructions found in it, and allow it only the Planitude tools it needs.
// after verifyPlanitude(rawBody, signature, secret) and dedupe on Planitude-Event-Id
res.sendStatus(200); // answer at once, work afterwards
const { type, projectId, data } = event;
if (type === "run.cancel") return stopAgent(data.runId);
if (!data.newRun) return;
await callTool("planitude_claim_task", { projectId, task: data.taskId, leaseMinutes: data.leaseMinutes });
const out = await runAgent(data); // renew with planitude_extend_lease while it works
if (out.lastLine === "PLANITUDE: DONE") {
await callTool("planitude_complete_task", { runId: data.runId, report: out.report }); // task -> 100%
} else {
await callTool("planitude_fail_task", { runId: data.runId, reason: out.whatIsLeft });
}
// callTool = JSON-RPC tools/call to the MCP URL
// with Authorization: Bearer <agent key>
# mcp.json: {"mcpServers":{"planitude":{"type":"http","url":"https://planitude.app/mcp",
# "headers":{"Authorization":"Bearer pln_..."}}}}
claude -p --output-format text \
--mcp-config mcp.json --allowedTools mcp__planitude < prompt.txt
OData feed (Power BI, Excel)
A read-only OData v4 feed for Power BI, Excel and any OData client, at two addresses: one project, or every project the key owner can see in a workspace.
- https://planitude.app/api/odata/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8
- https://planitude.app/api/odata/workspaces/824650d2-d0b3-4cac-8df8-5743ae9ff65a
Authenticate with a personal API key with read access: Authorization: Bearer, or HTTP Basic with the key as password (any user name), which is the method Power BI and Excel offer. OAuth tokens and session cookies are not accepted. A key limited to one workspace cannot read the others.
Tables: Projects, Tasks, Milestones, Dependencies, Resources, Assignments. The schema is at $metadata (CSDL XML) and the service document at the root address.
Supported options: $select, $filter (comparisons, and/or/not, in, contains, startswith, endswith, tolower, toupper), $orderby, $top, $skip, $count and /$count. Pages hold 2000 rows by default, up to 5000 with Prefer: odata.maxpagesize; follow @odata.nextLink. Responses carry an ETag: send If-None-Match to get 304. $expand, $apply and $search are not supported (400).
A feed covers at most 500 projects and 25000 tasks (beyond that, 413). Requests count toward the read limit of the key, 240 per minute.
curl "https://planitude.app/api/odata/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/Tasks?$select=Key,Name,Start,Finish,ProgressPct,IsCritical&$filter=IsCritical&$orderby=Start&$top=2&$count=true" \
-u "powerbi:$PLANITUDE_API_KEY"
# schema (CSDL XML), for Power BI, Excel and any OData client
curl "https://planitude.app/api/odata/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/$metadata" -u "powerbi:$PLANITUDE_API_KEY"
{
"@odata.context": "https://planitude.app/api/odata/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/$metadata#Tasks(Key,Name,Start,Finish,ProgressPct,IsCritical)",
"@odata.count": 3,
"value": [
{
"Key": "WHX-2",
"Name": "Foundations",
"Start": "2026-10-05T07:00:00Z",
"Finish": "2026-10-06T15:00:00Z",
"ProgressPct": 100,
"IsCritical": true
},
{
"Key": "WHX-3",
"Name": "Walls",
"Start": "2026-10-07T07:00:00Z",
"Finish": "2026-10-09T15:00:00Z",
"ProgressPct": 50,
"IsCritical": true
}
]
}
Step-by-step guide for Power BI and Excel
Ready-made integrations
No code needed: these integrations use the same API and webhooks and are set up in a few clicks.
- Jira (Available): Two-way sync of tasks, dates and dependencies with Jira issues.
- Azure DevOps (Available): Two-way sync with Azure DevOps Boards work items.
- Slack (Available): Project updates, daily summaries and a slash command in your channels.
- Microsoft Teams (Available): Updates, summaries and a Planitude tab in Microsoft Teams.
- Power BI, Excel (Available): Read-only OData feed with projects, tasks, milestones and assignments for reports and dashboards.
- Zapier, Make, n8n (Coming soon): Instant triggers and actions on tasks and projects for no-code automations.
Changelog
- 2026-10-15: New read-only OData v4 feed at /api/odata/projects/:projectId and /api/odata/workspaces/:workspaceId for Power BI, Excel and other OData clients: six tables, $metadata, $select, $filter, $orderby, $top, $skip, $count, server-driven paging and ETag.
- 2026-10-15: New endpoint GET /api/projects/:projectId/export: the plan as Microsoft Project XML, CSV, PNG image or PDF, with options for scale, date range, critical path, baseline, dependencies, paper size and orientation.
- 2026-10-15: All API keys now start with pln_. Keys that start with gnt_ no longer work: create a new one.
- 2026-10-14: Agents run tasks: signed run webhook (task.assigned, task.start_due, task.due_today, task.overdue, run.retry, run.cancel), 6 run tools with a lease and a guide to writing a runner.
- 2026-10-14: MCP read tools wrap text written by people in planitude-data blocks with a nonce, as JSON strings with a SUSPICIOUS mark, for every kind of key.
- 2026-10-14: AI agents as workspace members: agent keys that work only with the MCP server, 10 agent tools to read, comment and propose changes, and an event queue.
- 2026-10-02: New developer documentation, generated from the OpenAPI document. The OpenAPI document now describes every field returned for members, comments, baselines, templates and assignees.
- 2026-10-01: API version 2026-10-01: signed outgoing webhooks, documented public REST API (OpenAPI 3.1), cursor pagination and search by externalId.
- 2026-09-30: OAuth 2.1 for the MCP server and the API: ChatGPT, Claude and Gemini connect without copying a key.
- 2026-09-28: MCP server and personal API keys with read or read + write access.