# Enabling “Work item Types” via REST API silently and permanently prevents the project from ever having the native Epic type
**Product:** Plane Cloud (`app.plane.so` / `api.plane.so`, API `v1`)
**Workspace:** `cortex-intelligence` (plan: Pro)
**Observed:** 2026-08-04
**Auth used:** Personal Access Token (`X-API-Key`)
**Severity (our view):** high — the resulting project state is unrecoverable and the API gives no error
-–
## Summary
Enabling the **Work item Types** feature produces a *different result* depending on
whether it is done through the web UI or through the REST API:
| Enabled via | Types created | `POST /epics/` afterwards |
|—|—|—|
| Web UI (`Project settings > Work item Types > Enable`) | `Task` (level 0) **and `Epic` (level 1)** | `400` — epic feature is on |
| REST API (`PATCH /projects/{id}/ {“is_issue_type_enabled”: true}`) | **only `Task` (level 0)** | `404 {“error”:“Epic is not enabled for this project”}` |
Three properties combine to make this unrecoverable:
1. The API path never creates the level-1 `Epic` type.
2. The UI itself states **“Once enabled, Work item Types can’t be disabled.”** — so
the operator cannot turn it off and re-enable it correctly through the UI.
3. There is no API way to create a level-1 / `is_epic` type afterwards (details in
*What we tried*, below). `POST /work-item-types/` accepts `level: 1` and
`is_epic: true`, returns `201`, and stores `level=0, is_epic=false`.
Net effect: **a single API call permanently removes the ability to use native Epics
in that project**, and it does so without any error, warning, or difference in the
project resource. The only recovery we found is to delete the project and recreate it.
## Why this matters to us
We provision Plane projects as code (idempotent plan/apply script over API v1) so
that every team’s project is born with the same structure: work item types, custom
properties, labels, states, modules, cycles and saved views. In our model the native
**Epic (level 1) is the monitored unit** — it carries the 7 custom properties our
rules engine reads (owner, sponsor, health, original due date, blocked reason, etc.).
Because our script enabled Work item Types over the API — the natural thing to do in
an automated provisioning flow — every project it created was silently left without
Epics, and could not be fixed after the fact. We only found out by diffing a
UI-created project against an API-created one.
## Steps to reproduce
Controlled experiment, two brand-new empty projects in the same workspace. Both start
identical: `is_issue_type_enabled: false`, zero work item types,
`POST /epics/` → `404`.
**Group A — enable through the UI**
1. Create project `teste2` (UI or API).
2. Open `Project settings > Work item Types` and click **Enable** (confirm the
“can’t be disabled” dialog).
3. `GET /api/v1/workspaces/{slug}/projects/{project_id}/work-item-types/`
Result: **2 types** — `Task` (level 0) and `Epic` (level 1, `is_epic: true`).
**Group B — enable through the API**
1. Create project `teste3`.
2. `PATCH /api/v1/workspaces/{slug}/projects/{project_id}/` with
`{“is_issue_type_enabled”: true}` → `200`.
3. `GET …/work-item-types/`
Result: **1 type** — `Task` (level 0) only. Re-read after 4s and after 16s to rule
out asynchronous creation: still only `Task`.
## Evidence
Project ids replaced with `<project_id:NAME>`; everything else is verbatim.
### E1 — Types after enabling: UI vs API
```
GET /api/v1/workspaces/{slug}/projects/<project_id:TESTE2>/work-item-types/ [enabled via UI]
→ 200 2 type(s): {name:“Task”, level:0, is_epic:false}, {name:“Epic”, level:1, is_epic:true}
GET /api/v1/workspaces/{slug}/projects/<project_id:TESTE3>/work-item-types/ [enabled via API]
→ 200 1 type(s): {name:“Task”, level:0, is_epic:false}
```
### E2 — The project resource does not record the difference
```
TESTE2 (UI) : is_issue_type_enabled=true … is_member=true issue_views_view=true page_view=true intake_view=false is_voting_enabled=true
TESTE3 (API): is_issue_type_enabled=true … is_member=true issue_views_view=true page_view=true intake_view=false is_voting_enabled=true
```
`is_issue_type_enabled` is `true` on both, and no other boolean on the project
resource distinguishes them. (`cycle_view`/`module_view` differ only because our
script later enabled those on TESTE2; they are unrelated to this issue.) So an
automated flow has **no way to detect** that it landed in the broken state, other
than noticing that no level-1 type exists.
### E3 — `POST /epics/` as a probe for the epic state
```
POST …/projects/<project_id:TESTE2>/epics/ {} [enabled via UI]
→ 400 {“name”:[“This field is required.”]} ← feature is ON (validation error)
POST …/projects/<project_id:TESTE3>/epics/ {} [enabled via API]
→ 404 {“error”:“Epic is not enabled for this project”}
```
This confirms the server has a notion of “epic enabled for this project”, and that
the API-enabled project is not in it.
### E4 — A level-1 (epic) type cannot be created via API: `201` but stored as level 0
```
POST …/projects/<project_id:TESTE3>/work-item-types/ {“name”:“EvidenceEpic1”,“level”:1,“is_epic”:true}
→ 201 stored as: level=0 is_epic=false (requested level=1 is_epic=true)
PATCH …/work-item-types/{id}/ {“level”:1,“is_epic”:true}
→ 200 stored as: level=0 is_epic=false (silently ignored)
POST …/projects/<project_id:TESTE3>/work-item-types/ {“name”:“EvidenceEpic2”,“level”:1,“is_epic”:true,“is_active”:true,“rollout”:“none”,“governance_mode”:“any”}
→ 201 stored as: level=0 is_epic=false (requested level=1 is_epic=true)
```
The second payload copies every field of a real `Epic` type read from a working
project (`is_active`, `rollout`, `governance_mode`, `logo_props`). Both `level` and
`is_epic` are accepted and dropped, with a success status.
### E5 — No feature field accepts an epic toggle
```
PATCH …/projects/{id}/ {“is_epic_enabled”:true} → 200 field present in GET afterwards: false
PATCH …/projects/{id}/ {“epic_view”:true} → 200 field present in GET afterwards: false
PATCH …/projects/{id}/ {“is_epics_enabled”:true} → 200 field present in GET afterwards: false
PATCH …/projects/{id}/ {“epics_view”:true} → 200 field present in GET afterwards: false
PATCH …/projects/{id}/ {“is_epic_view_enabled”:true} → 200 field present in GET afterwards: false
```
Every unknown field returns `200 OK` and changes nothing — see *Secondary issue* below.
### E6 — `import-work-item-types` is plan-gated
```
OPTIONS …/projects/{id}/import-work-item-types/ → 405 allow=POST
POST …/projects/{id}/import-work-item-types/ {} → 402 {“error”:“Payment required”,“error_code”:1999}
POST …/projects/{id}/import-work-item-types/ {“work_item_type_ids”:[…]} → 402 {“error”:“Payment required”,“error_code”:1999}
```
The route exists and only accepts POST, but returns `402` for every payload shape,
including an empty one — so it is gated before validation and cannot be used as a
recovery path on our plan.
### E7 — The internal route the web app uses rejects a PAT
```
GET /api/workspaces/{slug}/projects/{id}/work-item-types/ (no /v1 segment)
→ 401 {“detail”:“Authentication credentials were not provided.”}
```
So we cannot replicate whatever call the UI’s **Enable** button performs.
### E8 — UI copy that closes the loop
- `Project settings > Work item Types` (before enabling), confirmation dialog:
**“Once enabled, Work item Types can’t be disabled.”**
- `Project settings > Features > Epics` (current builds): the page has **no toggle**,
only the notice *"Epics now live under Work Item Types. Configure epic properties,
layouts, and defaults from the Work Item Types page. Your existing epics aren’t
affected."* This makes the state look enabled when it is not, and gives an operator
no way to fix an affected project.
## What we tried (all failed)
| Attempt | Result |
|—|—|
| `PATCH` project with 5 candidate epic feature fields (E5) | `200`, silently ignored |
| `POST /work-item-types/` with `level:1, is_epic:true` (E4) | `201`, stored as level 0 |
| Same, with the full field set copied from a working Epic type | `201`, stored as level 0 |
| `PATCH` an existing type to `level:1, is_epic:true` | `200`, ignored |
| Invented shapes (`{“type”:“EPIC”}`, `{“kind”:“epic”}`) | `201`, plain level-0 type |
| `POST /projects/{id}/import-work-item-types/` (E6) | `402 Payment required` (`error_code 1999`) |
| Internal (non-`/v1`) route with PAT (E7) | `401` |
| Waiting for async creation (4s, 16s) | no change |
| `GET /projects/{id}/epics/` as a detection probe | returns `200` + empty list whether the feature is on or off; only `POST` distinguishes |
## Expected behavior
Any one of these would resolve it, in our order of preference:
1. **`PATCH is_issue_type_enabled: true` should create the same types the UI creates**
(`Task` **and** `Epic`). Same feature, same outcome, regardless of client.
2. Expose a documented way to enable epics for a project via API v1 — either a real
project field (e.g. `is_epic_enabled`) that is readable *and* writable, or an
endpoint such as `POST /projects/{id}/epics/enable/`.
3. Allow `POST /work-item-types/` to honour `level: 1` / `is_epic: true` — or reject
it with `400` instead of returning `201` and silently downgrading to level 0.
4. At minimum: make the project resource expose the epic state so automation can
*detect* the broken state, and make it recoverable without deleting the project.
## Secondary issue: unknown fields are silently accepted
`PATCH /projects/{id}/` returns `200 OK` for fields that do not exist on the
serializer (E5), changing nothing. The same applies to `level` / `is_epic` on work
item types (E4). This turns every typo and every version drift into a silent no-op:
we only discovered the original problem because we re-read the resource after every
write. Returning `400` for unknown or non-writable fields would have surfaced all of
this immediately.
## Workaround we adopted
Our provisioning script no longer writes `is_issue_type_enabled` at all. It only
**reads** it, and stops with an instruction when the feature is off:
> Work item Types is disabled. Do **not** enable it via API: the PATCH creates only
> the `Task` type, while enabling through the UI creates `Task` + `Epic` — and since
> the feature cannot be disabled afterwards, enabling it here leaves the project
> permanently without the native Epic. Enable it in
> `Project settings > Work item Types > Enable`, then re-run.
So the required order for a new project is: create the project → **enable Work item
Types in the UI (human step)** → run the automation, which does everything else. We
would very much like to remove that human step.
## Questions
1. Is the UI’s **Enable** action expected to create the `Epic` type as a side effect?
If so, can the API path be made to do the same?
2. Is there any supported way, on the Pro plan, to add native Epics to a project that
was enabled through the API — without deleting and recreating the project?
3. Is `import-work-item-types` intended to be plan-gated (`402`), and which plan
includes it?
4. Does `Features > Epics` still have a toggle in any current build, or has it been
fully replaced by the Work Item Types page?
Someone can help me with this???
I am running tests to adopt the tool at our company, but this is a blocker for proceeding with the purchase.
I need to be able to enable this via the API, just as I can through the UI.
Additionally, I noticed that I cannot manipulate or update project pages via the API; that is also a major issue.