---
title: "API, webhook, dan MCP Planitude untuk developer"
description: "REST API dengan OpenAPI 3.1, webhook bertanda tangan, dan server MCP untuk asisten AI: bangun di atas Planitude, alat manajemen proyek online gratis."
url: https://planitude.app/id/developers
language: id
---

# Bangun dengan Planitude

REST API, webhook bertanda tangan, dan server MCP: jaga sistem Anda tetap sinkron dengan rencana, tanggapi setiap perubahan, dan biarkan asisten AI mengerjakan proyek Anda.

*Kode, nama field, dan referensi teknis dalam bahasa Inggris, seperti kebanyakan dokumentasi API.*

- OpenAPI 3.1: https://planitude.app/api/openapi.json
- URL dasar: https://planitude.app
- MCP: https://planitude.app/mcp
- API version: 2026-10-01

- **Sinkronkan tugas dengan sistem Anda**: Buat proyek dan tugas dari CRM, ERP, atau skrip, temukan kembali dengan id Anda sendiri melalui `externalId`, dan biarkan mesin penjadwalan menghitung tanggalnya.
- **Tanggapi setiap perubahan**: Terima permintaan HTTPS bertanda tangan saat tugas dibuat, diselesaikan, atau dipindahkan, baik perubahan berasal dari aplikasi, API, impor, maupun asisten AI.
- **Hubungkan asisten AI**: ChatGPT, Claude, Gemini, Cursor, dan VS Code membaca rencana Anda dan mengusulkan perubahan melalui server MCP, dengan izin Anda dan tidak lebih.

Permintaan dan respons berupa JSON melalui HTTPS. Durasi dan lag dalam **menit kerja** (hari kerja 8 jam adalah 480), waktu berupa ISO 8601 dalam UTC, tanggal tanpa waktu berformat `YYYY-MM-DD`.

## Mulai cepat

Dari nol hingga webhook pertama Anda dalam tiga langkah. Anda hanya memerlukan terminal.

### 1. Buat kunci API

