---
title: "개발자를 위한 Planitude API, 웹훅, MCP"
description: "OpenAPI 3.1 기반 REST API, 서명된 웹훅, AI 어시스턴트용 MCP 서버: 무료 온라인 프로젝트 관리 도구 Planitude 위에서 개발하세요."
url: https://planitude.app/ko/developers
language: ko
---

# Planitude로 개발하기

REST API, 서명된 웹훅, MCP 서버로 시스템을 계획과 동기화하고, 모든 변경에 대응하며, AI 어시스턴트가 프로젝트에서 작업하도록 하세요.

*대부분의 API 문서와 마찬가지로 코드, 필드 이름, 기술 레퍼런스는 영어로 제공됩니다.*

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

- **시스템과 작업 동기화**: CRM, ERP 또는 스크립트에서 프로젝트와 작업을 만들고, `externalId`로 자체 ID를 사용해 다시 찾으며, 날짜는 일정 계산 엔진이 산출하도록 하세요.
- **모든 변경에 대응**: 작업이 생성, 완료 또는 이동될 때마다 서명된 HTTPS 요청을 받습니다. 변경이 앱, API, 가져오기, AI 어시스턴트 중 어디에서 발생했든 동일합니다.
- **AI 어시스턴트 연결**: ChatGPT, Claude, Gemini, Cursor, VS Code가 MCP 서버를 통해 계획을 읽고 변경을 제안합니다. 사용자의 권한 범위 내에서만 동작합니다.

요청과 응답은 HTTPS 기반 JSON입니다. 기간과 지연 기간은 **근무 분** 단위(8시간 근무일은 480), 시점은 UTC 기준 ISO 8601, 시간이 없는 날짜는 `YYYY-MM-DD` 형식입니다.

## 빠른 시작

세 단계로 처음부터 첫 웹훅까지 완료합니다. 터미널만 있으면 됩니다.

### 1. API 키 만들기

