Create a project state
Add a stage to the workspace's project state catalog. Any project in the workspace can then move to it through its state_id.
The new state is never the default, even if you send is_default. To make it the default, call Mark a project state as default.
Path Parameters
slug:requiredstringThe workspace slug. It appears in your Plane URLs. In https://app.plane.so/my-team/projects/, the slug is my-team.
Body Parameters
name:requiredstringDisplay name, unique in the workspace. Maximum 50 characters. A name already in use returns 409 conflict. The comparison is case-sensitive, so Discovery and discovery can both exist.
color:requiredstringHex color used wherever the project state is rendered, for example #F59E0B. Maximum 255 characters.
group:optionalstringThe lifecycle group. Defaults to draft.
draftplanningexecutionmonitoringcompletedcancelled
Work item state groups such as started are not accepted and return 400 invalid_request.
description:optionalstringFree-form description of what the stage means.
sequence:optionalnumberOrdering weight. Lower values sort first. Plane places a new project state after the last one and uses your value only when the workspace has no project states. To reposition a state, send sequence in Update a project state.
external_id:optionalstringYour system's identifier for this project state. Maximum 255 characters.
external_source:optionalstringThe system external_id came from. Maximum 255 characters.
Response shaping
fields:optionalstringComma-separated list of fields to return. Unrequested keys are omitted from the response, not returned as null, so absent means "not requested" and null means "actually null". id always comes back whether or not you name it.
Pass all for every requestable field. An unknown name is a 400 that lists the valid set and suggests the closest match, so a typo can't silently cost you the saving.
Requestable here: color, created_at, created_by_id, description, external_id, external_source, group, id, is_default, name, sequence, updated_at, updated_by_id.
See Sparse fields.
Scopes
workspaces.project_states:write
Errors
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | Missing name or color, a group outside the six lifecycle groups, or a name over 50 characters. |
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | Project states aren't included in the workspace's plan, or are turned off in its settings. |
403 | forbidden | Your role or token scope can't create project states. |
404 | not_found | No such workspace, or it's outside your tenant. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
409 | conflict | A project state with this name already exists in the workspace. |
409 | governance_migration_in_progress | A governance migration is running, so the workspace is read-only. Retry after it finishes. |
413 | payload_too_large | The request body is over the size limit. |
415 | unsupported_media_type | The Content-Type isn't one this endpoint accepts. |
429 | rate_limited | Throttled. Honor the Retry-After header before retrying. |
curl -X POST \
"https://api.plane.so/api/v2/workspaces/my-team/project-states/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Discovery",
"color": "#3E63DD",
"group": "planning",
"description": "Scoping and research before a plan is agreed"
}'import requests
response = requests.post(
"https://api.plane.so/api/v2/workspaces/my-team/project-states/",
headers={"X-Api-Key": "your-api-key"},
json={
"name": "Discovery",
"color": "#3E63DD",
"group": "planning",
"description": "Scoping and research before a plan is agreed",
},
)
print(response.json())const response = await fetch("https://api.plane.so/api/v2/workspaces/my-team/project-states/", {
method: "POST",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Discovery",
color: "#3E63DD",
group: "planning",
description: "Scoping and research before a plan is agreed",
}),
});
const data = await response.json();{
"id": "6f0c3a85-9b2e-4d17-a8f6-3e5b1c7d9a42",
"name": "Discovery",
"description": "Scoping and research before a plan is agreed",
"color": "#3E63DD",
"group": "planning",
"sequence": 80000,
"is_default": false,
"external_id": null,
"external_source": null,
"created_at": "2026-02-03T16:45:12.904117Z",
"updated_at": "2026-02-03T16:45:12.904117Z",
"created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
"updated_by_id": null
}{
"type": "conflict",
"code": "conflict",
"detail": "A project state with this name already exists in this workspace."
}