Buka [Profil → Kunci API](https://planitude.app/id/app/profile/api-keys), pilih **Baca dan tulis**, lalu salin kuncinya: diawali `gnt_` dan hanya ditampilkan sekali. Simpan di variabel lingkungan.

```bash
export PLANITUDE_API_KEY="gnt_…"
```

### 2. Lakukan panggilan pertama Anda

Daftarkan proyek yang dapat Anda lihat. Kunci bertindak sebagai Anda: proyek dan peran yang sama.

```bash
curl https://planitude.app/api/projects \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Hasil yang diharapkan:

```json
{
  "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. Terima webhook

Langganankan endpoint HTTPS publik ke event yang Anda butuhkan, lalu kirim event uji segera dan verifikasi tanda tangannya.

```bash
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"
}'
```

```json
{
  "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"
      ]
    }
  }
}
```

## Autentikasi

Autentikasi setiap permintaan dengan kunci API pribadi di header `Authorization`, sebagai bearer token. Kunci bersifat pribadi: bertindak sebagai Anda, dengan peran Anda pada setiap proyek, tidak pernah lebih. Anda dapat membatasi kunci pada satu workspace, memberinya masa berlaku, dan mencabutnya kapan saja; setiap panggilan dicatat di samping kunci.

| Scope | Mengizinkan |
| --- | --- |
| `read` (`planitude:read`) | Membaca proyek, tugas, dependensi, anggota, komentar, baseline, dan webhook. |
| `write` (`planitude:write`) | Semua yang ada pada akses baca, ditambah membuat dan mengubah proyek, tugas, dependensi, komentar, baseline, dan webhook. |

Kunci dan aplikasi OAuth **tidak dapat menghapus** proyek atau tugas: penghapusan hanya dilakukan di aplikasi web, tempat seseorang mengonfirmasinya. Skrip yang menargetkan id yang salah tidak dapat menghapus rencana.

### OAuth 2.1 untuk aplikasi pihak ketiga

Aplikasi yang bertindak atas nama orang lain menggunakan OAuth 2.1: authorization code dengan PKCE (S256), refresh token yang dirotasi pada setiap penggunaan, dynamic client registration (RFC 7591). Minta resource `https://planitude.app/api` dan scope `planitude:read` atau `planitude:write`.

Endpoint dipublikasikan dalam dokumen discovery standar, sehingga pustaka OAuth mengonfigurasi dirinya sendiri.

```json
{
  "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"
  ]
}
```

### Praktik yang baik

- Simpan kunci di variabel lingkungan atau secret manager, jangan pernah di kode atau repositori. Kunci diawali `gnt_`, sehingga pemindai rahasia dapat mendeteksinya.
- Gunakan kunci hanya-baca jika Anda hanya membaca, dan satu kunci per integrasi: Anda dapat mencabut satu kunci tanpa menghentikan yang lain.
- Beri masa berlaku pada kunci yang digunakan skrip dan rotasikan secara berkala.
- Panggil API dari server Anda, bukan dari browser: kunci di kode front-end sama dengan kunci publik.

## Kesalahan

Planitude menggunakan kode status HTTP standar. Galat selalu memiliki bentuk JSON yang sama: kode `error` yang stabil untuk percabangan logika dan, bila perlu, `details` berisi alasan atau kolom yang harus diperbaiki.

| Status | error | Arti |
| --- | --- | --- |
| 400 | `invalid_request` | Permintaan tidak valid: `details.fields` mencantumkan setiap kolom beserta masalahnya. |
| 401 | `unauthorized` | Kunci tidak ada, tidak valid, dicabut, atau kedaluwarsa. |
| 403 | `forbidden` | Kunci atau peran Anda tidak mengizinkannya (`details.reason`), misalnya penulisan dengan kunci hanya-baca, atau penghapusan. |
| 404 | `not_found` | Tidak ditemukan, atau tidak terlihat oleh Anda. |
| 409 | `dependency_cycle` | Konflik dengan rencana, misalnya dependensi yang akan membentuk siklus (`dependency_cycle`). |
| 429 | `rate_limited` | Terlalu banyak permintaan: tunggu sesuai jumlah detik di `Retry-After`. |
| 500 | `server_error` | Terjadi kesalahan di sisi kami: coba lagi dengan 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 |  |  |

```json
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"
      }
    ]
  }
}
```

```json
HTTP 403
{
  "error": "forbidden",
  "details": {
    "reason": "destructive"
  }
}
```

```json
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"
        ]
      }
    ]
  }
}
```

## Batas laju

Batas berlaku per kunci, dalam jendela geser satu menit: **240** permintaan baca dan **60** permintaan tulis. Jika melebihi batas, Anda menerima `429 rate_limited` dengan header `Retry-After`, dalam detik.

Tunggu `Retry-After` sebelum mencoba lagi, sebarkan pekerjaan massal dari waktu ke waktu, dan kelompokkan banyak perubahan dalam satu change set: hingga 1.000 perubahan dalam satu permintaan dan satu kali penghitungan ulang.

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json

{
  "error": "rate_limited",
  "details": {
    "retryAfterSec": 12
  }
}
```

## Paginasi dan pencarian

Daftar tugas dipaginasi dengan kursor. Kirim `limit` (1–500, default 100) dan, untuk halaman berikutnya, `nextCursor` dari respons sebelumnya sebagai `cursor`. Jika `nextCursor` bernilai `null`, tidak ada halaman lagi.

Filter dengan `externalId` (kecocokan persis dengan id Anda sendiri), `q` (nama mengandung), `updatedSince`, dan `completed`; urutkan dengan `sort=-number` (terbaru dulu) atau `-updatedAt`. Daftar yang lebih pendek, seperti proyek, anggota, dan dependensi, dikembalikan utuh dalam satu respons.

```js
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);
```

## Versi

API dan payload webhook diberi versi berdasarkan tanggal. Versi saat ini adalah `2026-10-01`: ini adalah kolom `apiVersion` pada setiap event dan `info.version` pada dokumen OpenAPI.

Dalam satu versi kami hanya menambah: endpoint baru, kolom opsional baru, tipe event baru. Kolom tidak pernah dihapus atau diubah tipenya tanpa versi baru, jadi abaikan kolom yang tidak Anda kenal.

## Referensi API

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 |  |  |

```json
{
  "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`. |

```json
{
  "id": "824650d2-d0b3-4cac-8df8-5743ae9ff65a",
  "name": "Acme Construction",
  "role": "owner"
}
```

#### Current user (connection test)

`GET /api/auth/me` (scope: read)

```bash
curl https://planitude.app/api/auth/me \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: The user

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `user` | User |  |  |

```json
{
  "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)

```bash
curl https://planitude.app/api/workspaces \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: Workspaces

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `workspaces` | array of Workspace |  |  |

```json
{
  "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) |  |  |

```json
{
  "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. |

```json
{
  "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 |  |  |

```json
{
  "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.

Parameter query:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `archived` | string |  | One of: `1`. |

```bash
curl https://planitude.app/api/projects \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: Projects

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projects` | array of Project |  |  |

```json
{
  "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)

Isi permintaan:

| 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 |  |  |

```bash
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"}'
```

Mengembalikan 201 Created: Created

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `project` | Project |  |  |

```json
{
  "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)

Isi permintaan:

| 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) |  |  |

```bash
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"
}'
```

Mengembalikan 201 Created: Created

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `project` | Project |  |  |
| `taskCount` | integer |  |  |
| `conflicts` | array of Conflict |  |  |

```json
{
  "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)

Parameter query:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `locale` | string |  |  |

```bash
curl https://planitude.app/api/templates \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: Templates

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `templates` | array of Template |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |

```bash
curl https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8 \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: Project

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `project` | Project |  |  |
| `tasks` | array of Task |  |  |
| `dependencies` | array of Dependency |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |

Isi permintaan:

| 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 |  |  |

```bash
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"}'
```

Mengembalikan 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). |

```json
{
  "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
}
```

#### List baselines

`GET /api/projects/{projectId}/baseline` (scope: read)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |

```bash
curl https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/baseline \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: Baselines

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `baselines` | array of Baseline |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |

Isi permintaan:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string |  |  |

```bash
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"}'
```

Mengembalikan 201 Created: Saved

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `baseline` | Baseline |  |  |

```json
{
  "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. |

```json
{
  "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 |  |  |

```json
{
  "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 |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |

Parameter query:

| 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`. |

```bash
curl "https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/tasks?externalId=crm-4711" \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: A page of tasks

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `tasks` | array of Task |  |  |
| `nextCursor` | string, nullable |  |  |

```json
{
  "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.

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |

Isi permintaan (`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=`. |

Isi permintaan (atau `ChangeSet`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `createTasks` | array of TaskCreate |  |  |
| `updateTasks` | array of any |  |  |
| `createDependencies` | array of DependencyCreate |  |  |
| `deleteDependencyIds` | array of string |  |  |
| `fullReschedule` | boolean |  |  |

```bash
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"}'
```

Mengembalikan 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 |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |
| `taskId` | string (uuid) | yes | Task id. |

```bash
curl https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/tasks/4dbd104a-00cd-4035-8955-255f52ea586a \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: The task

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `task` | Task |  |  |

```json
{
  "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.

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |
| `taskId` | string (uuid) | yes | Task id. |

Isi permintaan:

| 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. |

```bash
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}'
```

Mengembalikan 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 |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |
| `taskId` | string (uuid) | yes | Task id. |

```bash
curl https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/tasks/4dbd104a-00cd-4035-8955-255f52ea586a/comments \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: Comments

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `comments` | array of Comment |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |
| `taskId` | string (uuid) | yes | Task id. |

Isi permintaan:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `body` | string | yes | Plain text. |
| `mentions` | array of string |  | User ids to notify. |

```bash
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."}'
```

Mengembalikan 201 Created: Created

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `comment` | Comment |  |  |

```json
{
  "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. |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |

```bash
curl https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/dependencies \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: Dependencies

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `dependencies` | array of Dependency |  |  |

```json
{
  "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`.

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |

Isi permintaan (`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`. |

Isi permintaan (atau `dependencies`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `dependencies` | array of DependencyCreate | yes |  |

```bash
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
}'
```

Mengembalikan 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 |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |
| `dependencyId` | string (uuid) | yes |  |

```bash
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"
```

Mengembalikan 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 |  |  |

```json
{
  "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 |  |  |

```json
{
  "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 |  |  |

```json
{
  "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.

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |
| `taskId` | string (uuid) | yes | Task id. |

Isi permintaan:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `userIds` | array of string | yes |  |
| `resourceIds` | array of string |  |  |

```bash
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"]}'
```

Mengembalikan 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 |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string (uuid) | yes | Project id. |

```bash
curl https://planitude.app/api/projects/40be00e0-7e34-4f7a-864b-5cc986f575b8/members \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: Members

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `members` | array of Member |  |  |
| `role` | string |  |  |

```json
{
  "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 |  |  |

```json
{
  "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) |  |  |

```json
{
  "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 |  |

```json
{
  "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) |  |  |

```json
{
  "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)

Parameter query:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `workspaceId` | string (uuid) |  |  |
| `projectId` | string (uuid) |  |  |

```bash
curl https://planitude.app/api/webhooks \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: Subscriptions

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhooks` | array of Webhook |  |  |

```json
{
  "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.

Isi permintaan:

| 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) |  |  |

```bash
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"
}'
```

Mengembalikan 201 Created: Created

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhook` | Webhook |  |  |
| `secret` | string |  | whsec_... |

```json
{
  "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)

```bash
curl https://planitude.app/api/webhooks/events
```

Mengembalikan 200 OK: Events

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `apiVersion` | string |  |  |
| `events` | array of string |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhookId` | string (uuid) | yes | Webhook id. |

```bash
curl https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88 \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: Subscription

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhook` | Webhook |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhookId` | string (uuid) | yes | Webhook id. |

Isi permintaan:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string |  |  |
| `url` | string (uri) |  |  |
| `events` | array of string |  |  |
| `active` | boolean |  | false = pause; true = resume (also resets the failure counter). |

```bash
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}'
```

Mengembalikan 200 OK: Updated

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhook` | Webhook |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhookId` | string (uuid) | yes | Webhook id. |

```bash
curl -X DELETE https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88 \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 204 No Content: Deleted

#### Delivery log (newest first, kept 30 days)

`GET /api/webhooks/{webhookId}/deliveries` (scope: read)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhookId` | string (uuid) | yes | Webhook id. |

Parameter query:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `limit` | integer |  | 1-100. Default: `50`. |
| `before` | string (date-time) |  | `nextCursor` of the previous page. |

```bash
curl https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88/deliveries \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: Deliveries

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `deliveries` | array of Delivery |  |  |
| `nextCursor` | string, nullable |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhookId` | string (uuid) | yes | Webhook id. |
| `deliveryId` | string (uuid) | yes |  |

```bash
curl https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88/deliveries/4b207161-80f9-40bb-bc3f-d8f70315be00 \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: Payload

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `payload` | Event |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhookId` | string (uuid) | yes | Webhook id. |
| `deliveryId` | string (uuid) | yes |  |

```bash
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"
```

Mengembalikan 202 Accepted: Queued

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `delivery` | Delivery |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhookId` | string (uuid) | yes | Webhook id. |

```bash
curl -X POST https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88/test \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 202 Accepted: Queued

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `delivery` | Delivery |  |  |

```json
{
  "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)

Parameter path:

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhookId` | string (uuid) | yes | Webhook id. |

```bash
curl -X POST https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88/rotate-secret \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

Mengembalikan 200 OK: New secret (shown once)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhook` | Webhook |  |  |
| `secret` | string |  |  |

```json
{
  "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"
}
```

## Webhook

Webhook mengirim `POST` HTTPS dengan isi JSON ke endpoint Anda segera setelah ada perubahan, baik perubahan itu berasal dari aplikasi web, API, server MCP, maupun impor. Satu webhook mencakup satu proyek, atau seluruh proyek dalam sebuah ruang kerja (pemilik dan admin).

Buat di [Profil → Webhook](https://planitude.app/id/app/profile/webhooks) atau lewat API, sebagai REST hook: berlangganan dengan `POST /api/webhooks`, berhenti berlangganan dengan `DELETE`. Rahasia penandatanganan hanya ditampilkan satu kali.

| Header | Deskripsi |
| --- | --- |
| `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` |

### Jenis event

Berlangganan event tertentu, atau `*` untuk semuanya. Setiap event memiliki amplop yang sama; `data` berubah menurut tipenya.

| Event | Deskripsi |
| --- | --- |
| `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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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

```json
{
  "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"
    }
  }
}
```

### Verifikasi tanda tangan

Setiap permintaan ditandatangani dengan HMAC-SHA256 dan rahasia webhook Anda. Header `Planitude-Signature` berisi stempel waktu `t` dan tanda tangan `v1`, dihitung dari `t`, sebuah titik, dan isi mentah.

1. Gunakan isi mentah, sebelum parsing JSON apa pun: isi yang diserialisasi ulang memiliki tanda tangan berbeda.
2. Hitung HMAC dan bandingkan dalam waktu konstan.
3. Tolak permintaan yang stempel waktunya berselisih lebih dari 5 menit dari jam Anda: permintaan yang tertangkap tidak dapat diputar ulang.
4. Rotasikan rahasia kapan pun Anda mau: yang lama langsung berhenti berlaku.

```js
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
// });
```

```python
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"])
```

### Percobaan ulang dan pengiriman

Balas dengan **2xx** apa pun dalam 10 detik, lalu kerjakan tugas sebenarnya setelahnya, dalam antrean. Pengalihan tidak diikuti. Jika terjadi galat dan waktu habis, Planitude mencoba hingga 6 kali secara total: segera, lalu setelah 1 min, 5 min, 30 min, 2 h, 12 h.

Pengiriman bersifat **setidaknya sekali** dan **tidak berurutan**. Simpan id event (`Planitude-Event-Id`, juga `id` di isi) dan lewati yang sudah Anda proses; urutkan berdasarkan `createdAt`.

Setelah 5 event berturut-turut gagal di setiap percobaan, webhook dinonaktifkan dan pemiliknya menerima email. Jawaban `410 Gone` langsung menonaktifkannya. Aktifkan kembali dari profil, atau dengan `PATCH` dan `active: true`.

Log pengiriman (status, kode HTTP, cuplikan respons, payload, kirim ulang manual) disimpan selama 30 hari. Hanya alamat publik yang diterima sebagai tujuan.

### Uji endpoint Anda

1. Kirim event `webhook.ping` dengan endpoint pengujian, atau dengan tombol Uji di profil.
2. Untuk menerima event di komputer Anda, ekspos server lokal Anda dengan tunnel (misalnya ngrok atau Cloudflare Tunnel) dan berlangganan ke alamat HTTPS publiknya.
3. Buka log pengiriman untuk melihat status, kode HTTP, dan respons, lalu kirim ulang pengiriman setelah endpoint Anda diperbaiki.

```bash
curl -X POST https://planitude.app/api/webhooks/1747ee7f-a395-4d3c-94b0-54b513d71d88/test \
  -H "Authorization: Bearer $PLANITUDE_API_KEY"
```

## Server MCP

Planitude adalah server MCP (Model Context Protocol): asisten AI dan editor kode membaca proyek, menganalisis jalur kritis dan beban kerja, serta membuat atau mengubah tugas, sesuai izin Anda. Aplikasi web terhubung dengan OAuth (Anda masuk dan mengizinkan akses); alat baris perintah dan editor menggunakan kunci API.

URL server: https://planitude.app/mcp

### ChatGPT (OAuth)

Di ChatGPT buka Settings › Security and login dan aktifkan Developer mode. Buat aplikasi dengan URL server dan autentikasi OAuth, lalu masuk ke Planitude dan izinkan akses.

Server URL:

```
https://planitude.app/mcp
```

Dokumentasi resmi: https://developers.openai.com/api/docs/guides/developer-mode

### Claude (OAuth)

Buka Customize › Connectors › Add custom connector, tempel URL server dan pilih Connect: Planitude terbuka, Anda masuk dan mengizinkan akses. Berfungsi di claude.ai, Claude Desktop, dan aplikasi seluler.

Server URL:

```
https://planitude.app/mcp
```

Dokumentasi resmi: https://claude.com/docs/connectors/custom/add-unlisted

### Gemini (OAuth)

Di gemini.google.com buka Settings › Connected Apps, pilih Add a custom app dan tempel URL server; lalu masuk ke Planitude dan izinkan akses.

Server URL:

```
https://planitude.app/mcp
```

Dokumentasi resmi: https://support.google.com/gemini/answer/17209137

### Claude Code (Kunci API)

Jalankan perintah dengan kunci Anda di variabel lingkungan. Tambahkan `--scope user` agar berlaku di setiap proyek dan periksa dengan `claude mcp list`.

Terminal:

```bash
claude mcp add --transport http planitude https://planitude.app/mcp \
  --header "Authorization: Bearer $PLANITUDE_API_KEY"
```

Dokumentasi resmi: https://code.claude.com/docs/en/mcp

### Codex (Kunci API)

Taruh kunci di `PLANITUDE_API_KEY` dan tambahkan blok ke `~/.codex/config.toml`: berlaku untuk CLI, aplikasi, dan ekstensi IDE.

~/.codex/config.toml:

```toml
[mcp_servers.planitude]
url = "https://planitude.app/mcp"
bearer_token_env_var = "PLANITUDE_API_KEY"
```

Dokumentasi resmi: https://learn.chatgpt.com/docs/extend/mcp

### Cursor (Kunci API)

Tambahkan blok ke `~/.cursor/mcp.json` (semua proyek) atau `.cursor/mcp.json` (satu proyek); Cursor membaca kunci dari lingkungan.

~/.cursor/mcp.json:

```json
{
  "mcpServers": {
    "planitude": {
      "url": "https://planitude.app/mcp",
      "headers": {
        "Authorization": "Bearer ${env:PLANITUDE_API_KEY}"
      }
    }
  }
}
```

Dokumentasi resmi: https://cursor.com/docs/context/mcp

### Visual Studio Code (Kunci API)

Tambahkan `.vscode/mcp.json` ke ruang kerja: VS Code meminta kunci sekali dan menyimpannya dengan aman, di luar file.

.vscode/mcp.json:

```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
    }
  ]
}
```

Dokumentasi resmi: https://code.visualstudio.com/docs/copilot/customization/mcp-servers

### Gemini CLI (Kunci API)

Jalankan perintah dengan kunci Anda di variabel lingkungan, atau tambahkan blok ke `~/.gemini/settings.json`.

Terminal:

```bash
gemini mcp add --transport http \
  --header "Authorization: Bearer $PLANITUDE_API_KEY" planitude https://planitude.app/mcp
```

~/.gemini/settings.json:

```json
{
  "mcpServers": {
    "planitude": {
      "httpUrl": "https://planitude.app/mcp",
      "headers": {
        "Authorization": "Bearer $PLANITUDE_API_KEY"
      }
    }
  }
}
```

Dokumentasi resmi: https://geminicli.com/docs/tools/mcp-server/

### Alat

Server menyediakan 29 alat, dari pembacaan dan analisis hingga perubahan yang aman. Tidak satu pun menghapus proyek atau tugas.

- 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`

[Panduan langkah demi langkah untuk ChatGPT, Claude, dan Gemini](https://planitude.app/id/blog/chatgpt-claude-gemini-mcp-project-plans)

## Integrasi siap pakai

Tanpa kode: integrasi ini memakai API dan webhook yang sama dan disiapkan hanya dengan beberapa klik.

- [Jira](https://planitude.app/id/integrations/jira) (Tersedia): Sinkronisasi dua arah tugas, tanggal, dan dependensi dengan isu Jira.
- [Azure DevOps](https://planitude.app/id/integrations/azure-devops) (Tersedia): Sinkronisasi dua arah dengan work item Azure DevOps Boards.
- [Slack](https://planitude.app/id/integrations/slack) (Tersedia): Pembaruan proyek, ringkasan harian, dan perintah slash di kanal Anda.
- [Microsoft Teams](https://planitude.app/id/integrations/teams) (Tersedia): Pembaruan, ringkasan, dan tab Planitude di Microsoft Teams.
- [Zapier, Make, n8n](https://planitude.app/id/integrations?category=automation) (Segera hadir): Pemicu dan aksi instan pada tugas dan proyek untuk otomasi tanpa kode.

## Catatan perubahan

- **2026-10-02**: Dokumentasi pengembang baru, dihasilkan dari dokumen OpenAPI. Dokumen OpenAPI kini menjelaskan setiap kolom yang dikembalikan untuk anggota, komentar, baseline, templat, dan penerima tugas.
- **2026-10-01**: API versi 2026-10-01: webhook keluar bertanda tangan, REST API publik terdokumentasi (OpenAPI 3.1), paginasi kursor, dan pencarian berdasarkan externalId.
- **2026-09-30**: OAuth 2.1 untuk server MCP dan API: ChatGPT, Claude, dan Gemini terhubung tanpa menyalin kunci.
- **2026-09-28**: Server MCP dan kunci API pribadi dengan akses baca atau baca + tulis.
