---
title: "Planitude API, webhooks og MCP til udviklere"
description: "REST API med OpenAPI 3.1, signerede webhooks og en MCP-server til AI-assistenter: byg på Planitude, det gratis onlineværktøj til projektstyring."
url: https://planitude.app/da/developers
language: da
---

# Byg med Planitude

En REST API, signerede webhooks og en MCP-server: hold dine systemer synkroniseret med planen, reagér på hver ændring, og lad en AI-assistent arbejde på dine projekter.

*Kode, feltnavne og den tekniske reference er på engelsk, som i det meste API-dokumentation.*

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

- **Synkronisér opgaver med dine systemer**: Opret projekter og opgaver fra et CRM, et ERP eller et script, find dem igen med dit eget id via `externalId`, og lad planlægningsmotoren beregne datoerne.
- **Reagér på hver ændring**: Modtag en signeret HTTPS-anmodning, når en opgave oprettes, fuldføres eller flyttes, uanset om ændringen kommer fra appen, API'en, en import eller en AI-assistent.
- **Forbind en AI-assistent**: ChatGPT, Claude, Gemini, Cursor og VS Code læser dine planer og foreslår ændringer via MCP-serveren, med dine tilladelser og intet udover det.

Anmodninger og svar er JSON over HTTPS. Varigheder og forskydninger angives i **arbejdsminutter** (en arbejdsdag på 8 timer er 480), tidspunkter er ISO 8601 i UTC, og datoer uden klokkeslæt er `YYYY-MM-DD`.

## Hurtig start

Fra nul til din første webhook i tre trin. Du skal kun bruge en terminal.

### 1. Opret en API-nøgle