[프로필 → API 키](https://planitude.app/ko/app/profile/api-keys)를 열고 **읽기 및 쓰기**를 선택한 뒤 키를 복사하세요. 키는 `gnt_`로 시작하며 한 번만 표시됩니다. 환경 변수에 보관하세요.

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

### 2. 첫 번째 호출 실행

볼 수 있는 프로젝트를 나열합니다. 키는 사용자와 동일하게 동작하므로 프로젝트와 역할이 같습니다.

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

예상 결과:

```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. 웹훅 수신

필요한 이벤트에 공개 HTTPS 엔드포인트를 구독시킨 뒤 곧바로 테스트 이벤트를 보내 서명을 검증하세요.

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

## 인증

모든 요청은 `Authorization` 헤더에 베어러 토큰으로 개인 API 키를 담아 인증합니다. 키는 개인용이며 각 프로젝트에서 사용자의 역할 범위 내에서만 동작하고 그 이상은 허용되지 않습니다. 키를 하나의 워크스페이스로 제한하거나 만료일을 설정하고 언제든지 해제할 수 있으며, 모든 호출은 키와 함께 기록됩니다.

| 스코프 | 허용 범위 |
| --- | --- |
| `read` (`planitude:read`) | 프로젝트, 작업, 종속 관계, 구성원, 댓글, 기준선, 웹훅을 읽습니다. |
| `write` (`planitude:write`) | 읽기 권한의 모든 기능에 더해 프로젝트, 작업, 종속 관계, 댓글, 기준선, 웹훅을 생성하고 변경합니다. |

키와 OAuth 앱은 프로젝트나 작업을 **삭제할 수 없습니다**. 삭제는 사용자가 직접 확인하는 웹 앱에서만 가능합니다. 잘못된 ID를 대상으로 한 스크립트가 계획을 지울 수 없습니다.

### 타사 앱용 OAuth 2.1

다른 사용자를 대신해 동작하는 앱은 OAuth 2.1을 사용합니다. PKCE(S256)를 적용한 인증 코드 방식, 사용할 때마다 교체되는 리프레시 토큰, 동적 클라이언트 등록(RFC 7591)을 지원합니다. 리소스 `https://planitude.app/api`와 스코프 `planitude:read` 또는 `planitude:write`를 요청하세요.

엔드포인트는 표준 디스커버리 문서에 게시되므로 OAuth 라이브러리가 자동으로 설정됩니다.

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

### 권장 사항

- 키는 환경 변수 또는 시크릿 관리자에 보관하고 코드나 저장소에는 절대 넣지 마세요. 키는 `gnt_`로 시작하므로 시크릿 스캐너가 탐지할 수 있습니다.
- 읽기만 할 때는 읽기 전용 키를 사용하고 연동마다 키를 하나씩 사용하세요. 다른 연동을 중단하지 않고 하나만 해제할 수 있습니다.
- 스크립트에서 사용하는 키에는 만료일을 설정하고 주기적으로 교체하세요.
- API는 브라우저가 아닌 서버에서 호출하세요. 프런트엔드 코드에 있는 키는 공개된 키나 마찬가지입니다.

## 오류

Planitude는 표준 HTTP 상태 코드를 사용합니다. 오류는 항상 동일한 JSON 구조를 가집니다. 분기 처리에 쓸 수 있는 안정적인 `error` 코드와, 필요한 경우 원인이나 수정할 필드를 담은 `details`가 포함됩니다.

| 상태 | error | 의미 |
| --- | --- | --- |
| 400 | `invalid_request` | 요청이 올바르지 않습니다. `details.fields`에 각 필드와 문제가 나열됩니다. |
| 401 | `unauthorized` | 키가 없거나, 유효하지 않거나, 해제되었거나, 만료되었습니다. |
| 403 | `forbidden` | 키 또는 사용자의 역할로는 허용되지 않습니다(`details.reason`). 예: 읽기 전용 키로 쓰기를 시도하거나 삭제를 시도한 경우. |
| 404 | `not_found` | 찾을 수 없거나 사용자에게 보이지 않습니다. |
| 409 | `dependency_cycle` | 계획과 충돌합니다. 예: 순환을 만드는 종속 관계(`dependency_cycle`). |
| 429 | `rate_limited` | 요청이 너무 많습니다. `Retry-After`에 표시된 초만큼 기다리세요. |
| 500 | `server_error` | 서버 측에서 문제가 발생했습니다. 지수 백오프로 재시도하세요. |

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

## 요청 한도

한도는 키별로 1분 슬라이딩 윈도우 기준으로 적용됩니다. 읽기 요청 **240**건, 쓰기 요청 **60**건입니다. 한도를 초과하면 초 단위의 `Retry-After` 헤더와 함께 `429 rate_limited`가 반환됩니다.

재시도하기 전에 `Retry-After`만큼 기다리고, 대량 작업은 시간을 분산하며, 여러 변경은 하나의 변경 세트로 묶으세요. 한 번의 요청과 한 번의 재계산으로 최대 1,000건의 변경을 처리할 수 있습니다.

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

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

## 페이지 매김 및 검색

작업 목록은 커서 방식으로 페이지가 나뉩니다. `limit`(1–500, 기본값 100)을 전달하고, 다음 페이지는 이전 응답의 `nextCursor`를 `cursor`로 전달하세요. `nextCursor`가 `null`이면 더 이상 페이지가 없습니다.

`externalId`(자체 ID와 정확히 일치), `q`(이름 포함), `updatedSince`, `completed`로 필터링하고, `sort=-number`(최신순) 또는 `-updatedAt`으로 정렬합니다. 프로젝트, 구성원, 종속 관계처럼 짧은 목록은 한 번의 응답으로 전체가 반환됩니다.

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

## 버전 관리

API와 웹훅 페이로드는 날짜로 버전을 관리합니다. 현재 버전은 `2026-10-01`이며, 모든 이벤트의 `apiVersion` 필드와 OpenAPI 문서의 `info.version`에 해당합니다.

한 버전 내에서는 추가만 이루어집니다. 새 엔드포인트, 새 선택 필드, 새 이벤트 유형이 추가될 수 있으며, 새 버전 없이 필드가 제거되거나 유형이 바뀌는 일은 없으므로 알 수 없는 필드는 무시하세요.

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

반환값 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"
```

반환값 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.

쿼리 매개변수:

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

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

반환값 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)

요청 본문:

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

반환값 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)

요청 본문:

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

반환값 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)

쿼리 매개변수:

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

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

반환값 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)

경로 매개변수:

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

반환값 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)

경로 매개변수:

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

요청 본문:

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

반환값 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)

경로 매개변수:

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

반환값 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)

경로 매개변수:

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

요청 본문:

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

반환값 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)

경로 매개변수:

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

쿼리 매개변수:

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

반환값 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.

경로 매개변수:

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

요청 본문 (`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=`. |

요청 본문 (또는 `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"}'
```

반환값 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)

경로 매개변수:

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

반환값 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.

경로 매개변수:

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

요청 본문:

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

반환값 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)

경로 매개변수:

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

반환값 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)

경로 매개변수:

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

요청 본문:

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

반환값 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)

경로 매개변수:

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

반환값 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`.

경로 매개변수:

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

요청 본문 (`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`. |

요청 본문 (또는 `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
}'
```

반환값 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)

경로 매개변수:

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

반환값 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.

경로 매개변수:

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

요청 본문:

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

반환값 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)

경로 매개변수:

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

반환값 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)

쿼리 매개변수:

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

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