Åbn [Profil → API-nøgler](https://planitude.app/da/app/profile/api-keys), vælg **Læs og skriv**, og kopiér nøglen: den begynder med `gnt_` og vises kun én gang. Gem den i en miljøvariabel.

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

### 2. Foretag dit første kald

List de projekter, du kan se. Nøglen handler som dig: samme projekter, samme roller.

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

Forventet resultat:

```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. Modtag en webhook

Abonnér et offentligt HTTPS-endpoint på de hændelser, du har brug for, send derefter straks en testhændelse, og bekræft dens signatur.

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

## Godkendelse

Godkend hver anmodning med en personlig API-nøgle i headeren `Authorization` som bearer-token. Nøgler er personlige: de handler som dig, med din rolle i hvert projekt, aldrig mere. Du kan begrænse en nøgle til ét workspace, give den en udløbsdato og tilbagekalde den når som helst; hvert kald logges sammen med nøglen.

| Scope | Tillader |
| --- | --- |
| `read` (`planitude:read`) | Læs projekter, opgaver, afhængigheder, medlemmer, kommentarer, baselines og webhooks. |
| `write` (`planitude:write`) | Alt i læs, plus oprettelse og ændring af projekter, opgaver, afhængigheder, kommentarer, baselines og webhooks. |

Nøgler og OAuth-apps **kan ikke slette** projekter eller opgaver: sletning sker kun i webappen, hvor en person bekræfter den. Et script, der rammer et forkert id, kan ikke slette en plan.

### OAuth 2.1 til tredjepartsapps

Apps, der handler på vegne af andre, bruger OAuth 2.1: autorisationskode med PKCE (S256), refresh-tokens der roteres ved hver brug, dynamisk klientregistrering (RFC 7591). Anmod om ressourcen `https://planitude.app/api` og scope `planitude:read` eller `planitude:write`.

Endpoints er publiceret i standardiserede discovery-dokumenter, så OAuth-biblioteker konfigurerer sig selv.

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

### God praksis

- Opbevar nøgler i miljøvariabler eller en secret manager, aldrig i kode eller i et repository. Nøgler begynder med `gnt_`, så secret scanners kan opdage dem.
- Brug en skrivebeskyttet nøgle, når du kun læser, og én nøgle pr. integration: du kan tilbagekalde én uden at stoppe de andre.
- Giv nøgler, der bruges af scripts, en udløbsdato, og roter dem.
- Kald API'en fra din server, ikke fra en browser: en nøgle i frontend-kode er en offentlig nøgle.

## Fejl

Planitude bruger standard HTTP-statuskoder. Fejl har altid samme JSON-form: en stabil `error`-kode at forgrene på og, når det er nyttigt, `details` med årsagen eller de felter, der skal rettes.

| Status | error | Betydning |
| --- | --- | --- |
| 400 | `invalid_request` | Anmodningen er ugyldig: `details.fields` viser hvert felt og problemet. |
| 401 | `unauthorized` | Nøglen mangler, er ugyldig, tilbagekaldt eller udløbet. |
| 403 | `forbidden` | Nøglen eller din rolle tillader det ikke (`details.reason`), for eksempel en skrivning med en skrivebeskyttet nøgle, eller en sletning. |
| 404 | `not_found` | Ikke fundet, eller ikke synlig for dig. |
| 409 | `dependency_cycle` | Konflikt med planen, for eksempel en afhængighed, der ville lukke en cyklus (`dependency_cycle`). |
| 429 | `rate_limited` | For mange anmodninger: vent det antal sekunder, der står i `Retry-After`. |
| 500 | `server_error` | Noget gik galt hos os: prøv igen med eksponentiel backoff. |

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `error` | string | yes | Stable error code: invalid_request, unauthorized, forbidden, not_found, conflict, dependency_cycle, limit_exceeded, rate_limited, server_error, ... |
| `details` | object |  |  |

```json
HTTP 400
{
  "error": "invalid_request",
  "details": {
    "fields": [
      {
        "path": "name",
        "message": "Invalid input: expected string, received undefined"
      },
      {
        "path": "durationMin",
        "message": "Too small: expected number to be >=0"
      }
    ]
  }
}
```

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

```json
HTTP 409
{
  "error": "dependency_cycle",
  "details": {
    "cycles": [
      {
        "path": "Foundations -> Walls -> Foundations",
        "taskIds": [
          "4dbd104a-00cd-4035-8955-255f52ea586a",
          "a22fd799-c4dc-4869-a466-0dd6e96f71ce"
        ],
        "taskNames": [
          "Foundations",
          "Walls"
        ]
      }
    ]
  }
}
```

## Hastighedsgrænser

Grænserne gælder pr. nøgle i et glidende vindue på ét minut: **240** læseanmodninger og **60** skriveanmodninger. Ved overskridelse får du `429 rate_limited` med en `Retry-After`-header i sekunder.

Vent på `Retry-After`, før du prøver igen, fordel massekørsler over tid, og saml mange ændringer i ét change set: op til 1.000 ændringer i én anmodning og én genberegning.

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

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

## Paginering og søgning

Opgavelister pagineres med en cursor. Angiv `limit` (1–500, standard 100) og, for næste side, `nextCursor` fra det forrige svar som `cursor`. Når `nextCursor` er `null`, er der ikke flere sider.

Filtrér med `externalId` (nøjagtigt match på dit eget id), `q` (navnet indeholder), `updatedSince` og `completed`; sortér med `sort=-number` (nyeste først) eller `-updatedAt`. Kortere lister, såsom projekter, medlemmer og afhængigheder, returneres samlet i ét svar.

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

## Versionering

API'en og webhook-payloads versioneres efter dato. Den aktuelle version er `2026-10-01`: den findes i feltet `apiVersion` i hver hændelse og i `info.version` i OpenAPI-dokumentet.

Inden for en version tilføjer vi kun: nye endpoints, nye valgfrie felter, nye hændelsestyper. Felter fjernes eller ændrer aldrig type uden en ny version, så ignorér de felter, du ikke kender.

## API-reference

Every request: `Authorization: Bearer $PLANITUDE_API_KEY`. Examples use the same project (`WHX`), task and person throughout.

### Account

Who is calling, and their workspaces.

#### User object

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) |  |  |
| `email` | string (email) |  |  |
| `name` | string, nullable |  |  |
| `avatarUrl` | string, nullable |  |  |
| `locale` | string |  |  |
| `emailVerified` | boolean |  |  |

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

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

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

Forespørgselsparametre:

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

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

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

Anmodningens brødtekst:

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

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

Anmodningens brødtekst:

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

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

Forespørgselsparametre:

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

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

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

Stiparametre:

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

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

Stiparametre:

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

Anmodningens brødtekst:

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

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

Stiparametre:

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

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

Stiparametre:

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

Anmodningens brødtekst:

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

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

Stiparametre:

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

Forespørgselsparametre:

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

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

Stiparametre:

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