반환값 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.

요청 본문:

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

반환값 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
```

반환값 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)

경로 매개변수:

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

반환값 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)

경로 매개변수:

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

요청 본문:

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

반환값 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)

경로 매개변수:

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

반환값 204 No Content: Deleted

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

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

경로 매개변수:

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

쿼리 매개변수:

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

반환값 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)

경로 매개변수:

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

반환값 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)

경로 매개변수:

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

반환값 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)

경로 매개변수:

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

반환값 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)

경로 매개변수:

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

반환값 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"
}
```

## 웹훅

웹훅은 웹 앱, API, MCP 서버, 가져오기 중 어디에서 변경이 발생하든 변경 즉시 JSON 본문이 담긴 HTTPS `POST`를 엔드포인트로 보냅니다. 웹훅은 하나의 프로젝트 또는 워크스페이스의 모든 프로젝트(소유자 및 관리자)를 대상으로 합니다.

[프로필 → 웹훅](https://planitude.app/ko/app/profile/webhooks)에서 또는 API로 REST 훅 방식으로 생성합니다. `POST /api/webhooks`로 구독하고 `DELETE`로 구독을 해제합니다. 서명 시크릿은 한 번만 표시됩니다.

| 헤더 | 설명 |
| --- | --- |
| `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` |

### 이벤트 유형

개별 이벤트 또는 모든 이벤트를 의미하는 `*`를 구독하세요. 모든 이벤트의 봉투(envelope) 구조는 같으며 `data`는 유형에 따라 달라집니다.

| 이벤트 | 설명 |
| --- | --- |
| `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"
    }
  }
}
```

### 서명 검증

모든 요청은 HMAC-SHA256과 웹훅 시크릿으로 서명됩니다. `Planitude-Signature` 헤더에는 타임스탬프 `t`와 서명 `v1`이 들어 있으며, 서명은 `t`, 마침표, 원본 본문을 이어 붙여 계산합니다.

1. JSON을 파싱하기 전의 원본 본문을 사용하세요. 다시 직렬화한 본문은 서명이 다릅니다.
2. HMAC를 계산하고 상수 시간 비교로 대조하세요.
3. 타임스탬프가 시스템 시계와 5분 넘게 차이 나는 요청은 거부하세요. 가로챈 요청을 재전송할 수 없습니다.
4. 시크릿은 원할 때 언제든 교체할 수 있으며, 이전 시크릿은 즉시 사용할 수 없게 됩니다.

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

### 재시도 및 전달

10초 이내에 아무 **2xx**로 응답하고 실제 작업은 이후 큐에서 처리하세요. 리디렉션은 따라가지 않습니다. 오류나 시간 초과가 발생하면 Planitude는 총 6회까지 시도합니다. 즉시 시도한 뒤 1 min, 5 min, 30 min, 2 h, 12 h 후에 재시도합니다.

전달은 **최소 한 번**이며 **순서가 보장되지 않습니다**. 이벤트 ID(`Planitude-Event-Id`, 본문의 `id`와 동일)를 저장해 이미 처리한 이벤트는 건너뛰고, 순서는 `createdAt` 기준으로 정렬하세요.

5개 이벤트가 연속으로 모든 시도에 실패하면 웹훅이 비활성화되고 소유자에게 이메일이 발송됩니다. `410 Gone` 응답은 즉시 비활성화합니다. 프로필에서, 또는 `PATCH`와 `active: true`로 다시 활성화할 수 있습니다.

전달 로그(상태, HTTP 코드, 응답 일부, 페이로드, 수동 재전송)는 30일간 보관됩니다. 대상 주소는 공개 주소만 허용됩니다.

### 엔드포인트 테스트

1. 테스트 엔드포인트 또는 프로필의 테스트 버튼으로 `webhook.ping` 이벤트를 보내세요.
2. 내 컴퓨터에서 이벤트를 받으려면 터널(예: ngrok 또는 Cloudflare Tunnel)로 로컬 서버를 공개하고 그 공개 HTTPS 주소를 구독하세요.
3. 전달 로그를 열어 상태, HTTP 코드, 응답을 확인하고, 엔드포인트를 수정한 뒤 전달을 다시 보내세요.

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

## MCP 서버

Planitude는 MCP(Model Context Protocol) 서버입니다. AI 어시스턴트와 코드 편집기가 사용자의 권한 범위 내에서 프로젝트를 읽고, 크리티컬 패스와 작업량을 분석하며, 작업을 생성하거나 변경합니다. 웹 앱은 OAuth(로그인 후 접근 허용)로 연결하고, 명령줄 도구와 편집기는 API 키를 사용합니다.

서버 URL: https://planitude.app/mcp

### ChatGPT (OAuth)

ChatGPT에서 설정 › 보안 및 로그인을 열고 개발자 모드를 켜세요. 서버 URL과 OAuth 인증으로 앱을 만든 뒤 Planitude에 로그인하고 접근을 허용합니다.

Server URL:

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

공식 문서: https://developers.openai.com/api/docs/guides/developer-mode

### Claude (OAuth)

맞춤 설정 › 커넥터 › 사용자 지정 커넥터 추가를 열고 서버 URL을 붙여넣은 뒤 연결을 선택하세요. Planitude가 열리면 로그인하고 접근을 허용합니다. claude.ai, Claude Desktop, 모바일 앱에서 작동합니다.

Server URL:

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

공식 문서: https://claude.com/docs/connectors/custom/add-unlisted

### Gemini (OAuth)

gemini.google.com에서 설정 › 연결된 앱을 열고 사용자 지정 앱 추가를 선택해 서버 URL을 붙여넣으세요. 그런 다음 Planitude에 로그인하고 접근을 허용합니다.

Server URL:

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

공식 문서: https://support.google.com/gemini/answer/17209137

### Claude Code (API 키)

키를 환경 변수에 넣고 명령을 실행하세요. 모든 프로젝트에서 사용하려면 `--scope user`를 추가하고 `claude mcp list`로 확인합니다.

Terminal:

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

공식 문서: https://code.claude.com/docs/en/mcp

### Codex (API 키)

키를 `PLANITUDE_API_KEY`에 넣고 `~/.codex/config.toml`에 블록을 추가하세요. CLI, 앱, IDE 확장 프로그램 모두에 적용됩니다.

~/.codex/config.toml:

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

공식 문서: https://learn.chatgpt.com/docs/extend/mcp

### Cursor (API 키)

`~/.cursor/mcp.json`(모든 프로젝트) 또는 `.cursor/mcp.json`(단일 프로젝트)에 블록을 추가하세요. Cursor는 환경 변수에서 키를 읽습니다.

~/.cursor/mcp.json:

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

공식 문서: https://cursor.com/docs/context/mcp

### Visual Studio Code (API 키)

워크스페이스에 `.vscode/mcp.json`을 추가하세요. VS Code가 키를 한 번 묻고 파일 밖에 안전하게 저장합니다.

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

공식 문서: https://code.visualstudio.com/docs/copilot/customization/mcp-servers

### Gemini CLI (API 키)

키를 환경 변수에 넣고 명령을 실행하거나, `~/.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"
      }
    }
  }
}
```

공식 문서: https://geminicli.com/docs/tools/mcp-server/

### 도구

서버는 읽기와 분석부터 안전한 변경까지 29개의 도구를 제공합니다. 프로젝트나 작업을 삭제하는 도구는 없습니다.

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

[ChatGPT, Claude, Gemini 단계별 가이드](https://planitude.app/ko/blog/chatgpt-claude-gemini-mcp-project-plans)

## 기본 제공 연동

코드가 필요 없습니다. 이러한 연동은 동일한 API와 웹훅을 사용하며 몇 번의 클릭으로 설정할 수 있습니다.

- [Jira](https://planitude.app/ko/integrations/jira) (사용 가능): 작업, 날짜, 종속 관계를 Jira 이슈와 양방향으로 동기화합니다.
- [Azure DevOps](https://planitude.app/ko/integrations/azure-devops) (사용 가능): Azure DevOps Boards 작업 항목과 양방향으로 동기화합니다.
- [Slack](https://planitude.app/ko/integrations/slack) (사용 가능): 채널에서 프로젝트 업데이트, 일일 요약, 슬래시 명령을 사용합니다.
- [Microsoft Teams](https://planitude.app/ko/integrations/teams) (사용 가능): Microsoft Teams에서 업데이트, 요약, Planitude 탭을 사용합니다.
- [Zapier, Make, n8n](https://planitude.app/ko/integrations?category=automation) (출시 예정): 노코드 자동화를 위한 작업 및 프로젝트의 즉시 트리거와 액션.

## 변경 이력

- **2026-10-02**: OpenAPI 문서에서 생성한 새로운 개발자 문서. OpenAPI 문서에 구성원, 댓글, 기준선, 템플릿, 담당자에 대해 반환되는 모든 필드가 설명되었습니다.
- **2026-10-01**: API 버전 2026-10-01: 서명된 발신 웹훅, 문서화된 공개 REST API(OpenAPI 3.1), 커서 페이지 매김, externalId 검색.
- **2026-09-30**: MCP 서버와 API용 OAuth 2.1: ChatGPT, Claude, Gemini가 키를 복사하지 않고 연결됩니다.
- **2026-09-28**: MCP 서버와 읽기 또는 읽기+쓰기 권한의 개인 API 키.