Anmodningens brødtekst (`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=`. |

Anmodningens brødtekst (eller `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"}'
```

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

Stiparametre:

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

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

Stiparametre:

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

Anmodningens brødtekst:

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

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

Stiparametre:

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

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

Stiparametre:

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

Anmodningens brødtekst:

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

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

Stiparametre:

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

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

Stiparametre:

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

Anmodningens brødtekst (`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`. |

Anmodningens brødtekst (eller `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
}'
```

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

Stiparametre:

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

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

Stiparametre:

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

Anmodningens brødtekst:

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

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

Stiparametre:

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

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

Forespørgselsparametre:

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

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

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

Anmodningens brødtekst:

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

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

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

Stiparametre:

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

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

Stiparametre:

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

Anmodningens brødtekst:

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

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

Stiparametre:

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

Returnerer 204 No Content: Deleted

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

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

Stiparametre:

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

Forespørgselsparametre:

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

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

Stiparametre:

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

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

Stiparametre:

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

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

Stiparametre:

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

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

Stiparametre:

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

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

## Webhooks

Webhooks sender en HTTPS-`POST` med en JSON-body til dit endpoint, så snart noget ændres, uanset om ændringen kommer fra webappen, API'en, MCP-serveren eller en import. En webhook dækker ét projekt eller alle projekter i et workspace (ejere og administratorer).

Opret dem under [Profil → Webhooks](https://planitude.app/da/app/profile/webhooks) eller via API'en som REST hooks: abonnér med `POST /api/webhooks`, afmeld med `DELETE`. Signeringshemmeligheden vises kun én gang.

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

### Hændelsestyper

Abonnér på enkelte hændelser eller på `*` for dem alle. Hver hændelse har samme kuvert; `data` ændres med typen.

| Hændelse | Beskrivelse |
| --- | --- |
| `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"
    }
  }
}
```

### Bekræft signaturer

Hver anmodning signeres med HMAC-SHA256 og din webhooks hemmelighed. Headeren `Planitude-Signature` indeholder et tidsstempel `t` og signaturen `v1`, beregnet på `t`, et punktum og den rå body.

1. Brug den rå body, før enhver JSON-parsing: en gen-serialiseret body har en anden signatur.
2. Beregn HMAC'en, og sammenlign den i konstant tid.
3. Afvis anmodninger, hvis tidsstempel afviger mere end 5 minutter fra dit ur: en opfanget anmodning kan ikke afspilles igen.
4. Roter hemmeligheden, når du vil: den gamle holder op med at virke med det samme.

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

### Genforsøg og levering

Svar med en vilkårlig **2xx** inden for 10 sekunder, og udfør det egentlige arbejde bagefter i en kø. Omdirigeringer følges ikke. Ved fejl og timeouts forsøger Planitude op til 6 gange i alt: med det samme og derefter efter 1 min, 5 min, 30 min, 2 h, 12 h.

Levering sker **mindst én gang** og **uden fast rækkefølge**. Gem hændelsens id (`Planitude-Event-Id`, også `id` i body) og spring dem over, du allerede har behandlet; sortér efter `createdAt`.

Hvis 5 hændelser i træk fejler ved alle forsøg, deaktiveres webhooken, og dens ejer modtager en e-mail. Et `410 Gone`-svar deaktiverer den med det samme. Aktivér den igen fra profilen eller med `PATCH` og `active: true`.

Leveringsloggen (status, HTTP-kode, uddrag af svar, payload, manuel gensendelse) gemmes i 30 dage. Kun offentlige adresser accepteres som destinationer.

### Test dit endpoint

1. Send en `webhook.ping`-hændelse med test-endpointet eller med knappen Test i profilen.
2. For at modtage hændelser på din computer skal du eksponere din lokale server med en tunnel (for eksempel ngrok eller Cloudflare Tunnel) og abonnere på dens offentlige HTTPS-adresse.
3. Åbn leveringsloggen for at se status, HTTP-kode og svar, og gensend en levering, når dit endpoint er rettet.

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

## MCP-server

Planitude er en MCP-server (Model Context Protocol): AI-assistenter og kodeeditorer læser projekter, analyserer kritisk vej og arbejdsbelastning samt opretter eller ændrer opgaver, med dine tilladelser. Webapps forbinder via OAuth (du logger ind og giver adgang); kommandolinjeværktøjer og editorer bruger en API-nøgle.

Server-URL: https://planitude.app/mcp

### ChatGPT (OAuth)

Åbn Indstillinger › Sikkerhed og login i ChatGPT, og slå Developer mode til. Opret en app med server-URL'en og OAuth-godkendelse, log derefter ind på Planitude, og giv adgang.

Server URL:

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

Officiel dokumentation: https://developers.openai.com/api/docs/guides/developer-mode

### Claude (OAuth)

Åbn Customize › Connectors › Add custom connector, indsæt server-URL'en, og vælg Connect: Planitude åbnes, du logger ind og giver adgang. Det fungerer i claude.ai, Claude Desktop og mobilappen.

Server URL:

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

Officiel dokumentation: https://claude.com/docs/connectors/custom/add-unlisted

### Gemini (OAuth)

Åbn Indstillinger › Connected Apps på gemini.google.com, vælg Add a custom app, og indsæt server-URL'en; log derefter ind på Planitude, og giv adgang.

Server URL:

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

Officiel dokumentation: https://support.google.com/gemini/answer/17209137

### Claude Code (API-nøgle)

Kør kommandoen med din nøgle i en miljøvariabel. Tilføj `--scope user` for at bruge den i alle projekter, og kontrollér med `claude mcp list`.

Terminal:

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

Officiel dokumentation: https://code.claude.com/docs/en/mcp

### Codex (API-nøgle)

Læg nøglen i `PLANITUDE_API_KEY`, og tilføj blokken i `~/.codex/config.toml`: den gælder for CLI'en, appen og IDE-udvidelsen.

~/.codex/config.toml:

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

Officiel dokumentation: https://learn.chatgpt.com/docs/extend/mcp

### Cursor (API-nøgle)

Tilføj blokken i `~/.cursor/mcp.json` (alle projekter) eller `.cursor/mcp.json` (ét projekt); Cursor læser nøglen fra miljøet.

~/.cursor/mcp.json:

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

Officiel dokumentation: https://cursor.com/docs/context/mcp

### Visual Studio Code (API-nøgle)

Tilføj `.vscode/mcp.json` til workspacet: VS Code spørger efter nøglen én gang og gemmer den sikkert uden for filen.

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

Officiel dokumentation: https://code.visualstudio.com/docs/copilot/customization/mcp-servers

### Gemini CLI (API-nøgle)

Kør kommandoen med din nøgle i en miljøvariabel, eller tilføj blokken i `~/.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"
      }
    }
  }
}
```

Officiel dokumentation: https://geminicli.com/docs/tools/mcp-server/

### Værktøjer

Serveren stiller 29 værktøjer til rådighed, fra læsning og analyse til sikre ændringer. Ingen af dem sletter projekter eller opgaver.

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

[Trin-for-trin-guide til ChatGPT, Claude og Gemini](https://planitude.app/da/blog/chatgpt-claude-gemini-mcp-project-plans)

## Færdige integrationer

Ingen kode nødvendig: disse integrationer bruger den samme API og de samme webhooks og sættes op med få klik.

- [Jira](https://planitude.app/da/integrations/jira) (Tilgængelig): Tovejssynkronisering af opgaver, datoer og afhængigheder med Jira-sager.
- [Azure DevOps](https://planitude.app/da/integrations/azure-devops) (Tilgængelig): Tovejssynkronisering med arbejdselementer i Azure DevOps Boards.
- [Slack](https://planitude.app/da/integrations/slack) (Tilgængelig): Projektopdateringer, daglige sammenfatninger og en slash-kommando i dine kanaler.
- [Microsoft Teams](https://planitude.app/da/integrations/teams) (Tilgængelig): Opdateringer, sammenfatninger og en Planitude-fane i Microsoft Teams.
- [Zapier, Make, n8n](https://planitude.app/da/integrations?category=automation) (Kommer snart): Øjeblikkelige triggere og handlinger på opgaver og projekter til no-code-automatiseringer.

## Ændringslog

- **2026-10-02**: Ny udviklerdokumentation, genereret ud fra OpenAPI-dokumentet. OpenAPI-dokumentet beskriver nu alle felter, der returneres for medlemmer, kommentarer, baselines, skabeloner og tildelte personer.
- **2026-10-01**: API-version 2026-10-01: signerede udgående webhooks, dokumenteret offentlig REST API (OpenAPI 3.1), cursor-paginering og søgning efter externalId.
- **2026-09-30**: OAuth 2.1 til MCP-serveren og API'en: ChatGPT, Claude og Gemini forbinder uden at kopiere en nøgle.
- **2026-09-28**: MCP-server og personlige API-nøgler med læse- eller læse + skriveadgang.
