diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 7d2ce701..1c3a5dbb 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -275,7 +275,7 @@ export default extendConfig( nav: [ { text: "Self-hosting", link: "/self-hosting/overview" }, - { text: "API Reference", link: "/api-reference/introduction" }, + { text: "API Reference", link: "/api-reference/v2/introduction" }, { text: "Build and extend", link: "/dev-tools/build-plane-app/overview" }, { text: "Plane Docs", link: "https://docs.plane.so", noIcon: true }, { text: "Sign in", link: "https://app.plane.so/sign-in" }, @@ -288,7 +288,7 @@ export default extendConfig( items: [ { text: "Home", link: "/" }, { text: "Self-hosting", link: "/self-hosting/overview" }, - { text: "API Reference", link: "/api-reference/introduction" }, + { text: "API Reference", link: "/api-reference/v2/introduction" }, { text: "Build and extend", link: "/dev-tools/build-plane-app/overview" }, ], }, @@ -304,9 +304,9 @@ export default extendConfig( { text: "API Reference", items: [ - { text: "Introduction", link: "/api-reference/introduction" }, - { text: "Work items", link: "/api-reference/issue/overview" }, - { text: "Projects", link: "/api-reference/project/overview" }, + { text: "Introduction", link: "/api-reference/v2/introduction" }, + { text: "Work items", link: "/api-reference/v2/work-items/overview" }, + { text: "Migrating from v1", link: "/api-reference/v2/migrating-from-v1" }, ], }, { @@ -504,38 +504,38 @@ export default extendConfig( }, ], - "/api-reference/": [ + "/api-reference/v1/": [ { text: "API Reference", items: [ - { text: "Introduction", link: "/api-reference/introduction" }, + { text: "Introduction", link: "/api-reference/v1/introduction" }, { text: "Project", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/project/overview" }, - { text: "Create Project", link: "/api-reference/project/add-project" }, + { text: "Overview", link: "/api-reference/v1/project/overview" }, + { text: "Create Project", link: "/api-reference/v1/project/add-project" }, { text: "Create Project with Template", - link: "/api-reference/project/create-project-with-template", + link: "/api-reference/v1/project/create-project-with-template", }, - { text: "List Projects", link: "/api-reference/project/list-projects" }, - { text: "Get Project", link: "/api-reference/project/get-project-detail" }, - { text: "Update Project", link: "/api-reference/project/update-project-detail" }, - { text: "Archive Project", link: "/api-reference/project/archive-project" }, - { text: "Unarchive Project", link: "/api-reference/project/unarchive-project" }, - { text: "Delete Project", link: "/api-reference/project/delete-project" }, + { text: "List Projects", link: "/api-reference/v1/project/list-projects" }, + { text: "Get Project", link: "/api-reference/v1/project/get-project-detail" }, + { text: "Update Project", link: "/api-reference/v1/project/update-project-detail" }, + { text: "Archive Project", link: "/api-reference/v1/project/archive-project" }, + { text: "Unarchive Project", link: "/api-reference/v1/project/unarchive-project" }, + { text: "Delete Project", link: "/api-reference/v1/project/delete-project" }, ], }, { text: "Project Features", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/project-features/overview" }, - { text: "Get Project Features", link: "/api-reference/project-features/get-project-features" }, + { text: "Overview", link: "/api-reference/v1/project-features/overview" }, + { text: "Get Project Features", link: "/api-reference/v1/project-features/get-project-features" }, { text: "Update Project Features", - link: "/api-reference/project-features/update-project-features", + link: "/api-reference/v1/project-features/update-project-features", }, ], }, @@ -543,114 +543,129 @@ export default extendConfig( text: "Project Labels", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/project-labels/overview" }, - { text: "Create Project Label", link: "/api-reference/project-labels/add-project-label" }, - { text: "List Project Labels", link: "/api-reference/project-labels/list-project-labels" }, + { text: "Overview", link: "/api-reference/v1/project-labels/overview" }, + { text: "Create Project Label", link: "/api-reference/v1/project-labels/add-project-label" }, + { text: "List Project Labels", link: "/api-reference/v1/project-labels/list-project-labels" }, { text: "Get Project Label", - link: "/api-reference/project-labels/get-project-label-detail", + link: "/api-reference/v1/project-labels/get-project-label-detail", }, { text: "Update Project Label", - link: "/api-reference/project-labels/update-project-label-detail", + link: "/api-reference/v1/project-labels/update-project-label-detail", }, - { text: "Delete Project Label", link: "/api-reference/project-labels/delete-project-label" }, + { text: "Delete Project Label", link: "/api-reference/v1/project-labels/delete-project-label" }, ], }, { text: "Work Item", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/issue/overview" }, - { text: "Create Work Item", link: "/api-reference/issue/add-issue" }, - { text: "List Work Items", link: "/api-reference/issue/list-issues" }, - { text: "Get Work Item", link: "/api-reference/issue/get-issue-detail" }, - { text: "Get by identifier", link: "/api-reference/issue/get-issue-sequence-id" }, - { text: "Search Work Items", link: "/api-reference/issue/search-issues" }, - { text: "Advanced Search", link: "/api-reference/issue/advanced-search-work-items" }, - { text: "Update Work Item", link: "/api-reference/issue/update-issue-detail" }, - { text: "Delete Work Item", link: "/api-reference/issue/delete-issue" }, + { text: "Overview", link: "/api-reference/v1/issue/overview" }, + { text: "Create Work Item", link: "/api-reference/v1/issue/add-issue" }, + { text: "List Work Items", link: "/api-reference/v1/issue/list-issues" }, + { text: "Get Work Item", link: "/api-reference/v1/issue/get-issue-detail" }, + { text: "Get by identifier", link: "/api-reference/v1/issue/get-issue-sequence-id" }, + { text: "Search Work Items", link: "/api-reference/v1/issue/search-issues" }, + { text: "Advanced Search", link: "/api-reference/v1/issue/advanced-search-work-items" }, + { text: "Update Work Item", link: "/api-reference/v1/issue/update-issue-detail" }, + { text: "Delete Work Item", link: "/api-reference/v1/issue/delete-issue" }, ], }, { text: "Work Item States", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/state/overview" }, - { text: "Create State", link: "/api-reference/state/add-state" }, - { text: "List States", link: "/api-reference/state/list-states" }, - { text: "Get State", link: "/api-reference/state/get-state-detail" }, - { text: "Update State", link: "/api-reference/state/update-state-detail" }, - { text: "Delete State", link: "/api-reference/state/delete-state" }, + { text: "Overview", link: "/api-reference/v1/state/overview" }, + { text: "Create State", link: "/api-reference/v1/state/add-state" }, + { text: "List States", link: "/api-reference/v1/state/list-states" }, + { text: "Get State", link: "/api-reference/v1/state/get-state-detail" }, + { text: "Update State", link: "/api-reference/v1/state/update-state-detail" }, + { text: "Delete State", link: "/api-reference/v1/state/delete-state" }, ], }, { text: "Work Item Labels", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/label/overview" }, - { text: "Create Label", link: "/api-reference/label/add-label" }, - { text: "List Labels", link: "/api-reference/label/list-labels" }, - { text: "Get Label", link: "/api-reference/label/get-label-detail" }, - { text: "Update Label", link: "/api-reference/label/update-label-detail" }, - { text: "Delete Label", link: "/api-reference/label/delete-label" }, + { text: "Overview", link: "/api-reference/v1/label/overview" }, + { text: "Create Label", link: "/api-reference/v1/label/add-label" }, + { text: "List Labels", link: "/api-reference/v1/label/list-labels" }, + { text: "Get Label", link: "/api-reference/v1/label/get-label-detail" }, + { text: "Update Label", link: "/api-reference/v1/label/update-label-detail" }, + { text: "Delete Label", link: "/api-reference/v1/label/delete-label" }, ], }, { text: "Work Item Types", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/issue-types/types/overview" }, - { text: "Add Type", link: "/api-reference/issue-types/types/add-issue-type" }, - { text: "List Types", link: "/api-reference/issue-types/types/list-issue-types" }, - { text: "Get Type Details", link: "/api-reference/issue-types/types/get-issue-type-details" }, - { text: "Get Type Schema", link: "/api-reference/issue-types/types/get-work-item-type-schema" }, - { text: "Update Type", link: "/api-reference/issue-types/types/update-issue-types" }, - { text: "Delete Type", link: "/api-reference/issue-types/types/delete-issue-type" }, + { text: "Overview", link: "/api-reference/v1/issue-types/types/overview" }, + { text: "Add Type", link: "/api-reference/v1/issue-types/types/add-issue-type" }, + { text: "List Types", link: "/api-reference/v1/issue-types/types/list-issue-types" }, + { text: "Get Type Details", link: "/api-reference/v1/issue-types/types/get-issue-type-details" }, + { text: "Get Type Schema", link: "/api-reference/v1/issue-types/types/get-work-item-type-schema" }, + { text: "Update Type", link: "/api-reference/v1/issue-types/types/update-issue-types" }, + { text: "Delete Type", link: "/api-reference/v1/issue-types/types/delete-issue-type" }, ], }, { text: "Custom Properties", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/issue-types/properties/overview" }, - { text: "Add Property", link: "/api-reference/issue-types/properties/add-property" }, - { text: "List Properties", link: "/api-reference/issue-types/properties/list-properties" }, + { text: "Overview", link: "/api-reference/v1/issue-types/properties/overview" }, + { text: "Add Property", link: "/api-reference/v1/issue-types/properties/add-property" }, + { text: "List Properties", link: "/api-reference/v1/issue-types/properties/list-properties" }, { text: "Get Property Details", - link: "/api-reference/issue-types/properties/get-property-details", + link: "/api-reference/v1/issue-types/properties/get-property-details", }, - { text: "Update Property", link: "/api-reference/issue-types/properties/update-property" }, - { text: "Delete Property", link: "/api-reference/issue-types/properties/delete-property" }, + { text: "Update Property", link: "/api-reference/v1/issue-types/properties/update-property" }, + { text: "Delete Property", link: "/api-reference/v1/issue-types/properties/delete-property" }, ], }, { text: "Custom Property Values", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/issue-types/values/overview" }, - { text: "Add Property Values", link: "/api-reference/issue-types/values/add-property-values" }, - { text: "List Property Values", link: "/api-reference/issue-types/values/list-property-values" }, - { text: "Get Property Value", link: "/api-reference/issue-types/values/get-property-value-detail" }, - { text: "Update Property Value", link: "/api-reference/issue-types/values/update-property-value" }, - { text: "Delete Property Value", link: "/api-reference/issue-types/values/delete-property-value" }, + { text: "Overview", link: "/api-reference/v1/issue-types/values/overview" }, + { text: "Add Property Values", link: "/api-reference/v1/issue-types/values/add-property-values" }, + { text: "List Property Values", link: "/api-reference/v1/issue-types/values/list-property-values" }, + { + text: "Get Property Value", + link: "/api-reference/v1/issue-types/values/get-property-value-detail", + }, + { + text: "Update Property Value", + link: "/api-reference/v1/issue-types/values/update-property-value", + }, + { + text: "Delete Property Value", + link: "/api-reference/v1/issue-types/values/delete-property-value", + }, ], }, { text: "Custom Property Options", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/issue-types/options/overview" }, - { text: "Add Dropdown Options", link: "/api-reference/issue-types/options/add-dropdown-options" }, - { text: "List Dropdown Options", link: "/api-reference/issue-types/options/list-dropdown-options" }, - { text: "Get Option Details", link: "/api-reference/issue-types/options/get-option-details" }, + { text: "Overview", link: "/api-reference/v1/issue-types/options/overview" }, + { + text: "Add Dropdown Options", + link: "/api-reference/v1/issue-types/options/add-dropdown-options", + }, + { + text: "List Dropdown Options", + link: "/api-reference/v1/issue-types/options/list-dropdown-options", + }, + { text: "Get Option Details", link: "/api-reference/v1/issue-types/options/get-option-details" }, { text: "Update Dropdown Options", - link: "/api-reference/issue-types/options/update-dropdown-options", + link: "/api-reference/v1/issue-types/options/update-dropdown-options", }, { text: "Delete Dropdown Options", - link: "/api-reference/issue-types/options/delete-dropdown-options", + link: "/api-reference/v1/issue-types/options/delete-dropdown-options", }, ], }, @@ -658,63 +673,72 @@ export default extendConfig( text: "Work Item Links", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/link/overview" }, - { text: "Add Link", link: "/api-reference/link/add-link" }, - { text: "List Links", link: "/api-reference/link/list-links" }, - { text: "Get Link", link: "/api-reference/link/get-link-detail" }, - { text: "Update Link", link: "/api-reference/link/update-link-detail" }, - { text: "Delete Link", link: "/api-reference/link/delete-link" }, + { text: "Overview", link: "/api-reference/v1/link/overview" }, + { text: "Add Link", link: "/api-reference/v1/link/add-link" }, + { text: "List Links", link: "/api-reference/v1/link/list-links" }, + { text: "Get Link", link: "/api-reference/v1/link/get-link-detail" }, + { text: "Update Link", link: "/api-reference/v1/link/update-link-detail" }, + { text: "Delete Link", link: "/api-reference/v1/link/delete-link" }, ], }, { text: "Work Item Activity", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/issue-activity/overview" }, - { text: "List Activities", link: "/api-reference/issue-activity/list-issue-activities" }, - { text: "Get Activity", link: "/api-reference/issue-activity/get-issue-activity-detail" }, + { text: "Overview", link: "/api-reference/v1/issue-activity/overview" }, + { text: "List Activities", link: "/api-reference/v1/issue-activity/list-issue-activities" }, + { text: "Get Activity", link: "/api-reference/v1/issue-activity/get-issue-activity-detail" }, ], }, { text: "Work Item Comments", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/issue-comment/overview" }, - { text: "Add Comment", link: "/api-reference/issue-comment/add-issue-comment" }, - { text: "List Comments", link: "/api-reference/issue-comment/list-issue-comments" }, - { text: "Get Comment", link: "/api-reference/issue-comment/get-issue-comment-detail" }, - { text: "Update Comment", link: "/api-reference/issue-comment/update-issue-comment-detail" }, - { text: "Delete Comment", link: "/api-reference/issue-comment/delete-issue-comment" }, + { text: "Overview", link: "/api-reference/v1/issue-comment/overview" }, + { text: "Add Comment", link: "/api-reference/v1/issue-comment/add-issue-comment" }, + { text: "List Comments", link: "/api-reference/v1/issue-comment/list-issue-comments" }, + { text: "Get Comment", link: "/api-reference/v1/issue-comment/get-issue-comment-detail" }, + { text: "Update Comment", link: "/api-reference/v1/issue-comment/update-issue-comment-detail" }, + { text: "Delete Comment", link: "/api-reference/v1/issue-comment/delete-issue-comment" }, ], }, { text: "Work Item Attachments", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/issue-attachments/overview" }, - { text: "Get Attachments", link: "/api-reference/issue-attachments/get-attachments" }, - { text: "Get Attachment Detail", link: "/api-reference/issue-attachments/get-attachment-detail" }, - { text: "Get Upload Credentials", link: "/api-reference/issue-attachments/get-upload-credentials" }, - { text: "Upload File", link: "/api-reference/issue-attachments/upload-file" }, - { text: "Complete Upload", link: "/api-reference/issue-attachments/complete-upload" }, - { text: "Update Attachment", link: "/api-reference/issue-attachments/update-attachment" }, - { text: "Delete Attachment", link: "/api-reference/issue-attachments/delete-attachment" }, + { text: "Overview", link: "/api-reference/v1/issue-attachments/overview" }, + { text: "Get Attachments", link: "/api-reference/v1/issue-attachments/get-attachments" }, + { + text: "Get Attachment Detail", + link: "/api-reference/v1/issue-attachments/get-attachment-detail", + }, + { + text: "Get Upload Credentials", + link: "/api-reference/v1/issue-attachments/get-upload-credentials", + }, + { text: "Upload File", link: "/api-reference/v1/issue-attachments/upload-file" }, + { text: "Complete Upload", link: "/api-reference/v1/issue-attachments/complete-upload" }, + { text: "Update Attachment", link: "/api-reference/v1/issue-attachments/update-attachment" }, + { text: "Delete Attachment", link: "/api-reference/v1/issue-attachments/delete-attachment" }, ], }, { text: "Work Item Page Links", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/work-item-pages/overview" }, - { text: "Create Work Item Page Link", link: "/api-reference/work-item-pages/add-work-item-page" }, - { text: "List Work Item Pages", link: "/api-reference/work-item-pages/list-work-item-pages" }, + { text: "Overview", link: "/api-reference/v1/work-item-pages/overview" }, + { + text: "Create Work Item Page Link", + link: "/api-reference/v1/work-item-pages/add-work-item-page", + }, + { text: "List Work Item Pages", link: "/api-reference/v1/work-item-pages/list-work-item-pages" }, { text: "Get Work Item Page Link", - link: "/api-reference/work-item-pages/get-work-item-page-detail", + link: "/api-reference/v1/work-item-pages/get-work-item-page-detail", }, { text: "Delete Work Item Page Link", - link: "/api-reference/work-item-pages/delete-work-item-page", + link: "/api-reference/v1/work-item-pages/delete-work-item-page", }, ], }, @@ -723,275 +747,281 @@ export default extendConfig( text: "Cycles", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/cycle/overview" }, - { text: "Create Cycle", link: "/api-reference/cycle/add-cycle" }, - { text: "Add Work Items", link: "/api-reference/cycle/add-cycle-work-items" }, - { text: "Transfer Work Items", link: "/api-reference/cycle/transfer-cycle-work-items" }, - { text: "Archive Cycle", link: "/api-reference/cycle/archive-cycle" }, - { text: "List Cycles", link: "/api-reference/cycle/list-cycles" }, - { text: "Get Cycle", link: "/api-reference/cycle/get-cycle-detail" }, - { text: "List Cycle Work Items", link: "/api-reference/cycle/list-cycle-work-items" }, - { text: "List Archived Cycles", link: "/api-reference/cycle/list-archived-cycles" }, - { text: "Update Cycle", link: "/api-reference/cycle/update-cycle-detail" }, - { text: "Unarchive Cycle", link: "/api-reference/cycle/unarchive-cycle" }, - { text: "Remove Work Item", link: "/api-reference/cycle/remove-cycle-work-item" }, - { text: "Delete Cycle", link: "/api-reference/cycle/delete-cycle" }, + { text: "Overview", link: "/api-reference/v1/cycle/overview" }, + { text: "Create Cycle", link: "/api-reference/v1/cycle/add-cycle" }, + { text: "Add Work Items", link: "/api-reference/v1/cycle/add-cycle-work-items" }, + { text: "Transfer Work Items", link: "/api-reference/v1/cycle/transfer-cycle-work-items" }, + { text: "Archive Cycle", link: "/api-reference/v1/cycle/archive-cycle" }, + { text: "List Cycles", link: "/api-reference/v1/cycle/list-cycles" }, + { text: "Get Cycle", link: "/api-reference/v1/cycle/get-cycle-detail" }, + { text: "List Cycle Work Items", link: "/api-reference/v1/cycle/list-cycle-work-items" }, + { text: "List Archived Cycles", link: "/api-reference/v1/cycle/list-archived-cycles" }, + { text: "Update Cycle", link: "/api-reference/v1/cycle/update-cycle-detail" }, + { text: "Unarchive Cycle", link: "/api-reference/v1/cycle/unarchive-cycle" }, + { text: "Remove Work Item", link: "/api-reference/v1/cycle/remove-cycle-work-item" }, + { text: "Delete Cycle", link: "/api-reference/v1/cycle/delete-cycle" }, ], }, { text: "Modules", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/module/overview" }, - { text: "Create Module", link: "/api-reference/module/add-module" }, - { text: "Add Work Items", link: "/api-reference/module/add-module-work-items" }, - { text: "Archive Module", link: "/api-reference/module/archive-module" }, - { text: "List Modules", link: "/api-reference/module/list-modules" }, - { text: "Get Module", link: "/api-reference/module/get-module-detail" }, - { text: "List Module Work Items", link: "/api-reference/module/list-module-work-items" }, - { text: "List Archived Modules", link: "/api-reference/module/list-archived-modules" }, - { text: "Update Module", link: "/api-reference/module/update-module-detail" }, - { text: "Unarchive Module", link: "/api-reference/module/unarchive-module" }, - { text: "Remove Work Item", link: "/api-reference/module/remove-module-work-item" }, - { text: "Delete Module", link: "/api-reference/module/delete-module" }, + { text: "Overview", link: "/api-reference/v1/module/overview" }, + { text: "Create Module", link: "/api-reference/v1/module/add-module" }, + { text: "Add Work Items", link: "/api-reference/v1/module/add-module-work-items" }, + { text: "Archive Module", link: "/api-reference/v1/module/archive-module" }, + { text: "List Modules", link: "/api-reference/v1/module/list-modules" }, + { text: "Get Module", link: "/api-reference/v1/module/get-module-detail" }, + { text: "List Module Work Items", link: "/api-reference/v1/module/list-module-work-items" }, + { text: "List Archived Modules", link: "/api-reference/v1/module/list-archived-modules" }, + { text: "Update Module", link: "/api-reference/v1/module/update-module-detail" }, + { text: "Unarchive Module", link: "/api-reference/v1/module/unarchive-module" }, + { text: "Remove Work Item", link: "/api-reference/v1/module/remove-module-work-item" }, + { text: "Delete Module", link: "/api-reference/v1/module/delete-module" }, ], }, { text: "Pages", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/page/overview" }, - { text: "List Workspace Pages", link: "/api-reference/page/list-workspace-pages" }, - { text: "Add Workspace Page", link: "/api-reference/page/add-workspace-page" }, - { text: "List Project Pages", link: "/api-reference/page/list-project-pages" }, - { text: "Add Project Page", link: "/api-reference/page/add-project-page" }, - { text: "Get Workspace Page", link: "/api-reference/page/get-workspace-page" }, - { text: "Get Project Page", link: "/api-reference/page/get-project-page" }, + { text: "Overview", link: "/api-reference/v1/page/overview" }, + { text: "List Workspace Pages", link: "/api-reference/v1/page/list-workspace-pages" }, + { text: "Add Workspace Page", link: "/api-reference/v1/page/add-workspace-page" }, + { text: "List Project Pages", link: "/api-reference/v1/page/list-project-pages" }, + { text: "Add Project Page", link: "/api-reference/v1/page/add-project-page" }, + { text: "Get Workspace Page", link: "/api-reference/v1/page/get-workspace-page" }, + { text: "Get Project Page", link: "/api-reference/v1/page/get-project-page" }, ], }, { text: "Intake", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/intake-issue/overview" }, - { text: "Add Intake Issue", link: "/api-reference/intake-issue/add-intake-issue" }, - { text: "List Intake Issues", link: "/api-reference/intake-issue/list-intake-issues" }, - { text: "Get Intake Issue", link: "/api-reference/intake-issue/get-intake-issue-detail" }, - { text: "Update Intake Issue", link: "/api-reference/intake-issue/update-intake-issue-detail" }, - { text: "Delete Intake Issue", link: "/api-reference/intake-issue/delete-intake-issue" }, + { text: "Overview", link: "/api-reference/v1/intake-issue/overview" }, + { text: "Add Intake Issue", link: "/api-reference/v1/intake-issue/add-intake-issue" }, + { text: "List Intake Issues", link: "/api-reference/v1/intake-issue/list-intake-issues" }, + { text: "Get Intake Issue", link: "/api-reference/v1/intake-issue/get-intake-issue-detail" }, + { text: "Update Intake Issue", link: "/api-reference/v1/intake-issue/update-intake-issue-detail" }, + { text: "Delete Intake Issue", link: "/api-reference/v1/intake-issue/delete-intake-issue" }, ], }, { text: "Assets", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/assets/overview" }, - { text: "Create User Asset Upload", link: "/api-reference/assets/create-user-asset-upload" }, - { text: "Update User Asset", link: "/api-reference/assets/update-user-asset" }, - { text: "Delete User Asset", link: "/api-reference/assets/delete-user-asset" }, + { text: "Overview", link: "/api-reference/v1/assets/overview" }, + { text: "Create User Asset Upload", link: "/api-reference/v1/assets/create-user-asset-upload" }, + { text: "Update User Asset", link: "/api-reference/v1/assets/update-user-asset" }, + { text: "Delete User Asset", link: "/api-reference/v1/assets/delete-user-asset" }, { text: "Create Workspace Asset Upload", - link: "/api-reference/assets/create-workspace-asset-upload", + link: "/api-reference/v1/assets/create-workspace-asset-upload", }, - { text: "Get Workspace Asset", link: "/api-reference/assets/get-workspace-asset" }, - { text: "Update Workspace Asset", link: "/api-reference/assets/update-workspace-asset" }, + { text: "Get Workspace Asset", link: "/api-reference/v1/assets/get-workspace-asset" }, + { text: "Update Workspace Asset", link: "/api-reference/v1/assets/update-workspace-asset" }, ], }, { text: "Milestones", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/milestones/overview" }, - { text: "Create Milestone", link: "/api-reference/milestones/add-milestone" }, - { text: "List Milestones", link: "/api-reference/milestones/list-milestones" }, - { text: "Get Milestone", link: "/api-reference/milestones/get-milestone-detail" }, - { text: "List Milestone Work Items", link: "/api-reference/milestones/list-milestone-work-items" }, - { text: "Update Milestone", link: "/api-reference/milestones/update-milestone-detail" }, - { text: "Delete Milestone", link: "/api-reference/milestones/delete-milestone" }, + { text: "Overview", link: "/api-reference/v1/milestones/overview" }, + { text: "Create Milestone", link: "/api-reference/v1/milestones/add-milestone" }, + { text: "List Milestones", link: "/api-reference/v1/milestones/list-milestones" }, + { text: "Get Milestone", link: "/api-reference/v1/milestones/get-milestone-detail" }, + { + text: "List Milestone Work Items", + link: "/api-reference/v1/milestones/list-milestone-work-items", + }, + { text: "Update Milestone", link: "/api-reference/v1/milestones/update-milestone-detail" }, + { text: "Delete Milestone", link: "/api-reference/v1/milestones/delete-milestone" }, ], }, { text: "Estimates", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/estimate/overview" }, - { text: "Create Estimate", link: "/api-reference/estimate/add-estimate" }, - { text: "Get Estimate", link: "/api-reference/estimate/get-estimate" }, - { text: "Update Estimate", link: "/api-reference/estimate/update-estimate" }, - { text: "Delete Estimate", link: "/api-reference/estimate/delete-estimate" }, - { text: "List Estimate Points", link: "/api-reference/estimate/list-estimate-points" }, - { text: "Create Estimate Points", link: "/api-reference/estimate/add-estimate-points" }, - { text: "Update Estimate Point", link: "/api-reference/estimate/update-estimate-point" }, - { text: "Delete Estimate Point", link: "/api-reference/estimate/delete-estimate-point" }, + { text: "Overview", link: "/api-reference/v1/estimate/overview" }, + { text: "Create Estimate", link: "/api-reference/v1/estimate/add-estimate" }, + { text: "Get Estimate", link: "/api-reference/v1/estimate/get-estimate" }, + { text: "Update Estimate", link: "/api-reference/v1/estimate/update-estimate" }, + { text: "Delete Estimate", link: "/api-reference/v1/estimate/delete-estimate" }, + { text: "List Estimate Points", link: "/api-reference/v1/estimate/list-estimate-points" }, + { text: "Create Estimate Points", link: "/api-reference/v1/estimate/add-estimate-points" }, + { text: "Update Estimate Point", link: "/api-reference/v1/estimate/update-estimate-point" }, + { text: "Delete Estimate Point", link: "/api-reference/v1/estimate/delete-estimate-point" }, ], }, { text: "Time Tracking", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/worklogs/overview" }, - { text: "Create Worklog", link: "/api-reference/worklogs/create-worklog" }, - { text: "Get Worklogs for Issue", link: "/api-reference/worklogs/get-worklogs-for-issue" }, - { text: "Get Total Time", link: "/api-reference/worklogs/get-total-time" }, - { text: "Update Worklog", link: "/api-reference/worklogs/update-worklog" }, - { text: "Delete Worklog", link: "/api-reference/worklogs/delete-worklog" }, + { text: "Overview", link: "/api-reference/v1/worklogs/overview" }, + { text: "Create Worklog", link: "/api-reference/v1/worklogs/create-worklog" }, + { text: "Get Worklogs for Issue", link: "/api-reference/v1/worklogs/get-worklogs-for-issue" }, + { text: "Get Total Time", link: "/api-reference/v1/worklogs/get-total-time" }, + { text: "Update Worklog", link: "/api-reference/v1/worklogs/update-worklog" }, + { text: "Delete Worklog", link: "/api-reference/v1/worklogs/delete-worklog" }, ], }, { text: "Epics", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/epics/overview" }, - { text: "Create Epic", link: "/api-reference/epics/create-epic" }, - { text: "List Epics", link: "/api-reference/epics/list-epics" }, - { text: "Get Epic", link: "/api-reference/epics/get-epic-detail" }, - { text: "Update Epic", link: "/api-reference/epics/update-epic" }, - { text: "Delete Epic", link: "/api-reference/epics/delete-epic" }, - { text: "Add Epic Work Items", link: "/api-reference/epics/add-epic-work-items" }, - { text: "List Epic Work Items", link: "/api-reference/epics/list-epic-work-items" }, + { text: "Overview", link: "/api-reference/v1/epics/overview" }, + { text: "Create Epic", link: "/api-reference/v1/epics/create-epic" }, + { text: "List Epics", link: "/api-reference/v1/epics/list-epics" }, + { text: "Get Epic", link: "/api-reference/v1/epics/get-epic-detail" }, + { text: "Update Epic", link: "/api-reference/v1/epics/update-epic" }, + { text: "Delete Epic", link: "/api-reference/v1/epics/delete-epic" }, + { text: "Add Epic Work Items", link: "/api-reference/v1/epics/add-epic-work-items" }, + { text: "List Epic Work Items", link: "/api-reference/v1/epics/list-epic-work-items" }, ], }, { text: "Initiatives", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/initiative/overview" }, - { text: "Create Initiative", link: "/api-reference/initiative/add-initiative" }, - { text: "List Initiatives", link: "/api-reference/initiative/list-initiatives" }, - { text: "Get Initiative", link: "/api-reference/initiative/get-initiative-detail" }, - { text: "Update Initiative", link: "/api-reference/initiative/update-initiative-detail" }, - { text: "Delete Initiative", link: "/api-reference/initiative/delete-initiative" }, + { text: "Overview", link: "/api-reference/v1/initiative/overview" }, + { text: "Create Initiative", link: "/api-reference/v1/initiative/add-initiative" }, + { text: "List Initiatives", link: "/api-reference/v1/initiative/list-initiatives" }, + { text: "Get Initiative", link: "/api-reference/v1/initiative/get-initiative-detail" }, + { text: "Update Initiative", link: "/api-reference/v1/initiative/update-initiative-detail" }, + { text: "Delete Initiative", link: "/api-reference/v1/initiative/delete-initiative" }, ], }, { text: "Initiative Labels", collapsed: true, items: [ - { text: "Add Label", link: "/api-reference/initiative/add-initiative-label" }, - { text: "Add Labels to Initiative", link: "/api-reference/initiative/add-labels-to-initiative" }, - { text: "List Initiative Labels", link: "/api-reference/initiative/list-initiative-labels" }, - { text: "Get Label Detail", link: "/api-reference/initiative/get-initiative-label-detail" }, + { text: "Add Label", link: "/api-reference/v1/initiative/add-initiative-label" }, + { text: "Add Labels to Initiative", link: "/api-reference/v1/initiative/add-labels-to-initiative" }, + { text: "List Initiative Labels", link: "/api-reference/v1/initiative/list-initiative-labels" }, + { text: "Get Label Detail", link: "/api-reference/v1/initiative/get-initiative-label-detail" }, { text: "List Labels for Initiative", - link: "/api-reference/initiative/list-initiative-labels-for-initiative", + link: "/api-reference/v1/initiative/list-initiative-labels-for-initiative", }, - { text: "Update Label", link: "/api-reference/initiative/update-initiative-label-detail" }, - { text: "Remove Labels", link: "/api-reference/initiative/remove-labels-from-initiative" }, - { text: "Delete Label", link: "/api-reference/initiative/delete-initiative-label" }, + { text: "Update Label", link: "/api-reference/v1/initiative/update-initiative-label-detail" }, + { text: "Remove Labels", link: "/api-reference/v1/initiative/remove-labels-from-initiative" }, + { text: "Delete Label", link: "/api-reference/v1/initiative/delete-initiative-label" }, ], }, { text: "Initiative Projects", collapsed: true, items: [ - { text: "Add Projects", link: "/api-reference/initiative/add-projects-to-initiative" }, - { text: "List Projects", link: "/api-reference/initiative/list-initiative-projects" }, - { text: "Remove Projects", link: "/api-reference/initiative/remove-projects-from-initiative" }, + { text: "Add Projects", link: "/api-reference/v1/initiative/add-projects-to-initiative" }, + { text: "List Projects", link: "/api-reference/v1/initiative/list-initiative-projects" }, + { text: "Remove Projects", link: "/api-reference/v1/initiative/remove-projects-from-initiative" }, ], }, { text: "Initiative Epics", collapsed: true, items: [ - { text: "Add Epics", link: "/api-reference/initiative/add-epics-to-initiative" }, - { text: "List Epics", link: "/api-reference/initiative/list-initiative-epics" }, - { text: "Remove Epics", link: "/api-reference/initiative/remove-epics-from-initiative" }, + { text: "Add Epics", link: "/api-reference/v1/initiative/add-epics-to-initiative" }, + { text: "List Epics", link: "/api-reference/v1/initiative/list-initiative-epics" }, + { text: "Remove Epics", link: "/api-reference/v1/initiative/remove-epics-from-initiative" }, ], }, { text: "Customers", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/customer/overview" }, - { text: "Add Customer", link: "/api-reference/customer/add-customer" }, - { text: "Link Work Items", link: "/api-reference/customer/link-work-items-to-customer" }, - { text: "List Customers", link: "/api-reference/customer/list-customers" }, - { text: "Get Customer", link: "/api-reference/customer/get-customer-detail" }, - { text: "List Customer Work Items", link: "/api-reference/customer/list-customer-work-items" }, - { text: "Update Customer", link: "/api-reference/customer/update-customer-detail" }, - { text: "Unlink Work Item", link: "/api-reference/customer/unlink-work-item-from-customer" }, - { text: "Delete Customer", link: "/api-reference/customer/delete-customer" }, + { text: "Overview", link: "/api-reference/v1/customer/overview" }, + { text: "Add Customer", link: "/api-reference/v1/customer/add-customer" }, + { text: "Link Work Items", link: "/api-reference/v1/customer/link-work-items-to-customer" }, + { text: "List Customers", link: "/api-reference/v1/customer/list-customers" }, + { text: "Get Customer", link: "/api-reference/v1/customer/get-customer-detail" }, + { text: "List Customer Work Items", link: "/api-reference/v1/customer/list-customer-work-items" }, + { text: "Update Customer", link: "/api-reference/v1/customer/update-customer-detail" }, + { text: "Unlink Work Item", link: "/api-reference/v1/customer/unlink-work-item-from-customer" }, + { text: "Delete Customer", link: "/api-reference/v1/customer/delete-customer" }, ], }, { text: "Customer Properties", collapsed: true, items: [ - { text: "Add Property", link: "/api-reference/customer/add-customer-property" }, - { text: "List Properties", link: "/api-reference/customer/list-customer-properties" }, - { text: "Get Property Detail", link: "/api-reference/customer/get-customer-property-detail" }, - { text: "List Property Values", link: "/api-reference/customer/list-customer-property-values" }, - { text: "Get Property Value", link: "/api-reference/customer/get-customer-property-value" }, - { text: "Update Property", link: "/api-reference/customer/update-customer-property-detail" }, - { text: "Update Property Value", link: "/api-reference/customer/update-customer-property-value" }, - { text: "Delete Property", link: "/api-reference/customer/delete-customer-property" }, + { text: "Add Property", link: "/api-reference/v1/customer/add-customer-property" }, + { text: "List Properties", link: "/api-reference/v1/customer/list-customer-properties" }, + { text: "Get Property Detail", link: "/api-reference/v1/customer/get-customer-property-detail" }, + { text: "List Property Values", link: "/api-reference/v1/customer/list-customer-property-values" }, + { text: "Get Property Value", link: "/api-reference/v1/customer/get-customer-property-value" }, + { text: "Update Property", link: "/api-reference/v1/customer/update-customer-property-detail" }, + { + text: "Update Property Value", + link: "/api-reference/v1/customer/update-customer-property-value", + }, + { text: "Delete Property", link: "/api-reference/v1/customer/delete-customer-property" }, ], }, { text: "Customer Requests", collapsed: true, items: [ - { text: "Add Request", link: "/api-reference/customer/add-customer-request" }, - { text: "List Requests", link: "/api-reference/customer/list-customer-requests" }, - { text: "Get Request Detail", link: "/api-reference/customer/get-customer-request-detail" }, - { text: "Update Request", link: "/api-reference/customer/update-customer-request-detail" }, - { text: "Delete Request", link: "/api-reference/customer/delete-customer-request" }, + { text: "Add Request", link: "/api-reference/v1/customer/add-customer-request" }, + { text: "List Requests", link: "/api-reference/v1/customer/list-customer-requests" }, + { text: "Get Request Detail", link: "/api-reference/v1/customer/get-customer-request-detail" }, + { text: "Update Request", link: "/api-reference/v1/customer/update-customer-request-detail" }, + { text: "Delete Request", link: "/api-reference/v1/customer/delete-customer-request" }, ], }, { text: "Teamspaces", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/teamspace/overview" }, - { text: "Create Teamspace", link: "/api-reference/teamspace/add-teamspace" }, - { text: "List Teamspaces", link: "/api-reference/teamspace/list-teamspaces" }, - { text: "Get Teamspace", link: "/api-reference/teamspace/get-teamspace-detail" }, - { text: "Update Teamspace", link: "/api-reference/teamspace/update-teamspace-detail" }, - { text: "Delete Teamspace", link: "/api-reference/teamspace/delete-teamspace" }, + { text: "Overview", link: "/api-reference/v1/teamspace/overview" }, + { text: "Create Teamspace", link: "/api-reference/v1/teamspace/add-teamspace" }, + { text: "List Teamspaces", link: "/api-reference/v1/teamspace/list-teamspaces" }, + { text: "Get Teamspace", link: "/api-reference/v1/teamspace/get-teamspace-detail" }, + { text: "Update Teamspace", link: "/api-reference/v1/teamspace/update-teamspace-detail" }, + { text: "Delete Teamspace", link: "/api-reference/v1/teamspace/delete-teamspace" }, ], }, { text: "Teamspace Members", collapsed: true, items: [ - { text: "List Members", link: "/api-reference/teamspace/list-teamspace-members" }, - { text: "Add Members", link: "/api-reference/teamspace/add-teamspace-members" }, - { text: "Remove Members", link: "/api-reference/teamspace/remove-teamspace-members" }, + { text: "List Members", link: "/api-reference/v1/teamspace/list-teamspace-members" }, + { text: "Add Members", link: "/api-reference/v1/teamspace/add-teamspace-members" }, + { text: "Remove Members", link: "/api-reference/v1/teamspace/remove-teamspace-members" }, ], }, { text: "Teamspace Projects", collapsed: true, items: [ - { text: "List Projects", link: "/api-reference/teamspace/list-teamspace-projects" }, - { text: "Add Projects", link: "/api-reference/teamspace/add-projects-to-teamspace" }, - { text: "Remove Projects", link: "/api-reference/teamspace/remove-projects-from-teamspace" }, + { text: "List Projects", link: "/api-reference/v1/teamspace/list-teamspace-projects" }, + { text: "Add Projects", link: "/api-reference/v1/teamspace/add-projects-to-teamspace" }, + { text: "Remove Projects", link: "/api-reference/v1/teamspace/remove-projects-from-teamspace" }, ], }, { text: "Stickies", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/sticky/overview" }, - { text: "Add Sticky", link: "/api-reference/sticky/add-sticky" }, - { text: "List Stickies", link: "/api-reference/sticky/list-stickies" }, - { text: "Get Sticky", link: "/api-reference/sticky/get-sticky-detail" }, - { text: "Update Sticky", link: "/api-reference/sticky/update-sticky-detail" }, - { text: "Delete Sticky", link: "/api-reference/sticky/delete-sticky" }, + { text: "Overview", link: "/api-reference/v1/sticky/overview" }, + { text: "Add Sticky", link: "/api-reference/v1/sticky/add-sticky" }, + { text: "List Stickies", link: "/api-reference/v1/sticky/list-stickies" }, + { text: "Get Sticky", link: "/api-reference/v1/sticky/get-sticky-detail" }, + { text: "Update Sticky", link: "/api-reference/v1/sticky/update-sticky-detail" }, + { text: "Delete Sticky", link: "/api-reference/v1/sticky/delete-sticky" }, ], }, { text: "Workspace Features", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/workspace-features/overview" }, + { text: "Overview", link: "/api-reference/v1/workspace-features/overview" }, { text: "Get Workspace Features", - link: "/api-reference/workspace-features/get-workspace-features", + link: "/api-reference/v1/workspace-features/get-workspace-features", }, { text: "Update Workspace Features", - link: "/api-reference/workspace-features/update-workspace-features", + link: "/api-reference/v1/workspace-features/update-workspace-features", }, ], }, @@ -999,26 +1029,26 @@ export default extendConfig( text: "Workspace Invitations", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/workspace-invitations/overview" }, + { text: "Overview", link: "/api-reference/v1/workspace-invitations/overview" }, { text: "Create Workspace Invitation", - link: "/api-reference/workspace-invitations/add-workspace-invitation", + link: "/api-reference/v1/workspace-invitations/add-workspace-invitation", }, { text: "List Workspace Invitations", - link: "/api-reference/workspace-invitations/list-workspace-invitations", + link: "/api-reference/v1/workspace-invitations/list-workspace-invitations", }, { text: "Get Workspace Invitation", - link: "/api-reference/workspace-invitations/get-workspace-invitation-detail", + link: "/api-reference/v1/workspace-invitations/get-workspace-invitation-detail", }, { text: "Update Workspace Invitation", - link: "/api-reference/workspace-invitations/update-workspace-invitation", + link: "/api-reference/v1/workspace-invitations/update-workspace-invitation", }, { text: "Delete Workspace Invitation", - link: "/api-reference/workspace-invitations/delete-workspace-invitation", + link: "/api-reference/v1/workspace-invitations/delete-workspace-invitation", }, ], }, @@ -1026,59 +1056,1526 @@ export default extendConfig( text: "Members", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/members/overview" }, - { text: "Get Workspace Members", link: "/api-reference/members/get-workspace-members" }, - { text: "Get Project Members", link: "/api-reference/members/get-project-members" }, - { text: "Create Project Member", link: "/api-reference/members/add-project-member" }, - { text: "Get Project Member", link: "/api-reference/members/get-project-member-detail" }, - { text: "Update Project Member", link: "/api-reference/members/update-project-member" }, - { text: "Delete Project Member", link: "/api-reference/members/delete-project-member" }, - { text: "Remove Workspace Members", link: "/api-reference/members/remove-workspace-member" }, + { text: "Overview", link: "/api-reference/v1/members/overview" }, + { text: "Get Workspace Members", link: "/api-reference/v1/members/get-workspace-members" }, + { text: "Get Project Members", link: "/api-reference/v1/members/get-project-members" }, + { text: "Create Project Member", link: "/api-reference/v1/members/add-project-member" }, + { text: "Get Project Member", link: "/api-reference/v1/members/get-project-member-detail" }, + { text: "Update Project Member", link: "/api-reference/v1/members/update-project-member" }, + { text: "Delete Project Member", link: "/api-reference/v1/members/delete-project-member" }, + { text: "Remove Workspace Members", link: "/api-reference/v1/members/remove-workspace-member" }, ], }, { text: "User", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/user/overview" }, - { text: "Get Current User", link: "/api-reference/user/get-current-user" }, + { text: "Overview", link: "/api-reference/v1/user/overview" }, + { text: "Get Current User", link: "/api-reference/v1/user/get-current-user" }, ], }, { text: "IDP Group Sync", collapsed: true, items: [ - { text: "Overview", link: "/api-reference/idp-group-sync/overview" }, - { text: "Get Group Sync Config", link: "/api-reference/idp-group-sync/get-group-sync-config" }, + { text: "Overview", link: "/api-reference/v1/idp-group-sync/overview" }, + { text: "Get Group Sync Config", link: "/api-reference/v1/idp-group-sync/get-group-sync-config" }, { text: "Update Group Sync Config", - link: "/api-reference/idp-group-sync/update-group-sync-config", + link: "/api-reference/v1/idp-group-sync/update-group-sync-config", }, - { text: "List Project Mappings", link: "/api-reference/idp-group-sync/list-project-mappings" }, - { text: "Create Project Mapping", link: "/api-reference/idp-group-sync/create-project-mapping" }, - { text: "Get Project Mapping", link: "/api-reference/idp-group-sync/get-project-mapping" }, - { text: "Update Project Mapping", link: "/api-reference/idp-group-sync/update-project-mapping" }, - { text: "Delete Project Mapping", link: "/api-reference/idp-group-sync/delete-project-mapping" }, + { text: "List Project Mappings", link: "/api-reference/v1/idp-group-sync/list-project-mappings" }, + { text: "Create Project Mapping", link: "/api-reference/v1/idp-group-sync/create-project-mapping" }, + { text: "Get Project Mapping", link: "/api-reference/v1/idp-group-sync/get-project-mapping" }, + { text: "Update Project Mapping", link: "/api-reference/v1/idp-group-sync/update-project-mapping" }, + { text: "Delete Project Mapping", link: "/api-reference/v1/idp-group-sync/delete-project-mapping" }, { text: "List Workspace Mappings", - link: "/api-reference/idp-group-sync/list-workspace-mappings", + link: "/api-reference/v1/idp-group-sync/list-workspace-mappings", }, { text: "Create Workspace Mapping", - link: "/api-reference/idp-group-sync/create-workspace-mapping", + link: "/api-reference/v1/idp-group-sync/create-workspace-mapping", }, { text: "Get Workspace Mapping", - link: "/api-reference/idp-group-sync/get-workspace-mapping", + link: "/api-reference/v1/idp-group-sync/get-workspace-mapping", }, { text: "Update Workspace Mapping", - link: "/api-reference/idp-group-sync/update-workspace-mapping", + link: "/api-reference/v1/idp-group-sync/update-workspace-mapping", }, { text: "Delete Workspace Mapping", - link: "/api-reference/idp-group-sync/delete-workspace-mapping", + link: "/api-reference/v1/idp-group-sync/delete-workspace-mapping", + }, + ], + }, + ], + }, + ], + + "/api-reference/v2/": [ + { + text: "API Reference", + items: [ + { text: "Introduction", link: "/api-reference/v2/introduction" }, + { text: "Authentication", link: "/api-reference/v2/authentication" }, + { text: "Pagination", link: "/api-reference/v2/pagination" }, + { text: "Filtering & Ordering", link: "/api-reference/v2/filtering-and-ordering" }, + { text: "Sparse Fields", link: "/api-reference/v2/sparse-fields" }, + { text: "Expanding Relations", link: "/api-reference/v2/expanding-relations" }, + { text: "Errors", link: "/api-reference/v2/errors" }, + { text: "Work Item Type Modes", link: "/api-reference/v2/work-item-type-modes" }, + { text: "Migrating from v1", link: "/api-reference/v2/migrating-from-v1" }, + ], + }, + { + text: "Core Resources", + items: [ + { + text: "Work Items", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-items/overview" }, + { text: "List work items", link: "/api-reference/v2/work-items/list-work-items" }, + { + text: "List work items across a workspace", + link: "/api-reference/v2/work-items/list-workspace-work-items", + }, + { text: "Get a work item", link: "/api-reference/v2/work-items/get-work-item" }, + { + text: "Get a work item by identifier", + link: "/api-reference/v2/work-items/get-work-item-by-identifier", + }, + { text: "Create a work item", link: "/api-reference/v2/work-items/create-work-item" }, + { text: "Update a work item", link: "/api-reference/v2/work-items/update-work-item" }, + { text: "Delete a work item", link: "/api-reference/v2/work-items/delete-work-item" }, + { text: "Upsert a work item", link: "/api-reference/v2/work-items/upsert-work-item" }, + { text: "Bulk write work items", link: "/api-reference/v2/work-items/bulk-work-items" }, + { text: "Archive a work item", link: "/api-reference/v2/work-items/archive-work-item" }, + { text: "Unarchive a work item", link: "/api-reference/v2/work-items/unarchive-work-item" }, + ], + }, + { + text: "Comments", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-comments/overview" }, + { text: "List comments", link: "/api-reference/v2/work-item-comments/list-comments" }, + { text: "Get a comment", link: "/api-reference/v2/work-item-comments/get-comment" }, + { text: "Create a comment", link: "/api-reference/v2/work-item-comments/create-comment" }, + { text: "Update a comment", link: "/api-reference/v2/work-item-comments/update-comment" }, + { text: "Delete a comment", link: "/api-reference/v2/work-item-comments/delete-comment" }, + { text: "Upsert a comment", link: "/api-reference/v2/work-item-comments/upsert-comment" }, + { text: "Bulk write comments", link: "/api-reference/v2/work-item-comments/bulk-comments" }, + ], + }, + { + text: "Attachments", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-attachments/overview" }, + { text: "List attachments", link: "/api-reference/v2/work-item-attachments/list-attachments" }, + { text: "Get a attachment", link: "/api-reference/v2/work-item-attachments/get-attachment" }, + { + text: "Create a work item attachment upload", + link: "/api-reference/v2/work-item-attachments/create-attachment-upload", + }, + { text: "Delete a attachment", link: "/api-reference/v2/work-item-attachments/delete-attachment" }, + { + text: "Confirm a work item attachment upload", + link: "/api-reference/v2/work-item-attachments/confirm-attachment-upload", + }, + ], + }, + { + text: "Links", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-links/overview" }, + { text: "List links", link: "/api-reference/v2/work-item-links/list-links" }, + { text: "Get a link", link: "/api-reference/v2/work-item-links/get-link" }, + { text: "Create a link", link: "/api-reference/v2/work-item-links/create-link" }, + { text: "Update a link", link: "/api-reference/v2/work-item-links/update-link" }, + { text: "Delete a link", link: "/api-reference/v2/work-item-links/delete-link" }, + ], + }, + { + text: "Activities", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-activities/overview" }, + { text: "List activities", link: "/api-reference/v2/work-item-activities/list-activities" }, + { text: "Get a activity", link: "/api-reference/v2/work-item-activities/get-activity" }, + ], + }, + { + text: "Worklogs", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-worklogs/overview" }, + { text: "List worklogs", link: "/api-reference/v2/work-item-worklogs/list-worklogs" }, + { + text: "Get the project worklog summary", + link: "/api-reference/v2/work-item-worklogs/get-project-worklog-summary", + }, + { text: "Get a worklog", link: "/api-reference/v2/work-item-worklogs/get-worklog" }, + { text: "Create a worklog", link: "/api-reference/v2/work-item-worklogs/create-worklog" }, + { text: "Update a worklog", link: "/api-reference/v2/work-item-worklogs/update-worklog" }, + { text: "Delete a worklog", link: "/api-reference/v2/work-item-worklogs/delete-worklog" }, + ], + }, + { + text: "Relations", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-relations/overview" }, + { + text: "List work item relations", + link: "/api-reference/v2/work-item-relations/list-work-item-relations", + }, + { + text: "Create work item relations", + link: "/api-reference/v2/work-item-relations/create-work-item-relations", + }, + { + text: "Delete a work item relation", + link: "/api-reference/v2/work-item-relations/delete-work-item-relation", + }, + ], + }, + { + text: "Dependencies", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-dependencies/overview" }, + { + text: "List work item dependencies", + link: "/api-reference/v2/work-item-dependencies/list-work-item-dependencies", + }, + { + text: "Create work item dependencies", + link: "/api-reference/v2/work-item-dependencies/create-work-item-dependencies", + }, + { + text: "Delete a work item dependency", + link: "/api-reference/v2/work-item-dependencies/delete-work-item-dependency", + }, + ], + }, + { + text: "Relation Definitions", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-relation-definitions/overview" }, + { + text: "List relation definitions", + link: "/api-reference/v2/work-item-relation-definitions/list-relation-definitions", + }, + { + text: "Get a relation definition", + link: "/api-reference/v2/work-item-relation-definitions/get-relation-definition", + }, + { + text: "Create a relation definition", + link: "/api-reference/v2/work-item-relation-definitions/create-relation-definition", + }, + { + text: "Update a relation definition", + link: "/api-reference/v2/work-item-relation-definitions/update-relation-definition", + }, + { + text: "Delete a relation definition", + link: "/api-reference/v2/work-item-relation-definitions/delete-relation-definition", + }, + ], + }, + { + text: "States", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/states/overview" }, + { text: "List states", link: "/api-reference/v2/states/list-states" }, + { text: "Get a state", link: "/api-reference/v2/states/get-state" }, + { text: "Create a state", link: "/api-reference/v2/states/create-state" }, + { text: "Update a state", link: "/api-reference/v2/states/update-state" }, + { text: "Delete a state", link: "/api-reference/v2/states/delete-state" }, + { text: "Upsert a state", link: "/api-reference/v2/states/upsert-state" }, + { text: "Bulk write states", link: "/api-reference/v2/states/bulk-states" }, + ], + }, + { + text: "Labels", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/labels/overview" }, + { text: "List labels", link: "/api-reference/v2/labels/list-labels" }, + { text: "Get a label", link: "/api-reference/v2/labels/get-label" }, + { text: "Create a label", link: "/api-reference/v2/labels/create-label" }, + { text: "Update a label", link: "/api-reference/v2/labels/update-label" }, + { text: "Delete a label", link: "/api-reference/v2/labels/delete-label" }, + { text: "Upsert a label", link: "/api-reference/v2/labels/upsert-label" }, + { text: "Bulk write labels", link: "/api-reference/v2/labels/bulk-labels" }, + ], + }, + { + text: "Cycles", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/cycles/overview" }, + { text: "List cycles", link: "/api-reference/v2/cycles/list-cycles" }, + { text: "Get a cycle", link: "/api-reference/v2/cycles/get-cycle" }, + { text: "Create a cycle", link: "/api-reference/v2/cycles/create-cycle" }, + { text: "Update a cycle", link: "/api-reference/v2/cycles/update-cycle" }, + { text: "Delete a cycle", link: "/api-reference/v2/cycles/delete-cycle" }, + { text: "Upsert a cycle", link: "/api-reference/v2/cycles/upsert-cycle" }, + { text: "Bulk write cycles", link: "/api-reference/v2/cycles/bulk-cycles" }, + { + text: "Add or remove cycle work items", + link: "/api-reference/v2/cycles/manage-cycle-work-items", + }, + { + text: "Transfer work items between cycles", + link: "/api-reference/v2/cycles/transfer-cycle-work-items", + }, + ], + }, + { + text: "Modules", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/modules/overview" }, + { text: "List modules", link: "/api-reference/v2/modules/list-modules" }, + { text: "Get a module", link: "/api-reference/v2/modules/get-module" }, + { text: "Create a module", link: "/api-reference/v2/modules/create-module" }, + { text: "Update a module", link: "/api-reference/v2/modules/update-module" }, + { text: "Delete a module", link: "/api-reference/v2/modules/delete-module" }, + { text: "Upsert a module", link: "/api-reference/v2/modules/upsert-module" }, + { text: "Bulk write modules", link: "/api-reference/v2/modules/bulk-modules" }, + { + text: "Add or remove module work items", + link: "/api-reference/v2/modules/manage-module-work-items", + }, + ], + }, + { + text: "Milestones", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/milestones/overview" }, + { text: "List milestones", link: "/api-reference/v2/milestones/list-milestones" }, + { text: "Get a milestone", link: "/api-reference/v2/milestones/get-milestone" }, + { text: "Create a milestone", link: "/api-reference/v2/milestones/create-milestone" }, + { text: "Update a milestone", link: "/api-reference/v2/milestones/update-milestone" }, + { text: "Delete a milestone", link: "/api-reference/v2/milestones/delete-milestone" }, + { text: "Upsert a milestone", link: "/api-reference/v2/milestones/upsert-milestone" }, + { text: "Bulk write milestones", link: "/api-reference/v2/milestones/bulk-milestones" }, + { + text: "Add or remove milestone work items", + link: "/api-reference/v2/milestones/manage-milestone-work-items", + }, + ], + }, + { + text: "Intake", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/intake-work-items/overview" }, + { + text: "List intake work items", + link: "/api-reference/v2/intake-work-items/list-intake-work-items", + }, + { + text: "Get a intake work item", + link: "/api-reference/v2/intake-work-items/get-intake-work-item", + }, + { + text: "Create a intake work item", + link: "/api-reference/v2/intake-work-items/create-intake-work-item", + }, + { + text: "Update a intake work item", + link: "/api-reference/v2/intake-work-items/update-intake-work-item", + }, + { + text: "Delete a intake work item", + link: "/api-reference/v2/intake-work-items/delete-intake-work-item", + }, + ], + }, + ], + }, + { + text: "Work Item Types (Project)", + items: [ + { + text: "Work Item Types", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-types/overview" }, + { text: "List work item types", link: "/api-reference/v2/work-item-types/list-work-item-types" }, + { text: "Get a work item type", link: "/api-reference/v2/work-item-types/get-work-item-type" }, + { + text: "Get a work item type schema", + link: "/api-reference/v2/work-item-types/get-work-item-type-schema", + }, + { + text: "Create a work item type", + link: "/api-reference/v2/work-item-types/create-work-item-type", + }, + { + text: "Update a work item type", + link: "/api-reference/v2/work-item-types/update-work-item-type", + }, + { + text: "Delete a work item type", + link: "/api-reference/v2/work-item-types/delete-work-item-type", + }, + { + text: "Enable work item types", + link: "/api-reference/v2/work-item-types/enable-work-item-types", + }, + { + text: "Import work item types", + link: "/api-reference/v2/work-item-types/import-work-item-types", + }, + { + text: "Mark a work item type as default", + link: "/api-reference/v2/work-item-types/mark-default-work-item-type", + }, + ], + }, + { + text: "Properties", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-properties/overview" }, + { + text: "List work item properties", + link: "/api-reference/v2/work-item-properties/list-work-item-properties", + }, + { + text: "Get a work item property", + link: "/api-reference/v2/work-item-properties/get-work-item-property", + }, + { + text: "Create a work item property", + link: "/api-reference/v2/work-item-properties/create-work-item-property", + }, + { + text: "Update a work item property", + link: "/api-reference/v2/work-item-properties/update-work-item-property", + }, + { + text: "Delete a work item property", + link: "/api-reference/v2/work-item-properties/delete-work-item-property", + }, + ], + }, + { + text: "Property Options", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-property-options/overview" }, + { + text: "List property options", + link: "/api-reference/v2/work-item-property-options/list-property-options", + }, + { + text: "Get a property option", + link: "/api-reference/v2/work-item-property-options/get-property-option", + }, + { + text: "Create a property option", + link: "/api-reference/v2/work-item-property-options/create-property-option", + }, + { + text: "Update a property option", + link: "/api-reference/v2/work-item-property-options/update-property-option", + }, + { + text: "Delete a property option", + link: "/api-reference/v2/work-item-property-options/delete-property-option", + }, + ], + }, + { + text: "Type Properties", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-type-properties/overview" }, + { + text: "List type properties", + link: "/api-reference/v2/work-item-type-properties/list-type-properties", + }, + { + text: "Get a type property", + link: "/api-reference/v2/work-item-type-properties/get-type-property", + }, + { + text: "Attach a property to a type", + link: "/api-reference/v2/work-item-type-properties/attach-type-property", + }, + { + text: "Detach a property from a type", + link: "/api-reference/v2/work-item-type-properties/detach-type-property", + }, + ], + }, + ], + }, + { + text: "Work Item Types (Workspace)", + items: [ + { + text: "Work Item Types", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workspace-work-item-types/overview" }, + { + text: "List workspace work item types", + link: "/api-reference/v2/workspace-work-item-types/list-workspace-work-item-types", + }, + { + text: "Get a workspace work item type", + link: "/api-reference/v2/workspace-work-item-types/get-workspace-work-item-type", + }, + { + text: "Create a workspace work item type", + link: "/api-reference/v2/workspace-work-item-types/create-workspace-work-item-type", + }, + { + text: "Update a workspace work item type", + link: "/api-reference/v2/workspace-work-item-types/update-workspace-work-item-type", + }, + { + text: "Delete a workspace work item type", + link: "/api-reference/v2/workspace-work-item-types/delete-workspace-work-item-type", + }, + { + text: "Mark a workspace work item type as default", + link: "/api-reference/v2/workspace-work-item-types/mark-default-workspace-work-item-type", + }, + ], + }, + { + text: "Properties", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workspace-work-item-properties/overview" }, + { + text: "List workspace work item properties", + link: "/api-reference/v2/workspace-work-item-properties/list-workspace-work-item-properties", }, + { + text: "Get a workspace work item property", + link: "/api-reference/v2/workspace-work-item-properties/get-workspace-work-item-property", + }, + { + text: "Create a workspace work item property", + link: "/api-reference/v2/workspace-work-item-properties/create-workspace-work-item-property", + }, + { + text: "Update a workspace work item property", + link: "/api-reference/v2/workspace-work-item-properties/update-workspace-work-item-property", + }, + { + text: "Delete a workspace work item property", + link: "/api-reference/v2/workspace-work-item-properties/delete-workspace-work-item-property", + }, + ], + }, + { + text: "Property Options", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workspace-work-item-property-options/overview" }, + { + text: "List workspace property options", + link: "/api-reference/v2/workspace-work-item-property-options/list-workspace-property-options", + }, + { + text: "Get a workspace property option", + link: "/api-reference/v2/workspace-work-item-property-options/get-workspace-property-option", + }, + { + text: "Create a workspace property option", + link: "/api-reference/v2/workspace-work-item-property-options/create-workspace-property-option", + }, + { + text: "Update a workspace property option", + link: "/api-reference/v2/workspace-work-item-property-options/update-workspace-property-option", + }, + { + text: "Delete a workspace property option", + link: "/api-reference/v2/workspace-work-item-property-options/delete-workspace-property-option", + }, + ], + }, + { + text: "Type Properties", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workspace-work-item-type-properties/overview" }, + { + text: "List properties on a workspace type", + link: "/api-reference/v2/workspace-work-item-type-properties/list-workspace-type-properties", + }, + { + text: "Get a property on a workspace type", + link: "/api-reference/v2/workspace-work-item-type-properties/get-workspace-type-property", + }, + { + text: "Attach properties to a workspace type", + link: "/api-reference/v2/workspace-work-item-type-properties/attach-workspace-type-property", + }, + { + text: "Detach a property from a workspace type", + link: "/api-reference/v2/workspace-work-item-type-properties/detach-workspace-type-property", + }, + ], + }, + { + text: "Property Contexts", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/work-item-property-contexts/overview" }, + { + text: "List property contexts", + link: "/api-reference/v2/work-item-property-contexts/list-property-contexts", + }, + { + text: "Get a property context", + link: "/api-reference/v2/work-item-property-contexts/get-property-context", + }, + { + text: "Create a property context", + link: "/api-reference/v2/work-item-property-contexts/create-property-context", + }, + { + text: "Update a property context", + link: "/api-reference/v2/work-item-property-contexts/update-property-context", + }, + { + text: "Delete a property context", + link: "/api-reference/v2/work-item-property-contexts/delete-property-context", + }, + ], + }, + ], + }, + { + text: "Projects & Planning", + items: [ + { + text: "Projects", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/projects/overview" }, + { text: "List projects", link: "/api-reference/v2/projects/list-projects" }, + { text: "Get a project", link: "/api-reference/v2/projects/get-project" }, + { text: "Get a project summary", link: "/api-reference/v2/projects/get-project-summary" }, + { text: "Create a project", link: "/api-reference/v2/projects/create-project" }, + { text: "Update a project", link: "/api-reference/v2/projects/update-project" }, + { text: "Delete a project", link: "/api-reference/v2/projects/delete-project" }, + { text: "Upsert a project", link: "/api-reference/v2/projects/upsert-project" }, + { text: "Bulk write projects", link: "/api-reference/v2/projects/bulk-projects" }, + { text: "Archive a project", link: "/api-reference/v2/projects/archive-project" }, + { text: "Unarchive a project", link: "/api-reference/v2/projects/unarchive-project" }, + ], + }, + { + text: "Project Features", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/project-features/overview" }, + { text: "Get project features", link: "/api-reference/v2/project-features/get-project-features" }, + { + text: "Update project features", + link: "/api-reference/v2/project-features/update-project-features", + }, + ], + }, + { + text: "Views (Project)", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/project-views/overview" }, + { text: "List project views", link: "/api-reference/v2/project-views/list-project-views" }, + { text: "Get a project view", link: "/api-reference/v2/project-views/get-project-view" }, + { text: "Create a project view", link: "/api-reference/v2/project-views/create-project-view" }, + { text: "Update a project view", link: "/api-reference/v2/project-views/update-project-view" }, + { text: "Delete a project view", link: "/api-reference/v2/project-views/delete-project-view" }, + ], + }, + { + text: "Views (Workspace)", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workspace-views/overview" }, + { text: "List workspace views", link: "/api-reference/v2/workspace-views/list-workspace-views" }, + { text: "Get a workspace view", link: "/api-reference/v2/workspace-views/get-workspace-view" }, + { + text: "Create a workspace view", + link: "/api-reference/v2/workspace-views/create-workspace-view", + }, + { + text: "Update a workspace view", + link: "/api-reference/v2/workspace-views/update-workspace-view", + }, + { + text: "Delete a workspace view", + link: "/api-reference/v2/workspace-views/delete-workspace-view", + }, + ], + }, + { + text: "Estimates", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/estimates/overview" }, + { text: "List estimates", link: "/api-reference/v2/estimates/list-estimates" }, + { text: "Get a estimate", link: "/api-reference/v2/estimates/get-estimate" }, + { text: "Create a estimate", link: "/api-reference/v2/estimates/create-estimate" }, + { text: "Update a estimate", link: "/api-reference/v2/estimates/update-estimate" }, + { text: "Delete a estimate", link: "/api-reference/v2/estimates/delete-estimate" }, + { text: "Upsert a estimate", link: "/api-reference/v2/estimates/upsert-estimate" }, + { text: "Bulk write estimates", link: "/api-reference/v2/estimates/bulk-estimates" }, + ], + }, + { + text: "Estimate Points", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/estimate-points/overview" }, + { text: "List estimate points", link: "/api-reference/v2/estimate-points/list-estimate-points" }, + { text: "Get a estimate point", link: "/api-reference/v2/estimate-points/get-estimate-point" }, + { + text: "Create a estimate point", + link: "/api-reference/v2/estimate-points/create-estimate-point", + }, + { + text: "Update a estimate point", + link: "/api-reference/v2/estimate-points/update-estimate-point", + }, + { + text: "Delete a estimate point", + link: "/api-reference/v2/estimate-points/delete-estimate-point", + }, + { + text: "Upsert a estimate point", + link: "/api-reference/v2/estimate-points/upsert-estimate-point", + }, + { + text: "Bulk write estimate points", + link: "/api-reference/v2/estimate-points/bulk-estimate-points", + }, + ], + }, + { + text: "Initiatives", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/initiatives/overview" }, + { text: "List initiatives", link: "/api-reference/v2/initiatives/list-initiatives" }, + { text: "Get a initiative", link: "/api-reference/v2/initiatives/get-initiative" }, + { text: "Create a initiative", link: "/api-reference/v2/initiatives/create-initiative" }, + { text: "Update a initiative", link: "/api-reference/v2/initiatives/update-initiative" }, + { text: "Delete a initiative", link: "/api-reference/v2/initiatives/delete-initiative" }, + { + text: "Add or remove initiative labels", + link: "/api-reference/v2/initiatives/manage-initiative-labels", + }, + { + text: "Add or remove initiative projects", + link: "/api-reference/v2/initiatives/manage-initiative-projects", + }, + { + text: "Add or remove initiative work items", + link: "/api-reference/v2/initiatives/manage-initiative-work-items", + }, + ], + }, + { + text: "Initiative Labels", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/initiative-labels/overview" }, + { + text: "List initiative labels", + link: "/api-reference/v2/initiative-labels/list-initiative-labels", + }, + { + text: "Get a initiative label", + link: "/api-reference/v2/initiative-labels/get-initiative-label", + }, + { + text: "Create a initiative label", + link: "/api-reference/v2/initiative-labels/create-initiative-label", + }, + { + text: "Update a initiative label", + link: "/api-reference/v2/initiative-labels/update-initiative-label", + }, + { + text: "Delete a initiative label", + link: "/api-reference/v2/initiative-labels/delete-initiative-label", + }, + ], + }, + { + text: "Teamspaces", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/teamspaces/overview" }, + { text: "List teamspaces", link: "/api-reference/v2/teamspaces/list-teamspaces" }, + { text: "Get a teamspace", link: "/api-reference/v2/teamspaces/get-teamspace" }, + { text: "Create a teamspace", link: "/api-reference/v2/teamspaces/create-teamspace" }, + { text: "Update a teamspace", link: "/api-reference/v2/teamspaces/update-teamspace" }, + { text: "Delete a teamspace", link: "/api-reference/v2/teamspaces/delete-teamspace" }, + ], + }, + { + text: "Templates (Project)", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/project-work-item-templates/overview" }, + { + text: "List project work item templates", + link: "/api-reference/v2/project-work-item-templates/list-project-work-item-templates", + }, + { + text: "Get a project work item template", + link: "/api-reference/v2/project-work-item-templates/get-project-work-item-template", + }, + { + text: "Create a project work item template", + link: "/api-reference/v2/project-work-item-templates/create-project-work-item-template", + }, + { + text: "Update a project work item template", + link: "/api-reference/v2/project-work-item-templates/update-project-work-item-template", + }, + { + text: "Delete a project work item template", + link: "/api-reference/v2/project-work-item-templates/delete-project-work-item-template", + }, + { + text: "Create a work item from a template", + link: "/api-reference/v2/project-work-item-templates/use-work-item-template", + }, + ], + }, + { + text: "Templates (Workspace)", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workspace-work-item-templates/overview" }, + { + text: "List workspace work item templates", + link: "/api-reference/v2/workspace-work-item-templates/list-workspace-work-item-templates", + }, + { + text: "Get a workspace work item template", + link: "/api-reference/v2/workspace-work-item-templates/get-workspace-work-item-template", + }, + { + text: "Create a workspace work item template", + link: "/api-reference/v2/workspace-work-item-templates/create-workspace-work-item-template", + }, + { + text: "Update a workspace work item template", + link: "/api-reference/v2/workspace-work-item-templates/update-workspace-work-item-template", + }, + { + text: "Delete a workspace work item template", + link: "/api-reference/v2/workspace-work-item-templates/delete-workspace-work-item-template", + }, + ], + }, + ], + }, + { + text: "Wiki & Notes", + items: [ + { + text: "Collections", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/collections/overview" }, + { text: "List collection members", link: "/api-reference/v2/collections/list-collection-members" }, + { text: "List collections", link: "/api-reference/v2/collections/list-collections" }, + { text: "Get a collection", link: "/api-reference/v2/collections/get-collection" }, + { text: "Create a collection", link: "/api-reference/v2/collections/create-collection" }, + { text: "Update a collection", link: "/api-reference/v2/collections/update-collection" }, + { text: "Delete a collection", link: "/api-reference/v2/collections/delete-collection" }, + { + text: "Add or remove collection members", + link: "/api-reference/v2/collections/manage-collection-members", + }, + { + text: "Add or remove pages in a collection", + link: "/api-reference/v2/collections/manage-collection-pages", + }, + { + text: "Search pages for a collection", + link: "/api-reference/v2/collections/search-collection-pages", + }, + ], + }, + { + text: "Pages (Project)", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/project-pages/overview" }, + { text: "List project pages", link: "/api-reference/v2/project-pages/list-project-pages" }, + { text: "Get a project page", link: "/api-reference/v2/project-pages/get-project-page" }, + { text: "Create a project page", link: "/api-reference/v2/project-pages/create-project-page" }, + { text: "Update a project page", link: "/api-reference/v2/project-pages/update-project-page" }, + { text: "Delete a project page", link: "/api-reference/v2/project-pages/delete-project-page" }, + ], + }, + { + text: "Pages (Workspace)", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workspace-pages/overview" }, + { text: "List workspace pages", link: "/api-reference/v2/workspace-pages/list-workspace-pages" }, + { text: "Get a workspace page", link: "/api-reference/v2/workspace-pages/get-workspace-page" }, + { + text: "Create a workspace page", + link: "/api-reference/v2/workspace-pages/create-workspace-page", + }, + { + text: "Update a workspace page", + link: "/api-reference/v2/workspace-pages/update-workspace-page", + }, + { + text: "Delete a workspace page", + link: "/api-reference/v2/workspace-pages/delete-workspace-page", + }, + ], + }, + { + text: "Stickies", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/stickies/overview" }, + { text: "List stickies", link: "/api-reference/v2/stickies/list-stickies" }, + { text: "Get a sticky", link: "/api-reference/v2/stickies/get-sticky" }, + { text: "Create a sticky", link: "/api-reference/v2/stickies/create-sticky" }, + { text: "Update a sticky", link: "/api-reference/v2/stickies/update-sticky" }, + { text: "Delete a sticky", link: "/api-reference/v2/stickies/delete-sticky" }, + ], + }, + ], + }, + { + text: "Customers", + items: [ + { + text: "Customers", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/customers/overview" }, + { text: "List customers", link: "/api-reference/v2/customers/list-customers" }, + { text: "Get a customer", link: "/api-reference/v2/customers/get-customer" }, + { text: "Create a customer", link: "/api-reference/v2/customers/create-customer" }, + { text: "Update a customer", link: "/api-reference/v2/customers/update-customer" }, + { text: "Delete a customer", link: "/api-reference/v2/customers/delete-customer" }, + { text: "Upsert a customer", link: "/api-reference/v2/customers/upsert-customer" }, + { + text: "Link or unlink customer work items", + link: "/api-reference/v2/customers/manage-customer-work-items", + }, + ], + }, + { + text: "Customer Requests", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/customer-requests/overview" }, + { + text: "List customer requests", + link: "/api-reference/v2/customer-requests/list-customer-requests", + }, + { + text: "Get a customer request", + link: "/api-reference/v2/customer-requests/get-customer-request", + }, + { + text: "Create a customer request", + link: "/api-reference/v2/customer-requests/create-customer-request", + }, + { + text: "Update a customer request", + link: "/api-reference/v2/customer-requests/update-customer-request", + }, + { + text: "Delete a customer request", + link: "/api-reference/v2/customer-requests/delete-customer-request", + }, + ], + }, + { + text: "Customer Properties", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/customer-properties/overview" }, + { + text: "List customer properties", + link: "/api-reference/v2/customer-properties/list-customer-properties", + }, + { + text: "Get a customer property", + link: "/api-reference/v2/customer-properties/get-customer-property", + }, + { + text: "Create a customer property", + link: "/api-reference/v2/customer-properties/create-customer-property", + }, + { + text: "Update a customer property", + link: "/api-reference/v2/customer-properties/update-customer-property", + }, + { + text: "Delete a customer property", + link: "/api-reference/v2/customer-properties/delete-customer-property", + }, + ], + }, + { + text: "Customer Property Values", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/customer-property-values/overview" }, + { + text: "List customer property values", + link: "/api-reference/v2/customer-property-values/list-customer-property-values", + }, + { + text: "Set customer property values", + link: "/api-reference/v2/customer-property-values/set-customer-property-values", + }, + ], + }, + ], + }, + { + text: "Releases", + items: [ + { + text: "Releases", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/releases/overview" }, + { text: "List releases", link: "/api-reference/v2/releases/list-releases" }, + { text: "Get a release", link: "/api-reference/v2/releases/get-release" }, + { text: "Get a release changelog", link: "/api-reference/v2/releases/get-release-changelog" }, + { text: "Create a release", link: "/api-reference/v2/releases/create-release" }, + { text: "Update a release", link: "/api-reference/v2/releases/update-release" }, + { text: "Update a release changelog", link: "/api-reference/v2/releases/update-release-changelog" }, + { text: "Delete a release", link: "/api-reference/v2/releases/delete-release" }, + { text: "Add or remove release labels", link: "/api-reference/v2/releases/manage-release-labels" }, + { + text: "Add or remove release work items", + link: "/api-reference/v2/releases/manage-release-work-items", + }, + ], + }, + { + text: "Release Tags", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/release-tags/overview" }, + { text: "List release tags", link: "/api-reference/v2/release-tags/list-release-tags" }, + { text: "Get a release tag", link: "/api-reference/v2/release-tags/get-release-tag" }, + { text: "Create a release tag", link: "/api-reference/v2/release-tags/create-release-tag" }, + { text: "Update a release tag", link: "/api-reference/v2/release-tags/update-release-tag" }, + { text: "Delete a release tag", link: "/api-reference/v2/release-tags/delete-release-tag" }, + ], + }, + { + text: "Release Labels", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/release-labels/overview" }, + { text: "List release labels", link: "/api-reference/v2/release-labels/list-release-labels" }, + { text: "Get a release label", link: "/api-reference/v2/release-labels/get-release-label" }, + { text: "Create a release label", link: "/api-reference/v2/release-labels/create-release-label" }, + { text: "Update a release label", link: "/api-reference/v2/release-labels/update-release-label" }, + { text: "Delete a release label", link: "/api-reference/v2/release-labels/delete-release-label" }, + ], + }, + { + text: "Release Comments", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/release-comments/overview" }, + { text: "List release comments", link: "/api-reference/v2/release-comments/list-release-comments" }, + { text: "Get a release comment", link: "/api-reference/v2/release-comments/get-release-comment" }, + { + text: "Create a release comment", + link: "/api-reference/v2/release-comments/create-release-comment", + }, + { + text: "Update a release comment", + link: "/api-reference/v2/release-comments/update-release-comment", + }, + { + text: "Delete a release comment", + link: "/api-reference/v2/release-comments/delete-release-comment", + }, + ], + }, + { + text: "Release Links", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/release-links/overview" }, + { text: "List release links", link: "/api-reference/v2/release-links/list-release-links" }, + { text: "Get a release link", link: "/api-reference/v2/release-links/get-release-link" }, + { text: "Create a release link", link: "/api-reference/v2/release-links/create-release-link" }, + { text: "Update a release link", link: "/api-reference/v2/release-links/update-release-link" }, + { text: "Delete a release link", link: "/api-reference/v2/release-links/delete-release-link" }, + ], + }, + ], + }, + { + text: "Automation & Workflows", + items: [ + { + text: "Automations (Project)", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/project-automations/overview" }, + { + text: "List automation activities", + link: "/api-reference/v2/project-automations/list-automation-activities", + }, + { + text: "List automation edges", + link: "/api-reference/v2/project-automations/list-automation-edges", + }, + { + text: "List automation nodes", + link: "/api-reference/v2/project-automations/list-automation-nodes", + }, + { + text: "List project automations", + link: "/api-reference/v2/project-automations/list-project-automations", + }, + { + text: "Get a automation activity", + link: "/api-reference/v2/project-automations/get-automation-activity", + }, + { + text: "Get a automation edge", + link: "/api-reference/v2/project-automations/get-automation-edge", + }, + { + text: "Get a automation node", + link: "/api-reference/v2/project-automations/get-automation-node", + }, + { + text: "Get a project automation", + link: "/api-reference/v2/project-automations/get-project-automation", + }, + { + text: "Create a automation edge", + link: "/api-reference/v2/project-automations/create-automation-edge", + }, + { + text: "Create a automation node", + link: "/api-reference/v2/project-automations/create-automation-node", + }, + { + text: "Create a project automation", + link: "/api-reference/v2/project-automations/create-project-automation", + }, + { + text: "Update a automation edge", + link: "/api-reference/v2/project-automations/update-automation-edge", + }, + { + text: "Update a automation node", + link: "/api-reference/v2/project-automations/update-automation-node", + }, + { + text: "Update a project automation", + link: "/api-reference/v2/project-automations/update-project-automation", + }, + { + text: "Delete a automation edge", + link: "/api-reference/v2/project-automations/delete-automation-edge", + }, + { + text: "Delete a automation node", + link: "/api-reference/v2/project-automations/delete-automation-node", + }, + { + text: "Delete a project automation", + link: "/api-reference/v2/project-automations/delete-project-automation", + }, + { + text: "Regenerate an automation node webhook secret", + link: "/api-reference/v2/project-automations/regenerate-node-webhook-secret", + }, + { + text: "Enable or disable a project automation", + link: "/api-reference/v2/project-automations/set-project-automation-status", + }, + ], + }, + { + text: "Automations (Workspace)", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workspace-automations/overview" }, + { + text: "List automation activities", + link: "/api-reference/v2/workspace-automations/list-automation-activities", + }, + { + text: "List automation edges", + link: "/api-reference/v2/workspace-automations/list-automation-edges", + }, + { + text: "List automation nodes", + link: "/api-reference/v2/workspace-automations/list-automation-nodes", + }, + { + text: "List workspace automations", + link: "/api-reference/v2/workspace-automations/list-workspace-automations", + }, + { + text: "Get a automation activity", + link: "/api-reference/v2/workspace-automations/get-automation-activity", + }, + { + text: "Get a automation edge", + link: "/api-reference/v2/workspace-automations/get-automation-edge", + }, + { + text: "Get a automation node", + link: "/api-reference/v2/workspace-automations/get-automation-node", + }, + { + text: "Get a workspace automation", + link: "/api-reference/v2/workspace-automations/get-workspace-automation", + }, + { + text: "Create a automation edge", + link: "/api-reference/v2/workspace-automations/create-automation-edge", + }, + { + text: "Create a automation node", + link: "/api-reference/v2/workspace-automations/create-automation-node", + }, + { + text: "Create a workspace automation", + link: "/api-reference/v2/workspace-automations/create-workspace-automation", + }, + { + text: "Update a automation edge", + link: "/api-reference/v2/workspace-automations/update-automation-edge", + }, + { + text: "Update a automation node", + link: "/api-reference/v2/workspace-automations/update-automation-node", + }, + { + text: "Update a workspace automation", + link: "/api-reference/v2/workspace-automations/update-workspace-automation", + }, + { + text: "Delete a automation edge", + link: "/api-reference/v2/workspace-automations/delete-automation-edge", + }, + { + text: "Delete a automation node", + link: "/api-reference/v2/workspace-automations/delete-automation-node", + }, + { + text: "Delete a workspace automation", + link: "/api-reference/v2/workspace-automations/delete-workspace-automation", + }, + { + text: "Regenerate an automation node webhook secret", + link: "/api-reference/v2/workspace-automations/regenerate-node-webhook-secret", + }, + { + text: "Enable or disable a workspace automation", + link: "/api-reference/v2/workspace-automations/set-workspace-automation-status", + }, + ], + }, + { + text: "Workflows", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workflows/overview" }, + { text: "List workflows", link: "/api-reference/v2/workflows/list-workflows" }, + { text: "Get a workflow", link: "/api-reference/v2/workflows/get-workflow" }, + { text: "Create a workflow", link: "/api-reference/v2/workflows/create-workflow" }, + { text: "Update a workflow", link: "/api-reference/v2/workflows/update-workflow" }, + { text: "Delete a workflow", link: "/api-reference/v2/workflows/delete-workflow" }, + ], + }, + { + text: "Workflow States", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workflow-states/overview" }, + { text: "List workflow states", link: "/api-reference/v2/workflow-states/list-workflow-states" }, + { text: "Get a workflow state", link: "/api-reference/v2/workflow-states/get-workflow-state" }, + { + text: "Create a workflow state", + link: "/api-reference/v2/workflow-states/create-workflow-state", + }, + { + text: "Update a workflow state", + link: "/api-reference/v2/workflow-states/update-workflow-state", + }, + { + text: "Delete a workflow state", + link: "/api-reference/v2/workflow-states/delete-workflow-state", + }, + ], + }, + { + text: "Workflow Transitions", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workflow-transitions/overview" }, + { + text: "List workflow transitions", + link: "/api-reference/v2/workflow-transitions/list-workflow-transitions", + }, + { + text: "Get a workflow transition", + link: "/api-reference/v2/workflow-transitions/get-workflow-transition", + }, + { + text: "Create a workflow transition", + link: "/api-reference/v2/workflow-transitions/create-workflow-transition", + }, + { + text: "Update a workflow transition", + link: "/api-reference/v2/workflow-transitions/update-workflow-transition", + }, + { + text: "Delete a workflow transition", + link: "/api-reference/v2/workflow-transitions/delete-workflow-transition", + }, + ], + }, + ], + }, + { + text: "Workspace & Admin", + items: [ + { + text: "Members", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/members/overview" }, + { text: "List project members", link: "/api-reference/v2/members/list-project-members" }, + { text: "List workspace members", link: "/api-reference/v2/members/list-workspace-members" }, + { text: "Get a project member", link: "/api-reference/v2/members/get-project-member" }, + { + text: "Get project role distribution", + link: "/api-reference/v2/members/get-project-role-distribution", + }, + { text: "Create a project member", link: "/api-reference/v2/members/create-project-member" }, + { text: "Update a project member", link: "/api-reference/v2/members/update-project-member" }, + { text: "Delete a project member", link: "/api-reference/v2/members/delete-project-member" }, + { text: "Remove a workspace member", link: "/api-reference/v2/members/remove-workspace-member" }, + ], + }, + { + text: "Invitations", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/invitations/overview" }, + { text: "List invitations", link: "/api-reference/v2/invitations/list-invitations" }, + { text: "Get a invitation", link: "/api-reference/v2/invitations/get-invitation" }, + { text: "Create a invitation", link: "/api-reference/v2/invitations/create-invitation" }, + { text: "Delete a invitation", link: "/api-reference/v2/invitations/delete-invitation" }, + { text: "Bulk write invitations", link: "/api-reference/v2/invitations/bulk-invitations" }, + ], + }, + { + text: "Roles", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/roles/overview" }, + { text: "List roles", link: "/api-reference/v2/roles/list-roles" }, + { text: "Get a role", link: "/api-reference/v2/roles/get-role" }, + ], + }, + { + text: "Permissions", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/permissions/overview" }, + { + text: "Get your effective project permissions", + link: "/api-reference/v2/permissions/get-project-permissions", + }, + { + text: "Get your effective workspace permissions", + link: "/api-reference/v2/permissions/get-workspace-permissions", + }, + ], + }, + { + text: "Permission Schemes", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/permission-schemes/overview" }, + { + text: "List permission schemes", + link: "/api-reference/v2/permission-schemes/list-permission-schemes", + }, + { + text: "Get a permission scheme", + link: "/api-reference/v2/permission-schemes/get-permission-scheme", + }, + ], + }, + { + text: "Workspace Features", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workspace-features/overview" }, + { + text: "Get workspace features", + link: "/api-reference/v2/workspace-features/get-workspace-features", + }, + { + text: "Update workspace features", + link: "/api-reference/v2/workspace-features/update-workspace-features", + }, + ], + }, + { + text: "Audit Logs", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/audit-logs/overview" }, + { text: "List audit logs", link: "/api-reference/v2/audit-logs/list-audit-logs" }, + { text: "Get an audit log", link: "/api-reference/v2/audit-logs/get-audit-log" }, + ], + }, + { + text: "Group Sync", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/group-sync/overview" }, + { text: "List project mappings", link: "/api-reference/v2/group-sync/list-project-mappings" }, + { text: "List workspace mappings", link: "/api-reference/v2/group-sync/list-workspace-mappings" }, + { + text: "Get the group sync configuration", + link: "/api-reference/v2/group-sync/get-group-sync-config", + }, + { text: "Get a project mapping", link: "/api-reference/v2/group-sync/get-project-mapping" }, + { text: "Get a workspace mapping", link: "/api-reference/v2/group-sync/get-workspace-mapping" }, + { text: "Create a project mapping", link: "/api-reference/v2/group-sync/create-project-mapping" }, + { + text: "Create a workspace mapping", + link: "/api-reference/v2/group-sync/create-workspace-mapping", + }, + { + text: "Update the group sync configuration", + link: "/api-reference/v2/group-sync/update-group-sync-config", + }, + { text: "Update a project mapping", link: "/api-reference/v2/group-sync/update-project-mapping" }, + { + text: "Update a workspace mapping", + link: "/api-reference/v2/group-sync/update-workspace-mapping", + }, + { text: "Delete a project mapping", link: "/api-reference/v2/group-sync/delete-project-mapping" }, + { + text: "Delete a workspace mapping", + link: "/api-reference/v2/group-sync/delete-workspace-mapping", + }, + ], + }, + { + text: "Webhooks", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/webhooks/overview" }, + { text: "List webhooks", link: "/api-reference/v2/webhooks/list-webhooks" }, + { text: "Get a webhook", link: "/api-reference/v2/webhooks/get-webhook" }, + { text: "Create a webhook", link: "/api-reference/v2/webhooks/create-webhook" }, + { text: "Update a webhook", link: "/api-reference/v2/webhooks/update-webhook" }, + { text: "Delete a webhook", link: "/api-reference/v2/webhooks/delete-webhook" }, + { + text: "Regenerate a webhook secret", + link: "/api-reference/v2/webhooks/regenerate-webhook-secret", + }, + ], + }, + { + text: "Webhook Logs", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/webhook-logs/overview" }, + { text: "List webhook logs", link: "/api-reference/v2/webhook-logs/list-webhook-logs" }, + { text: "Get a webhook log", link: "/api-reference/v2/webhook-logs/get-webhook-log" }, + ], + }, + { + text: "Workspace Assets", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/workspace-assets/overview" }, + { text: "List workspace assets", link: "/api-reference/v2/workspace-assets/list-workspace-assets" }, + { text: "Get a asset", link: "/api-reference/v2/workspace-assets/get-asset" }, + { + text: "Create a workspace asset upload", + link: "/api-reference/v2/workspace-assets/create-workspace-asset", + }, + { text: "Delete a asset", link: "/api-reference/v2/workspace-assets/delete-asset" }, + { + text: "Confirm a workspace asset upload", + link: "/api-reference/v2/workspace-assets/confirm-workspace-asset", + }, + ], + }, + { + text: "User Assets", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/user-assets/overview" }, + { text: "List user assets", link: "/api-reference/v2/user-assets/list-user-assets" }, + { text: "Get a user asset", link: "/api-reference/v2/user-assets/get-user-asset" }, + { text: "Create a user asset upload", link: "/api-reference/v2/user-assets/create-user-asset" }, + { text: "Delete a user asset", link: "/api-reference/v2/user-assets/delete-user-asset" }, + { text: "Confirm a user asset upload", link: "/api-reference/v2/user-assets/confirm-user-asset" }, + ], + }, + { + text: "Users", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/users/overview" }, + { text: "Get current user", link: "/api-reference/v2/users/get-current-user" }, + ], + }, + { + text: "Artifacts", + collapsed: true, + items: [ + { text: "Overview", link: "/api-reference/v2/artifacts/overview" }, + { text: "Get an artifact", link: "/api-reference/v2/artifacts/get-artifact" }, + { text: "Create an artifact", link: "/api-reference/v2/artifacts/create-artifact" }, + { text: "Append a new version", link: "/api-reference/v2/artifacts/update-artifact" }, + { text: "Publish an artifact", link: "/api-reference/v2/artifacts/publish-artifact" }, ], }, ], diff --git a/docs/.vitepress/theme/components/ApiVersionSwitcher.vue b/docs/.vitepress/theme/components/ApiVersionSwitcher.vue new file mode 100644 index 00000000..451c8cc6 --- /dev/null +++ b/docs/.vitepress/theme/components/ApiVersionSwitcher.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index a592b970..e7dbefa7 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -18,6 +18,7 @@ import Card from "./components/Card.vue"; import CardGroup from "./components/CardGroup.vue"; import Tags from "./components/Tags.vue"; import CookieConsent from "./components/CookieConsent.vue"; +import ApiVersionSwitcher from "./components/ApiVersionSwitcher.vue"; import PlaneLayout from "./Layout.vue"; const PLANE_FOOTER_BG = "https://media.docs.plane.so/logo/og-docs.webp"; @@ -27,8 +28,25 @@ function updateLayout() { if (typeof document === "undefined") return; const path = window.location.pathname; - const isApiPage = - path.includes("/api-reference/") && !path.endsWith("/introduction") && !path.endsWith("/introduction.html"); + // Prose pages under /api-reference/ (the introductions and the v2 concept + // guides) keep the normal doc layout; only endpoint pages go wide. + const PROSE_PAGES = [ + "introduction", + "authentication", + "pagination", + "filtering-and-ordering", + "expanding-relations", + "errors", + "work-item-type-modes", + "migrating-from-v1", + ]; + const slug = + path + .replace(/\.html$/, "") + .replace(/\/$/, "") + .split("/") + .pop() ?? ""; + const isApiPage = path.includes("/api-reference/") && !PROSE_PAGES.includes(slug); const vpDoc = document.querySelector(".VPDoc"); if (vpDoc) { @@ -104,6 +122,10 @@ export default { Layout() { return h(PlaneLayout, null, { "layout-bottom": () => h(CookieConsent), + // Renders above the sidebar nav; the component hides itself outside + // /api-reference/. Switching version navigates to that version's entry + // page, which swaps the whole sidebar tree via the path-prefix config. + "sidebar-nav-before": () => h(ApiVersionSwitcher), }); }, enhanceApp(ctx) { diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md new file mode 100644 index 00000000..576a6a86 --- /dev/null +++ b/docs/api-reference/index.md @@ -0,0 +1,29 @@ +--- +title: API Reference +description: Plane REST API reference. Redirects to the current API version, v2. +keywords: plane api, rest api reference, plane api v2 +aside: false +outline: false +--- + + + +# API Reference + +Redirecting to the current API version… + +If you are not redirected automatically, continue to the [Plane API v2 reference](/api-reference/v2/introduction). + +Looking for the previous version? See the [v1 reference](/api-reference/v1/introduction). diff --git a/docs/api-reference/assets/create-user-asset-upload.md b/docs/api-reference/v1/assets/create-user-asset-upload.md similarity index 100% rename from docs/api-reference/assets/create-user-asset-upload.md rename to docs/api-reference/v1/assets/create-user-asset-upload.md diff --git a/docs/api-reference/assets/create-workspace-asset-upload.md b/docs/api-reference/v1/assets/create-workspace-asset-upload.md similarity index 100% rename from docs/api-reference/assets/create-workspace-asset-upload.md rename to docs/api-reference/v1/assets/create-workspace-asset-upload.md diff --git a/docs/api-reference/assets/delete-user-asset.md b/docs/api-reference/v1/assets/delete-user-asset.md similarity index 100% rename from docs/api-reference/assets/delete-user-asset.md rename to docs/api-reference/v1/assets/delete-user-asset.md diff --git a/docs/api-reference/assets/get-workspace-asset.md b/docs/api-reference/v1/assets/get-workspace-asset.md similarity index 100% rename from docs/api-reference/assets/get-workspace-asset.md rename to docs/api-reference/v1/assets/get-workspace-asset.md diff --git a/docs/api-reference/assets/overview.md b/docs/api-reference/v1/assets/overview.md similarity index 96% rename from docs/api-reference/assets/overview.md rename to docs/api-reference/v1/assets/overview.md index d9311057..e88d4090 100644 --- a/docs/api-reference/assets/overview.md +++ b/docs/api-reference/v1/assets/overview.md @@ -8,7 +8,7 @@ keywords: plane, plane api, rest api, api integration, assets, uploads, files Assets let you upload and manage files used across user profiles and workspaces, including images and other binary resources. -[Learn more about using the Plane API](https://developers.plane.so/api-reference/introduction) +[Learn more about using the Plane API](https://developers.plane.so/api-reference/v1/introduction)
diff --git a/docs/api-reference/assets/update-user-asset.md b/docs/api-reference/v1/assets/update-user-asset.md similarity index 100% rename from docs/api-reference/assets/update-user-asset.md rename to docs/api-reference/v1/assets/update-user-asset.md diff --git a/docs/api-reference/assets/update-workspace-asset.md b/docs/api-reference/v1/assets/update-workspace-asset.md similarity index 100% rename from docs/api-reference/assets/update-workspace-asset.md rename to docs/api-reference/v1/assets/update-workspace-asset.md diff --git a/docs/api-reference/customer/add-customer-property.md b/docs/api-reference/v1/customer/add-customer-property.md similarity index 100% rename from docs/api-reference/customer/add-customer-property.md rename to docs/api-reference/v1/customer/add-customer-property.md diff --git a/docs/api-reference/customer/add-customer-request.md b/docs/api-reference/v1/customer/add-customer-request.md similarity index 100% rename from docs/api-reference/customer/add-customer-request.md rename to docs/api-reference/v1/customer/add-customer-request.md diff --git a/docs/api-reference/customer/add-customer.md b/docs/api-reference/v1/customer/add-customer.md similarity index 100% rename from docs/api-reference/customer/add-customer.md rename to docs/api-reference/v1/customer/add-customer.md diff --git a/docs/api-reference/customer/delete-customer-property.md b/docs/api-reference/v1/customer/delete-customer-property.md similarity index 100% rename from docs/api-reference/customer/delete-customer-property.md rename to docs/api-reference/v1/customer/delete-customer-property.md diff --git a/docs/api-reference/customer/delete-customer-request.md b/docs/api-reference/v1/customer/delete-customer-request.md similarity index 100% rename from docs/api-reference/customer/delete-customer-request.md rename to docs/api-reference/v1/customer/delete-customer-request.md diff --git a/docs/api-reference/customer/delete-customer.md b/docs/api-reference/v1/customer/delete-customer.md similarity index 100% rename from docs/api-reference/customer/delete-customer.md rename to docs/api-reference/v1/customer/delete-customer.md diff --git a/docs/api-reference/customer/get-customer-detail.md b/docs/api-reference/v1/customer/get-customer-detail.md similarity index 100% rename from docs/api-reference/customer/get-customer-detail.md rename to docs/api-reference/v1/customer/get-customer-detail.md diff --git a/docs/api-reference/customer/get-customer-property-detail.md b/docs/api-reference/v1/customer/get-customer-property-detail.md similarity index 100% rename from docs/api-reference/customer/get-customer-property-detail.md rename to docs/api-reference/v1/customer/get-customer-property-detail.md diff --git a/docs/api-reference/customer/get-customer-property-value.md b/docs/api-reference/v1/customer/get-customer-property-value.md similarity index 100% rename from docs/api-reference/customer/get-customer-property-value.md rename to docs/api-reference/v1/customer/get-customer-property-value.md diff --git a/docs/api-reference/customer/get-customer-request-detail.md b/docs/api-reference/v1/customer/get-customer-request-detail.md similarity index 100% rename from docs/api-reference/customer/get-customer-request-detail.md rename to docs/api-reference/v1/customer/get-customer-request-detail.md diff --git a/docs/api-reference/customer/link-work-items-to-customer.md b/docs/api-reference/v1/customer/link-work-items-to-customer.md similarity index 100% rename from docs/api-reference/customer/link-work-items-to-customer.md rename to docs/api-reference/v1/customer/link-work-items-to-customer.md diff --git a/docs/api-reference/customer/list-customer-properties.md b/docs/api-reference/v1/customer/list-customer-properties.md similarity index 100% rename from docs/api-reference/customer/list-customer-properties.md rename to docs/api-reference/v1/customer/list-customer-properties.md diff --git a/docs/api-reference/customer/list-customer-property-values.md b/docs/api-reference/v1/customer/list-customer-property-values.md similarity index 100% rename from docs/api-reference/customer/list-customer-property-values.md rename to docs/api-reference/v1/customer/list-customer-property-values.md diff --git a/docs/api-reference/customer/list-customer-requests.md b/docs/api-reference/v1/customer/list-customer-requests.md similarity index 100% rename from docs/api-reference/customer/list-customer-requests.md rename to docs/api-reference/v1/customer/list-customer-requests.md diff --git a/docs/api-reference/customer/list-customer-work-items.md b/docs/api-reference/v1/customer/list-customer-work-items.md similarity index 100% rename from docs/api-reference/customer/list-customer-work-items.md rename to docs/api-reference/v1/customer/list-customer-work-items.md diff --git a/docs/api-reference/customer/list-customers.md b/docs/api-reference/v1/customer/list-customers.md similarity index 100% rename from docs/api-reference/customer/list-customers.md rename to docs/api-reference/v1/customer/list-customers.md diff --git a/docs/api-reference/customer/overview.md b/docs/api-reference/v1/customer/overview.md similarity index 100% rename from docs/api-reference/customer/overview.md rename to docs/api-reference/v1/customer/overview.md diff --git a/docs/api-reference/customer/unlink-work-item-from-customer.md b/docs/api-reference/v1/customer/unlink-work-item-from-customer.md similarity index 100% rename from docs/api-reference/customer/unlink-work-item-from-customer.md rename to docs/api-reference/v1/customer/unlink-work-item-from-customer.md diff --git a/docs/api-reference/customer/update-customer-detail.md b/docs/api-reference/v1/customer/update-customer-detail.md similarity index 100% rename from docs/api-reference/customer/update-customer-detail.md rename to docs/api-reference/v1/customer/update-customer-detail.md diff --git a/docs/api-reference/customer/update-customer-property-detail.md b/docs/api-reference/v1/customer/update-customer-property-detail.md similarity index 100% rename from docs/api-reference/customer/update-customer-property-detail.md rename to docs/api-reference/v1/customer/update-customer-property-detail.md diff --git a/docs/api-reference/customer/update-customer-property-value.md b/docs/api-reference/v1/customer/update-customer-property-value.md similarity index 100% rename from docs/api-reference/customer/update-customer-property-value.md rename to docs/api-reference/v1/customer/update-customer-property-value.md diff --git a/docs/api-reference/customer/update-customer-request-detail.md b/docs/api-reference/v1/customer/update-customer-request-detail.md similarity index 100% rename from docs/api-reference/customer/update-customer-request-detail.md rename to docs/api-reference/v1/customer/update-customer-request-detail.md diff --git a/docs/api-reference/cycle/add-cycle-work-items.md b/docs/api-reference/v1/cycle/add-cycle-work-items.md similarity index 100% rename from docs/api-reference/cycle/add-cycle-work-items.md rename to docs/api-reference/v1/cycle/add-cycle-work-items.md diff --git a/docs/api-reference/cycle/add-cycle.md b/docs/api-reference/v1/cycle/add-cycle.md similarity index 100% rename from docs/api-reference/cycle/add-cycle.md rename to docs/api-reference/v1/cycle/add-cycle.md diff --git a/docs/api-reference/cycle/archive-cycle.md b/docs/api-reference/v1/cycle/archive-cycle.md similarity index 100% rename from docs/api-reference/cycle/archive-cycle.md rename to docs/api-reference/v1/cycle/archive-cycle.md diff --git a/docs/api-reference/cycle/delete-cycle.md b/docs/api-reference/v1/cycle/delete-cycle.md similarity index 100% rename from docs/api-reference/cycle/delete-cycle.md rename to docs/api-reference/v1/cycle/delete-cycle.md diff --git a/docs/api-reference/cycle/get-cycle-detail.md b/docs/api-reference/v1/cycle/get-cycle-detail.md similarity index 100% rename from docs/api-reference/cycle/get-cycle-detail.md rename to docs/api-reference/v1/cycle/get-cycle-detail.md diff --git a/docs/api-reference/cycle/list-archived-cycles.md b/docs/api-reference/v1/cycle/list-archived-cycles.md similarity index 100% rename from docs/api-reference/cycle/list-archived-cycles.md rename to docs/api-reference/v1/cycle/list-archived-cycles.md diff --git a/docs/api-reference/cycle/list-cycle-work-items.md b/docs/api-reference/v1/cycle/list-cycle-work-items.md similarity index 100% rename from docs/api-reference/cycle/list-cycle-work-items.md rename to docs/api-reference/v1/cycle/list-cycle-work-items.md diff --git a/docs/api-reference/cycle/list-cycles.md b/docs/api-reference/v1/cycle/list-cycles.md similarity index 100% rename from docs/api-reference/cycle/list-cycles.md rename to docs/api-reference/v1/cycle/list-cycles.md diff --git a/docs/api-reference/cycle/overview.md b/docs/api-reference/v1/cycle/overview.md similarity index 100% rename from docs/api-reference/cycle/overview.md rename to docs/api-reference/v1/cycle/overview.md diff --git a/docs/api-reference/cycle/remove-cycle-work-item.md b/docs/api-reference/v1/cycle/remove-cycle-work-item.md similarity index 100% rename from docs/api-reference/cycle/remove-cycle-work-item.md rename to docs/api-reference/v1/cycle/remove-cycle-work-item.md diff --git a/docs/api-reference/cycle/transfer-cycle-work-items.md b/docs/api-reference/v1/cycle/transfer-cycle-work-items.md similarity index 100% rename from docs/api-reference/cycle/transfer-cycle-work-items.md rename to docs/api-reference/v1/cycle/transfer-cycle-work-items.md diff --git a/docs/api-reference/cycle/unarchive-cycle.md b/docs/api-reference/v1/cycle/unarchive-cycle.md similarity index 100% rename from docs/api-reference/cycle/unarchive-cycle.md rename to docs/api-reference/v1/cycle/unarchive-cycle.md diff --git a/docs/api-reference/cycle/update-cycle-detail.md b/docs/api-reference/v1/cycle/update-cycle-detail.md similarity index 100% rename from docs/api-reference/cycle/update-cycle-detail.md rename to docs/api-reference/v1/cycle/update-cycle-detail.md diff --git a/docs/api-reference/epics/add-epic-work-items.md b/docs/api-reference/v1/epics/add-epic-work-items.md similarity index 100% rename from docs/api-reference/epics/add-epic-work-items.md rename to docs/api-reference/v1/epics/add-epic-work-items.md diff --git a/docs/api-reference/epics/create-epic.md b/docs/api-reference/v1/epics/create-epic.md similarity index 100% rename from docs/api-reference/epics/create-epic.md rename to docs/api-reference/v1/epics/create-epic.md diff --git a/docs/api-reference/epics/delete-epic.md b/docs/api-reference/v1/epics/delete-epic.md similarity index 100% rename from docs/api-reference/epics/delete-epic.md rename to docs/api-reference/v1/epics/delete-epic.md diff --git a/docs/api-reference/epics/get-epic-detail.md b/docs/api-reference/v1/epics/get-epic-detail.md similarity index 100% rename from docs/api-reference/epics/get-epic-detail.md rename to docs/api-reference/v1/epics/get-epic-detail.md diff --git a/docs/api-reference/epics/list-epic-work-items.md b/docs/api-reference/v1/epics/list-epic-work-items.md similarity index 100% rename from docs/api-reference/epics/list-epic-work-items.md rename to docs/api-reference/v1/epics/list-epic-work-items.md diff --git a/docs/api-reference/epics/list-epics.md b/docs/api-reference/v1/epics/list-epics.md similarity index 100% rename from docs/api-reference/epics/list-epics.md rename to docs/api-reference/v1/epics/list-epics.md diff --git a/docs/api-reference/epics/overview.md b/docs/api-reference/v1/epics/overview.md similarity index 100% rename from docs/api-reference/epics/overview.md rename to docs/api-reference/v1/epics/overview.md diff --git a/docs/api-reference/epics/update-epic.md b/docs/api-reference/v1/epics/update-epic.md similarity index 100% rename from docs/api-reference/epics/update-epic.md rename to docs/api-reference/v1/epics/update-epic.md diff --git a/docs/api-reference/estimate/add-estimate-points.md b/docs/api-reference/v1/estimate/add-estimate-points.md similarity index 100% rename from docs/api-reference/estimate/add-estimate-points.md rename to docs/api-reference/v1/estimate/add-estimate-points.md diff --git a/docs/api-reference/estimate/add-estimate.md b/docs/api-reference/v1/estimate/add-estimate.md similarity index 100% rename from docs/api-reference/estimate/add-estimate.md rename to docs/api-reference/v1/estimate/add-estimate.md diff --git a/docs/api-reference/estimate/delete-estimate-point.md b/docs/api-reference/v1/estimate/delete-estimate-point.md similarity index 100% rename from docs/api-reference/estimate/delete-estimate-point.md rename to docs/api-reference/v1/estimate/delete-estimate-point.md diff --git a/docs/api-reference/estimate/delete-estimate.md b/docs/api-reference/v1/estimate/delete-estimate.md similarity index 100% rename from docs/api-reference/estimate/delete-estimate.md rename to docs/api-reference/v1/estimate/delete-estimate.md diff --git a/docs/api-reference/estimate/get-estimate.md b/docs/api-reference/v1/estimate/get-estimate.md similarity index 100% rename from docs/api-reference/estimate/get-estimate.md rename to docs/api-reference/v1/estimate/get-estimate.md diff --git a/docs/api-reference/estimate/list-estimate-points.md b/docs/api-reference/v1/estimate/list-estimate-points.md similarity index 100% rename from docs/api-reference/estimate/list-estimate-points.md rename to docs/api-reference/v1/estimate/list-estimate-points.md diff --git a/docs/api-reference/estimate/overview.md b/docs/api-reference/v1/estimate/overview.md similarity index 100% rename from docs/api-reference/estimate/overview.md rename to docs/api-reference/v1/estimate/overview.md diff --git a/docs/api-reference/estimate/update-estimate-point.md b/docs/api-reference/v1/estimate/update-estimate-point.md similarity index 100% rename from docs/api-reference/estimate/update-estimate-point.md rename to docs/api-reference/v1/estimate/update-estimate-point.md diff --git a/docs/api-reference/estimate/update-estimate.md b/docs/api-reference/v1/estimate/update-estimate.md similarity index 100% rename from docs/api-reference/estimate/update-estimate.md rename to docs/api-reference/v1/estimate/update-estimate.md diff --git a/docs/api-reference/idp-group-sync/create-project-mapping.md b/docs/api-reference/v1/idp-group-sync/create-project-mapping.md similarity index 100% rename from docs/api-reference/idp-group-sync/create-project-mapping.md rename to docs/api-reference/v1/idp-group-sync/create-project-mapping.md diff --git a/docs/api-reference/idp-group-sync/create-workspace-mapping.md b/docs/api-reference/v1/idp-group-sync/create-workspace-mapping.md similarity index 100% rename from docs/api-reference/idp-group-sync/create-workspace-mapping.md rename to docs/api-reference/v1/idp-group-sync/create-workspace-mapping.md diff --git a/docs/api-reference/idp-group-sync/delete-project-mapping.md b/docs/api-reference/v1/idp-group-sync/delete-project-mapping.md similarity index 100% rename from docs/api-reference/idp-group-sync/delete-project-mapping.md rename to docs/api-reference/v1/idp-group-sync/delete-project-mapping.md diff --git a/docs/api-reference/idp-group-sync/delete-workspace-mapping.md b/docs/api-reference/v1/idp-group-sync/delete-workspace-mapping.md similarity index 100% rename from docs/api-reference/idp-group-sync/delete-workspace-mapping.md rename to docs/api-reference/v1/idp-group-sync/delete-workspace-mapping.md diff --git a/docs/api-reference/idp-group-sync/get-group-sync-config.md b/docs/api-reference/v1/idp-group-sync/get-group-sync-config.md similarity index 100% rename from docs/api-reference/idp-group-sync/get-group-sync-config.md rename to docs/api-reference/v1/idp-group-sync/get-group-sync-config.md diff --git a/docs/api-reference/idp-group-sync/get-project-mapping.md b/docs/api-reference/v1/idp-group-sync/get-project-mapping.md similarity index 100% rename from docs/api-reference/idp-group-sync/get-project-mapping.md rename to docs/api-reference/v1/idp-group-sync/get-project-mapping.md diff --git a/docs/api-reference/idp-group-sync/get-workspace-mapping.md b/docs/api-reference/v1/idp-group-sync/get-workspace-mapping.md similarity index 100% rename from docs/api-reference/idp-group-sync/get-workspace-mapping.md rename to docs/api-reference/v1/idp-group-sync/get-workspace-mapping.md diff --git a/docs/api-reference/idp-group-sync/list-project-mappings.md b/docs/api-reference/v1/idp-group-sync/list-project-mappings.md similarity index 100% rename from docs/api-reference/idp-group-sync/list-project-mappings.md rename to docs/api-reference/v1/idp-group-sync/list-project-mappings.md diff --git a/docs/api-reference/idp-group-sync/list-workspace-mappings.md b/docs/api-reference/v1/idp-group-sync/list-workspace-mappings.md similarity index 100% rename from docs/api-reference/idp-group-sync/list-workspace-mappings.md rename to docs/api-reference/v1/idp-group-sync/list-workspace-mappings.md diff --git a/docs/api-reference/idp-group-sync/overview.md b/docs/api-reference/v1/idp-group-sync/overview.md similarity index 100% rename from docs/api-reference/idp-group-sync/overview.md rename to docs/api-reference/v1/idp-group-sync/overview.md diff --git a/docs/api-reference/idp-group-sync/update-group-sync-config.md b/docs/api-reference/v1/idp-group-sync/update-group-sync-config.md similarity index 100% rename from docs/api-reference/idp-group-sync/update-group-sync-config.md rename to docs/api-reference/v1/idp-group-sync/update-group-sync-config.md diff --git a/docs/api-reference/idp-group-sync/update-project-mapping.md b/docs/api-reference/v1/idp-group-sync/update-project-mapping.md similarity index 100% rename from docs/api-reference/idp-group-sync/update-project-mapping.md rename to docs/api-reference/v1/idp-group-sync/update-project-mapping.md diff --git a/docs/api-reference/idp-group-sync/update-workspace-mapping.md b/docs/api-reference/v1/idp-group-sync/update-workspace-mapping.md similarity index 100% rename from docs/api-reference/idp-group-sync/update-workspace-mapping.md rename to docs/api-reference/v1/idp-group-sync/update-workspace-mapping.md diff --git a/docs/api-reference/inbox-issue/add-inbox-issue.md b/docs/api-reference/v1/inbox-issue/add-inbox-issue.md similarity index 100% rename from docs/api-reference/inbox-issue/add-inbox-issue.md rename to docs/api-reference/v1/inbox-issue/add-inbox-issue.md diff --git a/docs/api-reference/inbox-issue/delete-inbox-issue.md b/docs/api-reference/v1/inbox-issue/delete-inbox-issue.md similarity index 100% rename from docs/api-reference/inbox-issue/delete-inbox-issue.md rename to docs/api-reference/v1/inbox-issue/delete-inbox-issue.md diff --git a/docs/api-reference/inbox-issue/get-inbox-issue-detail.md b/docs/api-reference/v1/inbox-issue/get-inbox-issue-detail.md similarity index 100% rename from docs/api-reference/inbox-issue/get-inbox-issue-detail.md rename to docs/api-reference/v1/inbox-issue/get-inbox-issue-detail.md diff --git a/docs/api-reference/inbox-issue/list-inbox-issues.md b/docs/api-reference/v1/inbox-issue/list-inbox-issues.md similarity index 100% rename from docs/api-reference/inbox-issue/list-inbox-issues.md rename to docs/api-reference/v1/inbox-issue/list-inbox-issues.md diff --git a/docs/api-reference/inbox-issue/overview.md b/docs/api-reference/v1/inbox-issue/overview.md similarity index 100% rename from docs/api-reference/inbox-issue/overview.md rename to docs/api-reference/v1/inbox-issue/overview.md diff --git a/docs/api-reference/inbox-issue/update-inbox-issue-detail.md b/docs/api-reference/v1/inbox-issue/update-inbox-issue-detail.md similarity index 100% rename from docs/api-reference/inbox-issue/update-inbox-issue-detail.md rename to docs/api-reference/v1/inbox-issue/update-inbox-issue-detail.md diff --git a/docs/api-reference/initiative/add-epics-to-initiative.md b/docs/api-reference/v1/initiative/add-epics-to-initiative.md similarity index 100% rename from docs/api-reference/initiative/add-epics-to-initiative.md rename to docs/api-reference/v1/initiative/add-epics-to-initiative.md diff --git a/docs/api-reference/initiative/add-initiative-label.md b/docs/api-reference/v1/initiative/add-initiative-label.md similarity index 100% rename from docs/api-reference/initiative/add-initiative-label.md rename to docs/api-reference/v1/initiative/add-initiative-label.md diff --git a/docs/api-reference/initiative/add-initiative.md b/docs/api-reference/v1/initiative/add-initiative.md similarity index 100% rename from docs/api-reference/initiative/add-initiative.md rename to docs/api-reference/v1/initiative/add-initiative.md diff --git a/docs/api-reference/initiative/add-labels-to-initiative.md b/docs/api-reference/v1/initiative/add-labels-to-initiative.md similarity index 100% rename from docs/api-reference/initiative/add-labels-to-initiative.md rename to docs/api-reference/v1/initiative/add-labels-to-initiative.md diff --git a/docs/api-reference/initiative/add-projects-to-initiative.md b/docs/api-reference/v1/initiative/add-projects-to-initiative.md similarity index 100% rename from docs/api-reference/initiative/add-projects-to-initiative.md rename to docs/api-reference/v1/initiative/add-projects-to-initiative.md diff --git a/docs/api-reference/initiative/delete-initiative-label.md b/docs/api-reference/v1/initiative/delete-initiative-label.md similarity index 100% rename from docs/api-reference/initiative/delete-initiative-label.md rename to docs/api-reference/v1/initiative/delete-initiative-label.md diff --git a/docs/api-reference/initiative/delete-initiative.md b/docs/api-reference/v1/initiative/delete-initiative.md similarity index 100% rename from docs/api-reference/initiative/delete-initiative.md rename to docs/api-reference/v1/initiative/delete-initiative.md diff --git a/docs/api-reference/initiative/get-initiative-detail.md b/docs/api-reference/v1/initiative/get-initiative-detail.md similarity index 100% rename from docs/api-reference/initiative/get-initiative-detail.md rename to docs/api-reference/v1/initiative/get-initiative-detail.md diff --git a/docs/api-reference/initiative/get-initiative-label-detail.md b/docs/api-reference/v1/initiative/get-initiative-label-detail.md similarity index 100% rename from docs/api-reference/initiative/get-initiative-label-detail.md rename to docs/api-reference/v1/initiative/get-initiative-label-detail.md diff --git a/docs/api-reference/initiative/list-initiative-epics.md b/docs/api-reference/v1/initiative/list-initiative-epics.md similarity index 100% rename from docs/api-reference/initiative/list-initiative-epics.md rename to docs/api-reference/v1/initiative/list-initiative-epics.md diff --git a/docs/api-reference/initiative/list-initiative-labels-for-initiative.md b/docs/api-reference/v1/initiative/list-initiative-labels-for-initiative.md similarity index 100% rename from docs/api-reference/initiative/list-initiative-labels-for-initiative.md rename to docs/api-reference/v1/initiative/list-initiative-labels-for-initiative.md diff --git a/docs/api-reference/initiative/list-initiative-labels.md b/docs/api-reference/v1/initiative/list-initiative-labels.md similarity index 100% rename from docs/api-reference/initiative/list-initiative-labels.md rename to docs/api-reference/v1/initiative/list-initiative-labels.md diff --git a/docs/api-reference/initiative/list-initiative-projects.md b/docs/api-reference/v1/initiative/list-initiative-projects.md similarity index 100% rename from docs/api-reference/initiative/list-initiative-projects.md rename to docs/api-reference/v1/initiative/list-initiative-projects.md diff --git a/docs/api-reference/initiative/list-initiatives.md b/docs/api-reference/v1/initiative/list-initiatives.md similarity index 100% rename from docs/api-reference/initiative/list-initiatives.md rename to docs/api-reference/v1/initiative/list-initiatives.md diff --git a/docs/api-reference/initiative/overview.md b/docs/api-reference/v1/initiative/overview.md similarity index 100% rename from docs/api-reference/initiative/overview.md rename to docs/api-reference/v1/initiative/overview.md diff --git a/docs/api-reference/initiative/remove-epics-from-initiative.md b/docs/api-reference/v1/initiative/remove-epics-from-initiative.md similarity index 100% rename from docs/api-reference/initiative/remove-epics-from-initiative.md rename to docs/api-reference/v1/initiative/remove-epics-from-initiative.md diff --git a/docs/api-reference/initiative/remove-labels-from-initiative.md b/docs/api-reference/v1/initiative/remove-labels-from-initiative.md similarity index 100% rename from docs/api-reference/initiative/remove-labels-from-initiative.md rename to docs/api-reference/v1/initiative/remove-labels-from-initiative.md diff --git a/docs/api-reference/initiative/remove-projects-from-initiative.md b/docs/api-reference/v1/initiative/remove-projects-from-initiative.md similarity index 100% rename from docs/api-reference/initiative/remove-projects-from-initiative.md rename to docs/api-reference/v1/initiative/remove-projects-from-initiative.md diff --git a/docs/api-reference/initiative/update-initiative-detail.md b/docs/api-reference/v1/initiative/update-initiative-detail.md similarity index 100% rename from docs/api-reference/initiative/update-initiative-detail.md rename to docs/api-reference/v1/initiative/update-initiative-detail.md diff --git a/docs/api-reference/initiative/update-initiative-label-detail.md b/docs/api-reference/v1/initiative/update-initiative-label-detail.md similarity index 100% rename from docs/api-reference/initiative/update-initiative-label-detail.md rename to docs/api-reference/v1/initiative/update-initiative-label-detail.md diff --git a/docs/api-reference/intake-issue/add-intake-issue.md b/docs/api-reference/v1/intake-issue/add-intake-issue.md similarity index 100% rename from docs/api-reference/intake-issue/add-intake-issue.md rename to docs/api-reference/v1/intake-issue/add-intake-issue.md diff --git a/docs/api-reference/intake-issue/delete-intake-issue.md b/docs/api-reference/v1/intake-issue/delete-intake-issue.md similarity index 100% rename from docs/api-reference/intake-issue/delete-intake-issue.md rename to docs/api-reference/v1/intake-issue/delete-intake-issue.md diff --git a/docs/api-reference/intake-issue/get-intake-issue-detail.md b/docs/api-reference/v1/intake-issue/get-intake-issue-detail.md similarity index 100% rename from docs/api-reference/intake-issue/get-intake-issue-detail.md rename to docs/api-reference/v1/intake-issue/get-intake-issue-detail.md diff --git a/docs/api-reference/intake-issue/list-intake-issues.md b/docs/api-reference/v1/intake-issue/list-intake-issues.md similarity index 100% rename from docs/api-reference/intake-issue/list-intake-issues.md rename to docs/api-reference/v1/intake-issue/list-intake-issues.md diff --git a/docs/api-reference/intake-issue/overview.md b/docs/api-reference/v1/intake-issue/overview.md similarity index 100% rename from docs/api-reference/intake-issue/overview.md rename to docs/api-reference/v1/intake-issue/overview.md diff --git a/docs/api-reference/intake-issue/update-intake-issue-detail.md b/docs/api-reference/v1/intake-issue/update-intake-issue-detail.md similarity index 100% rename from docs/api-reference/intake-issue/update-intake-issue-detail.md rename to docs/api-reference/v1/intake-issue/update-intake-issue-detail.md diff --git a/docs/api-reference/introduction.md b/docs/api-reference/v1/introduction.md similarity index 100% rename from docs/api-reference/introduction.md rename to docs/api-reference/v1/introduction.md diff --git a/docs/api-reference/issue-activity/get-issue-activity-detail.md b/docs/api-reference/v1/issue-activity/get-issue-activity-detail.md similarity index 100% rename from docs/api-reference/issue-activity/get-issue-activity-detail.md rename to docs/api-reference/v1/issue-activity/get-issue-activity-detail.md diff --git a/docs/api-reference/issue-activity/list-issue-activities.md b/docs/api-reference/v1/issue-activity/list-issue-activities.md similarity index 100% rename from docs/api-reference/issue-activity/list-issue-activities.md rename to docs/api-reference/v1/issue-activity/list-issue-activities.md diff --git a/docs/api-reference/issue-activity/overview.md b/docs/api-reference/v1/issue-activity/overview.md similarity index 100% rename from docs/api-reference/issue-activity/overview.md rename to docs/api-reference/v1/issue-activity/overview.md diff --git a/docs/api-reference/issue-attachments/complete-upload.md b/docs/api-reference/v1/issue-attachments/complete-upload.md similarity index 100% rename from docs/api-reference/issue-attachments/complete-upload.md rename to docs/api-reference/v1/issue-attachments/complete-upload.md diff --git a/docs/api-reference/issue-attachments/delete-attachment.md b/docs/api-reference/v1/issue-attachments/delete-attachment.md similarity index 100% rename from docs/api-reference/issue-attachments/delete-attachment.md rename to docs/api-reference/v1/issue-attachments/delete-attachment.md diff --git a/docs/api-reference/issue-attachments/get-attachment-detail.md b/docs/api-reference/v1/issue-attachments/get-attachment-detail.md similarity index 100% rename from docs/api-reference/issue-attachments/get-attachment-detail.md rename to docs/api-reference/v1/issue-attachments/get-attachment-detail.md diff --git a/docs/api-reference/issue-attachments/get-attachments.md b/docs/api-reference/v1/issue-attachments/get-attachments.md similarity index 100% rename from docs/api-reference/issue-attachments/get-attachments.md rename to docs/api-reference/v1/issue-attachments/get-attachments.md diff --git a/docs/api-reference/issue-attachments/get-upload-credentials.md b/docs/api-reference/v1/issue-attachments/get-upload-credentials.md similarity index 100% rename from docs/api-reference/issue-attachments/get-upload-credentials.md rename to docs/api-reference/v1/issue-attachments/get-upload-credentials.md diff --git a/docs/api-reference/issue-attachments/overview.md b/docs/api-reference/v1/issue-attachments/overview.md similarity index 93% rename from docs/api-reference/issue-attachments/overview.md rename to docs/api-reference/v1/issue-attachments/overview.md index f32a0196..3b2f1241 100644 --- a/docs/api-reference/issue-attachments/overview.md +++ b/docs/api-reference/v1/issue-attachments/overview.md @@ -12,9 +12,9 @@ Allows you to manage file attachments associated with work items and Intake work ## Upload process -1. Get the [upload credentials](/api-reference/issue-attachments/get-upload-credentials). -2. [Upload the file](/api-reference/issue-attachments/upload-file) to storage. -3. [Complete attachment upload](/api-reference/issue-attachments/complete-upload) to notify server. +1. Get the [upload credentials](/api-reference/v1/issue-attachments/get-upload-credentials). +2. [Upload the file](/api-reference/v1/issue-attachments/upload-file) to storage. +3. [Complete attachment upload](/api-reference/v1/issue-attachments/complete-upload) to notify server.
diff --git a/docs/api-reference/issue-attachments/update-attachment.md b/docs/api-reference/v1/issue-attachments/update-attachment.md similarity index 100% rename from docs/api-reference/issue-attachments/update-attachment.md rename to docs/api-reference/v1/issue-attachments/update-attachment.md diff --git a/docs/api-reference/issue-attachments/upload-file.md b/docs/api-reference/v1/issue-attachments/upload-file.md similarity index 100% rename from docs/api-reference/issue-attachments/upload-file.md rename to docs/api-reference/v1/issue-attachments/upload-file.md diff --git a/docs/api-reference/issue-comment/add-issue-comment.md b/docs/api-reference/v1/issue-comment/add-issue-comment.md similarity index 100% rename from docs/api-reference/issue-comment/add-issue-comment.md rename to docs/api-reference/v1/issue-comment/add-issue-comment.md diff --git a/docs/api-reference/issue-comment/delete-issue-comment.md b/docs/api-reference/v1/issue-comment/delete-issue-comment.md similarity index 100% rename from docs/api-reference/issue-comment/delete-issue-comment.md rename to docs/api-reference/v1/issue-comment/delete-issue-comment.md diff --git a/docs/api-reference/issue-comment/get-issue-comment-detail.md b/docs/api-reference/v1/issue-comment/get-issue-comment-detail.md similarity index 100% rename from docs/api-reference/issue-comment/get-issue-comment-detail.md rename to docs/api-reference/v1/issue-comment/get-issue-comment-detail.md diff --git a/docs/api-reference/issue-comment/list-issue-comments.md b/docs/api-reference/v1/issue-comment/list-issue-comments.md similarity index 100% rename from docs/api-reference/issue-comment/list-issue-comments.md rename to docs/api-reference/v1/issue-comment/list-issue-comments.md diff --git a/docs/api-reference/issue-comment/overview.md b/docs/api-reference/v1/issue-comment/overview.md similarity index 100% rename from docs/api-reference/issue-comment/overview.md rename to docs/api-reference/v1/issue-comment/overview.md diff --git a/docs/api-reference/issue-comment/update-issue-comment-detail.md b/docs/api-reference/v1/issue-comment/update-issue-comment-detail.md similarity index 100% rename from docs/api-reference/issue-comment/update-issue-comment-detail.md rename to docs/api-reference/v1/issue-comment/update-issue-comment-detail.md diff --git a/docs/api-reference/issue-types/options/add-dropdown-options.md b/docs/api-reference/v1/issue-types/options/add-dropdown-options.md similarity index 100% rename from docs/api-reference/issue-types/options/add-dropdown-options.md rename to docs/api-reference/v1/issue-types/options/add-dropdown-options.md diff --git a/docs/api-reference/issue-types/options/delete-dropdown-options.md b/docs/api-reference/v1/issue-types/options/delete-dropdown-options.md similarity index 100% rename from docs/api-reference/issue-types/options/delete-dropdown-options.md rename to docs/api-reference/v1/issue-types/options/delete-dropdown-options.md diff --git a/docs/api-reference/issue-types/options/get-option-details.md b/docs/api-reference/v1/issue-types/options/get-option-details.md similarity index 100% rename from docs/api-reference/issue-types/options/get-option-details.md rename to docs/api-reference/v1/issue-types/options/get-option-details.md diff --git a/docs/api-reference/issue-types/options/list-dropdown-options.md b/docs/api-reference/v1/issue-types/options/list-dropdown-options.md similarity index 100% rename from docs/api-reference/issue-types/options/list-dropdown-options.md rename to docs/api-reference/v1/issue-types/options/list-dropdown-options.md diff --git a/docs/api-reference/issue-types/options/overview.md b/docs/api-reference/v1/issue-types/options/overview.md similarity index 100% rename from docs/api-reference/issue-types/options/overview.md rename to docs/api-reference/v1/issue-types/options/overview.md diff --git a/docs/api-reference/issue-types/options/update-dropdown-options.md b/docs/api-reference/v1/issue-types/options/update-dropdown-options.md similarity index 100% rename from docs/api-reference/issue-types/options/update-dropdown-options.md rename to docs/api-reference/v1/issue-types/options/update-dropdown-options.md diff --git a/docs/api-reference/issue-types/properties/add-property.md b/docs/api-reference/v1/issue-types/properties/add-property.md similarity index 100% rename from docs/api-reference/issue-types/properties/add-property.md rename to docs/api-reference/v1/issue-types/properties/add-property.md diff --git a/docs/api-reference/issue-types/properties/delete-property.md b/docs/api-reference/v1/issue-types/properties/delete-property.md similarity index 100% rename from docs/api-reference/issue-types/properties/delete-property.md rename to docs/api-reference/v1/issue-types/properties/delete-property.md diff --git a/docs/api-reference/issue-types/properties/get-property-details.md b/docs/api-reference/v1/issue-types/properties/get-property-details.md similarity index 100% rename from docs/api-reference/issue-types/properties/get-property-details.md rename to docs/api-reference/v1/issue-types/properties/get-property-details.md diff --git a/docs/api-reference/issue-types/properties/list-properties.md b/docs/api-reference/v1/issue-types/properties/list-properties.md similarity index 100% rename from docs/api-reference/issue-types/properties/list-properties.md rename to docs/api-reference/v1/issue-types/properties/list-properties.md diff --git a/docs/api-reference/issue-types/properties/overview.md b/docs/api-reference/v1/issue-types/properties/overview.md similarity index 100% rename from docs/api-reference/issue-types/properties/overview.md rename to docs/api-reference/v1/issue-types/properties/overview.md diff --git a/docs/api-reference/issue-types/properties/update-property.md b/docs/api-reference/v1/issue-types/properties/update-property.md similarity index 100% rename from docs/api-reference/issue-types/properties/update-property.md rename to docs/api-reference/v1/issue-types/properties/update-property.md diff --git a/docs/api-reference/issue-types/types/add-issue-type.md b/docs/api-reference/v1/issue-types/types/add-issue-type.md similarity index 100% rename from docs/api-reference/issue-types/types/add-issue-type.md rename to docs/api-reference/v1/issue-types/types/add-issue-type.md diff --git a/docs/api-reference/issue-types/types/delete-issue-type.md b/docs/api-reference/v1/issue-types/types/delete-issue-type.md similarity index 100% rename from docs/api-reference/issue-types/types/delete-issue-type.md rename to docs/api-reference/v1/issue-types/types/delete-issue-type.md diff --git a/docs/api-reference/issue-types/types/get-issue-type-details.md b/docs/api-reference/v1/issue-types/types/get-issue-type-details.md similarity index 100% rename from docs/api-reference/issue-types/types/get-issue-type-details.md rename to docs/api-reference/v1/issue-types/types/get-issue-type-details.md diff --git a/docs/api-reference/issue-types/types/get-work-item-type-schema.md b/docs/api-reference/v1/issue-types/types/get-work-item-type-schema.md similarity index 100% rename from docs/api-reference/issue-types/types/get-work-item-type-schema.md rename to docs/api-reference/v1/issue-types/types/get-work-item-type-schema.md diff --git a/docs/api-reference/issue-types/types/list-issue-types.md b/docs/api-reference/v1/issue-types/types/list-issue-types.md similarity index 100% rename from docs/api-reference/issue-types/types/list-issue-types.md rename to docs/api-reference/v1/issue-types/types/list-issue-types.md diff --git a/docs/api-reference/issue-types/types/overview.md b/docs/api-reference/v1/issue-types/types/overview.md similarity index 100% rename from docs/api-reference/issue-types/types/overview.md rename to docs/api-reference/v1/issue-types/types/overview.md diff --git a/docs/api-reference/issue-types/types/update-issue-types.md b/docs/api-reference/v1/issue-types/types/update-issue-types.md similarity index 100% rename from docs/api-reference/issue-types/types/update-issue-types.md rename to docs/api-reference/v1/issue-types/types/update-issue-types.md diff --git a/docs/api-reference/issue-types/values/add-property-values.md b/docs/api-reference/v1/issue-types/values/add-property-values.md similarity index 100% rename from docs/api-reference/issue-types/values/add-property-values.md rename to docs/api-reference/v1/issue-types/values/add-property-values.md diff --git a/docs/api-reference/issue-types/values/delete-property-value.md b/docs/api-reference/v1/issue-types/values/delete-property-value.md similarity index 100% rename from docs/api-reference/issue-types/values/delete-property-value.md rename to docs/api-reference/v1/issue-types/values/delete-property-value.md diff --git a/docs/api-reference/issue-types/values/get-property-value-detail.md b/docs/api-reference/v1/issue-types/values/get-property-value-detail.md similarity index 100% rename from docs/api-reference/issue-types/values/get-property-value-detail.md rename to docs/api-reference/v1/issue-types/values/get-property-value-detail.md diff --git a/docs/api-reference/issue-types/values/list-property-values.md b/docs/api-reference/v1/issue-types/values/list-property-values.md similarity index 100% rename from docs/api-reference/issue-types/values/list-property-values.md rename to docs/api-reference/v1/issue-types/values/list-property-values.md diff --git a/docs/api-reference/issue-types/values/overview.md b/docs/api-reference/v1/issue-types/values/overview.md similarity index 100% rename from docs/api-reference/issue-types/values/overview.md rename to docs/api-reference/v1/issue-types/values/overview.md diff --git a/docs/api-reference/issue-types/values/update-property-value.md b/docs/api-reference/v1/issue-types/values/update-property-value.md similarity index 100% rename from docs/api-reference/issue-types/values/update-property-value.md rename to docs/api-reference/v1/issue-types/values/update-property-value.md diff --git a/docs/api-reference/issue/add-issue.md b/docs/api-reference/v1/issue/add-issue.md similarity index 100% rename from docs/api-reference/issue/add-issue.md rename to docs/api-reference/v1/issue/add-issue.md diff --git a/docs/api-reference/issue/advanced-search-work-items.md b/docs/api-reference/v1/issue/advanced-search-work-items.md similarity index 100% rename from docs/api-reference/issue/advanced-search-work-items.md rename to docs/api-reference/v1/issue/advanced-search-work-items.md diff --git a/docs/api-reference/issue/delete-issue.md b/docs/api-reference/v1/issue/delete-issue.md similarity index 100% rename from docs/api-reference/issue/delete-issue.md rename to docs/api-reference/v1/issue/delete-issue.md diff --git a/docs/api-reference/issue/get-issue-detail.md b/docs/api-reference/v1/issue/get-issue-detail.md similarity index 100% rename from docs/api-reference/issue/get-issue-detail.md rename to docs/api-reference/v1/issue/get-issue-detail.md diff --git a/docs/api-reference/issue/get-issue-sequence-id.md b/docs/api-reference/v1/issue/get-issue-sequence-id.md similarity index 100% rename from docs/api-reference/issue/get-issue-sequence-id.md rename to docs/api-reference/v1/issue/get-issue-sequence-id.md diff --git a/docs/api-reference/issue/list-issues.md b/docs/api-reference/v1/issue/list-issues.md similarity index 100% rename from docs/api-reference/issue/list-issues.md rename to docs/api-reference/v1/issue/list-issues.md diff --git a/docs/api-reference/issue/overview.md b/docs/api-reference/v1/issue/overview.md similarity index 100% rename from docs/api-reference/issue/overview.md rename to docs/api-reference/v1/issue/overview.md diff --git a/docs/api-reference/issue/search-issues.md b/docs/api-reference/v1/issue/search-issues.md similarity index 100% rename from docs/api-reference/issue/search-issues.md rename to docs/api-reference/v1/issue/search-issues.md diff --git a/docs/api-reference/issue/update-issue-detail.md b/docs/api-reference/v1/issue/update-issue-detail.md similarity index 100% rename from docs/api-reference/issue/update-issue-detail.md rename to docs/api-reference/v1/issue/update-issue-detail.md diff --git a/docs/api-reference/label/add-label.md b/docs/api-reference/v1/label/add-label.md similarity index 100% rename from docs/api-reference/label/add-label.md rename to docs/api-reference/v1/label/add-label.md diff --git a/docs/api-reference/label/delete-label.md b/docs/api-reference/v1/label/delete-label.md similarity index 100% rename from docs/api-reference/label/delete-label.md rename to docs/api-reference/v1/label/delete-label.md diff --git a/docs/api-reference/label/get-label-detail.md b/docs/api-reference/v1/label/get-label-detail.md similarity index 100% rename from docs/api-reference/label/get-label-detail.md rename to docs/api-reference/v1/label/get-label-detail.md diff --git a/docs/api-reference/label/list-labels.md b/docs/api-reference/v1/label/list-labels.md similarity index 100% rename from docs/api-reference/label/list-labels.md rename to docs/api-reference/v1/label/list-labels.md diff --git a/docs/api-reference/label/overview.md b/docs/api-reference/v1/label/overview.md similarity index 100% rename from docs/api-reference/label/overview.md rename to docs/api-reference/v1/label/overview.md diff --git a/docs/api-reference/label/update-label-detail.md b/docs/api-reference/v1/label/update-label-detail.md similarity index 100% rename from docs/api-reference/label/update-label-detail.md rename to docs/api-reference/v1/label/update-label-detail.md diff --git a/docs/api-reference/link/add-link.md b/docs/api-reference/v1/link/add-link.md similarity index 100% rename from docs/api-reference/link/add-link.md rename to docs/api-reference/v1/link/add-link.md diff --git a/docs/api-reference/link/delete-link.md b/docs/api-reference/v1/link/delete-link.md similarity index 100% rename from docs/api-reference/link/delete-link.md rename to docs/api-reference/v1/link/delete-link.md diff --git a/docs/api-reference/link/get-link-detail.md b/docs/api-reference/v1/link/get-link-detail.md similarity index 100% rename from docs/api-reference/link/get-link-detail.md rename to docs/api-reference/v1/link/get-link-detail.md diff --git a/docs/api-reference/link/list-links.md b/docs/api-reference/v1/link/list-links.md similarity index 100% rename from docs/api-reference/link/list-links.md rename to docs/api-reference/v1/link/list-links.md diff --git a/docs/api-reference/link/overview.md b/docs/api-reference/v1/link/overview.md similarity index 100% rename from docs/api-reference/link/overview.md rename to docs/api-reference/v1/link/overview.md diff --git a/docs/api-reference/link/update-link-detail.md b/docs/api-reference/v1/link/update-link-detail.md similarity index 100% rename from docs/api-reference/link/update-link-detail.md rename to docs/api-reference/v1/link/update-link-detail.md diff --git a/docs/api-reference/members/add-project-member.md b/docs/api-reference/v1/members/add-project-member.md similarity index 100% rename from docs/api-reference/members/add-project-member.md rename to docs/api-reference/v1/members/add-project-member.md diff --git a/docs/api-reference/members/delete-project-member.md b/docs/api-reference/v1/members/delete-project-member.md similarity index 100% rename from docs/api-reference/members/delete-project-member.md rename to docs/api-reference/v1/members/delete-project-member.md diff --git a/docs/api-reference/members/get-project-member-detail.md b/docs/api-reference/v1/members/get-project-member-detail.md similarity index 100% rename from docs/api-reference/members/get-project-member-detail.md rename to docs/api-reference/v1/members/get-project-member-detail.md diff --git a/docs/api-reference/members/get-project-members.md b/docs/api-reference/v1/members/get-project-members.md similarity index 100% rename from docs/api-reference/members/get-project-members.md rename to docs/api-reference/v1/members/get-project-members.md diff --git a/docs/api-reference/members/get-workspace-members.md b/docs/api-reference/v1/members/get-workspace-members.md similarity index 100% rename from docs/api-reference/members/get-workspace-members.md rename to docs/api-reference/v1/members/get-workspace-members.md diff --git a/docs/api-reference/members/overview.md b/docs/api-reference/v1/members/overview.md similarity index 100% rename from docs/api-reference/members/overview.md rename to docs/api-reference/v1/members/overview.md diff --git a/docs/api-reference/members/remove-workspace-member.md b/docs/api-reference/v1/members/remove-workspace-member.md similarity index 100% rename from docs/api-reference/members/remove-workspace-member.md rename to docs/api-reference/v1/members/remove-workspace-member.md diff --git a/docs/api-reference/members/update-project-member.md b/docs/api-reference/v1/members/update-project-member.md similarity index 100% rename from docs/api-reference/members/update-project-member.md rename to docs/api-reference/v1/members/update-project-member.md diff --git a/docs/api-reference/milestones/add-milestone.md b/docs/api-reference/v1/milestones/add-milestone.md similarity index 100% rename from docs/api-reference/milestones/add-milestone.md rename to docs/api-reference/v1/milestones/add-milestone.md diff --git a/docs/api-reference/milestones/delete-milestone.md b/docs/api-reference/v1/milestones/delete-milestone.md similarity index 100% rename from docs/api-reference/milestones/delete-milestone.md rename to docs/api-reference/v1/milestones/delete-milestone.md diff --git a/docs/api-reference/milestones/get-milestone-detail.md b/docs/api-reference/v1/milestones/get-milestone-detail.md similarity index 100% rename from docs/api-reference/milestones/get-milestone-detail.md rename to docs/api-reference/v1/milestones/get-milestone-detail.md diff --git a/docs/api-reference/milestones/list-milestone-work-items.md b/docs/api-reference/v1/milestones/list-milestone-work-items.md similarity index 100% rename from docs/api-reference/milestones/list-milestone-work-items.md rename to docs/api-reference/v1/milestones/list-milestone-work-items.md diff --git a/docs/api-reference/milestones/list-milestones.md b/docs/api-reference/v1/milestones/list-milestones.md similarity index 100% rename from docs/api-reference/milestones/list-milestones.md rename to docs/api-reference/v1/milestones/list-milestones.md diff --git a/docs/api-reference/milestones/overview.md b/docs/api-reference/v1/milestones/overview.md similarity index 100% rename from docs/api-reference/milestones/overview.md rename to docs/api-reference/v1/milestones/overview.md diff --git a/docs/api-reference/milestones/update-milestone-detail.md b/docs/api-reference/v1/milestones/update-milestone-detail.md similarity index 100% rename from docs/api-reference/milestones/update-milestone-detail.md rename to docs/api-reference/v1/milestones/update-milestone-detail.md diff --git a/docs/api-reference/module/add-module-work-items.md b/docs/api-reference/v1/module/add-module-work-items.md similarity index 100% rename from docs/api-reference/module/add-module-work-items.md rename to docs/api-reference/v1/module/add-module-work-items.md diff --git a/docs/api-reference/module/add-module.md b/docs/api-reference/v1/module/add-module.md similarity index 100% rename from docs/api-reference/module/add-module.md rename to docs/api-reference/v1/module/add-module.md diff --git a/docs/api-reference/module/archive-module.md b/docs/api-reference/v1/module/archive-module.md similarity index 100% rename from docs/api-reference/module/archive-module.md rename to docs/api-reference/v1/module/archive-module.md diff --git a/docs/api-reference/module/delete-module.md b/docs/api-reference/v1/module/delete-module.md similarity index 100% rename from docs/api-reference/module/delete-module.md rename to docs/api-reference/v1/module/delete-module.md diff --git a/docs/api-reference/module/get-module-detail.md b/docs/api-reference/v1/module/get-module-detail.md similarity index 100% rename from docs/api-reference/module/get-module-detail.md rename to docs/api-reference/v1/module/get-module-detail.md diff --git a/docs/api-reference/module/list-archived-modules.md b/docs/api-reference/v1/module/list-archived-modules.md similarity index 100% rename from docs/api-reference/module/list-archived-modules.md rename to docs/api-reference/v1/module/list-archived-modules.md diff --git a/docs/api-reference/module/list-module-work-items.md b/docs/api-reference/v1/module/list-module-work-items.md similarity index 100% rename from docs/api-reference/module/list-module-work-items.md rename to docs/api-reference/v1/module/list-module-work-items.md diff --git a/docs/api-reference/module/list-modules.md b/docs/api-reference/v1/module/list-modules.md similarity index 100% rename from docs/api-reference/module/list-modules.md rename to docs/api-reference/v1/module/list-modules.md diff --git a/docs/api-reference/module/overview.md b/docs/api-reference/v1/module/overview.md similarity index 100% rename from docs/api-reference/module/overview.md rename to docs/api-reference/v1/module/overview.md diff --git a/docs/api-reference/module/remove-module-work-item.md b/docs/api-reference/v1/module/remove-module-work-item.md similarity index 100% rename from docs/api-reference/module/remove-module-work-item.md rename to docs/api-reference/v1/module/remove-module-work-item.md diff --git a/docs/api-reference/module/unarchive-module.md b/docs/api-reference/v1/module/unarchive-module.md similarity index 100% rename from docs/api-reference/module/unarchive-module.md rename to docs/api-reference/v1/module/unarchive-module.md diff --git a/docs/api-reference/module/update-module-detail.md b/docs/api-reference/v1/module/update-module-detail.md similarity index 100% rename from docs/api-reference/module/update-module-detail.md rename to docs/api-reference/v1/module/update-module-detail.md diff --git a/docs/api-reference/page/add-project-page.md b/docs/api-reference/v1/page/add-project-page.md similarity index 100% rename from docs/api-reference/page/add-project-page.md rename to docs/api-reference/v1/page/add-project-page.md diff --git a/docs/api-reference/page/add-workspace-page.md b/docs/api-reference/v1/page/add-workspace-page.md similarity index 100% rename from docs/api-reference/page/add-workspace-page.md rename to docs/api-reference/v1/page/add-workspace-page.md diff --git a/docs/api-reference/page/get-project-page.md b/docs/api-reference/v1/page/get-project-page.md similarity index 100% rename from docs/api-reference/page/get-project-page.md rename to docs/api-reference/v1/page/get-project-page.md diff --git a/docs/api-reference/page/get-workspace-page.md b/docs/api-reference/v1/page/get-workspace-page.md similarity index 100% rename from docs/api-reference/page/get-workspace-page.md rename to docs/api-reference/v1/page/get-workspace-page.md diff --git a/docs/api-reference/page/list-project-pages.md b/docs/api-reference/v1/page/list-project-pages.md similarity index 100% rename from docs/api-reference/page/list-project-pages.md rename to docs/api-reference/v1/page/list-project-pages.md diff --git a/docs/api-reference/page/list-workspace-pages.md b/docs/api-reference/v1/page/list-workspace-pages.md similarity index 100% rename from docs/api-reference/page/list-workspace-pages.md rename to docs/api-reference/v1/page/list-workspace-pages.md diff --git a/docs/api-reference/page/overview.md b/docs/api-reference/v1/page/overview.md similarity index 100% rename from docs/api-reference/page/overview.md rename to docs/api-reference/v1/page/overview.md diff --git a/docs/api-reference/project-features/get-project-features.md b/docs/api-reference/v1/project-features/get-project-features.md similarity index 100% rename from docs/api-reference/project-features/get-project-features.md rename to docs/api-reference/v1/project-features/get-project-features.md diff --git a/docs/api-reference/project-features/overview.md b/docs/api-reference/v1/project-features/overview.md similarity index 100% rename from docs/api-reference/project-features/overview.md rename to docs/api-reference/v1/project-features/overview.md diff --git a/docs/api-reference/project-features/update-project-features.md b/docs/api-reference/v1/project-features/update-project-features.md similarity index 100% rename from docs/api-reference/project-features/update-project-features.md rename to docs/api-reference/v1/project-features/update-project-features.md diff --git a/docs/api-reference/project-labels/add-project-label.md b/docs/api-reference/v1/project-labels/add-project-label.md similarity index 100% rename from docs/api-reference/project-labels/add-project-label.md rename to docs/api-reference/v1/project-labels/add-project-label.md diff --git a/docs/api-reference/project-labels/delete-project-label.md b/docs/api-reference/v1/project-labels/delete-project-label.md similarity index 100% rename from docs/api-reference/project-labels/delete-project-label.md rename to docs/api-reference/v1/project-labels/delete-project-label.md diff --git a/docs/api-reference/project-labels/get-project-label-detail.md b/docs/api-reference/v1/project-labels/get-project-label-detail.md similarity index 100% rename from docs/api-reference/project-labels/get-project-label-detail.md rename to docs/api-reference/v1/project-labels/get-project-label-detail.md diff --git a/docs/api-reference/project-labels/list-project-labels.md b/docs/api-reference/v1/project-labels/list-project-labels.md similarity index 100% rename from docs/api-reference/project-labels/list-project-labels.md rename to docs/api-reference/v1/project-labels/list-project-labels.md diff --git a/docs/api-reference/project-labels/overview.md b/docs/api-reference/v1/project-labels/overview.md similarity index 98% rename from docs/api-reference/project-labels/overview.md rename to docs/api-reference/v1/project-labels/overview.md index e5f29224..7aad212e 100644 --- a/docs/api-reference/project-labels/overview.md +++ b/docs/api-reference/v1/project-labels/overview.md @@ -8,7 +8,7 @@ keywords: plane, plane api, rest api, api integration, project labels, workspace Project labels define reusable classifications that can be attached to projects across a workspace. -[Learn more about Projects](https://developers.plane.so/api-reference/project/overview) +[Learn more about Projects](https://developers.plane.so/api-reference/v1/project/overview)
diff --git a/docs/api-reference/project-labels/update-project-label-detail.md b/docs/api-reference/v1/project-labels/update-project-label-detail.md similarity index 100% rename from docs/api-reference/project-labels/update-project-label-detail.md rename to docs/api-reference/v1/project-labels/update-project-label-detail.md diff --git a/docs/api-reference/project/add-project.md b/docs/api-reference/v1/project/add-project.md similarity index 100% rename from docs/api-reference/project/add-project.md rename to docs/api-reference/v1/project/add-project.md diff --git a/docs/api-reference/project/archive-project.md b/docs/api-reference/v1/project/archive-project.md similarity index 100% rename from docs/api-reference/project/archive-project.md rename to docs/api-reference/v1/project/archive-project.md diff --git a/docs/api-reference/project/create-project-with-template.md b/docs/api-reference/v1/project/create-project-with-template.md similarity index 100% rename from docs/api-reference/project/create-project-with-template.md rename to docs/api-reference/v1/project/create-project-with-template.md diff --git a/docs/api-reference/project/delete-project.md b/docs/api-reference/v1/project/delete-project.md similarity index 100% rename from docs/api-reference/project/delete-project.md rename to docs/api-reference/v1/project/delete-project.md diff --git a/docs/api-reference/project/get-project-detail.md b/docs/api-reference/v1/project/get-project-detail.md similarity index 100% rename from docs/api-reference/project/get-project-detail.md rename to docs/api-reference/v1/project/get-project-detail.md diff --git a/docs/api-reference/project/list-projects.md b/docs/api-reference/v1/project/list-projects.md similarity index 100% rename from docs/api-reference/project/list-projects.md rename to docs/api-reference/v1/project/list-projects.md diff --git a/docs/api-reference/project/overview.md b/docs/api-reference/v1/project/overview.md similarity index 100% rename from docs/api-reference/project/overview.md rename to docs/api-reference/v1/project/overview.md diff --git a/docs/api-reference/project/unarchive-project.md b/docs/api-reference/v1/project/unarchive-project.md similarity index 100% rename from docs/api-reference/project/unarchive-project.md rename to docs/api-reference/v1/project/unarchive-project.md diff --git a/docs/api-reference/project/update-project-detail.md b/docs/api-reference/v1/project/update-project-detail.md similarity index 100% rename from docs/api-reference/project/update-project-detail.md rename to docs/api-reference/v1/project/update-project-detail.md diff --git a/docs/api-reference/state/add-state.md b/docs/api-reference/v1/state/add-state.md similarity index 100% rename from docs/api-reference/state/add-state.md rename to docs/api-reference/v1/state/add-state.md diff --git a/docs/api-reference/state/delete-state.md b/docs/api-reference/v1/state/delete-state.md similarity index 100% rename from docs/api-reference/state/delete-state.md rename to docs/api-reference/v1/state/delete-state.md diff --git a/docs/api-reference/state/get-state-detail.md b/docs/api-reference/v1/state/get-state-detail.md similarity index 100% rename from docs/api-reference/state/get-state-detail.md rename to docs/api-reference/v1/state/get-state-detail.md diff --git a/docs/api-reference/state/list-states.md b/docs/api-reference/v1/state/list-states.md similarity index 100% rename from docs/api-reference/state/list-states.md rename to docs/api-reference/v1/state/list-states.md diff --git a/docs/api-reference/state/overview.md b/docs/api-reference/v1/state/overview.md similarity index 100% rename from docs/api-reference/state/overview.md rename to docs/api-reference/v1/state/overview.md diff --git a/docs/api-reference/state/update-state-detail.md b/docs/api-reference/v1/state/update-state-detail.md similarity index 100% rename from docs/api-reference/state/update-state-detail.md rename to docs/api-reference/v1/state/update-state-detail.md diff --git a/docs/api-reference/sticky/add-sticky.md b/docs/api-reference/v1/sticky/add-sticky.md similarity index 100% rename from docs/api-reference/sticky/add-sticky.md rename to docs/api-reference/v1/sticky/add-sticky.md diff --git a/docs/api-reference/sticky/delete-sticky.md b/docs/api-reference/v1/sticky/delete-sticky.md similarity index 100% rename from docs/api-reference/sticky/delete-sticky.md rename to docs/api-reference/v1/sticky/delete-sticky.md diff --git a/docs/api-reference/sticky/get-sticky-detail.md b/docs/api-reference/v1/sticky/get-sticky-detail.md similarity index 100% rename from docs/api-reference/sticky/get-sticky-detail.md rename to docs/api-reference/v1/sticky/get-sticky-detail.md diff --git a/docs/api-reference/sticky/list-stickies.md b/docs/api-reference/v1/sticky/list-stickies.md similarity index 100% rename from docs/api-reference/sticky/list-stickies.md rename to docs/api-reference/v1/sticky/list-stickies.md diff --git a/docs/api-reference/sticky/overview.md b/docs/api-reference/v1/sticky/overview.md similarity index 100% rename from docs/api-reference/sticky/overview.md rename to docs/api-reference/v1/sticky/overview.md diff --git a/docs/api-reference/sticky/update-sticky-detail.md b/docs/api-reference/v1/sticky/update-sticky-detail.md similarity index 100% rename from docs/api-reference/sticky/update-sticky-detail.md rename to docs/api-reference/v1/sticky/update-sticky-detail.md diff --git a/docs/api-reference/teamspace/add-projects-to-teamspace.md b/docs/api-reference/v1/teamspace/add-projects-to-teamspace.md similarity index 100% rename from docs/api-reference/teamspace/add-projects-to-teamspace.md rename to docs/api-reference/v1/teamspace/add-projects-to-teamspace.md diff --git a/docs/api-reference/teamspace/add-teamspace-members.md b/docs/api-reference/v1/teamspace/add-teamspace-members.md similarity index 100% rename from docs/api-reference/teamspace/add-teamspace-members.md rename to docs/api-reference/v1/teamspace/add-teamspace-members.md diff --git a/docs/api-reference/teamspace/add-teamspace.md b/docs/api-reference/v1/teamspace/add-teamspace.md similarity index 100% rename from docs/api-reference/teamspace/add-teamspace.md rename to docs/api-reference/v1/teamspace/add-teamspace.md diff --git a/docs/api-reference/teamspace/delete-teamspace.md b/docs/api-reference/v1/teamspace/delete-teamspace.md similarity index 100% rename from docs/api-reference/teamspace/delete-teamspace.md rename to docs/api-reference/v1/teamspace/delete-teamspace.md diff --git a/docs/api-reference/teamspace/get-teamspace-detail.md b/docs/api-reference/v1/teamspace/get-teamspace-detail.md similarity index 100% rename from docs/api-reference/teamspace/get-teamspace-detail.md rename to docs/api-reference/v1/teamspace/get-teamspace-detail.md diff --git a/docs/api-reference/teamspace/list-teamspace-members.md b/docs/api-reference/v1/teamspace/list-teamspace-members.md similarity index 100% rename from docs/api-reference/teamspace/list-teamspace-members.md rename to docs/api-reference/v1/teamspace/list-teamspace-members.md diff --git a/docs/api-reference/teamspace/list-teamspace-projects.md b/docs/api-reference/v1/teamspace/list-teamspace-projects.md similarity index 100% rename from docs/api-reference/teamspace/list-teamspace-projects.md rename to docs/api-reference/v1/teamspace/list-teamspace-projects.md diff --git a/docs/api-reference/teamspace/list-teamspaces.md b/docs/api-reference/v1/teamspace/list-teamspaces.md similarity index 100% rename from docs/api-reference/teamspace/list-teamspaces.md rename to docs/api-reference/v1/teamspace/list-teamspaces.md diff --git a/docs/api-reference/teamspace/overview.md b/docs/api-reference/v1/teamspace/overview.md similarity index 100% rename from docs/api-reference/teamspace/overview.md rename to docs/api-reference/v1/teamspace/overview.md diff --git a/docs/api-reference/teamspace/remove-projects-from-teamspace.md b/docs/api-reference/v1/teamspace/remove-projects-from-teamspace.md similarity index 100% rename from docs/api-reference/teamspace/remove-projects-from-teamspace.md rename to docs/api-reference/v1/teamspace/remove-projects-from-teamspace.md diff --git a/docs/api-reference/teamspace/remove-teamspace-members.md b/docs/api-reference/v1/teamspace/remove-teamspace-members.md similarity index 100% rename from docs/api-reference/teamspace/remove-teamspace-members.md rename to docs/api-reference/v1/teamspace/remove-teamspace-members.md diff --git a/docs/api-reference/teamspace/update-teamspace-detail.md b/docs/api-reference/v1/teamspace/update-teamspace-detail.md similarity index 100% rename from docs/api-reference/teamspace/update-teamspace-detail.md rename to docs/api-reference/v1/teamspace/update-teamspace-detail.md diff --git a/docs/api-reference/user/get-current-user.md b/docs/api-reference/v1/user/get-current-user.md similarity index 100% rename from docs/api-reference/user/get-current-user.md rename to docs/api-reference/v1/user/get-current-user.md diff --git a/docs/api-reference/user/overview.md b/docs/api-reference/v1/user/overview.md similarity index 100% rename from docs/api-reference/user/overview.md rename to docs/api-reference/v1/user/overview.md diff --git a/docs/api-reference/work-item-pages/add-work-item-page.md b/docs/api-reference/v1/work-item-pages/add-work-item-page.md similarity index 100% rename from docs/api-reference/work-item-pages/add-work-item-page.md rename to docs/api-reference/v1/work-item-pages/add-work-item-page.md diff --git a/docs/api-reference/work-item-pages/delete-work-item-page.md b/docs/api-reference/v1/work-item-pages/delete-work-item-page.md similarity index 100% rename from docs/api-reference/work-item-pages/delete-work-item-page.md rename to docs/api-reference/v1/work-item-pages/delete-work-item-page.md diff --git a/docs/api-reference/work-item-pages/get-work-item-page-detail.md b/docs/api-reference/v1/work-item-pages/get-work-item-page-detail.md similarity index 100% rename from docs/api-reference/work-item-pages/get-work-item-page-detail.md rename to docs/api-reference/v1/work-item-pages/get-work-item-page-detail.md diff --git a/docs/api-reference/work-item-pages/list-work-item-pages.md b/docs/api-reference/v1/work-item-pages/list-work-item-pages.md similarity index 100% rename from docs/api-reference/work-item-pages/list-work-item-pages.md rename to docs/api-reference/v1/work-item-pages/list-work-item-pages.md diff --git a/docs/api-reference/work-item-pages/overview.md b/docs/api-reference/v1/work-item-pages/overview.md similarity index 100% rename from docs/api-reference/work-item-pages/overview.md rename to docs/api-reference/v1/work-item-pages/overview.md diff --git a/docs/api-reference/work-item-relations/create-work-item-relation.md b/docs/api-reference/v1/work-item-relations/create-work-item-relation.md similarity index 100% rename from docs/api-reference/work-item-relations/create-work-item-relation.md rename to docs/api-reference/v1/work-item-relations/create-work-item-relation.md diff --git a/docs/api-reference/work-item-relations/list-work-item-relations.md b/docs/api-reference/v1/work-item-relations/list-work-item-relations.md similarity index 100% rename from docs/api-reference/work-item-relations/list-work-item-relations.md rename to docs/api-reference/v1/work-item-relations/list-work-item-relations.md diff --git a/docs/api-reference/work-item-relations/overview.md b/docs/api-reference/v1/work-item-relations/overview.md similarity index 100% rename from docs/api-reference/work-item-relations/overview.md rename to docs/api-reference/v1/work-item-relations/overview.md diff --git a/docs/api-reference/work-item-relations/remove-work-item-relation.md b/docs/api-reference/v1/work-item-relations/remove-work-item-relation.md similarity index 100% rename from docs/api-reference/work-item-relations/remove-work-item-relation.md rename to docs/api-reference/v1/work-item-relations/remove-work-item-relation.md diff --git a/docs/api-reference/worklogs/create-worklog.md b/docs/api-reference/v1/worklogs/create-worklog.md similarity index 100% rename from docs/api-reference/worklogs/create-worklog.md rename to docs/api-reference/v1/worklogs/create-worklog.md diff --git a/docs/api-reference/worklogs/delete-worklog.md b/docs/api-reference/v1/worklogs/delete-worklog.md similarity index 100% rename from docs/api-reference/worklogs/delete-worklog.md rename to docs/api-reference/v1/worklogs/delete-worklog.md diff --git a/docs/api-reference/worklogs/get-total-time.md b/docs/api-reference/v1/worklogs/get-total-time.md similarity index 100% rename from docs/api-reference/worklogs/get-total-time.md rename to docs/api-reference/v1/worklogs/get-total-time.md diff --git a/docs/api-reference/worklogs/get-worklogs-for-issue.md b/docs/api-reference/v1/worklogs/get-worklogs-for-issue.md similarity index 100% rename from docs/api-reference/worklogs/get-worklogs-for-issue.md rename to docs/api-reference/v1/worklogs/get-worklogs-for-issue.md diff --git a/docs/api-reference/worklogs/overview.md b/docs/api-reference/v1/worklogs/overview.md similarity index 100% rename from docs/api-reference/worklogs/overview.md rename to docs/api-reference/v1/worklogs/overview.md diff --git a/docs/api-reference/worklogs/update-worklog.md b/docs/api-reference/v1/worklogs/update-worklog.md similarity index 100% rename from docs/api-reference/worklogs/update-worklog.md rename to docs/api-reference/v1/worklogs/update-worklog.md diff --git a/docs/api-reference/workspace-features/get-workspace-features.md b/docs/api-reference/v1/workspace-features/get-workspace-features.md similarity index 100% rename from docs/api-reference/workspace-features/get-workspace-features.md rename to docs/api-reference/v1/workspace-features/get-workspace-features.md diff --git a/docs/api-reference/workspace-features/overview.md b/docs/api-reference/v1/workspace-features/overview.md similarity index 96% rename from docs/api-reference/workspace-features/overview.md rename to docs/api-reference/v1/workspace-features/overview.md index 079f5e38..fd599c48 100644 --- a/docs/api-reference/workspace-features/overview.md +++ b/docs/api-reference/v1/workspace-features/overview.md @@ -8,7 +8,7 @@ keywords: plane, plane api, rest api, api integration, workspace features, featu Workspace features control which major Plane capabilities are enabled for a workspace. -[Learn more about using the Plane API](https://developers.plane.so/api-reference/introduction) +[Learn more about using the Plane API](https://developers.plane.so/api-reference/v1/introduction)
diff --git a/docs/api-reference/workspace-features/update-workspace-features.md b/docs/api-reference/v1/workspace-features/update-workspace-features.md similarity index 100% rename from docs/api-reference/workspace-features/update-workspace-features.md rename to docs/api-reference/v1/workspace-features/update-workspace-features.md diff --git a/docs/api-reference/workspace-invitations/add-workspace-invitation.md b/docs/api-reference/v1/workspace-invitations/add-workspace-invitation.md similarity index 100% rename from docs/api-reference/workspace-invitations/add-workspace-invitation.md rename to docs/api-reference/v1/workspace-invitations/add-workspace-invitation.md diff --git a/docs/api-reference/workspace-invitations/delete-workspace-invitation.md b/docs/api-reference/v1/workspace-invitations/delete-workspace-invitation.md similarity index 100% rename from docs/api-reference/workspace-invitations/delete-workspace-invitation.md rename to docs/api-reference/v1/workspace-invitations/delete-workspace-invitation.md diff --git a/docs/api-reference/workspace-invitations/get-workspace-invitation-detail.md b/docs/api-reference/v1/workspace-invitations/get-workspace-invitation-detail.md similarity index 100% rename from docs/api-reference/workspace-invitations/get-workspace-invitation-detail.md rename to docs/api-reference/v1/workspace-invitations/get-workspace-invitation-detail.md diff --git a/docs/api-reference/workspace-invitations/list-workspace-invitations.md b/docs/api-reference/v1/workspace-invitations/list-workspace-invitations.md similarity index 100% rename from docs/api-reference/workspace-invitations/list-workspace-invitations.md rename to docs/api-reference/v1/workspace-invitations/list-workspace-invitations.md diff --git a/docs/api-reference/workspace-invitations/overview.md b/docs/api-reference/v1/workspace-invitations/overview.md similarity index 97% rename from docs/api-reference/workspace-invitations/overview.md rename to docs/api-reference/v1/workspace-invitations/overview.md index f187290b..3cb3f2ec 100644 --- a/docs/api-reference/workspace-invitations/overview.md +++ b/docs/api-reference/v1/workspace-invitations/overview.md @@ -8,7 +8,7 @@ keywords: plane, plane api, rest api, api integration, workspace invitations, me Workspace invitations let admins invite users to join a workspace with specific access settings. -[Learn more about Members](https://developers.plane.so/api-reference/members/overview) +[Learn more about Members](https://developers.plane.so/api-reference/v1/members/overview)
diff --git a/docs/api-reference/workspace-invitations/update-workspace-invitation.md b/docs/api-reference/v1/workspace-invitations/update-workspace-invitation.md similarity index 100% rename from docs/api-reference/workspace-invitations/update-workspace-invitation.md rename to docs/api-reference/v1/workspace-invitations/update-workspace-invitation.md diff --git a/docs/api-reference/v2/artifacts/create-artifact.md b/docs/api-reference/v2/artifacts/create-artifact.md new file mode 100644 index 00000000..e5b6c2f5 --- /dev/null +++ b/docs/api-reference/v2/artifacts/create-artifact.md @@ -0,0 +1,226 @@ +--- +title: Create an artifact +description: Create a versioned HTML artifact in a Plane workspace with the v2 REST API. Body parameters, data modes, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create artifact, applets, dashboard html, data_mode, snapshot, live, POST artifacts +--- + +# Create an artifact + +
+ POST + /api/v2/workspaces/{slug}/artifacts/ +
+ +
+
+ +Create an artifact together with its first HTML version. The new artifact starts at `current_version: 1` and is +unpublished until you call [Publish an artifact](/api-reference/v2/artifacts/publish-artifact). + +Requires the **Applets** feature and workspace **admin** or **owner** — see +[Artifacts overview](/api-reference/v2/artifacts/overview). + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The rendered HTML for version 1. This is the only genuinely required field — an empty or missing value is a `400`. + + + + + +Display name. Truncated to 255 characters. Falls back to `Untitled dashboard` when omitted, empty, or whitespace — it +never rejects a missing name. + + + + + +Free-form description. Truncated to 2000 characters rather than rejected. + + + + + +The prompt that produced this HTML, stored against the version for provenance. Not returned by any read endpoint. + + + + + +Optionally scope the artifact to a project. Note the field is `project`, not `project_id` — the `*_id` convention used +elsewhere in v2 does not apply on this surface. + + + + + +How the artifact's data is sourced. One of `snapshot` (frozen at generation time) or `live` (re-read on view). Defaults +to `snapshot`. Any other value is a `400`. + + + +
+
+ +::: warning A project id from another workspace is silently dropped +`project` is validated for membership in the calling workspace, and a value that fails that check is set to `null` +rather than rejected — the artifact is created workspace-scoped instead. If project scoping matters to you, read the +`project` back rather than assuming it stuck. +::: + +
+ +### Scopes + +`workspaces.artifacts:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | — | `html` is missing or empty, or `data_mode` is outside the enum. See the note below. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The Applets feature isn't enabled on your plan. | +| `403` | `forbidden` | You are not a workspace admin or owner, or your token lacks the scope. | +| `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` | Declared by the schema; no current condition produces it on this route. | +| `413` | `payload_too_large` | The request body is over the size limit. Large HTML can hit this. | +| `415` | `unsupported_media_type` | The `Content-Type` isn't one this endpoint accepts. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +::: info The `400` here is not problem+json +Unlike the rest of v2, the two validation failures on this route return a bare `{"detail": "…"}` body with no `type` or +`code` member. Code that branches on `problem.code` needs a fallback for this shape. The schema declares the standard +`ValidationProblemDetail` for `400`, so this is a known inconsistency rather than intended behavior. +::: + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Q1 velocity by squad", + "current_version": 1, + "is_published": false, + "anchor": null, + "data_mode": "snapshot" +} +``` + + + + + +```json +{ + "detail": "`html` is required." +} +``` + + + +
+
+ +## The response status is `201`, not `200` + +A successful create answers **`201 Created`**. The OpenAPI document declares `200` for this operation — a schema +annotation gap, not a behavior you should code against. Accept `2xx` rather than matching `200` exactly. + +The body is deliberately compact: it echoes the artifact's identity and publish state, not its HTML. Read the HTML back +with [Get an artifact](/api-reference/v2/artifacts/get-artifact) if you need it. + +## Next steps + +- [Publish an artifact](/api-reference/v2/artifacts/publish-artifact) — get a shareable anchor +- [Append a new version](/api-reference/v2/artifacts/update-artifact) — when the content is regenerated diff --git a/docs/api-reference/v2/artifacts/get-artifact.md b/docs/api-reference/v2/artifacts/get-artifact.md new file mode 100644 index 00000000..d5013fa9 --- /dev/null +++ b/docs/api-reference/v2/artifacts/get-artifact.md @@ -0,0 +1,155 @@ +--- +title: Get an artifact +description: Read a Plane artifact's metadata and current HTML with the v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get artifact, applets, artifact html, current_version, GET artifact +--- + +# Get an artifact + +
+ GET + /api/v2/workspaces/{slug}/artifacts/{artifact_id}/ +
+ +
+
+ +Read an artifact's metadata together with the HTML of its **current** version. + +Requires the **Applets** feature and workspace **admin** or **owner** — see +[Artifacts overview](/api-reference/v2/artifacts/overview). Unlike dashboards in the app, artifacts are not readable by +ordinary members. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The artifact to read. UUID only — this route has no human-readable key. + + + +
+
+ +::: info Only the current version is readable +The response carries the HTML of `current_version`. Earlier versions are retained server-side but v2 exposes no way to +read them, and there is no `?version=` parameter. If you need version history, keep your own copy as you append. +::: + +
+ +### Scopes + +`workspaces.artifacts:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ---------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The Applets feature isn't enabled on your plan. | +| `403` | `forbidden` | You are not a workspace admin or owner, or your token lacks the scope. | +| `404` | `not_found` | No such artifact in this workspace, or the workspace is outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +An artifact id that belongs to a different workspace returns `404`, not `403` — consistent with the rest of v2, which +never leaks the existence of a resource you cannot see. + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Q1 velocity by squad", + "description": "Throughput and cycle time, split by squad.", + "current_version": 3, + "data_mode": "snapshot", + "html": "

Q1 velocity by squad

" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "Artifact not found." +} +``` + + + +
+
+ +## What this response does not include + +The detail read is not the same shape as the create response. It adds `description` and `html`, and it omits +`is_published` and `anchor` — so **this endpoint cannot tell you whether an artifact is published**, or what its anchor +is. Those two fields come back only from +[create](/api-reference/v2/artifacts/create-artifact) and +[publish](/api-reference/v2/artifacts/publish-artifact). Store the anchor when you publish rather than expecting to read +it back later. diff --git a/docs/api-reference/v2/artifacts/overview.md b/docs/api-reference/v2/artifacts/overview.md new file mode 100644 index 00000000..ee479a77 --- /dev/null +++ b/docs/api-reference/v2/artifacts/overview.md @@ -0,0 +1,132 @@ +--- +title: Artifacts overview +description: The Plane API v2 artifact object. Versioned HTML dashboards generated by Plane Intelligence, their data modes, publish anchors, and endpoints. +keywords: plane api v2, artifacts, applets, dashboards, plane intelligence, data_mode, snapshot, live, publish anchor +--- + +# Artifacts overview + +An artifact is a **versioned HTML document** stored against a workspace — the storage and hosting layer behind +Plane's AI-generated dashboards. Each artifact has a name, an optional project scope, a data mode, and an +append-only chain of HTML versions. + +::: warning This is a POC surface, not a general integration surface +These endpoints exist so **Plane Intelligence** can host the dashboards it generates. Two consequences worth knowing +before you build on them: + +- **Scope is deliberately narrow.** Create, read, append-a-version, and publish. There is no list endpoint, no delete, + no way to read an older version, and no way to unpublish. +- **The shape may still change.** Unlike the rest of v2, this surface has not been through a public-API review, so + treat it as unstable. + +If you are looking for the dashboards a workspace already has, this is not that API. +::: + +## Requirements + +Every artifact endpoint enforces both of these: + +| Requirement | Failure | +| -------------------------------------------- | ---------------------- | +| The **Applets** feature enabled on your plan | `402 payment_required` | +| Workspace **admin** or **owner** | `403 forbidden` | + +The permission requirement is stricter than most of Plane. Dashboards are viewable by every member; artifacts are +admin-and-owner only, for reads as well as writes. + +
+
+ +## The artifact object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the artifact. + +- `name` _string_ + + Display name. Maximum 255 characters. Defaults to `Untitled dashboard` when created without one. + +- `description` _string_ + + Free-form description. Maximum 2000 characters. Returned on the detail read only. + +- `html` _string_ + + The rendered HTML of the **current** version. Returned on the detail read only. + +- `current_version` _integer_ + + Which version is current. Starts at `1` and increments by one each time you append a version. + +- `data_mode` _string_ + + How the artifact's data is sourced — `snapshot` (data frozen at generation time) or `live` (re-read on view). + Defaults to `snapshot`. + +- `is_published` _boolean_ + + Whether the artifact has a public anchor. Returned on create. + +- `anchor` _string_ + + The public anchor once published, otherwise `null`. Returned on create and publish. + +::: info Versions are append-only +Updating an artifact does not overwrite its HTML — it creates version `current_version + 1` and moves the pointer. +Earlier versions are retained server-side, but v2 exposes no endpoint to read them. +::: + +
+
+ + + +```json +{ + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Q1 velocity by squad", + "description": "Throughput and cycle time, split by squad.", + "current_version": 3, + "data_mode": "snapshot", + "html": "

Q1 velocity by squad

" +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| ------- | ------------------------------------------------------------ | -------------------- | +| `POST` | `/api/v2/workspaces/{slug}/artifacts/` | Create an artifact | +| `GET` | `/api/v2/workspaces/{slug}/artifacts/{artifact_id}/` | Get an artifact | +| `PATCH` | `/api/v2/workspaces/{slug}/artifacts/{artifact_id}/update/` | Append a new version | +| `POST` | `/api/v2/workspaces/{slug}/artifacts/{artifact_id}/publish/` | Publish an artifact | + +Note the two verb sub-paths. Appending a version is `PATCH …/{artifact_id}/update/`, not `PATCH …/{artifact_id}/`, and +publishing is a `POST` to its own segment. + +## The generate-and-host flow + +The sequence these endpoints were built for: + +1. **Create** the artifact with its first HTML version — `POST …/artifacts/`. +2. **Publish** it to get a shareable anchor — `POST …/artifacts/{id}/publish/`. +3. **Append** a new version whenever the content is regenerated — `PATCH …/artifacts/{id}/update/`. The anchor keeps + pointing at whatever is current, so publishing once is enough. + +## Response shaping + +These are `APIView` endpoints, so — like [current user](/api-reference/v2/users/get-current-user) and +[workspace features](/api-reference/v2/workspace-features/overview) — they do **not** accept +[`?fields=`](/api-reference/v2/sparse-fields) or `?expand=`. Each returns its own fixed shape. + +Errors otherwise follow the shared [problem+json contract](/api-reference/v2/errors), with one exception noted on the +write pages: the `html`-missing and `data_mode`-invalid validation failures return a bare `{"detail": "…"}` body rather +than the standard `{type, code, detail}` envelope. diff --git a/docs/api-reference/v2/artifacts/publish-artifact.md b/docs/api-reference/v2/artifacts/publish-artifact.md new file mode 100644 index 00000000..1077eb85 --- /dev/null +++ b/docs/api-reference/v2/artifacts/publish-artifact.md @@ -0,0 +1,173 @@ +--- +title: Publish an artifact +description: Publish a Plane artifact to a shareable anchor with the v2 REST API. Bodyless POST, idempotent behavior, OAuth scopes, error codes, and code examples. +keywords: plane api v2, publish artifact, applets, anchor, deploy board, share dashboard, POST publish +--- + +# Publish an artifact + +
+ POST + /api/v2/workspaces/{slug}/artifacts/{artifact_id}/publish/ +
+ +
+
+ +Publish an artifact and get back its **anchor** — the stable key its public URL is built from. The anchor serves +whatever version is current, so publishing once is enough no matter how many versions you append afterwards. + +This is a **bodyless** `POST` — send no JSON at all. + +Requires the **Applets** feature and workspace **admin** or **owner** — see +[Artifacts overview](/api-reference/v2/artifacts/overview). + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The artifact to publish. UUID only. + + + +
+
+ +
+ +### Body Parameters + +None. The endpoint takes no request body; anything you send is ignored. + +
+ +::: tip Safe to retry +Publishing is idempotent. An artifact that is already published returns its **existing** anchor rather than minting a +new one or erroring, so a retry after a timeout is harmless and the anchor you get back is stable. +::: + +::: warning There is no unpublish +`is_active` is always `true` in the response — v2 exposes no way to withdraw an artifact once published, and no way to +rotate its anchor. Treat publishing as a one-way step on this surface. +::: + +
+ +### Scopes + +`workspaces.artifacts:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ---------------------------------------------------------------------------------- | +| `400` | `invalid_request` | Declared by the schema; the route takes no body, so there is little to invalidate. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The Applets feature isn't enabled on your plan. | +| `403` | `forbidden` | You are not a workspace admin or owner, or your token lacks the scope. | +| `404` | `not_found` | No such artifact in this workspace, or the workspace is outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | Declared by the schema; publishing an already-published artifact succeeds instead. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "anchor": "5e2a7c81-4f39-4b60-a1d8-0c6b3e9f2d74", + "is_active": true +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "Artifact not found." +} +``` + + + +
+
+ +## The response status is `201`, not `200` + +A successful publish answers **`201 Created`**, including on a repeat call where nothing new was created. The OpenAPI +document declares `200` for this operation — a schema annotation gap rather than behavior to code against. Accept `2xx` +rather than matching an exact status. + +## Storing the anchor + +Save the anchor when you get it. The [detail read](/api-reference/v2/artifacts/get-artifact) does **not** return +`anchor` or `is_published`, so re-publishing is currently the only way to recover an anchor you have lost — which works, +because it is idempotent, but is not what the endpoint is for. + +Artifacts share Plane's anchor system with other publishable entities, so the anchor is unique across the workspace, not +just among artifacts. diff --git a/docs/api-reference/v2/artifacts/update-artifact.md b/docs/api-reference/v2/artifacts/update-artifact.md new file mode 100644 index 00000000..9043906b --- /dev/null +++ b/docs/api-reference/v2/artifacts/update-artifact.md @@ -0,0 +1,203 @@ +--- +title: Append a new version +description: Append a new HTML version to a Plane artifact with the v2 REST API. Append-only versioning, body parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update artifact, append artifact version, current_version, applets, PATCH artifact update +--- + +# Append a new version + +
+ PATCH + /api/v2/workspaces/{slug}/artifacts/{artifact_id}/update/ +
+ +
+
+ +Add a new HTML version to an existing artifact. The new version becomes current and `current_version` increments by +one. + +This is **not** a partial update in the usual v2 sense. It does not edit fields on the artifact — you cannot rename it, +change its description, or move it between projects through this route. The only thing it accepts is new content. + +Requires the **Applets** feature and workspace **admin** or **owner** — see +[Artifacts overview](/api-reference/v2/artifacts/overview). + +::: warning Note the `/update/` segment +The path is `PATCH …/artifacts/{artifact_id}/update/`, not `PATCH …/artifacts/{artifact_id}/`. A `PATCH` to the bare +detail route is not a defined operation. +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The artifact to append a version to. UUID only. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The rendered HTML for the new version. An empty or missing value is a `400`. + + + + + +The prompt that produced this HTML, stored against the version for provenance. Not returned by any read endpoint. + + + +
+
+ +::: info Every call creates a version — there is no no-op +Posting identical HTML still appends a version and still increments `current_version`. Nothing de-duplicates content, so +a retry after a network timeout can leave you a version ahead of where you think you are. Read `current_version` back +from the response rather than tracking it locally. +::: + +
+ +### Scopes + +`workspaces.artifacts:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------ | +| `400` | — | `html` is missing or empty. Returns a bare `{"detail": "…"}` body — see below. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The Applets feature isn't enabled on your plan. | +| `403` | `forbidden` | You are not a workspace admin or owner, or your token lacks the scope. | +| `404` | `not_found` | No such artifact in this workspace, or the workspace is outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | Declared by the schema; no current condition produces it on this route. | +| `413` | `payload_too_large` | The request body is over the size limit. Large HTML can hit this. | +| `415` | `unsupported_media_type` | The `Content-Type` isn't one this endpoint accepts. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +::: info The `400` here is not problem+json +The missing-`html` failure returns a bare `detail`-only body with no `type` or `code` member, unlike the rest of v2: + +```json +{ "detail": "`html` is required." } +``` + +The `404` **is** a standard problem document. Code that branches on `problem.code` needs a fallback for the `400` shape. +::: + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "current_version": 4, + "data_mode": "snapshot" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "Artifact not found." +} +``` + + + +
+
+ +## Publishing and versions are independent + +If the artifact is already published, its anchor keeps serving whatever version is current — so you publish once and +append as often as you like. There is no need to re-publish after an update, and no way to point an anchor at an older +version. + +See [Publish an artifact](/api-reference/v2/artifacts/publish-artifact). diff --git a/docs/api-reference/v2/audit-logs/get-audit-log.md b/docs/api-reference/v2/audit-logs/get-audit-log.md new file mode 100644 index 00000000..2132053f --- /dev/null +++ b/docs/api-reference/v2/audit-logs/get-audit-log.md @@ -0,0 +1,208 @@ +--- +title: Get an audit log +description: Retrieve a single Plane audit log entry by id with the v2 REST API. Path parameters, the full entry payload, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get audit log, audit entry by id, compliance evidence, security investigation, GET audit log +--- + +# Get an audit log + +
+ GET + /api/v2/workspaces/{slug}/audit-logs/{pk}/ +
+ +
+
+ +Retrieve one audit entry by id. Reach for this when an investigation has already narrowed to a single event — a ticket cites an entry id, an alert fired on one, or a list result needs to be cited as evidence. + +The payload is identical to a row in [List audit logs](/api-reference/v2/audit-logs/list-audit-logs), so use this endpoint for stable, linkable access to a single event rather than for bulk reading. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. Audit entries never cross workspaces: an entry id from another workspace returns `404`. + + + + + +The id of the audit log entry to retrieve. This is the entry's `id`, not its `event_id`. + + + +
+
+ +
+ +### Query Parameters + +None. The detail route takes no filters and no `?expand=` — `actor_id` and `target_id` stay as ids, so resolve them yourself if you need names beyond the snapshots already in the payload. + +
+ +
+ +### Response shaping + +
+ + + +Comma-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: `actor_display_name`, `actor_email`, `actor_id`, `actor_type`, `category`, `created_at`, `event_id`, `event_name`, `id`, `ip_address`, `metadata`, `new_value`, `old_value`, `outcome`, `project_id`, `reason`, `sequence_number`, `source`, `target_display_name`, `target_id`, `target_type`, `user_agent`, `workspace_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.audit_logs:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this workspace's audit logs. | +| `404` | `not_found` | No such entry or workspace, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info Entries are immutable +There is no `PATCH` or `DELETE` on this path. An audit entry is written once by Plane and cannot be edited or removed through the API, which is what makes a retrieved entry usable as evidence. +::: + +::: info `id` versus `event_id` +`id` addresses this record and is what belongs in the URL. `event_id` identifies the underlying event and is the better deduplication key when you are ingesting entries elsewhere. Passing an `event_id` here returns `404`. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "5c7f1a08-2b64-4d39-9e17-0a3b8c6d2f41", + "event_id": "e0b41d97-6c23-4a8f-b512-9d7a0e3c5f68", + "sequence_number": 48213, + "event_name": "member.role_updated", + "category": "role", + "outcome": "success", + "source": "platform", + "actor_type": "user", + "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "actor_display_name": "Priya Raghavan", + "actor_email": "priya@example.com", + "target_type": "workspace_member", + "target_id": "3e8a5d17-9c40-4b2f-81d6-4a7f2b9e0c53", + "target_display_name": "Devansh Kapoor", + "old_value": { "role": "member" }, + "new_value": { "role": "owner" }, + "reason": "", + "metadata": { "workspace_slug": "my-team" }, + "ip_address": "203.0.113.42", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", + "workspace_id": "d84c6f21-7a30-4e59-b6c8-1f9d5a2e7043", + "project_id": null, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No Audit Log matches the given query." +} +``` + + + +
+
+ +## Pivoting from one entry + +A single entry is usually the start of a thread, not the end of one. From the payload above: + +| Next question | Query on [List audit logs](/api-reference/v2/audit-logs/list-audit-logs) | +| --------------------------------- | ------------------------------------------------------------------------------ | +| What else did this actor do? | `?actor_id=16c61a3a-512a-48ac-b0be-b6b46fe6f430` | +| What else came from that address? | `?ip_address=203.0.113.42` | +| Full history of this target | `?target_type=workspace_member&target_id=3e8a5d17-9c40-4b2f-81d6-4a7f2b9e0c53` | +| Everything around the same moment | `?created_after=2026-01-14T09:00:00Z&created_before=2026-01-14T10:00:00Z` | + +Resolve `actor_id` to a current person with [List workspace members](/api-reference/v2/members/list-workspace-members) — the names on the entry are snapshots from event time, and may no longer match. + +::: tip When the entry explains a `409` +A `settings` entry that flipped `is_work_item_types_enabled` explains a wave of `409 work_item_types_managed_at_workspace` or `work_item_types_managed_at_project` responses: the workspace changed which surface accepts work item type writes. See [Work item type modes](/api-reference/v2/work-item-type-modes) and [Workspace features](/api-reference/v2/workspace-features/overview). +::: diff --git a/docs/api-reference/v2/audit-logs/list-audit-logs.md b/docs/api-reference/v2/audit-logs/list-audit-logs.md new file mode 100644 index 00000000..0d2ff5a5 --- /dev/null +++ b/docs/api-reference/v2/audit-logs/list-audit-logs.md @@ -0,0 +1,417 @@ +--- +title: List audit logs +description: Query a Plane workspace's audit trail with the v2 REST API. Date-range, actor, category, outcome and target filters, cursor pagination for exports, scopes, errors, and code examples. +keywords: plane api v2, list audit logs, audit trail query, compliance export, created_after, created_before, outcome failure, SIEM, cursor pagination +--- + +# List audit logs + +
+ GET + /api/v2/workspaces/{slug}/audit-logs/ +
+ +
+
+ +Query the workspace's audit trail. This is the compliance and security-investigation endpoint: scope it to a date range for a review period, to an actor for an access certification, or to `outcome=failure` when you are looking for attempts that were turned away. + +It is also the export endpoint. With `?paginate=cursor` you can walk months of history into a SIEM or warehouse without paying for a `COUNT(*)` on every page. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. The audit trail is per workspace; there is no cross-workspace query. + + + +
+
+ +
+ +### Query Parameters — time range + +Filters combine with `AND`. Check your spelling on `order_by` and `paginate` — neither is validated. An unrecognized `order_by` value falls back to the resource's default ordering, and anything other than `paginate=cursor` uses offset pagination. A typo shows up as an unexpected sort order or envelope, not as an error. + +
+ + + +Return entries recorded from this timestamp onward, for example `2026-01-01T00:00:00Z`. Pair it with `created_before` to bound a review period. + + + + + +Return entries recorded up to this timestamp. Send timestamps in UTC — `created_at` is returned in UTC, and mixing offsets is the usual reason a window looks empty. + + + +
+
+ +
+ +### Query Parameters — who and what + +
+ + + +Return only what one user did. This is the same id as `member_id` on the [member rosters](/api-reference/v2/members/overview), so an access certification is a roster read followed by one call per person. + +Entries with no actor — `system` and `anonymous` events — are excluded by any `actor_id` filter, so run a second unfiltered query if you are asked to account for everything in a window. + + + + + +Return only one specific event, for example `member.role_updated`. Use it when you already know the action you are hunting for; use `category` when you want a whole class of them. + + + + + +Return only one family of events: `auth`, `member`, `role`, `settings`, `integration`, `webhook`, `security`, or `instance`. + +`role` and `member` answer "who gained access and when". `settings` answers "what changed about this workspace". `auth` plus `outcome=failure` answers "who tried to get in". + + + + + +Return only entries with this outcome: `success` or `failure`. Failed attempts are recorded, which is what makes this filter the fastest way to spot probing. + + + + + +Return only entries acting on this kind of object, for example `workspace_member`. Combine it with `target_id` to build the history of a single record. + + + + + +Return only entries acting on this specific object. `?target_type=…&target_id=…` is the "everything that ever happened to this thing" query. + + + + + +Return only entries recorded from this client IP. Start from a suspicious entry, then pivot on its `ip_address` to see everything else that address did. + + + + + +A free-text search term across the entry. Reach for it when you have a name or a fragment rather than an id. + + + +
+
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `-created_at` , `created_at` — newest first, or oldest first +- `id` , `-id` + +Use `-created_at` for an investigation, `created_at` for a replayable export. Two entries written in the same instant are separated by `sequence_number` in the payload. + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. Use 200 for exports. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000 — which an audit trail exceeds quickly, so use cursor pagination for anything deeper than a few pages. + + + + + +Set to `cursor` for the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead of `next` and `total_count`. This is the mode to use for a full export: it has no offset ceiling and it does not skip or repeat rows as new entries land mid-walk. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted. Worth doing on every page of an offset walk — cursor pagination never runs a `COUNT` and ignores this parameter. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `actor_display_name`, `actor_email`, `actor_id`, `actor_type`, `category`, `created_at`, `event_id`, `event_name`, `id`, `ip_address`, `metadata`, `new_value`, `old_value`, `outcome`, `project_id`, `reason`, `sequence_number`, `source`, `target_display_name`, `target_id`, `target_type`, `user_agent`, `workspace_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.audit_logs:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this workspace's audit logs. | +| `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. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: warning Audit reads are privileged +`workspaces.audit_logs:read` exposes actor emails, IP addresses, and user agents for the whole workspace. Scope the tokens you issue for exports to exactly this, keep them out of user-facing clients, and treat the exported data as it deserves. +::: + +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "5c7f1a08-2b64-4d39-9e17-0a3b8c6d2f41", + "event_id": "e0b41d97-6c23-4a8f-b512-9d7a0e3c5f68", + "sequence_number": 48213, + "event_name": "member.role_updated", + "category": "role", + "outcome": "success", + "source": "platform", + "actor_type": "user", + "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "actor_display_name": "Priya Raghavan", + "actor_email": "priya@example.com", + "target_type": "workspace_member", + "target_id": "3e8a5d17-9c40-4b2f-81d6-4a7f2b9e0c53", + "target_display_name": "Devansh Kapoor", + "old_value": { "role": "member" }, + "new_value": { "role": "owner" }, + "reason": "", + "metadata": { "workspace_slug": "my-team" }, + "ip_address": "203.0.113.42", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", + "workspace_id": "d84c6f21-7a30-4e59-b6c8-1f9d5a2e7043", + "project_id": null, + "created_at": "2026-01-14T09:22:41.478363Z" + }, + { + "id": "1f6b3c92-7d40-4a85-9c21-8e5d0a4f2b76", + "event_id": "c3a97e15-4b82-4d06-9f31-7a2e8c0d6b53", + "sequence_number": 48120, + "event_name": "role.permissions_updated", + "category": "role", + "outcome": "failure", + "source": "api", + "actor_type": "api_token", + "actor_id": "7f2b9e04-6c1d-4a58-9e3b-0d4c8a2f6b71", + "actor_display_name": "Deploy bot", + "actor_email": "bots@example.com", + "target_type": "role", + "target_id": "release-manager", + "target_display_name": "Release manager", + "old_value": null, + "new_value": null, + "reason": "insufficient_permissions", + "metadata": { "requested": "projects.states:write" }, + "ip_address": "198.51.100.7", + "user_agent": "PlaneDeploy/2.4", + "workspace_id": "d84c6f21-7a30-4e59-b6c8-1f9d5a2e7043", + "project_id": null, + "created_at": "2026-01-13T17:04:58.113402Z" + } + ], + "next": 50, + "previous": null, + "total_count": 327, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "5c7f1a08-2b64-4d39-9e17-0a3b8c6d2f41", + "event_id": "e0b41d97-6c23-4a8f-b512-9d7a0e3c5f68", + "sequence_number": 48213, + "event_name": "member.role_updated", + "category": "role", + "outcome": "success", + "source": "platform", + "actor_type": "user", + "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "actor_display_name": "Priya Raghavan", + "actor_email": "priya@example.com", + "target_type": "workspace_member", + "target_id": "3e8a5d17-9c40-4b2f-81d6-4a7f2b9e0c53", + "target_display_name": "Devansh Kapoor", + "old_value": { "role": "member" }, + "new_value": { "role": "owner" }, + "reason": "", + "metadata": { "workspace_slug": "my-team" }, + "ip_address": "203.0.113.42", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", + "workspace_id": "d84c6f21-7a30-4e59-b6c8-1f9d5a2e7043", + "project_id": null, + "created_at": "2026-01-14T09:22:41.478363Z" + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + + + +
+
+ +## Exporting the trail + +Walk it with the cursor envelope, oldest first, and store `event_id` as the deduplication key: + +```python +import requests + +BASE = "https://api.plane.so/api/v2/workspaces/my-team/audit-logs/" +params = {"paginate": "cursor", "per_page": 200, "order_by": "created_at"} +cursor = None + +while True: + if cursor: + params["cursor"] = cursor # the token carries position only — keep every other param + page = requests.get(BASE, headers={"X-Api-Key": "your-api-key"}, params=params).json() + for entry in page["data"]: + ingest(entry) # key on entry["event_id"] + if not page["has_more"]: + break + cursor = page["next_cursor"] +``` + +Resume an incremental export with `created_after` set to the `created_at` of the last entry you stored, and let `event_id` absorb the overlap at the boundary. + +::: tip Investigating a sudden wave of `409`s +`409 work_item_types_managed_at_workspace` and `work_item_types_managed_at_project` mean a client is writing to the wrong surface, usually because an admin switched the workspace's work item type mode. Query `?category=settings` around the time the errors started to find the change and who made it. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +## Related + +- [Get an audit log](/api-reference/v2/audit-logs/get-audit-log) +- [Audit logs overview](/api-reference/v2/audit-logs/overview) — every field and every enum value +- [Pagination](/api-reference/v2/pagination) diff --git a/docs/api-reference/v2/audit-logs/overview.md b/docs/api-reference/v2/audit-logs/overview.md new file mode 100644 index 00000000..8c608c78 --- /dev/null +++ b/docs/api-reference/v2/audit-logs/overview.md @@ -0,0 +1,217 @@ +--- +title: Audit logs overview +description: The Plane API v2 audit log object. Actor, target, category, outcome and source enums, old and new values, and how to query the workspace audit trail for compliance and security investigations. +keywords: plane api v2, audit logs, audit trail, compliance, security investigation, actor_type, category, outcome, source, SIEM export +--- + +# Audit logs overview + +The audit log is the workspace's tamper-evident record of who did what, from where, and whether it worked. It is the endpoint you reach for during a compliance review, an access certification, or a security investigation — and the one you point a SIEM or warehouse at for continuous export. + +Every entry names an **actor** (who), a **target** (what they acted on), an **event** (what they did), an **outcome** (whether it succeeded), and a **source** (which surface the request came through). Where the event changed something, `old_value` and `new_value` carry the before and after. + +Audit logs are read-only. There is no way to write, edit, or delete an entry through the API. + +
+
+ +## The audit log object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for this audit log record. This is the id you pass to [Get an audit log](/api-reference/v2/audit-logs/get-audit-log). + +- `event_id` _string (uuid)_ + + Identifier of the event the record describes, distinct from `id`. Use it as the idempotency key when you ingest entries into another system so a re-read never double-counts. + +- `sequence_number` _integer_ + + An ordering number assigned to the entry as it was written. Useful as a stable tiebreaker when two entries share the same `created_at`. + +- `event_name` _string_ + + The specific event, for example `member.role_updated`. Filterable — this is the field to pin when you know exactly which action you are hunting for. + +- `category` _string_ + + The family the event belongs to. One of `auth`, `member`, `role`, `settings`, `integration`, `webhook`, `security`, or `instance`. Filter on `category` when you want a whole class of activity rather than one event name. + +- `outcome` _string_ + + Whether the attempt succeeded. One of `success` or `failure`. Failed attempts are recorded, which is what makes this useful for detecting probing. + +- `source` _string_ + + The surface the request came through. One of `platform`, `api`, `graphql`, `auth`, or `system`. `api` marks calls made with a token like yours; `system` marks actions Plane took on its own. + +- `actor_type` _string_ + + What kind of principal acted. One of `user`, `api_token`, `system`, or `anonymous`. + +- `actor_id` _string (uuid)_ + + The acting user. Nullable — `system` and `anonymous` actors have no id. It matches `member_id` on the [member](/api-reference/v2/members/overview) rosters. + +- `actor_display_name` , `actor_email` _string_ + + The actor's name and email captured at the time of the event. They are snapshots: a later rename or email change does not rewrite past entries. + +- `target_type` _string_ + + The kind of thing that was acted on, for example `workspace_member`. + +- `target_id` _string_ + + Identifier of the thing that was acted on. + +- `target_display_name` _string_ + + A human-readable label for the target, captured at the time of the event. + +- `old_value` , `new_value` _any_ + + The state before and after the change. Both are nullable — an event that changes nothing, such as a sign-in, carries `null` on both sides. + +- `reason` _string_ + + A short explanation recorded alongside the event. Most informative on `outcome: failure` entries. + +- `metadata` _any_ + + Additional structured context. The shape varies by `event_name`, so read it defensively rather than typing it strictly. + +- `ip_address` _string_ + + The client IP the request came from. Nullable — `system` events have none. + +- `user_agent` _string_ + + The client's user agent string. + +- `workspace_id` _string (uuid)_ + + The workspace the event belongs to. + +- `project_id` _string (uuid)_ + + The project the event is scoped to, when it is scoped to one. + +- `created_at` _string (date-time)_ + + When the event was recorded. This is the field the date-range filters work on. + +
+
+ + + +```json +{ + "id": "5c7f1a08-2b64-4d39-9e17-0a3b8c6d2f41", + "event_id": "e0b41d97-6c23-4a8f-b512-9d7a0e3c5f68", + "sequence_number": 48213, + "event_name": "member.role_updated", + "category": "role", + "outcome": "success", + "source": "platform", + "actor_type": "user", + "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "actor_display_name": "Priya Raghavan", + "actor_email": "priya@example.com", + "target_type": "workspace_member", + "target_id": "3e8a5d17-9c40-4b2f-81d6-4a7f2b9e0c53", + "target_display_name": "Devansh Kapoor", + "old_value": { "role": "member" }, + "new_value": { "role": "owner" }, + "reason": "", + "metadata": { "workspace_slug": "my-team" }, + "ip_address": "203.0.113.42", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", + "workspace_id": "d84c6f21-7a30-4e59-b6c8-1f9d5a2e7043", + "project_id": null, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "id": "9a2c4e76-1f58-4b03-8d69-5e0a7c3b1d24", + "event_id": "b7f3da41-8c05-4e92-a37d-2b6f1c9e5804", + "sequence_number": 48197, + "event_name": "auth.sign_in", + "category": "auth", + "outcome": "failure", + "source": "auth", + "actor_type": "anonymous", + "actor_id": null, + "actor_display_name": "", + "actor_email": "devansh@example.com", + "target_type": "user", + "target_id": "7f2b9e04-6c1d-4a58-9e3b-0d4c8a2f6b71", + "target_display_name": "Devansh Kapoor", + "old_value": null, + "new_value": null, + "reason": "invalid_credentials", + "metadata": { "attempt": 3 }, + "ip_address": "198.51.100.7", + "user_agent": "curl/8.6.0", + "workspace_id": "d84c6f21-7a30-4e59-b6c8-1f9d5a2e7043", + "project_id": null, + "created_at": "2026-01-14T08:57:03.220914Z" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| ------ | -------------------------------------------- | ----------------- | +| `GET` | `/api/v2/workspaces/{slug}/audit-logs/` | List audit logs | +| `GET` | `/api/v2/workspaces/{slug}/audit-logs/{pk}/` | Get one audit log | + +Both require the `workspaces.audit_logs:read` scope. There is no `POST`, `PATCH`, or `DELETE` — entries are written by Plane and are not editable by any caller. + +## Querying for an investigation + +The list endpoint takes the filters an investigation actually needs. They combine with `AND`: + +| Question | Query | +| ------------------------------------ | ------------------------------------------------------------------------------ | +| What happened in this window? | `?created_after=2026-01-01T00:00:00Z&created_before=2026-02-01T00:00:00Z` | +| What did this person do? | `?actor_id=16c61a3a-512a-48ac-b0be-b6b46fe6f430` | +| Every permission change this quarter | `?category=role&created_after=2026-01-01T00:00:00Z` | +| Failed attempts only | `?outcome=failure` | +| Everything touching one object | `?target_type=workspace_member&target_id=3e8a5d17-9c40-4b2f-81d6-4a7f2b9e0c53` | +| Activity from one address | `?ip_address=198.51.100.7` | +| One specific action | `?event_name=member.role_updated` | + +For a full export, add `?paginate=cursor` and walk `next_cursor` — that skips the `COUNT(*)` on every page and stays stable as new entries arrive. Details on [List audit logs](/api-reference/v2/audit-logs/list-audit-logs). + +## Reading an entry + +- **Snapshots, not joins.** `actor_display_name`, `actor_email`, and `target_display_name` are recorded at event time. That is what makes the log evidential — the entry still names the person as they were, even after a rename or an offboarding. +- **Failures are first-class.** `outcome: failure` entries are exactly what a security review is looking for. A burst of them from one `ip_address` with `category: auth` is the shape of a credential-stuffing attempt. +- **`old_value` and `new_value` are free-form.** They describe the change for that `event_name` and are not a fixed schema. Render them, diff them, but do not require particular keys. +- **`source` separates humans from tokens.** `platform` is the Plane app, `api` is a REST client, `system` is Plane acting by itself. Combining `source: api` with `actor_type: api_token` isolates automation. + +::: tip Correlate a `409` with a settings change +If clients suddenly start getting `409 work_item_types_managed_at_workspace` or `work_item_types_managed_at_project`, someone toggled the work item type mode. Query `?category=settings` around the time the failures started to find the change and the actor behind it. See [Work item type modes](/api-reference/v2/work-item-type-modes) and [Update workspace features](/api-reference/v2/workspace-features/update-workspace-features). +::: + +## Related + +- [List audit logs](/api-reference/v2/audit-logs/list-audit-logs) +- [Get an audit log](/api-reference/v2/audit-logs/get-audit-log) +- [Members overview](/api-reference/v2/members/overview) — resolve `actor_id` to a person +- [Pagination](/api-reference/v2/pagination) — cursor traversal for exports diff --git a/docs/api-reference/v2/authentication.md b/docs/api-reference/v2/authentication.md new file mode 100644 index 00000000..3ee78362 --- /dev/null +++ b/docs/api-reference/v2/authentication.md @@ -0,0 +1,327 @@ +--- +title: Authentication +description: Authenticate Plane API v2 requests with an X-Api-Key personal access token or an OAuth 2.0 bearer token, and understand the global and fine-grained scope model. +keywords: plane api v2 authentication, X-Api-Key, personal access token, plane oauth 2.0, bearer token, oauth scopes, users me endpoint +--- + +# Authentication + +Every v2 request must authenticate. There are two ways to do it, and which one you pick depends on whose data you are +touching. + +| Method | Use it when | Header | +| ----------------------- | ------------------------------------------------------------ | ------------------------------- | +| [API key](#api-key) | A script, backend job, or integration acts as **you** | `X-Api-Key: ` | +| [OAuth 2.0](#oauth-2-0) | An app acts on **another user's** behalf, with their consent | `Authorization: Bearer ` | + +Both are checked before anything else. A missing or invalid credential returns `401 unauthorized` — see +[Errors](/api-reference/v2/errors). + +::: warning Header casing +v2 prose and examples use `X-Api-Key`. The v1 docs write the same header as `X-API-Key`. HTTP header names are +case-insensitive, so either spelling reaches the server, but match the v2 form when copying examples between versions. +::: + +## API key + +An API key is a personal access token. It authenticates as the user who created it. + +### Generate a key + +1. Log in to Plane and open **Profile Settings**. +2. Go to the **Personal Access Tokens** tab. +3. Click **Add personal access token**. +4. Give it a title and description so you can tell later what it was for and where it is deployed. +5. Optionally set an expiry so it stops working after a date you choose. + +The token value is shown once. Store it somewhere your application can read it as a secret — never in client-side code +or a committed file. + +### Use a key + +```bash +curl "https://api.plane.so/api/v2/users/me/" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +::: warning API keys are not scope-limited +An API key acts with its owner's **full permissions**. It is not constrained by OAuth scopes — there is no way to mint +a read-only key. It is still subject to the permission engine, so it can never do something the owner could not do in +the UI, but within that boundary it can do everything. + +Treat a key as equivalent to the account it belongs to. If one leaks, delete it in **Personal Access Tokens** and issue +a new one. +::: + +## OAuth 2.0 + +Use OAuth when a third-party app needs to act on behalf of a Plane user who is not you. v2 uses the standard +authorization-code flow. + +| Step | Endpoint | +| ----------------- | -------------------------------------- | +| Authorization URL | `/auth/o/authorize/` | +| Token URL | `/auth/o/token/` | +| Request header | `Authorization: Bearer ` | + +1. Redirect the user to `/auth/o/authorize/` with your client id, redirect URI, and the scopes you need. +2. The user approves the scopes. Plane redirects back with an authorization code. +3. Exchange that code for an access token at `/auth/o/token/`. +4. Send the token on every API call. + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/states/" \ + -H "Authorization: Bearer $PLANE_ACCESS_TOKEN" +``` + +::: info OAuth tokens fail closed +Unlike an API key, an OAuth token is **capped to the scopes the user granted**. If the token lacks a scope that +satisfies the operation, the request is rejected before the permission engine runs — a user with permission to do +something does not help if your token was never granted the scope for it. + +Request only the scopes you need. Adding one later requires the user to re-authorize. +::: + +## The scope model + +Scopes come in two tiers, and an operation is satisfied by **either** one (OR logic): + +- **Global tier** — `read` grants read access to all resources; `write` grants write access to all resources. +- **Fine-grained** — `:read` and `:write`, for example `projects.work_items:read` or + `projects.states:write`. + +So `GET …/states/` succeeds with **either** `read` **or** `projects.states:read`. You never need both, and a global +tier scope is not a prerequisite for the fine-grained one. + +::: tip Prefer fine-grained scopes +Ask for `read` or `write` only when your app genuinely spans the whole workspace surface. A consent screen listing +three specific scopes converts better than one asking for everything, and it limits the blast radius if a token leaks. +::: + +## Who am I + +`GET /api/v2/users/me/` echoes back whoever the credential you sent belongs to. It takes no workspace or project id, +so it isolates an auth problem from a permission or tenancy problem — reach for it first when you are debugging a `401` +or a `403`. + +
+ GET + /api/v2/users/me/ +
+ +No fine-grained scope gates it. An API key can always call it; an OAuth token needs the global `read` scope — a token +holding only fine-grained scopes such as `projects.states:read` cannot call it. + + + + + + + + + +```json +{ + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "email": "priya@my-team.io", + "display_name": "priya", + "principal_kind": "oauth", + "scopes": ["projects.work_items:read", "projects.states:read"] +} +``` + + + +### Response fields + +- `id` _string (uuid)_ + + The authenticated user. + +- `email` _string (email)_ + + The user's email address. + +- `display_name` _string_ + + The user's display name. + +- `principal_kind` _string_ + + How the request authenticated. One of `oauth`, `api_key`, or `other`. + +- `scopes` _array of string_ + + The scopes this credential carries. Populated for OAuth tokens. Because API keys are not scope-limited, this tells + you nothing about an API key's reach — check `principal_kind` first. + +::: tip Debugging a 403 +Call `/api/v2/users/me/` and compare `scopes` against the scope listed on the endpoint page you are calling. If the +scope is missing, the token needs re-authorization. If it is present and you still get `403`, the block is the user's +role, not the token. +::: + +## Fine-grained scopes for live endpoints + +These are the scopes that gate endpoints documented in this reference. Each is also satisfied by the matching global +tier scope (`read` or `write`). + +| Scope | Grants | +| ------------------------------------------------- | ------------------------------------------------------------ | +| `assets:read` | Read access to assets | +| `assets:write` | Write access to assets | +| `automations:read` | Read access to automations | +| `automations:write` | Write access to automations | +| `customers:read` | Read access to customers | +| `customers:write` | Write access to customers | +| `customers.properties:read` | Read access to customer properties | +| `customers.properties:write` | Write access to customer properties | +| `customers.property_values:read` | Read access to customer property values | +| `customers.property_values:write` | Write access to customer property values | +| `customers.requests:read` | Read access to customer requests | +| `customers.requests:write` | Write access to customer requests | +| `initiatives:read` | Read access to initiatives | +| `initiatives:write` | Write access to initiatives | +| `initiatives.labels:read` | Read access to initiatives labels | +| `initiatives.labels:write` | Write access to initiatives labels | +| `projects:read` | Read access to projects | +| `projects:write` | Write access to projects | +| `projects.cycles:read` | Read access to project cycles | +| `projects.cycles:write` | Write access to project cycles | +| `projects.estimates:read` | Read access to project estimates | +| `projects.estimates:write` | Write access to project estimates | +| `projects.features:read` | Read access to project features | +| `projects.features:write` | Write access to project features | +| `projects.intakes:read` | Read access to project intakes | +| `projects.intakes:write` | Write access to project intakes | +| `projects.labels:read` | Read access to project labels | +| `projects.labels:write` | Write access to project labels | +| `projects.members:read` | Read access to project members | +| `projects.members:write` | Write access to project members | +| `projects.milestones:read` | Read access to project milestones | +| `projects.milestones:write` | Write access to project milestones | +| `projects.modules:read` | Read access to project modules | +| `projects.modules:write` | Write access to project modules | +| `projects.pages:read` | Read access to project pages | +| `projects.pages:write` | Write access to project pages | +| `projects.states:read` | Read access to project states | +| `projects.states:write` | Write access to project states | +| `projects.views:read` | Read access to project views | +| `projects.views:write` | Write access to project views | +| `projects.work_item_properties:read` | Read access to work item properties | +| `projects.work_item_properties:write` | Write access to work item properties | +| `projects.work_item_types:read` | Read access to work item types | +| `projects.work_item_types:write` | Write access to work item types | +| `projects.work_items:read` | Read access to project work items | +| `projects.work_items:write` | Write access to project work items | +| `projects.work_items.activities:read` | Read access to work item activities | +| `projects.work_items.attachments:read` | Read access to work item attachments | +| `projects.work_items.attachments:write` | Write access to work item attachments | +| `projects.work_items.comments:read` | Read access to work item comments | +| `projects.work_items.comments:write` | Write access to work item comments | +| `projects.work_items.links:read` | Read access to work item links | +| `projects.work_items.links:write` | Write access to work item links | +| `projects.work_items.relations:read` | Read access to work item relations | +| `projects.work_items.relations:write` | Write access to work item relations | +| `projects.work_items.worklogs:read` | Read access to work item worklogs | +| `projects.work_items.worklogs:write` | Write access to work item worklogs | +| `projects.workflows:read` | Read access to project workflows | +| `projects.workflows:write` | Write access to project workflows | +| `releases:read` | Read access to releases | +| `releases:write` | Write access to releases | +| `releases.comments:read` | Read access to release comments | +| `releases.comments:write` | Write access to release comments | +| `releases.labels:read` | Read access to release labels | +| `releases.labels:write` | Write access to release labels | +| `releases.links:read` | Read access to release links | +| `releases.links:write` | Write access to release links | +| `releases.tags:read` | Read access to release tags | +| `releases.tags:write` | Write access to release tags | +| `stickies:read` | Read access to stickies | +| `stickies:write` | Write access to stickies | +| `teamspaces:read` | Read access to teamspaces | +| `teamspaces:write` | Write access to teamspaces | +| `templates.work_items:read` | Read access to work item templates | +| `templates.work_items:write` | Write access to work item templates | +| `webhooks:read` | Read access to workspace webhooks | +| `webhooks:write` | Write access to workspace webhooks | +| `wiki.pages:read` | Read access to wiki pages | +| `wiki.pages:write` | Write access to wiki pages | +| `workspaces.artifacts:read` | Read access to workspace artifacts | +| `workspaces.artifacts:write` | Write access to workspace artifacts | +| `workspaces.audit_logs:read` | Read access to workspace audit logs | +| `workspaces.features:read` | Read access to workspace features | +| `workspaces.features:write` | Write access to workspace features | +| `workspaces.group_sync:read` | Read access to workspace IdP group sync config and mappings | +| `workspaces.group_sync:write` | Write access to workspace IdP group sync config and mappings | +| `workspaces.members:read` | Read access to workspace members | +| `workspaces.members:write` | Write access to workspace members | +| `workspaces.roles:read` | Read access to workspace roles (system + custom) | +| `workspaces.views:read` | Read access to workspace views | +| `workspaces.views:write` | Write access to workspace views | +| `workspaces.work_item_properties:read` | Read access to workspace work item properties | +| `workspaces.work_item_properties:write` | Write access to workspace work item properties | +| `workspaces.work_item_relation_definitions:read` | Read access to workspace work item relation definitions | +| `workspaces.work_item_relation_definitions:write` | Write access to workspace work item relation definitions | +| `workspaces.work_item_types:read` | Read access to workspace work item types | +| `workspaces.work_item_types:write` | Write access to workspace work item types | +| `read` | Read access to all resources | +| `write` | Write access to all resources | + +::: info The catalog is larger than this table +The OAuth scope catalog advertises the full planned resource surface — pages, customers, initiatives, releases, +teamspaces, templates, webhooks, assets, and more. Those scopes exist and can be requested, but their endpoints are not +live in v2 yet, so only the scopes above currently gate real requests. + +The complete catalog is served under `components.securitySchemes.oauth2` in +[`GET /api/v2/schema/`](/api-reference/v2/introduction#machine-readable-schema). +::: + +The scope required by a specific operation is listed in the **Scopes** section of each endpoint page — for example +[Create a state](/api-reference/v2/states/create-state) requires `projects.states:write`. + +## Tenancy + +Authentication tells Plane who you are; tenancy decides what you can see. A workspace slug, project id, or record id +outside your tenant returns **`404 not_found`**, never `403`. + +::: warning A cross-tenant id looks identical to a deleted one +This is deliberate — the API never confirms that a resource you cannot access exists. When a request that should work +returns `404`, verify the workspace and project the id belongs to before assuming the record is gone. + +Reserve `403 forbidden` in your error handling for the case it actually means: you can see the resource, but your role +or token scope cannot perform this action. +::: + +## Next + +- [Errors](/api-reference/v2/errors) — how `401`, `403`, and `404` are reported. +- [Introduction](/api-reference/v2/introduction) — base URL, trailing slashes, and request conventions. diff --git a/docs/api-reference/v2/collections/create-collection.md b/docs/api-reference/v2/collections/create-collection.md new file mode 100644 index 00000000..0c3b878c --- /dev/null +++ b/docs/api-reference/v2/collections/create-collection.md @@ -0,0 +1,211 @@ +--- +title: Create a collection +description: Create a collection with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a collection, collections, pages create +--- + +# Create a collection + +
+ POST + /api/v2/workspaces/{slug}/collections/ +
+ +
+
+ +Collections group wiki pages in a workspace. Create a collection. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Who can see this. + + + + + +Make this the default for its parent. Setting it clears the flag on the previous default. + + + + + +Whether this lives at the workspace level rather than inside a project. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Display name. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `created_at`, `created_by_id`, `id`, `is_default`, `is_global`, `logo_props`, `name`, `owned_by_id`, `page_ids`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`wiki.pages:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_default": false, + "is_global": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "page_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "sort_order": 65535 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/collections/delete-collection.md b/docs/api-reference/v2/collections/delete-collection.md new file mode 100644 index 00000000..34445eeb --- /dev/null +++ b/docs/api-reference/v2/collections/delete-collection.md @@ -0,0 +1,156 @@ +--- +title: Delete a collection +description: Delete a collection with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a collection, collections, pages destroy +--- + +# Delete a collection + +
+ DELETE + /api/v2/workspaces/{slug}/collections/{pk}/ +
+ +
+
+ +Collections group wiki pages in a workspace. Delete a collection. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The collection id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `created_at`, `created_by_id`, `id`, `is_default`, `is_global`, `logo_props`, `name`, `owned_by_id`, `page_ids`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`wiki.pages:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No collection matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/collections/get-collection.md b/docs/api-reference/v2/collections/get-collection.md new file mode 100644 index 00000000..a6dd180b --- /dev/null +++ b/docs/api-reference/v2/collections/get-collection.md @@ -0,0 +1,166 @@ +--- +title: Get a collection +description: Read a collection with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a collection, collections, pages retrieve +--- + +# Get a collection + +
+ GET + /api/v2/workspaces/{slug}/collections/{pk}/ +
+ +
+
+ +Collections group wiki pages in a workspace. Read a single collection by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The collection id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `created_at`, `created_by_id`, `id`, `is_default`, `is_global`, `logo_props`, `name`, `owned_by_id`, `page_ids`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`wiki.pages:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_default": false, + "is_global": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "page_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No collection matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/collections/list-collection-members.md b/docs/api-reference/v2/collections/list-collection-members.md new file mode 100644 index 00000000..33bcec51 --- /dev/null +++ b/docs/api-reference/v2/collections/list-collection-members.md @@ -0,0 +1,152 @@ +--- +title: List collection members +description: List collection members with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list collection members, collection members, collections members list +--- + +# List collection members + +
+ GET + /api/v2/workspaces/{slug}/collections/{pk}/members/ +
+ +
+
+ +Collections group wiki pages in a workspace. List the collection members you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The collection member id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `collection_id`, `created_at`, `created_by_id`, `id`, `member_id`, `source`. + + + + + +Comma-separated relations to embed: `member`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`wiki.pages:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "collection_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "member_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "source": "manual" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/collections/list-collections.md b/docs/api-reference/v2/collections/list-collections.md new file mode 100644 index 00000000..0b0bb145 --- /dev/null +++ b/docs/api-reference/v2/collections/list-collections.md @@ -0,0 +1,244 @@ +--- +title: List collections +description: List collections with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list collections, collections, pages list +--- + +# List collections + +
+ GET + /api/v2/workspaces/{slug}/collections/ +
+ +
+
+ +Collections group wiki pages in a workspace. List the collections you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +- `0` - Public +- `1` - Private + +One of `0`, `1`. + + + + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `is_default`. + + + + + +Filter by `is_global`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Filter by `owned_by_id`. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `created_at`, `created_by_id`, `id`, `is_default`, `is_global`, `logo_props`, `name`, `owned_by_id`, `page_ids`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`wiki.pages:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "access": 0, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_default": false, + "is_global": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "page_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "sort_order": 65535 + }, + { + "access": 0, + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_default": false, + "is_global": false, + "logo_props": null, + "name": "Another example", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "page_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "sort_order": 65535 + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/collections/manage-collection-members.md b/docs/api-reference/v2/collections/manage-collection-members.md new file mode 100644 index 00000000..b8bd4b9f --- /dev/null +++ b/docs/api-reference/v2/collections/manage-collection-members.md @@ -0,0 +1,178 @@ +--- +title: Add or remove collection members +description: Add or remove collection members with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, add or remove collection members, collection members, collections members +--- + +# Add or remove collection members + +
+ POST + /api/v2/workspaces/{slug}/collections/{pk}/members/ +
+ +
+
+ +Collections group wiki pages in a workspace. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The collection member id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The add. + + + + + +The remove. + + + +
+
+ +
+ +### Scopes + +`wiki.pages:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "added": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "removed": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/collections/manage-collection-pages.md b/docs/api-reference/v2/collections/manage-collection-pages.md new file mode 100644 index 00000000..f37bf812 --- /dev/null +++ b/docs/api-reference/v2/collections/manage-collection-pages.md @@ -0,0 +1,167 @@ +--- +title: Add or remove pages in a collection +description: Add or remove pages in a collection with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, add or remove pages in a collection, collection pages, collections pages +--- + +# Add or remove pages in a collection + +
+ POST + /api/v2/workspaces/{slug}/collections/{pk}/pages/ +
+ +
+
+ +Collections group wiki pages in a workspace. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The collection page id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The add. + + + + + +The remove. + + + +
+
+ +
+ +### Scopes + +`wiki.pages:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "added": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "removed": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/collections/overview.md b/docs/api-reference/v2/collections/overview.md new file mode 100644 index 00000000..6307ef5e --- /dev/null +++ b/docs/api-reference/v2/collections/overview.md @@ -0,0 +1,104 @@ +--- +title: Collections overview +description: The Plane API v2 collection object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, collections, collection object +--- + +# Collections overview + +Collections group wiki pages in a workspace. + +
+
+ +## The collection object + +### Attributes + +- `access` _integer_ + + Who can see this. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_default` _boolean_ + + Make this the default for its parent. Setting it clears the flag on the previous default. + +- `is_global` _boolean_ + + Whether this lives at the workspace level rather than inside a project. + +- `logo_props` _string_ + + Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +- `name` _string_ + + Display name. + +- `owned_by_id` _string (uuid)_ + + The related owned by. + +- `page_ids` _array of string_ + + Ids of the associated pages. + +- `sort_order` _number_ + + Manual ordering weight. Lower sorts first. + +
+
+ + + +```json +{ + "access": 0, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_default": false, + "is_global": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "page_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "sort_order": 65535 +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------------- | ----------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/collections/` | List collections | +| `POST` | `/api/v2/workspaces/{slug}/collections/` | Create a collection | +| `DELETE` | `/api/v2/workspaces/{slug}/collections/{pk}/` | Delete a collection | +| `GET` | `/api/v2/workspaces/{slug}/collections/{pk}/` | Get a collection | +| `PATCH` | `/api/v2/workspaces/{slug}/collections/{pk}/` | Update a collection | +| `GET` | `/api/v2/workspaces/{slug}/collections/{pk}/members/` | List collection members | +| `POST` | `/api/v2/workspaces/{slug}/collections/{pk}/members/` | Add or remove collection members | +| `GET` | `/api/v2/workspaces/{slug}/collections/{pk}/pages-search/` | Search pages for a collection | +| `POST` | `/api/v2/workspaces/{slug}/collections/{pk}/pages/` | Add or remove pages in a collection | + +## Response shaping + +Every collection read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/collections/search-collection-pages.md b/docs/api-reference/v2/collections/search-collection-pages.md new file mode 100644 index 00000000..6a0441ef --- /dev/null +++ b/docs/api-reference/v2/collections/search-collection-pages.md @@ -0,0 +1,140 @@ +--- +title: Search pages for a collection +description: Search pages for a collection with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, search pages for a collection, collection page search, collections pages search +--- + +# Search pages for a collection + +
+ GET + /api/v2/workspaces/{slug}/collections/{pk}/pages-search/ +
+ +
+
+ +Collections group wiki pages in a workspace. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The collection page search id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `id`, `logo_props`, `name`. + + + +
+
+ +
+ +### Scopes + +`wiki.pages:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_props": null, + "name": "Example name" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/collections/update-collection.md b/docs/api-reference/v2/collections/update-collection.md new file mode 100644 index 00000000..01d64e5c --- /dev/null +++ b/docs/api-reference/v2/collections/update-collection.md @@ -0,0 +1,232 @@ +--- +title: Update a collection +description: Update a collection with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a collection, collections, pages partial update +--- + +# Update a collection + +
+ PATCH + /api/v2/workspaces/{slug}/collections/{pk}/ +
+ +
+
+ +Collections group wiki pages in a workspace. Update a collection. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The collection id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Who can see this. + + + + + +Make this the default for its parent. Setting it clears the flag on the previous default. + + + + + +Whether this lives at the workspace level rather than inside a project. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Display name. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `created_at`, `created_by_id`, `id`, `is_default`, `is_global`, `logo_props`, `name`, `owned_by_id`, `page_ids`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`wiki.pages:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_default": false, + "is_global": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "page_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No collection matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customer-properties/create-customer-property.md b/docs/api-reference/v2/customer-properties/create-customer-property.md new file mode 100644 index 00000000..baecc3c8 --- /dev/null +++ b/docs/api-reference/v2/customer-properties/create-customer-property.md @@ -0,0 +1,290 @@ +--- +title: Create a customer property +description: Create a customer property with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a customer property, customer properties, customer properties create +--- + +# Create a customer property + +
+ POST + /api/v2/workspaces/{slug}/customer-properties/ +
+ +
+
+ +Customer properties are the workspace-level custom field catalog for customers. Create a customer property. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The display name. + +Maximum 255 characters. + + + + + +- `TEXT` - Text +- `DATETIME` - Datetime +- `DECIMAL` - Decimal +- `BOOLEAN` - Boolean +- `OPTION` - Option +- `RELATION` - Relation +- `URL` - URL +- `EMAIL` - Email +- `FILE` - File + +One of `TEXT`, `DATETIME`, `DECIMAL`, `BOOLEAN`, `OPTION`, `RELATION`, `URL`, `EMAIL`, `FILE`. + + + + + +The default value. + + + + + +Free-form description. + +Nullable. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Whether the record is active. + + + + + +Whether is multi. + + + + + +Whether is required. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +The options. + + + + + +- `ISSUE` - Issue +- `USER` - User + +One of `ISSUE`, `USER`. Nullable. + + + + + +The settings. + + + + + +The validation rules. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `sort_order`, `validation_rules`. + + + +
+
+ +
+ +### Scopes + +`customers.properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "default_value": ["example"], + "description": "What this is for.", + "display_name": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "is_multi": false, + "is_required": false, + "logo_props": null, + "name": "Example name", + "options": [null], + "property_type": "TEXT", + "relation_type": null, + "settings": null, + "sort_order": 65535, + "validation_rules": null +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customer-properties/delete-customer-property.md b/docs/api-reference/v2/customer-properties/delete-customer-property.md new file mode 100644 index 00000000..c1736492 --- /dev/null +++ b/docs/api-reference/v2/customer-properties/delete-customer-property.md @@ -0,0 +1,148 @@ +--- +title: Delete a customer property +description: Delete a customer property with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a customer property, customer properties, customer properties destroy +--- + +# Delete a customer property + +
+ DELETE + /api/v2/workspaces/{slug}/customer-properties/{pk}/ +
+ +
+
+ +Customer properties are the workspace-level custom field catalog for customers. Delete a customer property. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer property id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `sort_order`, `validation_rules`. + + + +
+
+ +
+ +### Scopes + +`customers.properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No customer property matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customer-properties/get-customer-property.md b/docs/api-reference/v2/customer-properties/get-customer-property.md new file mode 100644 index 00000000..309d2b64 --- /dev/null +++ b/docs/api-reference/v2/customer-properties/get-customer-property.md @@ -0,0 +1,166 @@ +--- +title: Get a customer property +description: Read a customer property with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a customer property, customer properties, customer properties retrieve +--- + +# Get a customer property + +
+ GET + /api/v2/workspaces/{slug}/customer-properties/{pk}/ +
+ +
+
+ +Customer properties are the workspace-level custom field catalog for customers. Read a single customer property by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer property id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `sort_order`, `validation_rules`. + + + +
+
+ +
+ +### Scopes + +`customers.properties:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "default_value": ["example"], + "description": "What this is for.", + "display_name": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "is_multi": false, + "is_required": false, + "logo_props": null, + "name": "Example name", + "options": [null], + "property_type": "TEXT", + "relation_type": null, + "settings": null, + "sort_order": 65535, + "validation_rules": null +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No customer property matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customer-properties/list-customer-properties.md b/docs/api-reference/v2/customer-properties/list-customer-properties.md new file mode 100644 index 00000000..c1a50960 --- /dev/null +++ b/docs/api-reference/v2/customer-properties/list-customer-properties.md @@ -0,0 +1,228 @@ +--- +title: List customer properties +description: List customer properties with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list customer properties, customer properties, customer properties list +--- + +# List customer properties + +
+ GET + /api/v2/workspaces/{slug}/customer-properties/ +
+ +
+
+ +Customer properties are the workspace-level custom field catalog for customers. List the customer properties you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `is_active`. + + + + + +Filter by `is_required`. + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +Filter by `property_type`. + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `sort_order`, `validation_rules`. + + + +
+
+ +
+ +### Scopes + +`customers.properties:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "default_value": ["example"], + "description": "What this is for.", + "display_name": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "is_multi": false, + "is_required": false, + "logo_props": null, + "name": "Example name", + "options": [null], + "property_type": "TEXT", + "relation_type": null, + "settings": null, + "sort_order": 65535, + "validation_rules": null + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customer-properties/overview.md b/docs/api-reference/v2/customer-properties/overview.md new file mode 100644 index 00000000..9063d30b --- /dev/null +++ b/docs/api-reference/v2/customer-properties/overview.md @@ -0,0 +1,140 @@ +--- +title: Customer properties overview +description: The Plane API v2 customer property object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, customer properties, customer property object +--- + +# Customer properties overview + +Customer properties are the workspace-level custom field catalog for customers. + +
+
+ +## The customer property object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `default_value` _array of string_ + + The default value. + +- `description` _string_ + + Free-form description. + +- `display_name` _string_ + + The display name. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_active` _boolean_ + + Whether the record is active. + +- `is_multi` _boolean_ + + Whether is multi. + +- `is_required` _boolean_ + + Whether is required. + +- `logo_props` _string_ + + Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +- `name` _string_ + + Display name. + +- `options` _array of string_ + + The options. + +- `property_type` _string_ + + The property type. + +- `relation_type` _string_ + + The relation type. + +- `settings` _string_ + + The settings. + +- `sort_order` _number_ + + Manual ordering weight. Lower sorts first. + +- `validation_rules` _string_ + + The validation rules. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "default_value": ["example"], + "description": "What this is for.", + "display_name": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "is_multi": false, + "is_required": false, + "logo_props": null, + "name": "Example name", + "options": [null], + "property_type": "TEXT", + "relation_type": null, + "settings": null, + "sort_order": 65535, + "validation_rules": null +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ----------------------------------------------------- | -------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/customer-properties/` | List customer properties | +| `POST` | `/api/v2/workspaces/{slug}/customer-properties/` | Create a customer property | +| `DELETE` | `/api/v2/workspaces/{slug}/customer-properties/{pk}/` | Delete a customer property | +| `GET` | `/api/v2/workspaces/{slug}/customer-properties/{pk}/` | Get a customer property | +| `PATCH` | `/api/v2/workspaces/{slug}/customer-properties/{pk}/` | Update a customer property | + +## Response shaping + +Every customer property read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/customer-properties/update-customer-property.md b/docs/api-reference/v2/customer-properties/update-customer-property.md new file mode 100644 index 00000000..091420fc --- /dev/null +++ b/docs/api-reference/v2/customer-properties/update-customer-property.md @@ -0,0 +1,305 @@ +--- +title: Update a customer property +description: Update a customer property with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a customer property, customer properties, customer properties partial update +--- + +# Update a customer property + +
+ PATCH + /api/v2/workspaces/{slug}/customer-properties/{pk}/ +
+ +
+
+ +Customer properties are the workspace-level custom field catalog for customers. Update a customer property. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer property id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The default value. + + + + + +Free-form description. + +Nullable. + + + + + +The display name. + +Maximum 255 characters. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Whether the record is active. + + + + + +Whether is multi. + + + + + +Whether is required. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +The options. + + + + + +- `TEXT` - Text +- `DATETIME` - Datetime +- `DECIMAL` - Decimal +- `BOOLEAN` - Boolean +- `OPTION` - Option +- `RELATION` - Relation +- `URL` - URL +- `EMAIL` - Email +- `FILE` - File + +One of `TEXT`, `DATETIME`, `DECIMAL`, `BOOLEAN`, `OPTION`, `RELATION`, `URL`, `EMAIL`, `FILE`. + + + + + +- `ISSUE` - Issue +- `USER` - User + +One of `ISSUE`, `USER`. Nullable. + + + + + +The settings. + + + + + +The validation rules. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `sort_order`, `validation_rules`. + + + +
+
+ +
+ +### Scopes + +`customers.properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "default_value": ["example"], + "description": "What this is for.", + "display_name": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "is_multi": false, + "is_required": false, + "logo_props": null, + "name": "Example name", + "options": [null], + "property_type": "TEXT", + "relation_type": null, + "settings": null, + "sort_order": 65535, + "validation_rules": null +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No customer property matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customer-property-values/list-customer-property-values.md b/docs/api-reference/v2/customer-property-values/list-customer-property-values.md new file mode 100644 index 00000000..4cd547bd --- /dev/null +++ b/docs/api-reference/v2/customer-property-values/list-customer-property-values.md @@ -0,0 +1,117 @@ +--- +title: List customer property values +description: List customer property values with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list customer property values, customer property values, customer property values list +--- + +# List customer property values + +
+ GET + /api/v2/workspaces/{slug}/customers/{customer_id}/property-values/ +
+ +
+
+ +Customer property values hold a customer's answers to the workspace property catalog. List the customer property values you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer the resource belongs to. + + + +
+
+ +
+ +### Scopes + +`customers.property_values:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{} +``` + + + +
+
diff --git a/docs/api-reference/v2/customer-property-values/overview.md b/docs/api-reference/v2/customer-property-values/overview.md new file mode 100644 index 00000000..cbd383e5 --- /dev/null +++ b/docs/api-reference/v2/customer-property-values/overview.md @@ -0,0 +1,20 @@ +--- +title: Customer property values overview +description: The Plane API v2 customer property value object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, customer property values, customer property value object +--- + +# Customer property values overview + +Customer property values hold a customer's answers to the workspace property catalog. + +## Endpoints + +| Method | Path | Description | +| ------ | -------------------------------------------------------------------- | ----------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/customers/{customer_id}/property-values/` | List customer property values | +| `POST` | `/api/v2/workspaces/{slug}/customers/{customer_id}/property-values/` | Set customer property values | + +## Response shaping + +Every customer property value read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/customer-property-values/set-customer-property-values.md b/docs/api-reference/v2/customer-property-values/set-customer-property-values.md new file mode 100644 index 00000000..95715072 --- /dev/null +++ b/docs/api-reference/v2/customer-property-values/set-customer-property-values.md @@ -0,0 +1,145 @@ +--- +title: Set customer property values +description: Create a customer property value with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, set customer property values, customer property values, customer property values create +--- + +# Set customer property values + +
+ POST + /api/v2/workspaces/{slug}/customers/{customer_id}/property-values/ +
+ +
+
+ +Customer property values hold a customer's answers to the workspace property catalog. Create a customer property value. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer the resource belongs to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The values. + + + +
+
+ +
+ +### Scopes + +`customers.property_values:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + +
+
diff --git a/docs/api-reference/v2/customer-requests/create-customer-request.md b/docs/api-reference/v2/customer-requests/create-customer-request.md new file mode 100644 index 00000000..c213f0aa --- /dev/null +++ b/docs/api-reference/v2/customer-requests/create-customer-request.md @@ -0,0 +1,215 @@ +--- +title: Create a customer request +description: Create a customer request with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a customer request, customer requests, customer requests create +--- + +# Create a customer request + +
+ POST + /api/v2/workspaces/{slug}/customers/{customer_id}/requests/ +
+ +
+
+ +Customer requests capture what a customer asked for, and link that ask to work items. Create a customer request. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer the resource belongs to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Free-form description. + +Nullable. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + +Nullable. + + + + + +The link. + +Nullable. + + + + + +Ids of the work items to associate. Replaces the current set. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `customer_id`, `description`, `description_html`, `id`, `link`, `name`. + + + +
+
+ +
+ +### Scopes + +`customers.requests:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "customer_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "link": "https://example.com", + "name": "Example name" +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/customer-requests/delete-customer-request.md b/docs/api-reference/v2/customer-requests/delete-customer-request.md new file mode 100644 index 00000000..9c413e09 --- /dev/null +++ b/docs/api-reference/v2/customer-requests/delete-customer-request.md @@ -0,0 +1,154 @@ +--- +title: Delete a customer request +description: Delete a customer request with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a customer request, customer requests, customer requests destroy +--- + +# Delete a customer request + +
+ DELETE + /api/v2/workspaces/{slug}/customers/{customer_id}/requests/{pk}/ +
+ +
+
+ +Customer requests capture what a customer asked for, and link that ask to work items. Delete a customer request. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer the resource belongs to. + + + + + +The customer request id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `customer_id`, `description`, `description_html`, `id`, `link`, `name`. + + + +
+
+ +
+ +### Scopes + +`customers.requests:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No customer request matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customer-requests/get-customer-request.md b/docs/api-reference/v2/customer-requests/get-customer-request.md new file mode 100644 index 00000000..2c9ff482 --- /dev/null +++ b/docs/api-reference/v2/customer-requests/get-customer-request.md @@ -0,0 +1,162 @@ +--- +title: Get a customer request +description: Read a customer request with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a customer request, customer requests, customer requests retrieve +--- + +# Get a customer request + +
+ GET + /api/v2/workspaces/{slug}/customers/{customer_id}/requests/{pk}/ +
+ +
+
+ +Customer requests capture what a customer asked for, and link that ask to work items. Read a single customer request by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer the resource belongs to. + + + + + +The customer request id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `customer_id`, `description`, `description_html`, `id`, `link`, `name`. + + + +
+
+ +
+ +### Scopes + +`customers.requests:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "customer_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "link": "https://example.com", + "name": "Example name" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No customer request matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customer-requests/list-customer-requests.md b/docs/api-reference/v2/customer-requests/list-customer-requests.md new file mode 100644 index 00000000..5946f82d --- /dev/null +++ b/docs/api-reference/v2/customer-requests/list-customer-requests.md @@ -0,0 +1,214 @@ +--- +title: List customer requests +description: List customer requests with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list customer requests, customer requests, customer requests list +--- + +# List customer requests + +
+ GET + /api/v2/workspaces/{slug}/customers/{customer_id}/requests/ +
+ +
+
+ +Customer requests capture what a customer asked for, and link that ask to work items. List the customer requests you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer the resource belongs to. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `customer_id`, `description`, `description_html`, `id`, `link`, `name`. + + + +
+
+ +
+ +### Scopes + +`customers.requests:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "customer_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "link": "https://example.com", + "name": "Example name" + }, + { + "archived_at": null, + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "customer_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "description_html": "

Details go here.

", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "link": "https://example.com", + "name": "Another example" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/customer-requests/overview.md b/docs/api-reference/v2/customer-requests/overview.md new file mode 100644 index 00000000..f026fffe --- /dev/null +++ b/docs/api-reference/v2/customer-requests/overview.md @@ -0,0 +1,90 @@ +--- +title: Customer requests overview +description: The Plane API v2 customer request object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, customer requests, customer request object +--- + +# Customer requests overview + +Customer requests capture what a customer asked for, and link that ask to work items. + +
+
+ +## The customer request object + +### Attributes + +- `archived_at` _string (date-time)_ + + When the record was archived, or `null` if it is active. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `customer_id` _string (uuid)_ + + The related customer. + +- `description` _string_ + + Free-form description. + +- `description_html` _string_ + + Rich-text body as HTML. This is the field the Plane editor round-trips. + +- `id` _string (uuid)_ + + Unique identifier. + +- `link` _string (uri)_ + + The link. + +- `name` _string_ + + Display name. + +
+
+ + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "customer_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "link": "https://example.com", + "name": "Example name" +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------------------ | ------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/customers/{customer_id}/requests/` | List customer requests | +| `POST` | `/api/v2/workspaces/{slug}/customers/{customer_id}/requests/` | Create a customer request | +| `DELETE` | `/api/v2/workspaces/{slug}/customers/{customer_id}/requests/{pk}/` | Delete a customer request | +| `GET` | `/api/v2/workspaces/{slug}/customers/{customer_id}/requests/{pk}/` | Get a customer request | +| `PATCH` | `/api/v2/workspaces/{slug}/customers/{customer_id}/requests/{pk}/` | Update a customer request | + +## Response shaping + +Every customer request read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/customer-requests/update-customer-request.md b/docs/api-reference/v2/customer-requests/update-customer-request.md new file mode 100644 index 00000000..81bfd2a3 --- /dev/null +++ b/docs/api-reference/v2/customer-requests/update-customer-request.md @@ -0,0 +1,230 @@ +--- +title: Update a customer request +description: Update a customer request with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a customer request, customer requests, customer requests partial update +--- + +# Update a customer request + +
+ PATCH + /api/v2/workspaces/{slug}/customers/{customer_id}/requests/{pk}/ +
+ +
+
+ +Customer requests capture what a customer asked for, and link that ask to work items. Update a customer request. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer the resource belongs to. + + + + + +The customer request id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Free-form description. + +Nullable. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + +Nullable. + + + + + +The link. + +Nullable. + + + + + +Display name. + +Maximum 255 characters. + + + + + +Ids of the work items to associate. Replaces the current set. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `customer_id`, `description`, `description_html`, `id`, `link`, `name`. + + + +
+
+ +
+ +### Scopes + +`customers.requests:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "customer_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "link": "https://example.com", + "name": "Example name" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No customer request matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customers/create-customer.md b/docs/api-reference/v2/customers/create-customer.md new file mode 100644 index 00000000..85d14def --- /dev/null +++ b/docs/api-reference/v2/customers/create-customer.md @@ -0,0 +1,281 @@ +--- +title: Create a customer +description: Create a customer with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a customer, customers, customers create +--- + +# Create a customer + +
+ POST + /api/v2/workspaces/{slug}/customers/ +
+ +
+
+ +Customers are the workspace CRM records you attach requests and work items to. Create a customer. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Current contract state, in your own vocabulary. + +Maximum 255 characters. Nullable. + + + + + +Free-form description. + +Nullable. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + +Nullable. + + + + + +The customer's primary domain, for example `example.com`. + +Maximum 255 characters. Nullable. + + + + + +Email address. + +Nullable. + + + + + +Headcount, for segmentation. + +Nullable. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Annual revenue, for segmentation. + +Nullable. + + + + + +Where the customer sits in your funnel. + +Maximum 255 characters. Nullable. + + + + + +Public website URL. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `contract_status`, `created_at`, `created_by_id`, `customer_request_count`, `description`, `description_html`, `domain`, `email`, `employees`, `external_id`, `external_source`, `id`, `logo_asset_id`, `logo_props`, `logo_url`, `name`, `revenue`, `stage`, `website_url`. + + + +
+
+ +
+ +### Scopes + +`customers:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "contract_status": "active", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "customer_request_count": 3, + "description": "What this is for.", + "description_html": "

Details go here.

", + "domain": "example.com", + "email": "ana@example.com", + "employees": 250, + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_asset_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "logo_url": "https://example.com/logo.png", + "name": "Example name", + "revenue": "4.2M", + "stage": "customer", + "website_url": "https://example.com" +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/customers/delete-customer.md b/docs/api-reference/v2/customers/delete-customer.md new file mode 100644 index 00000000..4eda2430 --- /dev/null +++ b/docs/api-reference/v2/customers/delete-customer.md @@ -0,0 +1,148 @@ +--- +title: Delete a customer +description: Delete a customer with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a customer, customers, customers destroy +--- + +# Delete a customer + +
+ DELETE + /api/v2/workspaces/{slug}/customers/{pk}/ +
+ +
+
+ +Customers are the workspace CRM records you attach requests and work items to. Delete a customer. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `contract_status`, `created_at`, `created_by_id`, `customer_request_count`, `description`, `description_html`, `domain`, `email`, `employees`, `external_id`, `external_source`, `id`, `logo_asset_id`, `logo_props`, `logo_url`, `name`, `revenue`, `stage`, `website_url`. + + + +
+
+ +
+ +### Scopes + +`customers:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No customer matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customers/get-customer.md b/docs/api-reference/v2/customers/get-customer.md new file mode 100644 index 00000000..dc33aca6 --- /dev/null +++ b/docs/api-reference/v2/customers/get-customer.md @@ -0,0 +1,167 @@ +--- +title: Get a customer +description: Read a customer with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a customer, customers, customers retrieve +--- + +# Get a customer + +
+ GET + /api/v2/workspaces/{slug}/customers/{pk}/ +
+ +
+
+ +Customers are the workspace CRM records you attach requests and work items to. Read a single customer by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `contract_status`, `created_at`, `created_by_id`, `customer_request_count`, `description`, `description_html`, `domain`, `email`, `employees`, `external_id`, `external_source`, `id`, `logo_asset_id`, `logo_props`, `logo_url`, `name`, `revenue`, `stage`, `website_url`. + + + +
+
+ +
+ +### Scopes + +`customers:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "contract_status": "active", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "customer_request_count": 3, + "description": "What this is for.", + "description_html": "

Details go here.

", + "domain": "example.com", + "email": "ana@example.com", + "employees": 250, + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_asset_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "logo_url": "https://example.com/logo.png", + "name": "Example name", + "revenue": "4.2M", + "stage": "customer", + "website_url": "https://example.com" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No customer matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customers/list-customers.md b/docs/api-reference/v2/customers/list-customers.md new file mode 100644 index 00000000..cbbdb897 --- /dev/null +++ b/docs/api-reference/v2/customers/list-customers.md @@ -0,0 +1,241 @@ +--- +title: List customers +description: List customers with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list customers, customers, customers list +--- + +# List customers + +
+ GET + /api/v2/workspaces/{slug}/customers/ +
+ +
+
+ +Customers are the workspace CRM records you attach requests and work items to. List the customers you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Filter by `contract_status`. + + + + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `domain`. + + + + + +Filter by `external_id`. + + + + + +Filter by `external_source`. + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +Filter by `stage`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `contract_status`, `created_at`, `created_by_id`, `customer_request_count`, `description`, `description_html`, `domain`, `email`, `employees`, `external_id`, `external_source`, `id`, `logo_asset_id`, `logo_props`, `logo_url`, `name`, `revenue`, `stage`, `website_url`. + + + +
+
+ +
+ +### Scopes + +`customers:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "archived_at": null, + "contract_status": "active", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "customer_request_count": 3, + "description": "What this is for.", + "description_html": "

Details go here.

", + "domain": "example.com", + "email": "ana@example.com", + "employees": 250, + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_asset_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "logo_url": "https://example.com/logo.png", + "name": "Example name", + "revenue": "4.2M", + "stage": "customer", + "website_url": "https://example.com" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/customers/manage-customer-work-items.md b/docs/api-reference/v2/customers/manage-customer-work-items.md new file mode 100644 index 00000000..c02f0c12 --- /dev/null +++ b/docs/api-reference/v2/customers/manage-customer-work-items.md @@ -0,0 +1,167 @@ +--- +title: Link or unlink customer work items +description: Link or unlink customer work items with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, link or unlink customer work items, customer work items, customers work items +--- + +# Link or unlink customer work items + +
+ POST + /api/v2/workspaces/{slug}/customers/{pk}/work-items/ +
+ +
+
+ +Customers are the workspace CRM records you attach requests and work items to. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer work item id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The add. + + + + + +The remove. + + + +
+
+ +
+ +### Scopes + +`customers:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "added": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "removed": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customers/overview.md b/docs/api-reference/v2/customers/overview.md new file mode 100644 index 00000000..51728652 --- /dev/null +++ b/docs/api-reference/v2/customers/overview.md @@ -0,0 +1,147 @@ +--- +title: Customers overview +description: The Plane API v2 customer object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, customers, customer object +--- + +# Customers overview + +Customers are the workspace CRM records you attach requests and work items to. + +
+
+ +## The customer object + +### Attributes + +- `archived_at` _string (date-time)_ + + When the record was archived, or `null` if it is active. + +- `contract_status` _string_ + + Current contract state, in your own vocabulary. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `customer_request_count` _integer_ + + How many customer requests are attached. + +- `description` _string_ + + Free-form description. + +- `description_html` _string_ + + Rich-text body as HTML. This is the field the Plane editor round-trips. + +- `domain` _string_ + + The customer's primary domain, for example `example.com`. + +- `email` _string (email)_ + + Email address. + +- `employees` _integer_ + + Headcount, for segmentation. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `logo_asset_id` _string (uuid)_ + + The related logo asset. + +- `logo_props` _string_ + + Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +- `logo_url` _string_ + + URL of the logo image. + +- `name` _string_ + + Display name. + +- `revenue` _string_ + + Annual revenue, for segmentation. + +- `stage` _string_ + + Where the customer sits in your funnel. + +- `website_url` _string (uri)_ + + Public website URL. + +
+
+ + + +```json +{ + "archived_at": null, + "contract_status": "active", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "customer_request_count": 3, + "description": "What this is for.", + "description_html": "

Details go here.

", + "domain": "example.com", + "email": "ana@example.com", + "employees": 250, + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_asset_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "logo_url": "https://example.com/logo.png", + "name": "Example name", + "revenue": "4.2M", + "stage": "customer", + "website_url": "https://example.com" +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------ | ---------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/customers/` | List customers | +| `POST` | `/api/v2/workspaces/{slug}/customers/` | Create a customer | +| `POST` | `/api/v2/workspaces/{slug}/customers/upsert/` | Upsert a customer | +| `DELETE` | `/api/v2/workspaces/{slug}/customers/{pk}/` | Delete a customer | +| `GET` | `/api/v2/workspaces/{slug}/customers/{pk}/` | Get a customer | +| `PATCH` | `/api/v2/workspaces/{slug}/customers/{pk}/` | Update a customer | +| `POST` | `/api/v2/workspaces/{slug}/customers/{pk}/work-items/` | Link or unlink customer work items | + +## Response shaping + +Every customer read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/customers/update-customer.md b/docs/api-reference/v2/customers/update-customer.md new file mode 100644 index 00000000..716bce4a --- /dev/null +++ b/docs/api-reference/v2/customers/update-customer.md @@ -0,0 +1,299 @@ +--- +title: Update a customer +description: Update a customer with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a customer, customers, customers partial update +--- + +# Update a customer + +
+ PATCH + /api/v2/workspaces/{slug}/customers/{pk}/ +
+ +
+
+ +Customers are the workspace CRM records you attach requests and work items to. Update a customer. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The customer id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Current contract state, in your own vocabulary. + +Maximum 255 characters. Nullable. + + + + + +Free-form description. + +Nullable. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + +Nullable. + + + + + +The customer's primary domain, for example `example.com`. + +Maximum 255 characters. Nullable. + + + + + +Email address. + +Nullable. + + + + + +Headcount, for segmentation. + +Nullable. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Display name. + +Maximum 255 characters. + + + + + +Annual revenue, for segmentation. + +Nullable. + + + + + +Where the customer sits in your funnel. + +Maximum 255 characters. Nullable. + + + + + +Public website URL. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `contract_status`, `created_at`, `created_by_id`, `customer_request_count`, `description`, `description_html`, `domain`, `email`, `employees`, `external_id`, `external_source`, `id`, `logo_asset_id`, `logo_props`, `logo_url`, `name`, `revenue`, `stage`, `website_url`. + + + +
+
+ +
+ +### Scopes + +`customers:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "contract_status": "active", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "customer_request_count": 3, + "description": "What this is for.", + "description_html": "

Details go here.

", + "domain": "example.com", + "email": "ana@example.com", + "employees": 250, + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_asset_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "logo_url": "https://example.com/logo.png", + "name": "Example name", + "revenue": "4.2M", + "stage": "customer", + "website_url": "https://example.com" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No customer matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/customers/upsert-customer.md b/docs/api-reference/v2/customers/upsert-customer.md new file mode 100644 index 00000000..c8bbf169 --- /dev/null +++ b/docs/api-reference/v2/customers/upsert-customer.md @@ -0,0 +1,285 @@ +--- +title: Upsert a customer +description: Upsert a customer with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, upsert a customer, customers, customers upsert +--- + +# Upsert a customer + +
+ POST + /api/v2/workspaces/{slug}/customers/upsert/ +
+ +
+
+ +Customers are the workspace CRM records you attach requests and work items to. Create a customer, or update the existing one that carries the same `(external_source, external_id)` pair. + +- Both `external_source` and `external_id` are required — without them there is nothing to match on and the request is a `400`. +- A created record answers `201` with `X-Plane-Upsert: created`; an updated one answers `200` with `X-Plane-Upsert: updated`. Branch on the header rather than guessing from the status. +- Upsert is safe for **sequential** importers. Two simultaneous upserts of the same key can each miss and each create, so serialize your writes per key. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Current contract state, in your own vocabulary. + +Maximum 255 characters. Nullable. + + + + + +Free-form description. + +Nullable. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + +Nullable. + + + + + +The customer's primary domain, for example `example.com`. + +Maximum 255 characters. Nullable. + + + + + +Email address. + +Nullable. + + + + + +Headcount, for segmentation. + +Nullable. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Annual revenue, for segmentation. + +Nullable. + + + + + +Where the customer sits in your funnel. + +Maximum 255 characters. Nullable. + + + + + +Public website URL. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `contract_status`, `created_at`, `created_by_id`, `customer_request_count`, `description`, `description_html`, `domain`, `email`, `employees`, `external_id`, `external_source`, `id`, `logo_asset_id`, `logo_props`, `logo_url`, `name`, `revenue`, `stage`, `website_url`. + + + +
+
+ +
+ +### Scopes + +`customers:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "contract_status": "active", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "customer_request_count": 3, + "description": "What this is for.", + "description_html": "

Details go here.

", + "domain": "example.com", + "email": "ana@example.com", + "employees": 250, + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_asset_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "logo_url": "https://example.com/logo.png", + "name": "Example name", + "revenue": "4.2M", + "stage": "customer", + "website_url": "https://example.com" +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/cycles/bulk-cycles.md b/docs/api-reference/v2/cycles/bulk-cycles.md new file mode 100644 index 00000000..44d17203 --- /dev/null +++ b/docs/api-reference/v2/cycles/bulk-cycles.md @@ -0,0 +1,201 @@ +--- +title: Bulk write cycles +description: Bulk write cycles with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, bulk write cycles, cycles, cycles bulk +--- + +# Bulk write cycles + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/cycles/bulk/ +
+ +
+
+ +Cycles are time-boxed iterations in a project. Write up to 100 cycles in one request, with a per-row result for each. + +- Send `{"items": [...]}`, at most 100 per request. Add `"upsert": true` to match existing rows on `(external_source, external_id)` instead of always creating. +- The response is always `200`. Each row reports its own outcome, so a partial failure is reported rather than silently dropped — read `succeeded`/`failed` and the per-row `status`. +- Rows are written on independent savepoints: a failing row rolls back only itself. +- `?fields=` does not apply here — the bulk response is a per-row result envelope, not a resource body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The items. + + + + + +Reconcile each row on (external_source, external_id) instead of creating it. + + + +
+
+ +
+ +### Scopes + +`projects.cycles:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "failed": 1, + "results": [null], + "succeeded": 1 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/cycles/create-cycle.md b/docs/api-reference/v2/cycles/create-cycle.md new file mode 100644 index 00000000..1fba51de --- /dev/null +++ b/docs/api-reference/v2/cycles/create-cycle.md @@ -0,0 +1,270 @@ +--- +title: Create a cycle +description: Create a time-boxed cycle in a Plane project with the v2 REST API. Body parameters, dates and time zones, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create cycle, sprint, iteration, start_date, end_date, cycle timezone, POST cycles +--- + +# Create a cycle + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/cycles/ +
+ +
+
+ +Add a cycle to a project. Only `name` is required — leave the dates off to create an unscheduled cycle and fill them in later with [Update a cycle](/api-reference/v2/cycles/update-cycle). + +Cycle names must be unique within a project. Reusing one returns `409 conflict`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project to add the cycle to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name for the cycle, unique within the project. Maximum 255 characters. + + + + + +Free-form description of what the cycle covers. + + + + + +When the cycle opens, as an ISO 8601 date-time. Nullable — omit it or send `null` for an unscheduled cycle. + + + + + +When the cycle closes, as an ISO 8601 date-time. Nullable. + + + + + +The IANA time zone the cycle's dates are interpreted in, for example `America/New_York`, `Asia/Kolkata`, `Europe/London`, or `UTC`. Set it to the team's working zone so a cycle boundary lands at local midnight instead of UTC midnight. Any value outside the IANA list is rejected with `400 invalid_request`. + + + + + +Ordering weight for the cycle within the project. Lower values sort first when you list with `?order_by=sort_order`. + + + + + +JSON blob holding the cycle's icon configuration, as written by Plane clients. Stored and returned unchanged. + + + + + +Your system's identifier for this cycle, for sync and import correlation. Maximum 255 characters. You can find the cycle again later with `?external_id=` on [List cycles](/api-reference/v2/cycles/list-cycles). + + + + + +The system `external_id` came from, for example `jira` or `linear`. Maximum 255 characters. + + + +
+
+ +::: info Owner is not settable +`owned_by_id` is returned on the response but is not a body parameter — Plane assigns cycle ownership and the API does not accept an override. +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `created_by_id`, `description`, `end_date`, `external_id`, `external_source`, `id`, `logo_props`, `name`, `owned_by_id`, `sort_order`, `start_date`, `timezone`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `owned_by` (the cycle owner). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.cycles:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | Missing `name`, a name over 255 characters, an unparseable date, or a `timezone` outside the IANA list. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't create cycles in this project. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A cycle with this name already exists in the project. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "7c1f9a4e-3b6d-4a52-8f0c-2d9e6b18c3a7", + "name": "Sprint 24", + "description": "Checkout rewrite and billing cleanup", + "start_date": "2026-01-05T00:00:00Z", + "end_date": "2026-01-19T00:00:00Z", + "timezone": "America/New_York", + "owned_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "sort_order": 65535, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "conflict", + "detail": "A cycle with this name already exists in this project." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/cycles/delete-cycle.md b/docs/api-reference/v2/cycles/delete-cycle.md new file mode 100644 index 00000000..615d5430 --- /dev/null +++ b/docs/api-reference/v2/cycles/delete-cycle.md @@ -0,0 +1,174 @@ +--- +title: Delete a cycle +description: Delete a Plane cycle with the v2 REST API. Path parameters, the 204 response, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete cycle, remove cycle, soft delete, DELETE cycles +--- + +# Delete a cycle + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/cycles/{pk}/ +
+ +
+
+ +Remove a cycle from a project. The delete is a soft delete — the cycle stops appearing in reads and its name is freed for reuse. + +A successful call returns `204` with an empty body. There is nothing to parse, so branch on the status code. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the cycle belongs to. + + + + + +The cycle to delete. + + + +
+
+ +::: warning Deleting the cycle does not delete its work items +Work items that were in the cycle survive the delete — they simply lose their cycle assignment. Move them to another cycle first if the assignment matters, using the v1 [cycle work-item endpoints](/api-reference/v1/cycle/overview). +::: + +Deleting a cycle that is already deleted returns `404`, so the call is safe to retry: treat both `204` and `404` as "gone". + +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `created_by_id`, `description`, `end_date`, `external_id`, `external_source`, `id`, `logo_props`, `name`, `owned_by_id`, `sort_order`, `start_date`, `timezone`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `owned_by` (the cycle owner). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.cycles:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | A malformed path parameter, for example a `pk` that isn't a UUID. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't delete cycles in this project. | +| `404` | `not_found` | No such cycle, wrong project, or the record is outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The cycle's current state blocks deletion. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No cycle matches the given id in this project." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/cycles/get-cycle.md b/docs/api-reference/v2/cycles/get-cycle.md new file mode 100644 index 00000000..8c2c7854 --- /dev/null +++ b/docs/api-reference/v2/cycles/get-cycle.md @@ -0,0 +1,181 @@ +--- +title: Get a cycle +description: Retrieve a single Plane cycle by id with the v2 REST API. Path parameters, response fields, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get cycle, retrieve cycle, cycle by id, GET cycles +--- + +# Get a cycle + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/cycles/{pk}/ +
+ +
+
+ +Fetch one cycle by id. The response is the same cycle object the list endpoint returns for each row, so you only need this call when you already hold an id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the cycle belongs to. A cycle id from a different project returns `404`, not the record. + + + + + +The cycle to retrieve. + + + +
+
+ +::: info No `?expand=` on cycles +Cycles do not support `?expand=`. `owned_by_id` and `created_by_id` are always plain ids — resolve them against the members endpoints when you need display names. +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `created_by_id`, `description`, `end_date`, `external_id`, `external_source`, `id`, `logo_props`, `name`, `owned_by_id`, `sort_order`, `start_date`, `timezone`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `owned_by` (the cycle owner). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.cycles:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read cycles in this project. | +| `404` | `not_found` | No such cycle, wrong project, or the record is outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "7c1f9a4e-3b6d-4a52-8f0c-2d9e6b18c3a7", + "name": "Sprint 24", + "description": "Checkout rewrite and billing cleanup", + "start_date": "2026-01-05T00:00:00Z", + "end_date": "2026-01-19T00:00:00Z", + "timezone": "America/New_York", + "owned_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "sort_order": 65535, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No cycle matches the given id in this project." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/cycles/list-cycles.md b/docs/api-reference/v2/cycles/list-cycles.md new file mode 100644 index 00000000..2c883d62 --- /dev/null +++ b/docs/api-reference/v2/cycles/list-cycles.md @@ -0,0 +1,311 @@ +--- +title: List cycles +description: List the cycles in a Plane project with the v2 REST API. Filters, search, ordering, pagination, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list cycles, cycles pagination, owned_by_id filter, order_by sort_order, GET cycles +--- + +# List cycles + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/cycles/ +
+ +
+
+ +Return the cycles in a project as a paginated page. Use it to build a sprint picker, to find the cycle covering a date range, or to reconcile cycles you imported from another tracker. + +Filters combine with `AND` — `?owned_by_id=…&search=sprint` returns only cycles that match both. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project whose cycles you want. + + + +
+
+ +
+ +### Query Parameters + +**Filters** + +
+ + + +Return only cycles owned by this user. This is how you build a "my cycles" view — pass the authenticated user's id. Ownership is assigned by Plane, so this is a read-side filter only. + + + + + +Return only cycles whose `external_id` matches exactly. Pair it with `external_source` when the same identifier could come from more than one system. + + + + + +Return only cycles imported from this source, for example `jira`. + + + +
+ +**Search** + +
+ + + +Free-text search term matched against the cycle. + + + +
+ +**Ordering** + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `sort_order` / `-sort_order` — the project's manual cycle ordering +- `created_at` / `-created_at` — newest or oldest first +- `id` / `-id` + + + +
+ +**Pagination** + +
+ + + +Page size. Defaults to 50, maximum 200. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000 — past that, switch to cursor pagination. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope instead of the default offset envelope. The response then carries `next_cursor` and `has_more` rather than `next`, `previous`, and `total_count`; pass `next_cursor` back as `cursor` to walk to the following page. Use it for deep traversal, where offset paging gets expensive. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count` — the field is then omitted from the envelope. Worth doing when you only need the rows. + + + +
+ +::: warning Bad enum values fail silently +`order_by` and `paginate` are not validated against their allowed values. An unrecognized `order_by` falls back to the cycle default ordering, and anything other than `paginate=cursor` uses offset pagination — so check your spelling, because a typo shows up as an unexpected sort order or envelope rather than an error. +::: + +
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `created_at`, `created_by_id`, `description`, `end_date`, `external_id`, `external_source`, `id`, `logo_props`, `name`, `owned_by_id`, `sort_order`, `start_date`, `timezone`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `owned_by` (the cycle owner). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.cycles:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read cycles in this project. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "7c1f9a4e-3b6d-4a52-8f0c-2d9e6b18c3a7", + "name": "Sprint 24", + "description": "Checkout rewrite and billing cleanup", + "start_date": "2026-01-05T00:00:00Z", + "end_date": "2026-01-19T00:00:00Z", + "timezone": "America/New_York", + "owned_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "sort_order": 65535, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + }, + { + "id": "2b8d5e07-9a41-4c6f-b3d2-71e8a4c05f96", + "name": "Sprint 23", + "description": "", + "start_date": "2025-12-22T00:00:00Z", + "end_date": "2026-01-05T00:00:00Z", + "timezone": "America/New_York", + "owned_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "sort_order": 55535, + "logo_props": {}, + "external_id": "SPR-23", + "external_source": "jira", + "created_at": "2025-12-18T11:04:07.912004Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next": 50, + "previous": null, + "total_count": 27, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "7c1f9a4e-3b6d-4a52-8f0c-2d9e6b18c3a7", + "name": "Sprint 24", + "description": "Checkout rewrite and billing cleanup", + "start_date": "2026-01-05T00:00:00Z", + "end_date": "2026-01-19T00:00:00Z", + "timezone": "America/New_York", + "owned_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "sort_order": 65535, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next_cursor": "b3A9MTcxOjA6MA==", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + + + +
+
diff --git a/docs/api-reference/v2/cycles/manage-cycle-work-items.md b/docs/api-reference/v2/cycles/manage-cycle-work-items.md new file mode 100644 index 00000000..08f263e9 --- /dev/null +++ b/docs/api-reference/v2/cycles/manage-cycle-work-items.md @@ -0,0 +1,173 @@ +--- +title: Add or remove cycle work items +description: Add or remove cycle work items with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, add or remove cycle work items, cycle work items, cycles work items manage +--- + +# Add or remove cycle work items + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/cycles/{pk}/work-items/ +
+ +
+
+ +Cycles are time-boxed iterations in a project. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The cycle work item id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The add. + + + + + +The remove. + + + +
+
+ +
+ +### Scopes + +`projects.cycles:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "added": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "removed": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/cycles/overview.md b/docs/api-reference/v2/cycles/overview.md new file mode 100644 index 00000000..065b5fc6 --- /dev/null +++ b/docs/api-reference/v2/cycles/overview.md @@ -0,0 +1,147 @@ +--- +title: Cycles overview +description: The Plane API v2 cycle object. Attributes, start and end dates, time zones, cycle ownership, and the endpoints for managing project cycles. +keywords: plane api v2, cycles, sprints, iterations, start_date, end_date, cycle timezone, owned_by_id +--- + +# Cycles overview + +A cycle is a time-boxed iteration inside a project — a sprint, a release window, or any fixed period a team commits work to. A cycle belongs to exactly one project and is never shared across projects. + +Cycles are defined by a `start_date` and an `end_date`, interpreted in the cycle's own `timezone`. Both dates are optional, so a cycle can exist as an unscheduled container and be given dates later. + +[Learn more about cycles](https://docs.plane.so/core-concepts/cycles) + +
+
+ +## The cycle object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the cycle. + +- `name` _string_ + + Display name, unique within the project. Maximum 255 characters. + +- `description` _string_ + + Free-form description of what the cycle covers. + +- `start_date` _string (date-time)_ + + When the cycle opens. `null` if the cycle has not been scheduled. + +- `end_date` _string (date-time)_ + + When the cycle closes. `null` if the cycle has not been scheduled. + +- `timezone` _string_ + + The IANA time zone the cycle's dates are interpreted in, for example `America/New_York` or `UTC`. This is what makes a cycle boundary land at local midnight rather than UTC midnight for a distributed team. + +- `owned_by_id` _string (uuid)_ + + The user who owns the cycle. Read-only — assigned by Plane and not settable through the API. Use it to filter cycles on list. + +- `sort_order` _number_ + + Ordering weight for the cycle within the project. Lower values sort first when you order by `sort_order`. + +- `logo_props` _any_ + + JSON blob holding the cycle's icon configuration, as written by Plane clients. Passed through unchanged. + +- `external_id` , `external_source` _string_ + + Correlation fields for sync and import. Together they let you map a cycle to a record in another system and find it again later — both are exposed as list filters. + +- `created_at` _string (date-time)_ + + When the cycle was created. + +- `created_by_id` _string (uuid)_ + + The user who created the cycle. + +::: info Dates are timestamps, not calendar days +`start_date` and `end_date` are full date-times. Send them as ISO 8601 timestamps and read `timezone` to interpret where the boundary falls. +::: + +
+
+ + + +```json +{ + "id": "7c1f9a4e-3b6d-4a52-8f0c-2d9e6b18c3a7", + "name": "Sprint 24", + "description": "Checkout rewrite and billing cleanup", + "start_date": "2026-01-05T00:00:00Z", + "end_date": "2026-01-19T00:00:00Z", + "timezone": "America/New_York", + "owned_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "sort_order": 65535, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------------------------- | ---------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/cycles/` | List cycles | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/cycles/` | Create a cycle | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/cycles/bulk/` | Bulk write cycles | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/cycles/upsert/` | Upsert a cycle | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/cycles/{pk}/` | Delete a cycle | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/cycles/{pk}/` | Get a cycle | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/cycles/{pk}/` | Update a cycle | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/cycles/{pk}/transfer/` | Transfer work items between cycles | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/cycles/{pk}/work-items/` | Add or remove cycle work items | + +## Names are unique per project + +Cycle names must be unique within a project. Creating a second cycle with a name already in use, or renaming a cycle onto an existing name, returns `409 conflict`. Uniqueness is scoped to the project, so two different projects can both have a `Sprint 24`. + +## Ownership is read-only + +`owned_by_id` is returned on every read but is not part of the write body — you cannot assign a cycle owner through the v2 API. You can still filter by owner on list with `?owned_by_id=`, which is the common case for building "my cycles" views. + +## No `?expand=` on cycles + +Cycles do not support `?expand=`. Related records are referenced by id only — resolve `owned_by_id` and `created_by_id` against the members endpoints when you need names. + +## Cycle work items are v1-only + +v2 does not yet expose the cycle work-item sub-resources. Adding work items to a cycle, listing a cycle's work items, removing one, or transferring work items between cycles are all still v1 operations. + +::: warning Use v1 for cycle membership +Create and manage the cycle itself in v2, then use the v1 cycle work-item endpoints to populate it. See the +[v1 cycle reference](/api-reference/v1/cycle/overview). +::: + +Archiving is likewise not part of the v2 cycles surface — v2 exposes create, read, update, and delete only. + +## Changed from v1 + +- `owned_by` is now **`owned_by_id`**, and `created_by` is now **`created_by_id`**. +- `start_date` and `end_date` are **date-times**, not plain calendar dates. +- Reads no longer return `updated_at`, `updated_by`, `project`, `workspace`, or `view_props`. +- The list endpoint drops `cycle_view`, `expand`, and `fields`, and adds `search`, `owned_by_id`, `external_id`, and `external_source` filters. +- Updates are `PATCH` only — v2 has no `PUT`. + +See [Migrating from v1](/api-reference/v2/migrating-from-v1) for the full list. diff --git a/docs/api-reference/v2/cycles/transfer-cycle-work-items.md b/docs/api-reference/v2/cycles/transfer-cycle-work-items.md new file mode 100644 index 00000000..c141f373 --- /dev/null +++ b/docs/api-reference/v2/cycles/transfer-cycle-work-items.md @@ -0,0 +1,155 @@ +--- +title: Transfer work items between cycles +description: Transfer work items between cycles with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, transfer work items between cycles, cycle transfer, cycles transfer +--- + +# Transfer work items between cycles + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/cycles/{pk}/transfer/ +
+ +
+
+ +Cycles are time-boxed iterations in a project. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The cycle transfer id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Id of the related new cycle. + + + +
+
+ +
+ +### Scopes + +`projects.cycles:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "new_cycle_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/cycles/update-cycle.md b/docs/api-reference/v2/cycles/update-cycle.md new file mode 100644 index 00000000..a7ec6ffe --- /dev/null +++ b/docs/api-reference/v2/cycles/update-cycle.md @@ -0,0 +1,274 @@ +--- +title: Update a cycle +description: Partially update a Plane cycle with the v2 REST API. PATCH body parameters, date and time zone changes, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update cycle, patch cycle, reschedule cycle, start_date, end_date, PATCH cycles +--- + +# Update a cycle + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/cycles/{pk}/ +
+ +
+
+ +Change a cycle in place — rename it, reschedule it, or attach correlation ids after an import. + +The update is partial. Fields you omit are left untouched, and omitting a field is not the same as sending `null`: send `"end_date": null` to clear a date, omit `end_date` to keep it. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the cycle belongs to. + + + + + +The cycle to update. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +New display name, unique within the project. Maximum 255 characters. Renaming onto a name another cycle already holds returns `409 conflict`. + + + + + +Free-form description of what the cycle covers. + + + + + +New opening date-time, in ISO 8601. Send `null` to unschedule the start. + + + + + +New closing date-time, in ISO 8601. Send `null` to unschedule the end. + + + + + +The IANA time zone the cycle's dates are interpreted in, for example `America/New_York` or `UTC`. Changing it re-anchors where the existing boundaries fall locally, so send it together with the dates when you are moving a cycle between regions. Any value outside the IANA list is rejected with `400 invalid_request`. + + + + + +Ordering weight for the cycle within the project. Lower values sort first when you list with `?order_by=sort_order`. + + + + + +JSON blob holding the cycle's icon configuration. Replaces the stored value outright — it is not merged key by key. + + + + + +Your system's identifier for this cycle. Maximum 255 characters, nullable. + + + + + +The system `external_id` came from, for example `jira`. Maximum 255 characters, nullable. + + + +
+
+ +::: warning There is no `PUT` +v2 updates are `PATCH` only. A `PUT` to this path returns `405 method_not_allowed`. Audit fields such as `created_at`, `created_by_id`, and `owned_by_id` are read-only — including them has no effect. +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `created_by_id`, `description`, `end_date`, `external_id`, `external_source`, `id`, `logo_props`, `name`, `owned_by_id`, `sort_order`, `start_date`, `timezone`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `owned_by` (the cycle owner). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.cycles:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | --------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | A name over 255 characters, an unparseable date, or a `timezone` outside the IANA list. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't update cycles in this project. | +| `404` | `not_found` | No such cycle, wrong project, or the record is outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | Another cycle in the project already uses this name. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "7c1f9a4e-3b6d-4a52-8f0c-2d9e6b18c3a7", + "name": "Sprint 24 (extended)", + "description": "Checkout rewrite and billing cleanup", + "start_date": "2026-01-05T00:00:00Z", + "end_date": "2026-01-26T00:00:00Z", + "timezone": "America/New_York", + "owned_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "sort_order": 65535, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "The request body failed validation.", + "errors": [ + { + "field": "timezone", + "code": "invalid_choice", + "message": "\"America/Atlantis\" is not a valid choice." + } + ] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/cycles/upsert-cycle.md b/docs/api-reference/v2/cycles/upsert-cycle.md new file mode 100644 index 00000000..6c87a49a --- /dev/null +++ b/docs/api-reference/v2/cycles/upsert-cycle.md @@ -0,0 +1,691 @@ +--- +title: Upsert a cycle +description: Upsert a cycle with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, upsert a cycle, cycles, cycles upsert +--- + +# Upsert a cycle + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/cycles/upsert/ +
+ +
+
+ +Cycles are time-boxed iterations in a project. Create a cycle, or update the existing one that carries the same `(external_source, external_id)` pair. + +- Both `external_source` and `external_id` are required — without them there is nothing to match on and the request is a `400`. +- A created record answers `201` with `X-Plane-Upsert: created`; an updated one answers `200` with `X-Plane-Upsert: updated`. Branch on the header rather than guessing from the status. +- Upsert is safe for **sequential** importers. Two simultaneous upserts of the same key can each miss and each create, so serialize your writes per key. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Free-form description. + + + + + +End date, as `YYYY-MM-DD`. + +Nullable. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Manual ordering weight. Lower sorts first. + + + + + +Planned start date, as `YYYY-MM-DD`. + +Nullable. + + + + + +- `Africa/Abidjan` - Africa/Abidjan +- `Africa/Accra` - Africa/Accra +- `Africa/Addis_Ababa` - Africa/Addis_Ababa +- `Africa/Algiers` - Africa/Algiers +- `Africa/Asmara` - Africa/Asmara +- `Africa/Bamako` - Africa/Bamako +- `Africa/Bangui` - Africa/Bangui +- `Africa/Banjul` - Africa/Banjul +- `Africa/Bissau` - Africa/Bissau +- `Africa/Blantyre` - Africa/Blantyre +- `Africa/Brazzaville` - Africa/Brazzaville +- `Africa/Bujumbura` - Africa/Bujumbura +- `Africa/Cairo` - Africa/Cairo +- `Africa/Casablanca` - Africa/Casablanca +- `Africa/Ceuta` - Africa/Ceuta +- `Africa/Conakry` - Africa/Conakry +- `Africa/Dakar` - Africa/Dakar +- `Africa/Dar_es_Salaam` - Africa/Dar_es_Salaam +- `Africa/Djibouti` - Africa/Djibouti +- `Africa/Douala` - Africa/Douala +- `Africa/El_Aaiun` - Africa/El_Aaiun +- `Africa/Freetown` - Africa/Freetown +- `Africa/Gaborone` - Africa/Gaborone +- `Africa/Harare` - Africa/Harare +- `Africa/Johannesburg` - Africa/Johannesburg +- `Africa/Juba` - Africa/Juba +- `Africa/Kampala` - Africa/Kampala +- `Africa/Khartoum` - Africa/Khartoum +- `Africa/Kigali` - Africa/Kigali +- `Africa/Kinshasa` - Africa/Kinshasa +- `Africa/Lagos` - Africa/Lagos +- `Africa/Libreville` - Africa/Libreville +- `Africa/Lome` - Africa/Lome +- `Africa/Luanda` - Africa/Luanda +- `Africa/Lubumbashi` - Africa/Lubumbashi +- `Africa/Lusaka` - Africa/Lusaka +- `Africa/Malabo` - Africa/Malabo +- `Africa/Maputo` - Africa/Maputo +- `Africa/Maseru` - Africa/Maseru +- `Africa/Mbabane` - Africa/Mbabane +- `Africa/Mogadishu` - Africa/Mogadishu +- `Africa/Monrovia` - Africa/Monrovia +- `Africa/Nairobi` - Africa/Nairobi +- `Africa/Ndjamena` - Africa/Ndjamena +- `Africa/Niamey` - Africa/Niamey +- `Africa/Nouakchott` - Africa/Nouakchott +- `Africa/Ouagadougou` - Africa/Ouagadougou +- `Africa/Porto-Novo` - Africa/Porto-Novo +- `Africa/Sao_Tome` - Africa/Sao_Tome +- `Africa/Tripoli` - Africa/Tripoli +- `Africa/Tunis` - Africa/Tunis +- `Africa/Windhoek` - Africa/Windhoek +- `America/Adak` - America/Adak +- `America/Anchorage` - America/Anchorage +- `America/Anguilla` - America/Anguilla +- `America/Antigua` - America/Antigua +- `America/Araguaina` - America/Araguaina +- `America/Argentina/Buenos_Aires` - America/Argentina/Buenos_Aires +- `America/Argentina/Catamarca` - America/Argentina/Catamarca +- `America/Argentina/Cordoba` - America/Argentina/Cordoba +- `America/Argentina/Jujuy` - America/Argentina/Jujuy +- `America/Argentina/La_Rioja` - America/Argentina/La_Rioja +- `America/Argentina/Mendoza` - America/Argentina/Mendoza +- `America/Argentina/Rio_Gallegos` - America/Argentina/Rio_Gallegos +- `America/Argentina/Salta` - America/Argentina/Salta +- `America/Argentina/San_Juan` - America/Argentina/San_Juan +- `America/Argentina/San_Luis` - America/Argentina/San_Luis +- `America/Argentina/Tucuman` - America/Argentina/Tucuman +- `America/Argentina/Ushuaia` - America/Argentina/Ushuaia +- `America/Aruba` - America/Aruba +- `America/Asuncion` - America/Asuncion +- `America/Atikokan` - America/Atikokan +- `America/Bahia` - America/Bahia +- `America/Bahia_Banderas` - America/Bahia_Banderas +- `America/Barbados` - America/Barbados +- `America/Belem` - America/Belem +- `America/Belize` - America/Belize +- `America/Blanc-Sablon` - America/Blanc-Sablon +- `America/Boa_Vista` - America/Boa_Vista +- `America/Bogota` - America/Bogota +- `America/Boise` - America/Boise +- `America/Cambridge_Bay` - America/Cambridge_Bay +- `America/Campo_Grande` - America/Campo_Grande +- `America/Cancun` - America/Cancun +- `America/Caracas` - America/Caracas +- `America/Cayenne` - America/Cayenne +- `America/Cayman` - America/Cayman +- `America/Chicago` - America/Chicago +- `America/Chihuahua` - America/Chihuahua +- `America/Ciudad_Juarez` - America/Ciudad_Juarez +- `America/Costa_Rica` - America/Costa_Rica +- `America/Creston` - America/Creston +- `America/Cuiaba` - America/Cuiaba +- `America/Curacao` - America/Curacao +- `America/Danmarkshavn` - America/Danmarkshavn +- `America/Dawson` - America/Dawson +- `America/Dawson_Creek` - America/Dawson_Creek +- `America/Denver` - America/Denver +- `America/Detroit` - America/Detroit +- `America/Dominica` - America/Dominica +- `America/Edmonton` - America/Edmonton +- `America/Eirunepe` - America/Eirunepe +- `America/El_Salvador` - America/El_Salvador +- `America/Fort_Nelson` - America/Fort_Nelson +- `America/Fortaleza` - America/Fortaleza +- `America/Glace_Bay` - America/Glace_Bay +- `America/Goose_Bay` - America/Goose_Bay +- `America/Grand_Turk` - America/Grand_Turk +- `America/Grenada` - America/Grenada +- `America/Guadeloupe` - America/Guadeloupe +- `America/Guatemala` - America/Guatemala +- `America/Guayaquil` - America/Guayaquil +- `America/Guyana` - America/Guyana +- `America/Halifax` - America/Halifax +- `America/Havana` - America/Havana +- `America/Hermosillo` - America/Hermosillo +- `America/Indiana/Indianapolis` - America/Indiana/Indianapolis +- `America/Indiana/Knox` - America/Indiana/Knox +- `America/Indiana/Marengo` - America/Indiana/Marengo +- `America/Indiana/Petersburg` - America/Indiana/Petersburg +- `America/Indiana/Tell_City` - America/Indiana/Tell_City +- `America/Indiana/Vevay` - America/Indiana/Vevay +- `America/Indiana/Vincennes` - America/Indiana/Vincennes +- `America/Indiana/Winamac` - America/Indiana/Winamac +- `America/Inuvik` - America/Inuvik +- `America/Iqaluit` - America/Iqaluit +- `America/Jamaica` - America/Jamaica +- `America/Juneau` - America/Juneau +- `America/Kentucky/Louisville` - America/Kentucky/Louisville +- `America/Kentucky/Monticello` - America/Kentucky/Monticello +- `America/Kralendijk` - America/Kralendijk +- `America/La_Paz` - America/La_Paz +- `America/Lima` - America/Lima +- `America/Los_Angeles` - America/Los_Angeles +- `America/Lower_Princes` - America/Lower_Princes +- `America/Maceio` - America/Maceio +- `America/Managua` - America/Managua +- `America/Manaus` - America/Manaus +- `America/Marigot` - America/Marigot +- `America/Martinique` - America/Martinique +- `America/Matamoros` - America/Matamoros +- `America/Mazatlan` - America/Mazatlan +- `America/Menominee` - America/Menominee +- `America/Merida` - America/Merida +- `America/Metlakatla` - America/Metlakatla +- `America/Mexico_City` - America/Mexico_City +- `America/Miquelon` - America/Miquelon +- `America/Moncton` - America/Moncton +- `America/Monterrey` - America/Monterrey +- `America/Montevideo` - America/Montevideo +- `America/Montserrat` - America/Montserrat +- `America/Nassau` - America/Nassau +- `America/New_York` - America/New_York +- `America/Nome` - America/Nome +- `America/Noronha` - America/Noronha +- `America/North_Dakota/Beulah` - America/North_Dakota/Beulah +- `America/North_Dakota/Center` - America/North_Dakota/Center +- `America/North_Dakota/New_Salem` - America/North_Dakota/New_Salem +- `America/Nuuk` - America/Nuuk +- `America/Ojinaga` - America/Ojinaga +- `America/Panama` - America/Panama +- `America/Paramaribo` - America/Paramaribo +- `America/Phoenix` - America/Phoenix +- `America/Port-au-Prince` - America/Port-au-Prince +- `America/Port_of_Spain` - America/Port_of_Spain +- `America/Porto_Velho` - America/Porto_Velho +- `America/Puerto_Rico` - America/Puerto_Rico +- `America/Punta_Arenas` - America/Punta_Arenas +- `America/Rankin_Inlet` - America/Rankin_Inlet +- `America/Recife` - America/Recife +- `America/Regina` - America/Regina +- `America/Resolute` - America/Resolute +- `America/Rio_Branco` - America/Rio_Branco +- `America/Santarem` - America/Santarem +- `America/Santiago` - America/Santiago +- `America/Santo_Domingo` - America/Santo_Domingo +- `America/Sao_Paulo` - America/Sao_Paulo +- `America/Scoresbysund` - America/Scoresbysund +- `America/Sitka` - America/Sitka +- `America/St_Barthelemy` - America/St_Barthelemy +- `America/St_Johns` - America/St_Johns +- `America/St_Kitts` - America/St_Kitts +- `America/St_Lucia` - America/St_Lucia +- `America/St_Thomas` - America/St_Thomas +- `America/St_Vincent` - America/St_Vincent +- `America/Swift_Current` - America/Swift_Current +- `America/Tegucigalpa` - America/Tegucigalpa +- `America/Thule` - America/Thule +- `America/Tijuana` - America/Tijuana +- `America/Toronto` - America/Toronto +- `America/Tortola` - America/Tortola +- `America/Vancouver` - America/Vancouver +- `America/Whitehorse` - America/Whitehorse +- `America/Winnipeg` - America/Winnipeg +- `America/Yakutat` - America/Yakutat +- `Antarctica/Casey` - Antarctica/Casey +- `Antarctica/Davis` - Antarctica/Davis +- `Antarctica/DumontDUrville` - Antarctica/DumontDUrville +- `Antarctica/Macquarie` - Antarctica/Macquarie +- `Antarctica/Mawson` - Antarctica/Mawson +- `Antarctica/McMurdo` - Antarctica/McMurdo +- `Antarctica/Palmer` - Antarctica/Palmer +- `Antarctica/Rothera` - Antarctica/Rothera +- `Antarctica/Syowa` - Antarctica/Syowa +- `Antarctica/Troll` - Antarctica/Troll +- `Antarctica/Vostok` - Antarctica/Vostok +- `Arctic/Longyearbyen` - Arctic/Longyearbyen +- `Asia/Aden` - Asia/Aden +- `Asia/Almaty` - Asia/Almaty +- `Asia/Amman` - Asia/Amman +- `Asia/Anadyr` - Asia/Anadyr +- `Asia/Aqtau` - Asia/Aqtau +- `Asia/Aqtobe` - Asia/Aqtobe +- `Asia/Ashgabat` - Asia/Ashgabat +- `Asia/Atyrau` - Asia/Atyrau +- `Asia/Baghdad` - Asia/Baghdad +- `Asia/Bahrain` - Asia/Bahrain +- `Asia/Baku` - Asia/Baku +- `Asia/Bangkok` - Asia/Bangkok +- `Asia/Barnaul` - Asia/Barnaul +- `Asia/Beirut` - Asia/Beirut +- `Asia/Bishkek` - Asia/Bishkek +- `Asia/Brunei` - Asia/Brunei +- `Asia/Chita` - Asia/Chita +- `Asia/Choibalsan` - Asia/Choibalsan +- `Asia/Colombo` - Asia/Colombo +- `Asia/Damascus` - Asia/Damascus +- `Asia/Dhaka` - Asia/Dhaka +- `Asia/Dili` - Asia/Dili +- `Asia/Dubai` - Asia/Dubai +- `Asia/Dushanbe` - Asia/Dushanbe +- `Asia/Famagusta` - Asia/Famagusta +- `Asia/Gaza` - Asia/Gaza +- `Asia/Hebron` - Asia/Hebron +- `Asia/Ho_Chi_Minh` - Asia/Ho_Chi_Minh +- `Asia/Hong_Kong` - Asia/Hong_Kong +- `Asia/Hovd` - Asia/Hovd +- `Asia/Irkutsk` - Asia/Irkutsk +- `Asia/Jakarta` - Asia/Jakarta +- `Asia/Jayapura` - Asia/Jayapura +- `Asia/Jerusalem` - Asia/Jerusalem +- `Asia/Kabul` - Asia/Kabul +- `Asia/Kamchatka` - Asia/Kamchatka +- `Asia/Karachi` - Asia/Karachi +- `Asia/Kathmandu` - Asia/Kathmandu +- `Asia/Khandyga` - Asia/Khandyga +- `Asia/Kolkata` - Asia/Kolkata +- `Asia/Krasnoyarsk` - Asia/Krasnoyarsk +- `Asia/Kuala_Lumpur` - Asia/Kuala_Lumpur +- `Asia/Kuching` - Asia/Kuching +- `Asia/Kuwait` - Asia/Kuwait +- `Asia/Macau` - Asia/Macau +- `Asia/Magadan` - Asia/Magadan +- `Asia/Makassar` - Asia/Makassar +- `Asia/Manila` - Asia/Manila +- `Asia/Muscat` - Asia/Muscat +- `Asia/Nicosia` - Asia/Nicosia +- `Asia/Novokuznetsk` - Asia/Novokuznetsk +- `Asia/Novosibirsk` - Asia/Novosibirsk +- `Asia/Omsk` - Asia/Omsk +- `Asia/Oral` - Asia/Oral +- `Asia/Phnom_Penh` - Asia/Phnom_Penh +- `Asia/Pontianak` - Asia/Pontianak +- `Asia/Pyongyang` - Asia/Pyongyang +- `Asia/Qatar` - Asia/Qatar +- `Asia/Qostanay` - Asia/Qostanay +- `Asia/Qyzylorda` - Asia/Qyzylorda +- `Asia/Riyadh` - Asia/Riyadh +- `Asia/Sakhalin` - Asia/Sakhalin +- `Asia/Samarkand` - Asia/Samarkand +- `Asia/Seoul` - Asia/Seoul +- `Asia/Shanghai` - Asia/Shanghai +- `Asia/Singapore` - Asia/Singapore +- `Asia/Srednekolymsk` - Asia/Srednekolymsk +- `Asia/Taipei` - Asia/Taipei +- `Asia/Tashkent` - Asia/Tashkent +- `Asia/Tbilisi` - Asia/Tbilisi +- `Asia/Tehran` - Asia/Tehran +- `Asia/Thimphu` - Asia/Thimphu +- `Asia/Tokyo` - Asia/Tokyo +- `Asia/Tomsk` - Asia/Tomsk +- `Asia/Ulaanbaatar` - Asia/Ulaanbaatar +- `Asia/Urumqi` - Asia/Urumqi +- `Asia/Ust-Nera` - Asia/Ust-Nera +- `Asia/Vientiane` - Asia/Vientiane +- `Asia/Vladivostok` - Asia/Vladivostok +- `Asia/Yakutsk` - Asia/Yakutsk +- `Asia/Yangon` - Asia/Yangon +- `Asia/Yekaterinburg` - Asia/Yekaterinburg +- `Asia/Yerevan` - Asia/Yerevan +- `Atlantic/Azores` - Atlantic/Azores +- `Atlantic/Bermuda` - Atlantic/Bermuda +- `Atlantic/Canary` - Atlantic/Canary +- `Atlantic/Cape_Verde` - Atlantic/Cape_Verde +- `Atlantic/Faroe` - Atlantic/Faroe +- `Atlantic/Madeira` - Atlantic/Madeira +- `Atlantic/Reykjavik` - Atlantic/Reykjavik +- `Atlantic/South_Georgia` - Atlantic/South_Georgia +- `Atlantic/St_Helena` - Atlantic/St_Helena +- `Atlantic/Stanley` - Atlantic/Stanley +- `Australia/Adelaide` - Australia/Adelaide +- `Australia/Brisbane` - Australia/Brisbane +- `Australia/Broken_Hill` - Australia/Broken_Hill +- `Australia/Darwin` - Australia/Darwin +- `Australia/Eucla` - Australia/Eucla +- `Australia/Hobart` - Australia/Hobart +- `Australia/Lindeman` - Australia/Lindeman +- `Australia/Lord_Howe` - Australia/Lord_Howe +- `Australia/Melbourne` - Australia/Melbourne +- `Australia/Perth` - Australia/Perth +- `Australia/Sydney` - Australia/Sydney +- `Canada/Atlantic` - Canada/Atlantic +- `Canada/Central` - Canada/Central +- `Canada/Eastern` - Canada/Eastern +- `Canada/Mountain` - Canada/Mountain +- `Canada/Newfoundland` - Canada/Newfoundland +- `Canada/Pacific` - Canada/Pacific +- `Europe/Amsterdam` - Europe/Amsterdam +- `Europe/Andorra` - Europe/Andorra +- `Europe/Astrakhan` - Europe/Astrakhan +- `Europe/Athens` - Europe/Athens +- `Europe/Belgrade` - Europe/Belgrade +- `Europe/Berlin` - Europe/Berlin +- `Europe/Bratislava` - Europe/Bratislava +- `Europe/Brussels` - Europe/Brussels +- `Europe/Bucharest` - Europe/Bucharest +- `Europe/Budapest` - Europe/Budapest +- `Europe/Busingen` - Europe/Busingen +- `Europe/Chisinau` - Europe/Chisinau +- `Europe/Copenhagen` - Europe/Copenhagen +- `Europe/Dublin` - Europe/Dublin +- `Europe/Gibraltar` - Europe/Gibraltar +- `Europe/Guernsey` - Europe/Guernsey +- `Europe/Helsinki` - Europe/Helsinki +- `Europe/Isle_of_Man` - Europe/Isle_of_Man +- `Europe/Istanbul` - Europe/Istanbul +- `Europe/Jersey` - Europe/Jersey +- `Europe/Kaliningrad` - Europe/Kaliningrad +- `Europe/Kirov` - Europe/Kirov +- `Europe/Kyiv` - Europe/Kyiv +- `Europe/Lisbon` - Europe/Lisbon +- `Europe/Ljubljana` - Europe/Ljubljana +- `Europe/London` - Europe/London +- `Europe/Luxembourg` - Europe/Luxembourg +- `Europe/Madrid` - Europe/Madrid +- `Europe/Malta` - Europe/Malta +- `Europe/Mariehamn` - Europe/Mariehamn +- `Europe/Minsk` - Europe/Minsk +- `Europe/Monaco` - Europe/Monaco +- `Europe/Moscow` - Europe/Moscow +- `Europe/Oslo` - Europe/Oslo +- `Europe/Paris` - Europe/Paris +- `Europe/Podgorica` - Europe/Podgorica +- `Europe/Prague` - Europe/Prague +- `Europe/Riga` - Europe/Riga +- `Europe/Rome` - Europe/Rome +- `Europe/Samara` - Europe/Samara +- `Europe/San_Marino` - Europe/San_Marino +- `Europe/Sarajevo` - Europe/Sarajevo +- `Europe/Saratov` - Europe/Saratov +- `Europe/Simferopol` - Europe/Simferopol +- `Europe/Skopje` - Europe/Skopje +- `Europe/Sofia` - Europe/Sofia +- `Europe/Stockholm` - Europe/Stockholm +- `Europe/Tallinn` - Europe/Tallinn +- `Europe/Tirane` - Europe/Tirane +- `Europe/Ulyanovsk` - Europe/Ulyanovsk +- `Europe/Vaduz` - Europe/Vaduz +- `Europe/Vatican` - Europe/Vatican +- `Europe/Vienna` - Europe/Vienna +- `Europe/Vilnius` - Europe/Vilnius +- `Europe/Volgograd` - Europe/Volgograd +- `Europe/Warsaw` - Europe/Warsaw +- `Europe/Zagreb` - Europe/Zagreb +- `Europe/Zurich` - Europe/Zurich +- `GMT` - GMT +- `Indian/Antananarivo` - Indian/Antananarivo +- `Indian/Chagos` - Indian/Chagos +- `Indian/Christmas` - Indian/Christmas +- `Indian/Cocos` - Indian/Cocos +- `Indian/Comoro` - Indian/Comoro +- `Indian/Kerguelen` - Indian/Kerguelen +- `Indian/Mahe` - Indian/Mahe +- `Indian/Maldives` - Indian/Maldives +- `Indian/Mauritius` - Indian/Mauritius +- `Indian/Mayotte` - Indian/Mayotte +- `Indian/Reunion` - Indian/Reunion +- `Pacific/Apia` - Pacific/Apia +- `Pacific/Auckland` - Pacific/Auckland +- `Pacific/Bougainville` - Pacific/Bougainville +- `Pacific/Chatham` - Pacific/Chatham +- `Pacific/Chuuk` - Pacific/Chuuk +- `Pacific/Easter` - Pacific/Easter +- `Pacific/Efate` - Pacific/Efate +- `Pacific/Fakaofo` - Pacific/Fakaofo +- `Pacific/Fiji` - Pacific/Fiji +- `Pacific/Funafuti` - Pacific/Funafuti +- `Pacific/Galapagos` - Pacific/Galapagos +- `Pacific/Gambier` - Pacific/Gambier +- `Pacific/Guadalcanal` - Pacific/Guadalcanal +- `Pacific/Guam` - Pacific/Guam +- `Pacific/Honolulu` - Pacific/Honolulu +- `Pacific/Kanton` - Pacific/Kanton +- `Pacific/Kiritimati` - Pacific/Kiritimati +- `Pacific/Kosrae` - Pacific/Kosrae +- `Pacific/Kwajalein` - Pacific/Kwajalein +- `Pacific/Majuro` - Pacific/Majuro +- `Pacific/Marquesas` - Pacific/Marquesas +- `Pacific/Midway` - Pacific/Midway +- `Pacific/Nauru` - Pacific/Nauru +- `Pacific/Niue` - Pacific/Niue +- `Pacific/Norfolk` - Pacific/Norfolk +- `Pacific/Noumea` - Pacific/Noumea +- `Pacific/Pago_Pago` - Pacific/Pago_Pago +- `Pacific/Palau` - Pacific/Palau +- `Pacific/Pitcairn` - Pacific/Pitcairn +- `Pacific/Pohnpei` - Pacific/Pohnpei +- `Pacific/Port_Moresby` - Pacific/Port_Moresby +- `Pacific/Rarotonga` - Pacific/Rarotonga +- `Pacific/Saipan` - Pacific/Saipan +- `Pacific/Tahiti` - Pacific/Tahiti +- `Pacific/Tarawa` - Pacific/Tarawa +- `Pacific/Tongatapu` - Pacific/Tongatapu +- `Pacific/Wake` - Pacific/Wake +- `Pacific/Wallis` - Pacific/Wallis +- `US/Alaska` - US/Alaska +- `US/Arizona` - US/Arizona +- `US/Central` - US/Central +- `US/Eastern` - US/Eastern +- `US/Hawaii` - US/Hawaii +- `US/Mountain` - US/Mountain +- `US/Pacific` - US/Pacific +- `UTC` - UTC + +One of `Africa/Abidjan`, `Africa/Accra`, `Africa/Addis_Ababa`, `Africa/Algiers`, `Africa/Asmara`, `Africa/Bamako`, `Africa/Bangui`, `Africa/Banjul`, `Africa/Bissau`, `Africa/Blantyre`, `Africa/Brazzaville`, `Africa/Bujumbura`, `Africa/Cairo`, `Africa/Casablanca`, `Africa/Ceuta`, `Africa/Conakry`, `Africa/Dakar`, `Africa/Dar_es_Salaam`, `Africa/Djibouti`, `Africa/Douala`, `Africa/El_Aaiun`, `Africa/Freetown`, `Africa/Gaborone`, `Africa/Harare`, `Africa/Johannesburg`, `Africa/Juba`, `Africa/Kampala`, `Africa/Khartoum`, `Africa/Kigali`, `Africa/Kinshasa`, `Africa/Lagos`, `Africa/Libreville`, `Africa/Lome`, `Africa/Luanda`, `Africa/Lubumbashi`, `Africa/Lusaka`, `Africa/Malabo`, `Africa/Maputo`, `Africa/Maseru`, `Africa/Mbabane`, `Africa/Mogadishu`, `Africa/Monrovia`, `Africa/Nairobi`, `Africa/Ndjamena`, `Africa/Niamey`, `Africa/Nouakchott`, `Africa/Ouagadougou`, `Africa/Porto-Novo`, `Africa/Sao_Tome`, `Africa/Tripoli`, `Africa/Tunis`, `Africa/Windhoek`, `America/Adak`, `America/Anchorage`, `America/Anguilla`, `America/Antigua`, `America/Araguaina`, `America/Argentina/Buenos_Aires`, `America/Argentina/Catamarca`, `America/Argentina/Cordoba`, `America/Argentina/Jujuy`, `America/Argentina/La_Rioja`, `America/Argentina/Mendoza`, `America/Argentina/Rio_Gallegos`, `America/Argentina/Salta`, `America/Argentina/San_Juan`, `America/Argentina/San_Luis`, `America/Argentina/Tucuman`, `America/Argentina/Ushuaia`, `America/Aruba`, `America/Asuncion`, `America/Atikokan`, `America/Bahia`, `America/Bahia_Banderas`, `America/Barbados`, `America/Belem`, `America/Belize`, `America/Blanc-Sablon`, `America/Boa_Vista`, `America/Bogota`, `America/Boise`, `America/Cambridge_Bay`, `America/Campo_Grande`, `America/Cancun`, `America/Caracas`, `America/Cayenne`, `America/Cayman`, `America/Chicago`, `America/Chihuahua`, `America/Ciudad_Juarez`, `America/Costa_Rica`, `America/Creston`, `America/Cuiaba`, `America/Curacao`, `America/Danmarkshavn`, `America/Dawson`, `America/Dawson_Creek`, `America/Denver`, `America/Detroit`, `America/Dominica`, `America/Edmonton`, `America/Eirunepe`, `America/El_Salvador`, `America/Fort_Nelson`, `America/Fortaleza`, `America/Glace_Bay`, `America/Goose_Bay`, `America/Grand_Turk`, `America/Grenada`, `America/Guadeloupe`, `America/Guatemala`, `America/Guayaquil`, `America/Guyana`, `America/Halifax`, `America/Havana`, `America/Hermosillo`, `America/Indiana/Indianapolis`, `America/Indiana/Knox`, `America/Indiana/Marengo`, `America/Indiana/Petersburg`, `America/Indiana/Tell_City`, `America/Indiana/Vevay`, `America/Indiana/Vincennes`, `America/Indiana/Winamac`, `America/Inuvik`, `America/Iqaluit`, `America/Jamaica`, `America/Juneau`, `America/Kentucky/Louisville`, `America/Kentucky/Monticello`, `America/Kralendijk`, `America/La_Paz`, `America/Lima`, `America/Los_Angeles`, `America/Lower_Princes`, `America/Maceio`, `America/Managua`, `America/Manaus`, `America/Marigot`, `America/Martinique`, `America/Matamoros`, `America/Mazatlan`, `America/Menominee`, `America/Merida`, `America/Metlakatla`, `America/Mexico_City`, `America/Miquelon`, `America/Moncton`, `America/Monterrey`, `America/Montevideo`, `America/Montserrat`, `America/Nassau`, `America/New_York`, `America/Nome`, `America/Noronha`, `America/North_Dakota/Beulah`, `America/North_Dakota/Center`, `America/North_Dakota/New_Salem`, `America/Nuuk`, `America/Ojinaga`, `America/Panama`, `America/Paramaribo`, `America/Phoenix`, `America/Port-au-Prince`, `America/Port_of_Spain`, `America/Porto_Velho`, `America/Puerto_Rico`, `America/Punta_Arenas`, `America/Rankin_Inlet`, `America/Recife`, `America/Regina`, `America/Resolute`, `America/Rio_Branco`, `America/Santarem`, `America/Santiago`, `America/Santo_Domingo`, `America/Sao_Paulo`, `America/Scoresbysund`, `America/Sitka`, `America/St_Barthelemy`, `America/St_Johns`, `America/St_Kitts`, `America/St_Lucia`, `America/St_Thomas`, `America/St_Vincent`, `America/Swift_Current`, `America/Tegucigalpa`, `America/Thule`, `America/Tijuana`, `America/Toronto`, `America/Tortola`, `America/Vancouver`, `America/Whitehorse`, `America/Winnipeg`, `America/Yakutat`, `Antarctica/Casey`, `Antarctica/Davis`, `Antarctica/DumontDUrville`, `Antarctica/Macquarie`, `Antarctica/Mawson`, `Antarctica/McMurdo`, `Antarctica/Palmer`, `Antarctica/Rothera`, `Antarctica/Syowa`, `Antarctica/Troll`, `Antarctica/Vostok`, `Arctic/Longyearbyen`, `Asia/Aden`, `Asia/Almaty`, `Asia/Amman`, `Asia/Anadyr`, `Asia/Aqtau`, `Asia/Aqtobe`, `Asia/Ashgabat`, `Asia/Atyrau`, `Asia/Baghdad`, `Asia/Bahrain`, `Asia/Baku`, `Asia/Bangkok`, `Asia/Barnaul`, `Asia/Beirut`, `Asia/Bishkek`, `Asia/Brunei`, `Asia/Chita`, `Asia/Choibalsan`, `Asia/Colombo`, `Asia/Damascus`, `Asia/Dhaka`, `Asia/Dili`, `Asia/Dubai`, `Asia/Dushanbe`, `Asia/Famagusta`, `Asia/Gaza`, `Asia/Hebron`, `Asia/Ho_Chi_Minh`, `Asia/Hong_Kong`, `Asia/Hovd`, `Asia/Irkutsk`, `Asia/Jakarta`, `Asia/Jayapura`, `Asia/Jerusalem`, `Asia/Kabul`, `Asia/Kamchatka`, `Asia/Karachi`, `Asia/Kathmandu`, `Asia/Khandyga`, `Asia/Kolkata`, `Asia/Krasnoyarsk`, `Asia/Kuala_Lumpur`, `Asia/Kuching`, `Asia/Kuwait`, `Asia/Macau`, `Asia/Magadan`, `Asia/Makassar`, `Asia/Manila`, `Asia/Muscat`, `Asia/Nicosia`, `Asia/Novokuznetsk`, `Asia/Novosibirsk`, `Asia/Omsk`, `Asia/Oral`, `Asia/Phnom_Penh`, `Asia/Pontianak`, `Asia/Pyongyang`, `Asia/Qatar`, `Asia/Qostanay`, `Asia/Qyzylorda`, `Asia/Riyadh`, `Asia/Sakhalin`, `Asia/Samarkand`, `Asia/Seoul`, `Asia/Shanghai`, `Asia/Singapore`, `Asia/Srednekolymsk`, `Asia/Taipei`, `Asia/Tashkent`, `Asia/Tbilisi`, `Asia/Tehran`, `Asia/Thimphu`, `Asia/Tokyo`, `Asia/Tomsk`, `Asia/Ulaanbaatar`, `Asia/Urumqi`, `Asia/Ust-Nera`, `Asia/Vientiane`, `Asia/Vladivostok`, `Asia/Yakutsk`, `Asia/Yangon`, `Asia/Yekaterinburg`, `Asia/Yerevan`, `Atlantic/Azores`, `Atlantic/Bermuda`, `Atlantic/Canary`, `Atlantic/Cape_Verde`, `Atlantic/Faroe`, `Atlantic/Madeira`, `Atlantic/Reykjavik`, `Atlantic/South_Georgia`, `Atlantic/St_Helena`, `Atlantic/Stanley`, `Australia/Adelaide`, `Australia/Brisbane`, `Australia/Broken_Hill`, `Australia/Darwin`, `Australia/Eucla`, `Australia/Hobart`, `Australia/Lindeman`, `Australia/Lord_Howe`, `Australia/Melbourne`, `Australia/Perth`, `Australia/Sydney`, `Canada/Atlantic`, `Canada/Central`, `Canada/Eastern`, `Canada/Mountain`, `Canada/Newfoundland`, `Canada/Pacific`, `Europe/Amsterdam`, `Europe/Andorra`, `Europe/Astrakhan`, `Europe/Athens`, `Europe/Belgrade`, `Europe/Berlin`, `Europe/Bratislava`, `Europe/Brussels`, `Europe/Bucharest`, `Europe/Budapest`, `Europe/Busingen`, `Europe/Chisinau`, `Europe/Copenhagen`, `Europe/Dublin`, `Europe/Gibraltar`, `Europe/Guernsey`, `Europe/Helsinki`, `Europe/Isle_of_Man`, `Europe/Istanbul`, `Europe/Jersey`, `Europe/Kaliningrad`, `Europe/Kirov`, `Europe/Kyiv`, `Europe/Lisbon`, `Europe/Ljubljana`, `Europe/London`, `Europe/Luxembourg`, `Europe/Madrid`, `Europe/Malta`, `Europe/Mariehamn`, `Europe/Minsk`, `Europe/Monaco`, `Europe/Moscow`, `Europe/Oslo`, `Europe/Paris`, `Europe/Podgorica`, `Europe/Prague`, `Europe/Riga`, `Europe/Rome`, `Europe/Samara`, `Europe/San_Marino`, `Europe/Sarajevo`, `Europe/Saratov`, `Europe/Simferopol`, `Europe/Skopje`, `Europe/Sofia`, `Europe/Stockholm`, `Europe/Tallinn`, `Europe/Tirane`, `Europe/Ulyanovsk`, `Europe/Vaduz`, `Europe/Vatican`, `Europe/Vienna`, `Europe/Vilnius`, `Europe/Volgograd`, `Europe/Warsaw`, `Europe/Zagreb`, `Europe/Zurich`, `GMT`, `Indian/Antananarivo`, `Indian/Chagos`, `Indian/Christmas`, `Indian/Cocos`, `Indian/Comoro`, `Indian/Kerguelen`, `Indian/Mahe`, `Indian/Maldives`, `Indian/Mauritius`, `Indian/Mayotte`, `Indian/Reunion`, `Pacific/Apia`, `Pacific/Auckland`, `Pacific/Bougainville`, `Pacific/Chatham`, `Pacific/Chuuk`, `Pacific/Easter`, `Pacific/Efate`, `Pacific/Fakaofo`, `Pacific/Fiji`, `Pacific/Funafuti`, `Pacific/Galapagos`, `Pacific/Gambier`, `Pacific/Guadalcanal`, `Pacific/Guam`, `Pacific/Honolulu`, `Pacific/Kanton`, `Pacific/Kiritimati`, `Pacific/Kosrae`, `Pacific/Kwajalein`, `Pacific/Majuro`, `Pacific/Marquesas`, `Pacific/Midway`, `Pacific/Nauru`, `Pacific/Niue`, `Pacific/Norfolk`, `Pacific/Noumea`, `Pacific/Pago_Pago`, `Pacific/Palau`, `Pacific/Pitcairn`, `Pacific/Pohnpei`, `Pacific/Port_Moresby`, `Pacific/Rarotonga`, `Pacific/Saipan`, `Pacific/Tahiti`, `Pacific/Tarawa`, `Pacific/Tongatapu`, `Pacific/Wake`, `Pacific/Wallis`, `US/Alaska`, `US/Arizona`, `US/Central`, `US/Eastern`, `US/Hawaii`, `US/Mountain`, `US/Pacific`, `UTC`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `end_date`, `external_id`, `external_source`, `id`, `logo_props`, `name`, `owned_by_id`, `sort_order`, `start_date`, `timezone`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.cycles:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "end_date": "2026-01-31", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535, + "start_date": "2026-01-12", + "timezone": "Africa/Abidjan" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/errors.md b/docs/api-reference/v2/errors.md new file mode 100644 index 00000000..ee09c5b7 --- /dev/null +++ b/docs/api-reference/v2/errors.md @@ -0,0 +1,352 @@ +--- +title: Errors +description: The Plane API v2 error model — RFC 9457 problem+json responses, the full error code table, validation error arrays, rate limiting, and mode conflicts. +keywords: plane api v2 errors, rfc 9457, problem json, invalid_request, not_found, rate_limited, retry-after, plane api error codes +--- + +# Errors + +Every v2 error is served as `application/problem+json` with the same three members, whether the failure is a typo in a +request body or a throttled client. One error handler covers the whole API. + +```json +{ + "type": "conflict", + "code": "states_managed_at_workspace", + "detail": "States are managed at the workspace level." +} +``` + +## The problem shape + +
+
+ +### Fields + +Three members are always present and never `null`. + +- `type` _string_ + + The **coarse category**, from a closed set of 13 values (below). This is the fallback arm for a `code` you have never + seen — small and stable enough to embed in a client. + +- `code` _string_ + + The **specific condition**, and the field to branch on. An open vocabulary that grows as v2 grows. When a condition + needs no refinement, `code` is equal to `type`. + +- `detail` _string_ + + A human-readable explanation of this occurrence. Written for a developer reading a log — the wording is not part of + the contract. + +Plus one member on validation failures only: + +- `errors` _array_ + + Present **only** on `invalid_request`. One entry per rejected field, each carrying `field`, `code` and `message`. The + per-field `code` (`required`, `invalid`, `unique`, `invalid_choice`, `max_length`, `does_not_exist`) lets you branch + per field without matching English prose. + +::: warning Branch on `code`, fall back to `type` +Several conditions share a status: `403` covers both `forbidden` and `workflow_transition_denied`, and `409` covers +`conflict` plus four refinements. The HTTP status alone cannot tell them apart, and neither can `type`. + +Because `code` is always present — even when it just repeats `type` — you never need a `code ?? type` null check. +Switch on `code`, and let unrecognized values fall through to a `type` arm. + +`detail` is written for people. Matching on its text will break. +::: + +::: info No `status`, `title`, or URI `type` +Earlier v2 previews sent `status` and `title` in the body, and a `type` of the form +`https://api.plane.so/errors/`. All three are gone: `status` duplicated the HTTP status line, `title` was +derivable boilerplate, and the URI pointed at a page a self-hosted instance never serves. `type` is now a bare slug. +::: + +
+
+ + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "One or more fields failed validation.", + "errors": [ + { + "field": "name", + "code": "required", + "message": "This field is required." + }, + { + "field": "group", + "code": "invalid_choice", + "message": "\"in_review\" is not a valid choice." + } + ] +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "The requested resource was not found." +} +``` + + + +
+
+ +## Handling errors + +Read `code` first, and fall back to `type` for codes you do not recognize yet — new codes can appear as v2 grows, but +the `type` set will not. + + + + + + + +## The `type` vocabulary + +Closed set, 13 values. It grows only when a genuinely new _kind_ of failure appears. + +| `type` | Status | Meaning | +| ------------------------ | ----------------- | --------------------------------------------------------------------------------------------------- | +| `invalid_request` | `400` | The request itself is wrong. | +| `unauthorized` | `401` | Credentials missing or invalid. | +| `payment_required` | `402` | The feature isn't enabled on your plan. | +| `forbidden` | `403` | Authenticated, but not allowed. | +| `not_found` | `404` | No such resource, or it's outside your tenant. | +| `method_not_allowed` | `405` | Method not supported on this route — most often a `PUT`. | +| `not_acceptable` | `406` | Can't produce the representation your `Accept` header asks for. | +| `conflict` | `409` | The write collides with existing state or a business rule. | +| `payload_too_large` | `413` | Request body over the size limit. | +| `unsupported_media_type` | `415` | `Content-Type` not accepted here. | +| `rate_limited` | `429` | Throttled. | +| `server_error` | `500` | Unexpected server-side failure. Retry; alert if it persists. | +| `service_unavailable` | `502`/`503`/`504` | Temporarily unavailable. **This is the retryable 5xx** — back off and retry, unlike `server_error`. | + +## Error codes + +Every `code` maps to exactly one `type`, permanently. Codes are additive-only: they are never renamed or repurposed, so +a code you handle today keeps its meaning. + +Status-level codes are equal to their `type` — `invalid_request`, `unauthorized`, `payment_required`, `forbidden`, +`not_found`, `method_not_allowed`, `not_acceptable`, `conflict`, `payload_too_large`, `unsupported_media_type`, +`rate_limited`, `server_error`, `service_unavailable`. + +These refine one of those: + +| Status | Code | `type` | Meaning | +| ------ | -------------------------------------- | ----------------- | --------------------------------------------------------------------------- | +| `400` | `count_pagination_disabled` | `invalid_request` | Offset and COUNT are disabled for this resource. Use `?paginate=cursor`. | +| `400` | `ordering_not_cursor_eligible` | `invalid_request` | This ordering can't back a keyset cursor. Use offset. | +| `403` | `workflow_transition_denied` | `forbidden` | A workflow rule blocked the create or state transition. | +| `409` | `states_managed_at_workspace` | `conflict` | States are governed at the workspace level for this workspace. | +| `409` | `work_item_types_managed_at_workspace` | `conflict` | Wrong mode — this workspace manages work item types at the workspace level. | +| `409` | `work_item_types_managed_at_project` | `conflict` | Wrong mode — this workspace manages work item types at the project level. | +| `409` | `governance_migration_in_progress` | `conflict` | A governance migration is running. Retry shortly. | +| `500` | `listing_authorization_misconfigured` | `server_error` | A server-side authorization guard tripped. Report it; retrying won't help. | + +::: tip Handle the codes you care about, default the rest +The code set is deliberately large — a code exists when a client would write a _different branch_ for it, which is why +`count_pagination_disabled` ("switch to cursor") and `ordering_not_cursor_eligible` ("switch to offset") are separate. +A big enum costs you nothing: match what you act on, and send everything else to a `type`-based default arm. +::: + +## Validation failures + +A `400 invalid_request` is the only code that carries `errors[]`. Each entry names one rejected field, and a single +response can carry several — the API validates the whole payload rather than stopping at the first problem, so one +round trip tells you everything to fix. + +Query parameters are validated too. Enum-backed parameters such as `priority`, `state_group`, state `group`, and module +`status` are checked against their allowed values, so a typo returns a clean `400` rather than a silently empty list. + +`?fields=` and `?expand=` are strict in the same way, and their messages are written to be self-correcting — they name +the closest match and enumerate the valid set, so a client can fix itself in one round trip: + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "One or more fields failed validation.", + "errors": [ + { + "field": "fields", + "code": "invalid", + "message": "Unknown field(s): titel — did you mean 'title'? Valid fields: all, id, name, identifier, …" + } + ] +} +``` + +Naming an expandable relation in `?fields=` (or a field in `?expand=`) tells you which parameter you actually wanted +rather than guessing. See [Sparse fields](/api-reference/v2/sparse-fields). + +::: tip Surface `field` and `message` verbatim +When you are relaying an error to a human — a CLI, a form, a Slack notification — the `field`/`message` pairs are +already specific enough to act on. Passing them through beats collapsing them into "invalid request". Use the per-entry +`code` when you need to branch programmatically. +::: + +## Feature gating — `402` + +Endpoints that belong to a paid or optional feature answer `402 payment_required` when the feature is off, rather than +`403` or `404`: + + + +```json +{ + "type": "payment_required", + "code": "payment_required", + "detail": "This feature is not available on your current plan." +} +``` + + + +Two things can produce it: the feature isn't in your plan, or it is but hasn't been switched on for the workspace or +project. Both are configuration, not permissions — retrying or widening your token's scopes will not help. Affected +surfaces include customers, releases, workflows, automations, worklogs and custom relation definitions. + +Because `402` is declared on every operation, treat it as a first-class arm in shared client code rather than something +only certain endpoints can return. + +## Rate limiting + +Requests are throttled per token, with a separate bucket per token class — API keys, OAuth tokens, workspace tokens, +service tokens, and external tokens. Exceeding a bucket returns `429 rate_limited` with a `Retry-After` header. + + + +```json +{ + "type": "rate_limited", + "code": "rate_limited", + "detail": "Rate limit exceeded." +} +``` + + + +Wait the number of seconds in `Retry-After` before retrying. Backing off on a fixed schedule instead — or retrying +immediately — keeps you in the throttle. Because the buckets are per token, splitting a bulk job across several tokens +does not merge their limits, but it does multiply the load on the workspace. + +## Mode conflicts + +A workspace manages work item types in exactly one mode: at the **project** level or at the **workspace** level. Both +sets of endpoints exist at all times, so writing to the surface that is not currently in use returns `409` — not `404` +or `403`. The capability is real, it just lives on the other surface. + +- Writing to a **project-mode** endpoint while workspace mode is on → `work_item_types_managed_at_workspace`. +- Writing to a **workspace-mode** endpoint while project mode is on → `work_item_types_managed_at_project`. + +::: info Reads are never blocked by mode +Only writes conflict. You can list and read types and properties on either surface regardless of the active mode — a +project still surfaces its imported types while the workspace is in workspace mode. +::: + +The code tells you where to send the write. See +[Work item type modes](/api-reference/v2/work-item-type-modes) for the full picture. + +## Pagination errors + +Both pagination `400`s mean the same thing: the pagination style and the request do not fit together. + +- `ordering_not_cursor_eligible` — some orderings sort by meaning rather than by a stored column, for example + `?order_by=priority`. Those cannot back a keyset cursor. Drop `?paginate=cursor` and use offset for that ordering. +- `count_pagination_disabled` — offset with a COUNT is unavailable for this resource. Switch to `?paginate=cursor`. + +See [Pagination](/api-reference/v2/pagination) for the two envelopes and when to prefer each. + +## Related + +- [Authentication](/api-reference/v2/authentication) — what separates `401`, `403`, and a tenant-safe `404`. +- [Pagination](/api-reference/v2/pagination) — offset and cursor styles. +- [Introduction](/api-reference/v2/introduction) — request conventions, including why a missing trailing slash bites. diff --git a/docs/api-reference/v2/estimate-points/bulk-estimate-points.md b/docs/api-reference/v2/estimate-points/bulk-estimate-points.md new file mode 100644 index 00000000..3b393073 --- /dev/null +++ b/docs/api-reference/v2/estimate-points/bulk-estimate-points.md @@ -0,0 +1,195 @@ +--- +title: Bulk write estimate points +description: Bulk write estimate points with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, bulk write estimate points, estimate points, estimate points bulk +--- + +# Bulk write estimate points + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/bulk/ +
+ +
+
+ +Estimate points are the individual values inside a project estimate scale. Write up to 100 estimate points in one request, with a per-row result for each. + +- Send `{"items": [...]}`, at most 100 per request. Add `"upsert": true` to match existing rows on `(external_source, external_id)` instead of always creating. +- The response is always `200`. Each row reports its own outcome, so a partial failure is reported rather than silently dropped — read `succeeded`/`failed` and the per-row `status`. +- Rows are written on independent savepoints: a failing row rolls back only itself. +- `?fields=` does not apply here — the bulk response is a per-row result envelope, not a resource body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The estimate the points belong to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The items. + + + + + +Reconcile each row on (external_source, external_id) instead of creating it. + + + +
+
+ +
+ +### Scopes + +`projects.estimates:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "failed": 1, + "results": [null], + "succeeded": 1 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimate-points/create-estimate-point.md b/docs/api-reference/v2/estimate-points/create-estimate-point.md new file mode 100644 index 00000000..e6ceaae4 --- /dev/null +++ b/docs/api-reference/v2/estimate-points/create-estimate-point.md @@ -0,0 +1,219 @@ +--- +title: Create a estimate point +description: Create a estimate point with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a estimate point, estimate points, estimate points create +--- + +# Create a estimate point + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/ +
+ +
+
+ +Estimate points are the individual values inside a project estimate scale. Create a estimate point. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The estimate the points belong to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The value. + +Maximum 255 characters. + + + + + +Free-form description. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +The key. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `estimate_id`, `external_id`, `external_source`, `id`, `key`, `value`. + + + +
+
+ +
+ +### Scopes + +`projects.estimates:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "key": 1, + "value": "example" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimate-points/delete-estimate-point.md b/docs/api-reference/v2/estimate-points/delete-estimate-point.md new file mode 100644 index 00000000..068b1fd6 --- /dev/null +++ b/docs/api-reference/v2/estimate-points/delete-estimate-point.md @@ -0,0 +1,160 @@ +--- +title: Delete a estimate point +description: Delete a estimate point with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a estimate point, estimate points, estimate points destroy +--- + +# Delete a estimate point + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/{pk}/ +
+ +
+
+ +Estimate points are the individual values inside a project estimate scale. Delete a estimate point. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The estimate the points belong to. + + + + + +The estimate point id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `estimate_id`, `external_id`, `external_source`, `id`, `key`, `value`. + + + +
+
+ +
+ +### Scopes + +`projects.estimates:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No estimate point matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimate-points/get-estimate-point.md b/docs/api-reference/v2/estimate-points/get-estimate-point.md new file mode 100644 index 00000000..5480a341 --- /dev/null +++ b/docs/api-reference/v2/estimate-points/get-estimate-point.md @@ -0,0 +1,168 @@ +--- +title: Get a estimate point +description: Read a estimate point with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a estimate point, estimate points, estimate points retrieve +--- + +# Get a estimate point + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/{pk}/ +
+ +
+
+ +Estimate points are the individual values inside a project estimate scale. Read a single estimate point by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The estimate the points belong to. + + + + + +The estimate point id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `estimate_id`, `external_id`, `external_source`, `id`, `key`, `value`. + + + +
+
+ +
+ +### Scopes + +`projects.estimates:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "key": 1, + "value": "example" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No estimate point matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimate-points/list-estimate-points.md b/docs/api-reference/v2/estimate-points/list-estimate-points.md new file mode 100644 index 00000000..5d1bda78 --- /dev/null +++ b/docs/api-reference/v2/estimate-points/list-estimate-points.md @@ -0,0 +1,244 @@ +--- +title: List estimate points +description: List estimate points with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list estimate points, estimate points, estimate points list +--- + +# List estimate points + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/ +
+ +
+
+ +Estimate points are the individual values inside a project estimate scale. List the estimate points you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The estimate the points belong to. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `external_id`. + + + + + +Filter by `external_source`. + + + + + +Filter by `key`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +Filter by `value`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `estimate_id`, `external_id`, `external_source`, `id`, `key`, `value`. + + + +
+
+ +
+ +### Scopes + +`projects.estimates:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "key": 1, + "value": "example" + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "key": 1, + "value": "example" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimate-points/overview.md b/docs/api-reference/v2/estimate-points/overview.md new file mode 100644 index 00000000..ca6cc220 --- /dev/null +++ b/docs/api-reference/v2/estimate-points/overview.md @@ -0,0 +1,92 @@ +--- +title: Estimate points overview +description: The Plane API v2 estimate point object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, estimate points, estimate point object +--- + +# Estimate points overview + +Estimate points are the individual values inside a project estimate scale. + +
+
+ +## The estimate point object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description` _string_ + + Free-form description. + +- `estimate_id` _string (uuid)_ + + The related estimate. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `key` _integer_ + + The key. + +- `value` _string_ + + The value. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "key": 1, + "value": "example" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------------------------------------------- | -------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/` | List estimate points | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/` | Create a estimate point | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/bulk/` | Bulk write estimate points | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/upsert/` | Upsert a estimate point | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/{pk}/` | Delete a estimate point | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/{pk}/` | Get a estimate point | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/{pk}/` | Update a estimate point | + +## Response shaping + +Every estimate point read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/estimate-points/update-estimate-point.md b/docs/api-reference/v2/estimate-points/update-estimate-point.md new file mode 100644 index 00000000..a76e99e1 --- /dev/null +++ b/docs/api-reference/v2/estimate-points/update-estimate-point.md @@ -0,0 +1,234 @@ +--- +title: Update a estimate point +description: Update a estimate point with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a estimate point, estimate points, estimate points partial update +--- + +# Update a estimate point + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/{pk}/ +
+ +
+
+ +Estimate points are the individual values inside a project estimate scale. Update a estimate point. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The estimate the points belong to. + + + + + +The estimate point id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Free-form description. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +The key. + + + + + +The value. + +Maximum 255 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `estimate_id`, `external_id`, `external_source`, `id`, `key`, `value`. + + + +
+
+ +
+ +### Scopes + +`projects.estimates:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "key": 1, + "value": "example" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No estimate point matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimate-points/upsert-estimate-point.md b/docs/api-reference/v2/estimate-points/upsert-estimate-point.md new file mode 100644 index 00000000..f6e9adb4 --- /dev/null +++ b/docs/api-reference/v2/estimate-points/upsert-estimate-point.md @@ -0,0 +1,223 @@ +--- +title: Upsert a estimate point +description: Upsert a estimate point with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, upsert a estimate point, estimate points, estimate points upsert +--- + +# Upsert a estimate point + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/upsert/ +
+ +
+
+ +Estimate points are the individual values inside a project estimate scale. Create a estimate point, or update the existing one that carries the same `(external_source, external_id)` pair. + +- Both `external_source` and `external_id` are required — without them there is nothing to match on and the request is a `400`. +- A created record answers `201` with `X-Plane-Upsert: created`; an updated one answers `200` with `X-Plane-Upsert: updated`. Branch on the header rather than guessing from the status. +- Upsert is safe for **sequential** importers. Two simultaneous upserts of the same key can each miss and each create, so serialize your writes per key. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The estimate the points belong to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The value. + +Maximum 255 characters. + + + + + +Free-form description. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +The key. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `estimate_id`, `external_id`, `external_source`, `id`, `key`, `value`. + + + +
+
+ +
+ +### Scopes + +`projects.estimates:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "key": 1, + "value": "example" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimates/bulk-estimates.md b/docs/api-reference/v2/estimates/bulk-estimates.md new file mode 100644 index 00000000..60d9d711 --- /dev/null +++ b/docs/api-reference/v2/estimates/bulk-estimates.md @@ -0,0 +1,189 @@ +--- +title: Bulk write estimates +description: Bulk write estimates with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, bulk write estimates, estimates, estimates bulk +--- + +# Bulk write estimates + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/bulk/ +
+ +
+
+ +Estimates define the sizing scale a project uses. Write up to 100 estimates in one request, with a per-row result for each. + +- Send `{"items": [...]}`, at most 100 per request. Add `"upsert": true` to match existing rows on `(external_source, external_id)` instead of always creating. +- The response is always `200`. Each row reports its own outcome, so a partial failure is reported rather than silently dropped — read `succeeded`/`failed` and the per-row `status`. +- Rows are written on independent savepoints: a failing row rolls back only itself. +- `?fields=` does not apply here — the bulk response is a per-row result envelope, not a resource body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The items. + + + + + +Reconcile each row on (external_source, external_id) instead of creating it. + + + +
+
+ +
+ +### Scopes + +`projects.estimates:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "failed": 1, + "results": [null], + "succeeded": 1 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimates/create-estimate.md b/docs/api-reference/v2/estimates/create-estimate.md new file mode 100644 index 00000000..44f75142 --- /dev/null +++ b/docs/api-reference/v2/estimates/create-estimate.md @@ -0,0 +1,225 @@ +--- +title: Create a estimate +description: Create a estimate with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a estimate, estimates, estimates create +--- + +# Create a estimate + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/ +
+ +
+
+ +Estimates define the sizing scale a project uses. Create a estimate. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Free-form description. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +- `categories` - Categories +- `points` - Points +- `time` - Time + +One of `categories`, `points`, `time`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `last_used`, `name`, `type`. + + + + + +Comma-separated relations to embed: `points`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.estimates:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "last_used": false, + "name": "Example name", + "type": "categories" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimates/delete-estimate.md b/docs/api-reference/v2/estimates/delete-estimate.md new file mode 100644 index 00000000..1d74937f --- /dev/null +++ b/docs/api-reference/v2/estimates/delete-estimate.md @@ -0,0 +1,162 @@ +--- +title: Delete a estimate +description: Delete a estimate with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a estimate, estimates, estimates destroy +--- + +# Delete a estimate + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/{pk}/ +
+ +
+
+ +Estimates define the sizing scale a project uses. Delete a estimate. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The estimate id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `last_used`, `name`, `type`. + + + + + +Comma-separated relations to embed: `points`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.estimates:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No estimate matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimates/get-estimate.md b/docs/api-reference/v2/estimates/get-estimate.md new file mode 100644 index 00000000..81dfc2b2 --- /dev/null +++ b/docs/api-reference/v2/estimates/get-estimate.md @@ -0,0 +1,170 @@ +--- +title: Get a estimate +description: Read a estimate with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a estimate, estimates, estimates retrieve +--- + +# Get a estimate + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/{pk}/ +
+ +
+
+ +Estimates define the sizing scale a project uses. Read a single estimate by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The estimate id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `last_used`, `name`, `type`. + + + + + +Comma-separated relations to embed: `points`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.estimates:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "last_used": false, + "name": "Example name", + "type": "categories" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No estimate matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimates/list-estimates.md b/docs/api-reference/v2/estimates/list-estimates.md new file mode 100644 index 00000000..9e88851e --- /dev/null +++ b/docs/api-reference/v2/estimates/list-estimates.md @@ -0,0 +1,260 @@ +--- +title: List estimates +description: List estimates with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list estimates, estimates, estimates list +--- + +# List estimates + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/ +
+ +
+
+ +Estimates define the sizing scale a project uses. List the estimates you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `external_id`. + + + + + +Filter by `external_source`. + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +- `categories` - Categories +- `points` - Points +- `time` - Time + +One of `categories`, `points`, `time`. + + + + + +Multiple values may be separated by commas. + +- `categories` - Categories +- `points` - Points +- `time` - Time + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `last_used`, `name`, `type`. + + + + + +Comma-separated relations to embed: `points`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.estimates:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "last_used": false, + "name": "Example name", + "type": "categories" + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "last_used": false, + "name": "Another example", + "type": "categories" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimates/overview.md b/docs/api-reference/v2/estimates/overview.md new file mode 100644 index 00000000..79a97070 --- /dev/null +++ b/docs/api-reference/v2/estimates/overview.md @@ -0,0 +1,92 @@ +--- +title: Estimates overview +description: The Plane API v2 estimate object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, estimates, estimate object +--- + +# Estimates overview + +Estimates define the sizing scale a project uses. + +
+
+ +## The estimate object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description` _string_ + + Free-form description. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `last_used` _boolean_ + + Whether last used. + +- `name` _string_ + + Display name. + +- `type` _string_ + + The type. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "last_used": false, + "name": "Example name", + "type": "categories" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------------------- | -------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/` | List estimates | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/` | Create a estimate | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/bulk/` | Bulk write estimates | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/upsert/` | Upsert a estimate | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/{pk}/` | Delete a estimate | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/{pk}/` | Get a estimate | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/estimates/{pk}/` | Update a estimate | + +## Response shaping + +Every estimate read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/estimates/update-estimate.md b/docs/api-reference/v2/estimates/update-estimate.md new file mode 100644 index 00000000..8cee3cf5 --- /dev/null +++ b/docs/api-reference/v2/estimates/update-estimate.md @@ -0,0 +1,240 @@ +--- +title: Update a estimate +description: Update a estimate with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a estimate, estimates, estimates partial update +--- + +# Update a estimate + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/{pk}/ +
+ +
+
+ +Estimates define the sizing scale a project uses. Update a estimate. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The estimate id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Free-form description. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Display name. + +Maximum 255 characters. + + + + + +- `categories` - Categories +- `points` - Points +- `time` - Time + +One of `categories`, `points`, `time`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `last_used`, `name`, `type`. + + + + + +Comma-separated relations to embed: `points`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.estimates:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "last_used": false, + "name": "Example name", + "type": "categories" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No estimate matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/estimates/upsert-estimate.md b/docs/api-reference/v2/estimates/upsert-estimate.md new file mode 100644 index 00000000..ec5817a1 --- /dev/null +++ b/docs/api-reference/v2/estimates/upsert-estimate.md @@ -0,0 +1,229 @@ +--- +title: Upsert a estimate +description: Upsert a estimate with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, upsert a estimate, estimates, estimates upsert +--- + +# Upsert a estimate + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/estimates/upsert/ +
+ +
+
+ +Estimates define the sizing scale a project uses. Create a estimate, or update the existing one that carries the same `(external_source, external_id)` pair. + +- Both `external_source` and `external_id` are required — without them there is nothing to match on and the request is a `400`. +- A created record answers `201` with `X-Plane-Upsert: created`; an updated one answers `200` with `X-Plane-Upsert: updated`. Branch on the header rather than guessing from the status. +- Upsert is safe for **sequential** importers. Two simultaneous upserts of the same key can each miss and each create, so serialize your writes per key. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Free-form description. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +- `categories` - Categories +- `points` - Points +- `time` - Time + +One of `categories`, `points`, `time`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `last_used`, `name`, `type`. + + + + + +Comma-separated relations to embed: `points`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.estimates:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "last_used": false, + "name": "Example name", + "type": "categories" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/expanding-relations.md b/docs/api-reference/v2/expanding-relations.md new file mode 100644 index 00000000..3f07b47b --- /dev/null +++ b/docs/api-reference/v2/expanding-relations.md @@ -0,0 +1,302 @@ +--- +title: Expanding relations +description: How ?expand= works in Plane API v2. Sparse reads, the separate-key expansion contract, which resources support expansion, and the exact shape of each expanded object. +keywords: plane api v2, expand, sparse fields, state_id, assignee_ids, embedded relations, expand state, expand assignees, expand member +--- + +# Expanding relations + +v2 reads are **sparse by default**. A response gives you scalars plus identifiers for everything related to it — never an embedded object tree: + +- A to-one relation comes back as **`_id`**: `state_id`, `type_id`, `parent_id`, `created_by_id`. +- A to-many relation comes back as **`_ids`**, an array: `assignee_ids`, `label_ids`. + +`?expand=` opts a single request into richer output for specific relations. It takes a comma-separated list: + +```bash +GET .../work-items/?expand=state,assignees +``` + +## Expansion is separate-key + +This is the part that trips people up, so it is worth stating flatly: + +::: warning `?expand=` adds a key. It never replaces one. +`?expand=state` keeps `state_id` exactly where it was **and** adds a separate `state` object beside it. The identifier is not swapped for an object, and it does not disappear. Both are present in the same response. +::: + +Most APIs that offer expansion do the opposite — the `state` key holds a UUID string when you do not expand and an object when you do. That design forces every consumer to write `typeof x === "string" ? x : x.id` at each use site, and it means the shape of your response depends on a query parameter that some other part of your codebase set. + +v2 splits the two concerns into two keys with two stable types: + +| Key | Type | Present when | +| ---------- | --------------- | ------------------------- | +| `state_id` | `string (uuid)` | Always | +| `state` | object | Only with `?expand=state` | + +### Why this matters for your client + +- **One type per field, forever.** `state_id` is a `string` in every response your code will ever see. You never union a string with an object, and you never write a type guard to tell them apart. +- **Expansion is additive, so it is safe to change.** A caller can add or drop `?expand=` without invalidating any code that reads `state_id`. Your identifier-keyed caches, join tables, and foreign keys keep working untouched. +- **Optional, not conditional.** In a typed client, the expanded key is simply optional — `state?: State`. Compare that with a discriminated union of `string | State`, which every consumer has to narrow. +- **Nothing to reconcile.** Because both keys are present, `item.state.id` and `item.state_id` never disagree. + +The practical rule: **read identifiers from `*_id` / `*_ids`, and treat expanded objects purely as a display convenience** that saves you a round trip. + +## Where `?expand=` is supported + +Each resource declares its own allowlist. This is the complete list. + +| Resource | Allowed `expand` values | +| ------------------------------------------------------------------ | -------------------------------------------------------------------- | +| **Work items** (project list, workspace list, detail, writes) | `state`, `type`, `parent`, `assignees`, `labels`, `cycle`, `modules` | +| **Projects** | `project_lead`, `default_assignee` | +| **Cycles** | `owned_by` | +| **Modules** | `lead`, `members` | +| **Initiatives** | `lead` | +| **Teamspaces** | `lead` | +| **Releases** | `lead`, `tag` | +| **Estimates** | `points` | +| **Comments** and **work item activities** | `actor` | +| **Worklogs** | `logged_by` | +| **Project pages** and **workspace pages** | `owned_by`, `parent` | +| **Collections** | `owned_by` | +| **Project views** and **workspace views** | `owned_by` | +| **Workspace members**, **project members**, **collection members** | `member` | + +::: danger A resource with no expandable relations rejects every value +States, labels, milestones, work item types, properties, property options, property contexts, webhooks, workspace +features and audit logs declare no expandable relations. There is no partial support and no silent ignoring — any value +you pass to such a resource is an unknown value, and unknown values are rejected. +::: + +Passing a value the resource does not declare returns a `400`: + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "One or more fields failed validation.", + "errors": [ + { + "field": "expand", + "code": "invalid", + "message": "Unknown expand value(s): project." + } + ] +} +``` + + + +::: tip `?expand=` is in the published OpenAPI schema +The parameter is declared per operation in the served OpenAPI document at `/api/v2/schema/`, with its allowlist as an +**enum**. Generated SDKs and MCP tool definitions therefore constrain the value up front instead of discovering it from +a `400`, and a schema-driven validator will not strip it. +::: + +## Composing with `?fields=` + +`?expand=` and [`?fields=`](/api-reference/v2/sparse-fields) are **separate namespaces**, and they do not interact. + +``` +?fields=id,name&expand=state → { "id": "…", "name": "…", "state": { … } } +``` + +`state_id` is a field. `state` is an expandable relation. Because they are different keys in different namespaces: + +- Filtering fields **never drops an expansion**. `state` survives even though `state_id` was not requested. +- There is no precedence rule to learn, and no order dependency between the two parameters. + +The flip side is that the namespaces are strict in both directions. A relation name is not a valid `?fields=` token, and +a field name is not a valid `?expand=` value — but the `400` tells you which parameter you meant: + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "One or more fields failed validation.", + "errors": [ + { + "field": "fields", + "code": "invalid", + "message": "'state' is an expandable relation, not a response field — use ?expand=state to embed the object, or ?fields=state_id for just its id." + } + ] +} +``` + +::: info Why not unify them? +Letting `?fields=state` imply an expansion is the trap v1 fell into: expansion _replaced_ a field, so filtering fields +silently killed the expansion. Keeping the namespaces separate is what makes both parameters composable without +surprises. +::: + +## Before and after + +Take the same work item list, once sparse and once expanded. + +**Sparse (default)** + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + + + +```json +{ + "data": [ + { + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect", + "identifier": "PROJ-118", + "sequence_id": 118, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": null, + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": [], + "parent_id": null, + "start_date": null, + "target_date": "2026-02-02", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next": null, + "previous": null, + "total_count": 1, + "pagination": { "style": "offset" } +} +``` + + + +**Expanded** + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/?expand=state,assignees" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + + + +```json +{ + "data": [ + { + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect", + "identifier": "PROJ-118", + "sequence_id": 118, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": null, + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": [], + "parent_id": null, + "start_date": null, + "target_date": "2026-02-02", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "state": { + "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "In Progress", + "color": "#3f76ff", + "group": "started" + }, + "assignees": [ + { + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "display_name": "Priya Raghavan", + "avatar_url": "https://assets.plane.so/avatars/16c61a3a.png", + "email": "priya@example.com" + } + ] + } + ], + "next": null, + "previous": null, + "total_count": 1, + "pagination": { "style": "offset" } +} +``` + + + +Note what did **not** change: `state_id` is still `"f960d3c2-…"` and `assignee_ids` is still `["16c61a3a-…"]`. The two new keys sit alongside them. + +## Shape of each expanded object + +Expanded objects are deliberately minimal projections — enough to render a row without a second request, and nothing more. They are leaves: an expanded object never carries its own expansions or nested relations. + +### Work items + +| `expand` value | Adds key | Shape | +| -------------- | ----------- | ---------------------------------------------------- | +| `state` | `state` | `id`, `name`, `color`, `group` | +| `type` | `type` | `id`, `name`, `logo_props`, `is_epic` | +| `parent` | `parent` | `id`, `name`, `sequence_id` | +| `assignees` | `assignees` | Array of `id`, `display_name`, `avatar_url`, `email` | +| `labels` | `labels` | Array of `id`, `name`, `color` | + +### Members + +| `expand` value | Adds key | Shape | +| -------------- | -------- | ------------------------------------------- | +| `member` | `member` | `id`, `display_name`, `avatar_url`, `email` | + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/members/?expand=member" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + + + +```json +{ + "data": [ + { + "id": "c1b7e4a9-2f66-4a1d-9c3b-7d5e2f8a1b40", + "member_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "role": "admin", + "member": { + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "display_name": "Priya Raghavan", + "avatar_url": "https://assets.plane.so/avatars/16c61a3a.png", + "email": "priya@example.com" + } + } + ], + "next": null, + "previous": null, + "total_count": 1, + "pagination": { "style": "offset" } +} +``` + + + +Here too the identifier survives: `member_id` is the user's id, and `member` is the object. + +## Behavior notes + +- **A to-many expansion is always an array, never `null`.** With `?expand=assignees`, an unassigned work item gets `"assignees": []`. +- **A to-one expansion follows its id.** If `parent_id` is `null`, `?expand=parent` produces `"parent": null`. +- **Expansion works on detail routes too**, not only lists — `GET .../work-items/{pk}/?expand=state,labels` behaves the same way. +- **Expanding a list does not cost a query per row.** The relations are loaded in bulk, so `?expand=assignees` on a 200-row page is one additional query, not 200. +- **Expansion is read-only.** Writes always take ids (`state_id`, `assignee_ids`); sending a nested object on a `POST` or `PATCH` does not create or link anything. + +## Related + +- [Filtering and ordering](/api-reference/v2/filtering-and-ordering) — filter on the same relations with `state_id`, `assignee_id`, and friends +- [Pagination](/api-reference/v2/pagination) — the envelope that wraps every expanded list +- [Migrating from v1](/api-reference/v2/migrating-from-v1) — what to do about v1 responses that embedded these objects by default diff --git a/docs/api-reference/v2/filtering-and-ordering.md b/docs/api-reference/v2/filtering-and-ordering.md new file mode 100644 index 00000000..96b296e2 --- /dev/null +++ b/docs/api-reference/v2/filtering-and-ordering.md @@ -0,0 +1,281 @@ +--- +title: Filtering and ordering +description: How Plane API v2 list queries work. Exact, membership, null and range filters, relation filters by id, free-text search, and semantic ordering with order_by. +keywords: plane api v2, filtering, query parameters, order_by, search, state_group, priority, __in, __isnull, __gte, __lte +--- + +# Filtering and ordering + +v2 list endpoints draw their query parameters from the same four families: **filters** that narrow the set, **search** that matches free text, **ordering** that sorts what is left, and **pagination** that slices it. Every list endpoint supports ordering and pagination; each one declares its own filter and search surface, and a few smaller collections — work item types, properties, property options, property contexts — offer only ordering and pagination. Where they are available the four families compose, and every filter you add narrows the result further (they are combined with AND, never OR). + +Filters run after authorization, so a filter can only ever shrink the set of rows you were already allowed to see. There is no filter that widens access. + +## Filter shapes + +Public parameter names never contain `__`. The suffixes below are variants of a base name, and each list endpoint declares exactly which variants it supports. + +| Shape | Parameter form | Example | Matches | +| -------------- | ----------------------------- | --------------------------------------- | --------------------------------------- | +| **Exact** | `` | `?priority=urgent` | Rows equal to the value | +| **Membership** | `__in` | `?priority__in=urgent,high` | Rows equal to any comma-separated value | +| **Null** | `__isnull` | `?parent_id__isnull=true` | Rows where the field is (or is not) set | +| **Range** | `__gte` / `__lte` | `?created_at__gte=2026-01-01T00:00:00Z` | Rows on or after / on or before a bound | + +A few things that are easy to get wrong: + +- `__in` takes a **comma-separated list in a single parameter** — `?priority__in=urgent,high`. Repeating the parameter is not the same thing. +- `__gte` and `__lte` are inclusive on both ends. Pair them to express a window; send one alone for an open-ended bound. +- Date-time ranges want an ISO 8601 timestamp (`2026-01-01T00:00:00Z`); date ranges want a plain date (`2026-01-01`). Which one a parameter takes follows the field it filters. +- Not every base name offers every variant. `state_id` supports `__in` but not `__isnull`, because a work item always has a state. Check the endpoint's own parameter list rather than assuming the full set. + +## Filtering by a relation + +Filter a relation with its **`*_id` parameter**. There is no bare-name form — it is `state_id`, not `state`. + +| Resource | Relation filters | +| ---------- | -------------------------------------------------------------------------------------- | +| Work items | `state_id`, `assignee_id`, `label_id`, `parent_id`, `cycle_id`, `module_id`, `type_id` | +| Cycles | `owned_by_id` | +| Modules | `lead_id` | +| Labels | `parent_id` | +| Members | `member_id` | + +This mirrors how writes work — you set a relation with `state_id`, and you filter on it with `state_id` too. See [Migrating from v1](/api-reference/v2/migrating-from-v1) if you are used to v1's embedded relation objects. + +```bash +# every cycle owned by one person +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/cycles/?owned_by_id=16c61a3a-512a-48ac-b0be-b6b46fe6f430" \ + -H "X-Api-Key: $PLANE_API_KEY" + +# modules a person leads that are underway +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/modules/?lead_id=16c61a3a-512a-48ac-b0be-b6b46fe6f430&status=in-progress" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +## Looking something up by name + +Addressing by a **mutable attribute** — a name, a URL, a version string — is a collection concern in v2, never a path +segment. Filter the list instead: + +```bash +# the state called "In Progress" +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/ENG/states/?name=In%20Progress" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +Paired with [`?fields=`](/api-reference/v2/sparse-fields), that is a one-call name→id resolve of about fifteen tokens: + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/ENG/states/?name=In%20Progress&fields=id" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +```json +{ + "data": [{ "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" }], + "next": null, + "previous": null, + "total_count": 1, + "pagination": { "style": "offset" } +} +``` + +Matching is **case-insensitive and exact** — not a substring search. For substring matching use +[`?search=`](#search) instead. + +| Filter | Available on | +| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `?name=` | states, labels, cycles, modules, milestones, projects, project and workspace views, initiatives, initiative labels, releases, release labels, customers, customer properties, estimates, teamspaces, webhooks, work item types (project and workspace), work item properties (project and workspace), property options, property contexts, project and workspace automations, automation nodes | +| `?key=` | projects (matches `identifier`), estimate points | +| `?url=` | webhooks, work item links | +| `?title=` | work item links | +| `?value=` | estimate points | +| `?version=` | release tags | +| `?external_id=`, `?external_source=` | 22 list endpoints, for sync and import correlation | + +::: info Duplicates are visible, not guessed +A filtered list is honest about ambiguity. If two states share a name, you get **two rows** and decide which one you +meant. There is no path-level "ambiguous identifier" error to handle, because there is no path-level attribute lookup. +::: + +::: warning Scheme-prefixed path segments are gone +A brief v2 preview accepted `:` path segments — `…/states/name:In Progress/`, `…/projects/key:ENG/`, +`…/webhooks/url:https://…/`, `external:…`. Those were removed. A `name:`-style segment is now just an unknown id and +returns `404`. + +Use the filters above instead. Path segments address by **stable identifier only** — see +[Addressing](#addressing-a-resource-in-a-path). +::: + +## Addressing a resource in a path + +Path segments take UUIDs, with exactly three human-readable exceptions — all of them stable, none of them +scheme-prefixed: + +| Key | Where it works | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| Workspace **slug** | the `workspaces/{slug}/` prefix on every route | +| Project **identifier** | the project detail route (`…/projects/ENG/`) and as a parent segment (`…/projects/ENG/work-items/`) | +| Work item **`PROJ-123`** | the [by-identifier route](/api-reference/v2/work-items/get-work-item-by-identifier), project-scoped detail, and as a `work_item_id` parent segment | + +```bash +# both of these address the same project +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/ENG/" -H "X-Api-Key: $PLANE_API_KEY" +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/" -H "X-Api-Key: $PLANE_API_KEY" +``` + +A project identifier is unique per workspace and uppercased on save, so it can never be ambiguous. Everything else in a +path is a UUID. + +## Enum-backed filters are validated + +Parameters backed by an enum — work item `priority` and `state_group`, state `group`, module `status`, comment `access` — are checked against their allowed values before the query runs. A value outside the enum is a clean `400 invalid_request` naming the offending field. + +This matters more than it sounds. In an API that ignores unknown filter values, `?state_group=in_progress` (a plausible-looking guess; the real value is `started`) returns `200` with an empty list, and you spend an afternoon deciding whether the project is genuinely empty. In v2 it fails immediately: + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "One or more fields failed validation.", + "errors": [ + { + "field": "state_group", + "code": "invalid_choice", + "message": "Select a valid choice. in_progress is not one of the available choices." + } + ] +} +``` + + + +The `__in` variants validate every element of the list, not just the first. + +## Search + +`?search=` is a case-insensitive partial match over the resource's searchable text. It is a convenience for lookups, not a query language — no operators, no field prefixes, no quoting. + +| Resource | Searches | +| ------------------------------- | ---------------------------------------------------------------- | +| Work items | Name | +| States, labels, cycles, modules | Name | +| Work item comments | Comment text | +| Members | Display name, email, first name, last name | +| Audit logs | Event name, actor display name, actor email, target display name | + +Search composes with filters, so `?search=login&priority=urgent` finds urgent work items whose name contains "login". + +## Ordering + +`?order_by=` sorts the list. Prefix the field with `-` for descending order: + +```bash +?order_by=created_at # oldest first +?order_by=-created_at # newest first +``` + +Each resource declares its own allowed fields. Unlike the enum-backed _filters_ above, an unrecognized `order_by` does not fail — it falls back to the resource's default ordering. If a sort looks wrong, check the spelling against the endpoint's allowed values before assuming the data is wrong. Every ordering carries a unique tiebreak internally, so a page boundary never drops or duplicates a row just because two rows share a sort key. + +### Semantic orderings + +Two work item orderings sort by **meaning rather than by string**: + +- `order_by=priority` sorts `urgent` → `high` → `medium` → `low` → `none`. Alphabetically that would be `high, low, medium, none, urgent`, which is useless. +- `order_by=state_group` sorts `backlog` → `unstarted` → `started` → `completed` → `cancelled` → `triage`, following the workflow rather than the alphabet. + +`-priority` and `-state_group` reverse those sequences. + +::: warning Semantic orderings are offset-only +`priority` and `state_group` cannot be combined with `?paginate=cursor` — a cursor needs a unique, monotonic sort key and these have a handful of distinct values. The pairing returns `400 ordering_not_cursor_eligible`. `sort_order` is not cursor-eligible either, for the same reason. On work items the cursor-eligible orderings are `created_at`, `updated_at`, `sequence_id`, and `id`. See [Pagination](/api-reference/v2/pagination#not-every-ordering-can-use-a-cursor). +::: + +## Worked example: the work items list + +The work items list is the largest filter surface in v2. Its parameters, grouped by concept: + +**Relations** + +| Base name | Variants | Type | +| ------------- | ------------------ | --------------- | +| `state_id` | `__in` | `string (uuid)` | +| `type_id` | `__in` | `string (uuid)` | +| `assignee_id` | `__in`, `__isnull` | `string (uuid)` | +| `label_id` | `__in`, `__isnull` | `string (uuid)` | +| `parent_id` | `__in`, `__isnull` | `string (uuid)` | +| `cycle_id` | `__in`, `__isnull` | `string (uuid)` | +| `module_id` | `__in`, `__isnull` | `string (uuid)` | + +**Enums and scalars** + +| Name | Variants | Type | Values | +| ----------------- | -------- | --------- | --------------------------------------------------------------------- | +| `priority` | `__in` | `string` | `urgent`, `high`, `medium`, `low`, `none` | +| `state_group` | `__in` | `string` | `backlog`, `unstarted`, `started`, `completed`, `cancelled`, `triage` | +| `is_draft` | — | `boolean` | `true`, `false` | +| `sequence_id` | — | `integer` | The number in a `PROJ-123` identifier | +| `external_id` | — | `string` | Your system's id, for sync correlation | +| `external_source` | — | `string` | The system `external_id` came from | + +**Ranges** + +| Base name | Variants | Type | +| ------------- | ---------------- | -------------------- | +| `created_at` | `__gte`, `__lte` | `string (date-time)` | +| `updated_at` | `__gte`, `__lte` | `string (date-time)` | +| `start_date` | `__gte`, `__lte` | `string (date)` | +| `target_date` | `__gte`, `__lte` | `string (date)` | + +Date fields are range-only — there is no exact `?created_at=` filter. To match a single day, bracket it: `?created_at__gte=2026-01-14T00:00:00Z&created_at__lte=2026-01-14T23:59:59Z`. + +**Search** — `search`. + +**Ordering** — `order_by`, one of `created_at`, `updated_at`, `sequence_id`, `id`, `sort_order`, `priority`, `state_group`, each also available with a `-` prefix. + +**Pagination** — `per_page`, `offset`, `count`, `paginate`, `cursor`. See [Pagination](/api-reference/v2/pagination). + +## Combined filters + +```bash +# urgent and high-priority items that are in flight or already done +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/?priority__in=urgent,high&state_group__in=started,completed" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +```bash +# one person's open work, most urgent first +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/?assignee_id=16c61a3a-512a-48ac-b0be-b6b46fe6f430&state_group__in=backlog,unstarted,started&order_by=priority" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +```bash +# an incremental sync: everything touched since the last run, oldest first +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/?updated_at__gte=2026-01-14T09:22:41Z&order_by=updated_at&paginate=cursor&per_page=200" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +```bash +# top-level items only — nothing that is a sub-item of something else +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/?parent_id__isnull=true&is_draft=false" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +```bash +# unassigned work due this quarter, with the state object attached +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/?assignee_id__isnull=true&target_date__gte=2026-01-01&target_date__lte=2026-03-31&expand=state" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +```bash +# find the item you imported from another system +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/?external_source=jira&external_id=ENG-4417" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +## Related + +- [Pagination](/api-reference/v2/pagination) — the envelopes, and which orderings a cursor can use +- [Expanding relations](/api-reference/v2/expanding-relations) — attaching related objects to filtered results +- [Errors](/api-reference/v2/errors) — the `invalid_request` body and its `errors[]` array diff --git a/docs/api-reference/v2/group-sync/create-project-mapping.md b/docs/api-reference/v2/group-sync/create-project-mapping.md new file mode 100644 index 00000000..714c5a68 --- /dev/null +++ b/docs/api-reference/v2/group-sync/create-project-mapping.md @@ -0,0 +1,193 @@ +--- +title: Create a project mapping +description: Create a project mapping with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a project mapping, project mappings, group sync project mappings create +--- + +# Create a project mapping + +
+ POST + /api/v2/workspaces/{slug}/group-sync/project-mappings/ +
+ +
+
+ +Group sync maps identity-provider groups onto Plane workspace and project roles. Create a project mapping. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The idp group name. + +Maximum 255 characters. + + + + + +The role slug. + + + + + +Whether all projects. + + + + + +Id of the related project. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `all_projects`, `created_at`, `id`, `idp_group_name`, `project_id`, `role_slug`. + + + +
+
+ +
+ +### Scopes + +`workspaces.group_sync:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "all_projects": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "idp_group_name": "example", + "project_id": "4af68566-94a4-4eb3-94aa-50dc9427067b", + "role_slug": "example" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/group-sync/create-workspace-mapping.md b/docs/api-reference/v2/group-sync/create-workspace-mapping.md new file mode 100644 index 00000000..3fc5cc1e --- /dev/null +++ b/docs/api-reference/v2/group-sync/create-workspace-mapping.md @@ -0,0 +1,171 @@ +--- +title: Create a workspace mapping +description: Create a workspace mapping with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a workspace mapping, workspace mappings, group sync workspace mappings create +--- + +# Create a workspace mapping + +
+ POST + /api/v2/workspaces/{slug}/group-sync/workspace-mappings/ +
+ +
+
+ +Group sync maps identity-provider groups onto Plane workspace and project roles. Create a workspace mapping. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The idp group name. + +Maximum 255 characters. + + + + + +The role slug. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `id`, `idp_group_name`, `role_slug`. + + + +
+
+ +
+ +### Scopes + +`workspaces.group_sync:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "idp_group_name": "example", + "role_slug": "example" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/group-sync/delete-project-mapping.md b/docs/api-reference/v2/group-sync/delete-project-mapping.md new file mode 100644 index 00000000..a3949596 --- /dev/null +++ b/docs/api-reference/v2/group-sync/delete-project-mapping.md @@ -0,0 +1,148 @@ +--- +title: Delete a project mapping +description: Delete a project mapping with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a project mapping, project mappings, group sync project mappings destroy +--- + +# Delete a project mapping + +
+ DELETE + /api/v2/workspaces/{slug}/group-sync/project-mappings/{pk}/ +
+ +
+
+ +Group sync maps identity-provider groups onto Plane workspace and project roles. Delete a project mapping. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project mapping id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `all_projects`, `created_at`, `id`, `idp_group_name`, `project_id`, `role_slug`. + + + +
+
+ +
+ +### Scopes + +`workspaces.group_sync:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project mapping matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/group-sync/delete-workspace-mapping.md b/docs/api-reference/v2/group-sync/delete-workspace-mapping.md new file mode 100644 index 00000000..5ca14afd --- /dev/null +++ b/docs/api-reference/v2/group-sync/delete-workspace-mapping.md @@ -0,0 +1,148 @@ +--- +title: Delete a workspace mapping +description: Delete a workspace mapping with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a workspace mapping, workspace mappings, group sync workspace mappings destroy +--- + +# Delete a workspace mapping + +
+ DELETE + /api/v2/workspaces/{slug}/group-sync/workspace-mappings/{pk}/ +
+ +
+
+ +Group sync maps identity-provider groups onto Plane workspace and project roles. Delete a workspace mapping. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace mapping id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `id`, `idp_group_name`, `role_slug`. + + + +
+
+ +
+ +### Scopes + +`workspaces.group_sync:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace mapping matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/group-sync/get-group-sync-config.md b/docs/api-reference/v2/group-sync/get-group-sync-config.md new file mode 100644 index 00000000..df23ff44 --- /dev/null +++ b/docs/api-reference/v2/group-sync/get-group-sync-config.md @@ -0,0 +1,128 @@ +--- +title: Get the group sync configuration +description: Read a group sync config with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get the group sync configuration, group sync config, group sync config retrieve +--- + +# Get the group sync configuration + +
+ GET + /api/v2/workspaces/{slug}/group-sync/config/ +
+ +
+
+ +Group sync maps identity-provider groups onto Plane workspace and project roles. Read a single group sync config by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Scopes + +`workspaces.group_sync:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "auto_remove": false, + "default_workspace_role_slug": "example", + "group_attribute_key": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "sync_offline": false, + "sync_on_login": false +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No group sync config matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/group-sync/get-project-mapping.md b/docs/api-reference/v2/group-sync/get-project-mapping.md new file mode 100644 index 00000000..bc9503d9 --- /dev/null +++ b/docs/api-reference/v2/group-sync/get-project-mapping.md @@ -0,0 +1,153 @@ +--- +title: Get a project mapping +description: Read a project mapping with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a project mapping, project mappings, group sync project mappings retrieve +--- + +# Get a project mapping + +
+ GET + /api/v2/workspaces/{slug}/group-sync/project-mappings/{pk}/ +
+ +
+
+ +Group sync maps identity-provider groups onto Plane workspace and project roles. Read a single project mapping by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project mapping id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `all_projects`, `created_at`, `id`, `idp_group_name`, `project_id`, `role_slug`. + + + +
+
+ +
+ +### Scopes + +`workspaces.group_sync:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "all_projects": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "idp_group_name": "example", + "project_id": "4af68566-94a4-4eb3-94aa-50dc9427067b", + "role_slug": "example" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project mapping matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/group-sync/get-workspace-mapping.md b/docs/api-reference/v2/group-sync/get-workspace-mapping.md new file mode 100644 index 00000000..7ce45dbe --- /dev/null +++ b/docs/api-reference/v2/group-sync/get-workspace-mapping.md @@ -0,0 +1,151 @@ +--- +title: Get a workspace mapping +description: Read a workspace mapping with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a workspace mapping, workspace mappings, group sync workspace mappings retrieve +--- + +# Get a workspace mapping + +
+ GET + /api/v2/workspaces/{slug}/group-sync/workspace-mappings/{pk}/ +
+ +
+
+ +Group sync maps identity-provider groups onto Plane workspace and project roles. Read a single workspace mapping by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace mapping id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `id`, `idp_group_name`, `role_slug`. + + + +
+
+ +
+ +### Scopes + +`workspaces.group_sync:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "idp_group_name": "example", + "role_slug": "example" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace mapping matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/group-sync/list-project-mappings.md b/docs/api-reference/v2/group-sync/list-project-mappings.md new file mode 100644 index 00000000..66b940a5 --- /dev/null +++ b/docs/api-reference/v2/group-sync/list-project-mappings.md @@ -0,0 +1,199 @@ +--- +title: List project mappings +description: List project mappings with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list project mappings, project mappings, group sync project mappings list +--- + +# List project mappings + +
+ GET + /api/v2/workspaces/{slug}/group-sync/project-mappings/ +
+ +
+
+ +Group sync maps identity-provider groups onto Plane workspace and project roles. List the project mappings you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `all_projects`, `created_at`, `id`, `idp_group_name`, `project_id`, `role_slug`. + + + +
+
+ +
+ +### Scopes + +`workspaces.group_sync:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "all_projects": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "idp_group_name": "example", + "project_id": "4af68566-94a4-4eb3-94aa-50dc9427067b", + "role_slug": "example" + }, + { + "all_projects": false, + "created_at": "2026-01-13T16:04:02.911204Z", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "idp_group_name": "example", + "project_id": "4af68566-94a4-4eb3-94aa-50dc9427067b", + "role_slug": "example" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/group-sync/list-workspace-mappings.md b/docs/api-reference/v2/group-sync/list-workspace-mappings.md new file mode 100644 index 00000000..05504444 --- /dev/null +++ b/docs/api-reference/v2/group-sync/list-workspace-mappings.md @@ -0,0 +1,195 @@ +--- +title: List workspace mappings +description: List workspace mappings with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list workspace mappings, workspace mappings, group sync workspace mappings list +--- + +# List workspace mappings + +
+ GET + /api/v2/workspaces/{slug}/group-sync/workspace-mappings/ +
+ +
+
+ +Group sync maps identity-provider groups onto Plane workspace and project roles. List the workspace mappings you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `id`, `idp_group_name`, `role_slug`. + + + +
+
+ +
+ +### Scopes + +`workspaces.group_sync:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "idp_group_name": "example", + "role_slug": "example" + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "idp_group_name": "example", + "role_slug": "example" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/group-sync/overview.md b/docs/api-reference/v2/group-sync/overview.md new file mode 100644 index 00000000..03512608 --- /dev/null +++ b/docs/api-reference/v2/group-sync/overview.md @@ -0,0 +1,87 @@ +--- +title: Group sync overview +description: The Plane API v2 group mapping object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, group sync, group mapping object +--- + +# Group sync overview + +Group sync maps identity-provider groups onto Plane workspace and project roles. + +
+
+ +## The group mapping object + +### Attributes + +- `auto_remove` _boolean_ + + Whether auto remove. + +- `default_workspace_role_slug` _string_ + + The default workspace role slug. + +- `group_attribute_key` _string_ + + The group attribute key. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_enabled` _boolean_ + + Whether the rule is switched on. + +- `sync_offline` _boolean_ + + Whether sync offline. + +- `sync_on_login` _boolean_ + + Whether sync on login. + +
+
+ + + +```json +{ + "auto_remove": false, + "default_workspace_role_slug": "example", + "group_attribute_key": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "sync_offline": false, + "sync_on_login": false +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | --------------------------------------------------------------- | ----------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/group-sync/config/` | Get the group sync configuration | +| `PATCH` | `/api/v2/workspaces/{slug}/group-sync/config/` | Update the group sync configuration | +| `GET` | `/api/v2/workspaces/{slug}/group-sync/project-mappings/` | List project mappings | +| `POST` | `/api/v2/workspaces/{slug}/group-sync/project-mappings/` | Create a project mapping | +| `DELETE` | `/api/v2/workspaces/{slug}/group-sync/project-mappings/{pk}/` | Delete a project mapping | +| `GET` | `/api/v2/workspaces/{slug}/group-sync/project-mappings/{pk}/` | Get a project mapping | +| `PATCH` | `/api/v2/workspaces/{slug}/group-sync/project-mappings/{pk}/` | Update a project mapping | +| `GET` | `/api/v2/workspaces/{slug}/group-sync/workspace-mappings/` | List workspace mappings | +| `POST` | `/api/v2/workspaces/{slug}/group-sync/workspace-mappings/` | Create a workspace mapping | +| `DELETE` | `/api/v2/workspaces/{slug}/group-sync/workspace-mappings/{pk}/` | Delete a workspace mapping | +| `GET` | `/api/v2/workspaces/{slug}/group-sync/workspace-mappings/{pk}/` | Get a workspace mapping | +| `PATCH` | `/api/v2/workspaces/{slug}/group-sync/workspace-mappings/{pk}/` | Update a workspace mapping | + +## Response shaping + +Every group mapping read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/group-sync/update-group-sync-config.md b/docs/api-reference/v2/group-sync/update-group-sync-config.md new file mode 100644 index 00000000..569c78ec --- /dev/null +++ b/docs/api-reference/v2/group-sync/update-group-sync-config.md @@ -0,0 +1,186 @@ +--- +title: Update the group sync configuration +description: Update the group sync configuration with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update the group sync configuration, group sync config, group sync config update +--- + +# Update the group sync configuration + +
+ PATCH + /api/v2/workspaces/{slug}/group-sync/config/ +
+ +
+
+ +Group sync maps identity-provider groups onto Plane workspace and project roles. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Whether auto remove. + + + + + +The default workspace role slug. + +Nullable. + + + + + +The group attribute key. + +Maximum 255 characters. + + + + + +Whether the rule is switched on. + + + + + +Whether sync offline. + + + + + +Whether sync on login. + + + +
+
+ +
+ +### Scopes + +`workspaces.group_sync:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "auto_remove": false, + "default_workspace_role_slug": "example", + "group_attribute_key": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "sync_offline": false, + "sync_on_login": false +} +``` + + + +
+
diff --git a/docs/api-reference/v2/group-sync/update-project-mapping.md b/docs/api-reference/v2/group-sync/update-project-mapping.md new file mode 100644 index 00000000..73b16385 --- /dev/null +++ b/docs/api-reference/v2/group-sync/update-project-mapping.md @@ -0,0 +1,199 @@ +--- +title: Update a project mapping +description: Update a project mapping with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a project mapping, project mappings, group sync project mappings update +--- + +# Update a project mapping + +
+ PATCH + /api/v2/workspaces/{slug}/group-sync/project-mappings/{pk}/ +
+ +
+
+ +Group sync maps identity-provider groups onto Plane workspace and project roles. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project mapping id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Whether all projects. + + + + + +The idp group name. + +Maximum 255 characters. + + + + + +Id of the related project. + +Nullable. + + + + + +The role slug. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `all_projects`, `created_at`, `id`, `idp_group_name`, `project_id`, `role_slug`. + + + +
+
+ +
+ +### Scopes + +`workspaces.group_sync:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "all_projects": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "idp_group_name": "example", + "project_id": "4af68566-94a4-4eb3-94aa-50dc9427067b", + "role_slug": "example" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/group-sync/update-workspace-mapping.md b/docs/api-reference/v2/group-sync/update-workspace-mapping.md new file mode 100644 index 00000000..0948eb72 --- /dev/null +++ b/docs/api-reference/v2/group-sync/update-workspace-mapping.md @@ -0,0 +1,180 @@ +--- +title: Update a workspace mapping +description: Update a workspace mapping with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a workspace mapping, workspace mappings, group sync workspace mappings update +--- + +# Update a workspace mapping + +
+ PATCH + /api/v2/workspaces/{slug}/group-sync/workspace-mappings/{pk}/ +
+ +
+
+ +Group sync maps identity-provider groups onto Plane workspace and project roles. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace mapping id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The idp group name. + +Maximum 255 characters. + + + + + +The role slug. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `id`, `idp_group_name`, `role_slug`. + + + +
+
+ +
+ +### Scopes + +`workspaces.group_sync:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "idp_group_name": "example", + "role_slug": "example" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/initiative-labels/create-initiative-label.md b/docs/api-reference/v2/initiative-labels/create-initiative-label.md new file mode 100644 index 00000000..9243c8b6 --- /dev/null +++ b/docs/api-reference/v2/initiative-labels/create-initiative-label.md @@ -0,0 +1,194 @@ +--- +title: Create a initiative label +description: Create a initiative label with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a initiative label, initiative labels, initiative labels create +--- + +# Create a initiative label + +
+ POST + /api/v2/workspaces/{slug}/initiatives/labels/ +
+ +
+
+ +Initiative labels are the workspace-level label catalog for initiatives. Create a initiative label. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. + + + + + +Free-form description. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `description`, `id`, `name`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`initiatives.labels:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Example name", + "sort_order": 65535 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/initiative-labels/delete-initiative-label.md b/docs/api-reference/v2/initiative-labels/delete-initiative-label.md new file mode 100644 index 00000000..d6228271 --- /dev/null +++ b/docs/api-reference/v2/initiative-labels/delete-initiative-label.md @@ -0,0 +1,148 @@ +--- +title: Delete a initiative label +description: Delete a initiative label with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a initiative label, initiative labels, initiative labels destroy +--- + +# Delete a initiative label + +
+ DELETE + /api/v2/workspaces/{slug}/initiatives/labels/{pk}/ +
+ +
+
+ +Initiative labels are the workspace-level label catalog for initiatives. Delete a initiative label. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The initiative label id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `description`, `id`, `name`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`initiatives.labels:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No initiative label matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/initiative-labels/get-initiative-label.md b/docs/api-reference/v2/initiative-labels/get-initiative-label.md new file mode 100644 index 00000000..06df232a --- /dev/null +++ b/docs/api-reference/v2/initiative-labels/get-initiative-label.md @@ -0,0 +1,154 @@ +--- +title: Get a initiative label +description: Read a initiative label with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a initiative label, initiative labels, initiative labels retrieve +--- + +# Get a initiative label + +
+ GET + /api/v2/workspaces/{slug}/initiatives/labels/{pk}/ +
+ +
+
+ +Initiative labels are the workspace-level label catalog for initiatives. Read a single initiative label by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The initiative label id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `description`, `id`, `name`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`initiatives.labels:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Example name", + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No initiative label matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/initiative-labels/list-initiative-labels.md b/docs/api-reference/v2/initiative-labels/list-initiative-labels.md new file mode 100644 index 00000000..f9ac6ab7 --- /dev/null +++ b/docs/api-reference/v2/initiative-labels/list-initiative-labels.md @@ -0,0 +1,207 @@ +--- +title: List initiative labels +description: List initiative labels with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list initiative labels, initiative labels, initiative labels list +--- + +# List initiative labels + +
+ GET + /api/v2/workspaces/{slug}/initiatives/labels/ +
+ +
+
+ +Initiative labels are the workspace-level label catalog for initiatives. List the initiative labels you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `description`, `id`, `name`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`initiatives.labels:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Example name", + "sort_order": 65535 + }, + { + "color": "#3f76ff", + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Another example", + "sort_order": 65535 + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/initiative-labels/overview.md b/docs/api-reference/v2/initiative-labels/overview.md new file mode 100644 index 00000000..6d628f10 --- /dev/null +++ b/docs/api-reference/v2/initiative-labels/overview.md @@ -0,0 +1,80 @@ +--- +title: Initiative labels overview +description: The Plane API v2 initiative label object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, initiative labels, initiative label object +--- + +# Initiative labels overview + +Initiative labels are the workspace-level label catalog for initiatives. + +
+
+ +## The initiative label object + +### Attributes + +- `color` _string_ + + Hex color used wherever this is rendered, for example `#3f76ff`. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description` _string_ + + Free-form description. + +- `id` _string (uuid)_ + + Unique identifier. + +- `name` _string_ + + Display name. + +- `sort_order` _number_ + + Manual ordering weight. Lower sorts first. + +
+
+ + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Example name", + "sort_order": 65535 +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------- | ------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/initiatives/labels/` | List initiative labels | +| `POST` | `/api/v2/workspaces/{slug}/initiatives/labels/` | Create a initiative label | +| `DELETE` | `/api/v2/workspaces/{slug}/initiatives/labels/{pk}/` | Delete a initiative label | +| `GET` | `/api/v2/workspaces/{slug}/initiatives/labels/{pk}/` | Get a initiative label | +| `PATCH` | `/api/v2/workspaces/{slug}/initiatives/labels/{pk}/` | Update a initiative label | + +## Response shaping + +Every initiative label read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/initiative-labels/update-initiative-label.md b/docs/api-reference/v2/initiative-labels/update-initiative-label.md new file mode 100644 index 00000000..4ebb0ad9 --- /dev/null +++ b/docs/api-reference/v2/initiative-labels/update-initiative-label.md @@ -0,0 +1,212 @@ +--- +title: Update a initiative label +description: Update a initiative label with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a initiative label, initiative labels, initiative labels partial update +--- + +# Update a initiative label + +
+ PATCH + /api/v2/workspaces/{slug}/initiatives/labels/{pk}/ +
+ +
+
+ +Initiative labels are the workspace-level label catalog for initiatives. Update a initiative label. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The initiative label id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. + + + + + +Free-form description. + + + + + +Display name. + +Maximum 255 characters. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `description`, `id`, `name`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`initiatives.labels:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Example name", + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No initiative label matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/initiatives/create-initiative.md b/docs/api-reference/v2/initiatives/create-initiative.md new file mode 100644 index 00000000..33039841 --- /dev/null +++ b/docs/api-reference/v2/initiatives/create-initiative.md @@ -0,0 +1,253 @@ +--- +title: Create a initiative +description: Create a initiative with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a initiative, initiatives, initiatives create +--- + +# Create a initiative + +
+ POST + /api/v2/workspaces/{slug}/initiatives/ +
+ +
+
+ +Initiatives group projects and work items under one strategic objective. Create a initiative. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Free-form description. + +Nullable. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + +Nullable. + + + + + +End date, as `YYYY-MM-DD`. + +Nullable. + + + + + +Id of the related lead. + +Nullable. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Ids of the projects to associate. Replaces the current set. + + + + + +Planned start date, as `YYYY-MM-DD`. + +Nullable. + + + + + +- `DRAFT` - Draft +- `PLANNED` - Planned +- `ACTIVE` - Active +- `COMPLETED` - Completed +- `CLOSED` - Closed + +One of `DRAFT`, `PLANNED`, `ACTIVE`, `COMPLETED`, `CLOSED`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `description`, `description_html`, `end_date`, `id`, `label_ids`, `lead_id`, `logo_props`, `name`, `project_ids`, `start_date`, `state`. + + + + + +Comma-separated relations to embed: `lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`initiatives:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "description_html": "

Details go here.

", + "end_date": "2026-01-31", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "start_date": "2026-01-12", + "state": "DRAFT" +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/initiatives/delete-initiative.md b/docs/api-reference/v2/initiatives/delete-initiative.md new file mode 100644 index 00000000..353e782c --- /dev/null +++ b/docs/api-reference/v2/initiatives/delete-initiative.md @@ -0,0 +1,156 @@ +--- +title: Delete a initiative +description: Delete a initiative with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a initiative, initiatives, initiatives destroy +--- + +# Delete a initiative + +
+ DELETE + /api/v2/workspaces/{slug}/initiatives/{pk}/ +
+ +
+
+ +Initiatives group projects and work items under one strategic objective. Delete a initiative. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The initiative id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `description`, `description_html`, `end_date`, `id`, `label_ids`, `lead_id`, `logo_props`, `name`, `project_ids`, `start_date`, `state`. + + + + + +Comma-separated relations to embed: `lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`initiatives:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No initiative matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/initiatives/get-initiative.md b/docs/api-reference/v2/initiatives/get-initiative.md new file mode 100644 index 00000000..925c90a6 --- /dev/null +++ b/docs/api-reference/v2/initiatives/get-initiative.md @@ -0,0 +1,169 @@ +--- +title: Get a initiative +description: Read a initiative with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a initiative, initiatives, initiatives retrieve +--- + +# Get a initiative + +
+ GET + /api/v2/workspaces/{slug}/initiatives/{pk}/ +
+ +
+
+ +Initiatives group projects and work items under one strategic objective. Read a single initiative by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The initiative id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `description`, `description_html`, `end_date`, `id`, `label_ids`, `lead_id`, `logo_props`, `name`, `project_ids`, `start_date`, `state`. + + + + + +Comma-separated relations to embed: `lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`initiatives:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "description_html": "

Details go here.

", + "end_date": "2026-01-31", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "start_date": "2026-01-12", + "state": "DRAFT" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No initiative matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/initiatives/list-initiatives.md b/docs/api-reference/v2/initiatives/list-initiatives.md new file mode 100644 index 00000000..f14dc081 --- /dev/null +++ b/docs/api-reference/v2/initiatives/list-initiatives.md @@ -0,0 +1,259 @@ +--- +title: List initiatives +description: List initiatives with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list initiatives, initiatives, initiatives list +--- + +# List initiatives + +
+ GET + /api/v2/workspaces/{slug}/initiatives/ +
+ +
+
+ +Initiatives group projects and work items under one strategic objective. List the initiatives you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `lead_id`. + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +- `DRAFT` - Draft +- `PLANNED` - Planned +- `ACTIVE` - Active +- `COMPLETED` - Completed +- `CLOSED` - Closed + +One of `ACTIVE`, `CLOSED`, `COMPLETED`, `DRAFT`, `PLANNED`. + + + + + +Multiple values may be separated by commas. + +- `DRAFT` - Draft +- `PLANNED` - Planned +- `ACTIVE` - Active +- `COMPLETED` - Completed +- `CLOSED` - Closed + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `description`, `description_html`, `end_date`, `id`, `label_ids`, `lead_id`, `logo_props`, `name`, `project_ids`, `start_date`, `state`. + + + + + +Comma-separated relations to embed: `lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`initiatives:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "description_html": "

Details go here.

", + "end_date": "2026-01-31", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "start_date": "2026-01-12", + "state": "DRAFT" + }, + { + "archived_at": null, + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "description_html": "

Details go here.

", + "end_date": "2026-01-31", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "name": "Another example", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "start_date": "2026-01-12", + "state": "DRAFT" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/initiatives/manage-initiative-labels.md b/docs/api-reference/v2/initiatives/manage-initiative-labels.md new file mode 100644 index 00000000..14dcb4cd --- /dev/null +++ b/docs/api-reference/v2/initiatives/manage-initiative-labels.md @@ -0,0 +1,167 @@ +--- +title: Add or remove initiative labels +description: Add or remove initiative labels with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, add or remove initiative labels, initiative labels, initiatives labels +--- + +# Add or remove initiative labels + +
+ POST + /api/v2/workspaces/{slug}/initiatives/{pk}/labels/ +
+ +
+
+ +Initiatives group projects and work items under one strategic objective. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The initiative label id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The add. + + + + + +The remove. + + + +
+
+ +
+ +### Scopes + +`initiatives:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "added": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "removed": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/initiatives/manage-initiative-projects.md b/docs/api-reference/v2/initiatives/manage-initiative-projects.md new file mode 100644 index 00000000..065ce0fb --- /dev/null +++ b/docs/api-reference/v2/initiatives/manage-initiative-projects.md @@ -0,0 +1,167 @@ +--- +title: Add or remove initiative projects +description: Add or remove initiative projects with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, add or remove initiative projects, initiative projects, initiatives projects +--- + +# Add or remove initiative projects + +
+ POST + /api/v2/workspaces/{slug}/initiatives/{pk}/projects/ +
+ +
+
+ +Initiatives group projects and work items under one strategic objective. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The initiative project id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The add. + + + + + +The remove. + + + +
+
+ +
+ +### Scopes + +`initiatives:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "added": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "removed": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/initiatives/manage-initiative-work-items.md b/docs/api-reference/v2/initiatives/manage-initiative-work-items.md new file mode 100644 index 00000000..b628211b --- /dev/null +++ b/docs/api-reference/v2/initiatives/manage-initiative-work-items.md @@ -0,0 +1,167 @@ +--- +title: Add or remove initiative work items +description: Add or remove initiative work items with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, add or remove initiative work items, initiative work items, initiatives work items +--- + +# Add or remove initiative work items + +
+ POST + /api/v2/workspaces/{slug}/initiatives/{pk}/work-items/ +
+ +
+
+ +Initiatives group projects and work items under one strategic objective. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The initiative work item id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The add. + + + + + +The remove. + + + +
+
+ +
+ +### Scopes + +`initiatives:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "added": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "removed": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/initiatives/overview.md b/docs/api-reference/v2/initiatives/overview.md new file mode 100644 index 00000000..b891970b --- /dev/null +++ b/docs/api-reference/v2/initiatives/overview.md @@ -0,0 +1,118 @@ +--- +title: Initiatives overview +description: The Plane API v2 initiative object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, initiatives, initiative object +--- + +# Initiatives overview + +Initiatives group projects and work items under one strategic objective. + +
+
+ +## The initiative object + +### Attributes + +- `archived_at` _string (date-time)_ + + When the record was archived, or `null` if it is active. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description` _string_ + + Free-form description. + +- `description_html` _string_ + + Rich-text body as HTML. This is the field the Plane editor round-trips. + +- `end_date` _string (date-time)_ + + End date, as `YYYY-MM-DD`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `label_ids` _array of string_ + + Ids of the associated labels. + +- `lead_id` _string (uuid)_ + + The related lead. + +- `logo_props` _string_ + + Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +- `name` _string_ + + Display name. + +- `project_ids` _array of string_ + + Ids of the associated projects. + +- `start_date` _string (date-time)_ + + Planned start date, as `YYYY-MM-DD`. + +- `state` _string_ + + The state. + +
+
+ + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "description_html": "

Details go here.

", + "end_date": "2026-01-31", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "start_date": "2026-01-12", + "state": "DRAFT" +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | -------------------------------------------------------- | ----------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/initiatives/` | List initiatives | +| `POST` | `/api/v2/workspaces/{slug}/initiatives/` | Create a initiative | +| `DELETE` | `/api/v2/workspaces/{slug}/initiatives/{pk}/` | Delete a initiative | +| `GET` | `/api/v2/workspaces/{slug}/initiatives/{pk}/` | Get a initiative | +| `PATCH` | `/api/v2/workspaces/{slug}/initiatives/{pk}/` | Update a initiative | +| `POST` | `/api/v2/workspaces/{slug}/initiatives/{pk}/labels/` | Add or remove initiative labels | +| `POST` | `/api/v2/workspaces/{slug}/initiatives/{pk}/projects/` | Add or remove initiative projects | +| `POST` | `/api/v2/workspaces/{slug}/initiatives/{pk}/work-items/` | Add or remove initiative work items | + +## Response shaping + +Every initiative read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/initiatives/update-initiative.md b/docs/api-reference/v2/initiatives/update-initiative.md new file mode 100644 index 00000000..0cfc967d --- /dev/null +++ b/docs/api-reference/v2/initiatives/update-initiative.md @@ -0,0 +1,271 @@ +--- +title: Update a initiative +description: Update a initiative with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a initiative, initiatives, initiatives partial update +--- + +# Update a initiative + +
+ PATCH + /api/v2/workspaces/{slug}/initiatives/{pk}/ +
+ +
+
+ +Initiatives group projects and work items under one strategic objective. Update a initiative. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The initiative id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Free-form description. + +Nullable. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + +Nullable. + + + + + +End date, as `YYYY-MM-DD`. + +Nullable. + + + + + +Id of the related lead. + +Nullable. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Display name. + +Maximum 255 characters. + + + + + +Ids of the projects to associate. Replaces the current set. + + + + + +Planned start date, as `YYYY-MM-DD`. + +Nullable. + + + + + +- `DRAFT` - Draft +- `PLANNED` - Planned +- `ACTIVE` - Active +- `COMPLETED` - Completed +- `CLOSED` - Closed + +One of `DRAFT`, `PLANNED`, `ACTIVE`, `COMPLETED`, `CLOSED`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `description`, `description_html`, `end_date`, `id`, `label_ids`, `lead_id`, `logo_props`, `name`, `project_ids`, `start_date`, `state`. + + + + + +Comma-separated relations to embed: `lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`initiatives:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "description_html": "

Details go here.

", + "end_date": "2026-01-31", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "start_date": "2026-01-12", + "state": "DRAFT" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No initiative matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/intake-work-items/create-intake-work-item.md b/docs/api-reference/v2/intake-work-items/create-intake-work-item.md new file mode 100644 index 00000000..e2846496 --- /dev/null +++ b/docs/api-reference/v2/intake-work-items/create-intake-work-item.md @@ -0,0 +1,257 @@ +--- +title: Create a intake work item +description: Create a intake work item with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a intake work item, intake work items, intakes create +--- + +# Create a intake work item + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/intake-issues/ +
+ +
+
+ +Intake work items are the triage queue for a project. Create a intake work item. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Id of the related duplicate to. + +Nullable. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Nullable. + + + + + +Display name. + + + + + +- `none` - none +- `low` - low +- `medium` - medium +- `high` - high +- `urgent` - urgent + +One of `none`, `low`, `medium`, `high`, `urgent`. + + + + + +The snoozed till. + +Nullable. + + + + + +The source. + + + + + +The source email. + +Nullable. + + + + + +The status. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `duplicate_to_id`, `external_id`, `external_source`, `id`, `intake_id`, `name`, `priority`, `snoozed_till`, `source`, `source_email`, `state_id`, `status`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.intakes:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "duplicate_to_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "intake_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "Example name", + "priority": "high", + "snoozed_till": "2026-01-14T09:22:41.478363Z", + "source": "example", + "source_email": "example", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "status": -2, + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/intake-work-items/delete-intake-work-item.md b/docs/api-reference/v2/intake-work-items/delete-intake-work-item.md new file mode 100644 index 00000000..2a9a491c --- /dev/null +++ b/docs/api-reference/v2/intake-work-items/delete-intake-work-item.md @@ -0,0 +1,154 @@ +--- +title: Delete a intake work item +description: Delete a intake work item with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a intake work item, intake work items, intakes destroy +--- + +# Delete a intake work item + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/intake-issues/{pk}/ +
+ +
+
+ +Intake work items are the triage queue for a project. Delete a intake work item. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The intake work item id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `duplicate_to_id`, `external_id`, `external_source`, `id`, `intake_id`, `name`, `priority`, `snoozed_till`, `source`, `source_email`, `state_id`, `status`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.intakes:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No intake work item matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/intake-work-items/get-intake-work-item.md b/docs/api-reference/v2/intake-work-items/get-intake-work-item.md new file mode 100644 index 00000000..b11c6888 --- /dev/null +++ b/docs/api-reference/v2/intake-work-items/get-intake-work-item.md @@ -0,0 +1,169 @@ +--- +title: Get a intake work item +description: Read a intake work item with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a intake work item, intake work items, intakes retrieve +--- + +# Get a intake work item + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/intake-issues/{pk}/ +
+ +
+
+ +Intake work items are the triage queue for a project. Read a single intake work item by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The intake work item id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `duplicate_to_id`, `external_id`, `external_source`, `id`, `intake_id`, `name`, `priority`, `snoozed_till`, `source`, `source_email`, `state_id`, `status`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.intakes:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "duplicate_to_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "intake_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "Example name", + "priority": "high", + "snoozed_till": "2026-01-14T09:22:41.478363Z", + "source": "example", + "source_email": "example", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "status": -2, + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No intake work item matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/intake-work-items/list-intake-work-items.md b/docs/api-reference/v2/intake-work-items/list-intake-work-items.md new file mode 100644 index 00000000..4c8b391f --- /dev/null +++ b/docs/api-reference/v2/intake-work-items/list-intake-work-items.md @@ -0,0 +1,258 @@ +--- +title: List intake work items +description: List intake work items with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list intake work items, intake work items, intakes list +--- + +# List intake work items + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/intake-issues/ +
+ +
+
+ +Intake work items are the triage queue for a project. List the intake work items you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `external_id`. + + + + + +Filter by `external_source`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +Filter by `source`. + + + + + +- `-2` - Pending +- `-1` - Rejected +- `0` - Snoozed +- `1` - Accepted +- `2` - Duplicate + +One of `-1`, `-2`, `0`, `1`, `2`. + + + + + +Multiple values may be separated by commas. + +- `-2` - Pending +- `-1` - Rejected +- `0` - Snoozed +- `1` - Accepted +- `2` - Duplicate + + + + + +Filter by `work_item_id`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `duplicate_to_id`, `external_id`, `external_source`, `id`, `intake_id`, `name`, `priority`, `snoozed_till`, `source`, `source_email`, `state_id`, `status`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.intakes:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "duplicate_to_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "intake_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "Example name", + "priority": "high", + "snoozed_till": "2026-01-14T09:22:41.478363Z", + "source": "example", + "source_email": "example", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "status": -2, + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/intake-work-items/overview.md b/docs/api-reference/v2/intake-work-items/overview.md new file mode 100644 index 00000000..624bc061 --- /dev/null +++ b/docs/api-reference/v2/intake-work-items/overview.md @@ -0,0 +1,125 @@ +--- +title: Intake work items overview +description: The Plane API v2 intake work item object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, intake work items, intake work item object +--- + +# Intake work items overview + +Intake work items are the triage queue for a project. + +
+
+ +## The intake work item object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description_html` _string_ + + Rich-text body as HTML. This is the field the Plane editor round-trips. + +- `duplicate_to_id` _string (uuid)_ + + The related duplicate to. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `intake_id` _string (uuid)_ + + The related intake. + +- `name` _string_ + + Display name. + +- `priority` _string_ + + Urgency of the work item. + +- `snoozed_till` _string (date-time)_ + + The snoozed till. + +- `source` _string_ + + The source. + +- `source_email` _string_ + + The source email. + +- `state_id` _string (uuid)_ + + The related state. + +- `status` _integer_ + + The status. + +- `work_item_id` _string (uuid)_ + + The related work item. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "duplicate_to_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "intake_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "Example name", + "priority": "high", + "snoozed_till": "2026-01-14T09:22:41.478363Z", + "source": "example", + "source_email": "example", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "status": -2, + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | --------------------------------------------------------------------- | ------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/intake-issues/` | List intake work items | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/intake-issues/` | Create a intake work item | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/intake-issues/{pk}/` | Delete a intake work item | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/intake-issues/{pk}/` | Get a intake work item | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/intake-issues/{pk}/` | Update a intake work item | + +## Response shaping + +Every intake work item read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/intake-work-items/update-intake-work-item.md b/docs/api-reference/v2/intake-work-items/update-intake-work-item.md new file mode 100644 index 00000000..a641d845 --- /dev/null +++ b/docs/api-reference/v2/intake-work-items/update-intake-work-item.md @@ -0,0 +1,275 @@ +--- +title: Update a intake work item +description: Update a intake work item with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a intake work item, intake work items, intakes partial update +--- + +# Update a intake work item + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/intake-issues/{pk}/ +
+ +
+
+ +Intake work items are the triage queue for a project. Update a intake work item. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The intake work item id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Id of the related duplicate to. + +Nullable. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Nullable. + + + + + +Display name. + + + + + +- `none` - none +- `low` - low +- `medium` - medium +- `high` - high +- `urgent` - urgent + +One of `none`, `low`, `medium`, `high`, `urgent`. + + + + + +The snoozed till. + +Nullable. + + + + + +The source. + + + + + +The source email. + +Nullable. + + + + + +The status. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `duplicate_to_id`, `external_id`, `external_source`, `id`, `intake_id`, `name`, `priority`, `snoozed_till`, `source`, `source_email`, `state_id`, `status`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.intakes:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "duplicate_to_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "intake_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "Example name", + "priority": "high", + "snoozed_till": "2026-01-14T09:22:41.478363Z", + "source": "example", + "source_email": "example", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "status": -2, + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No intake work item matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/introduction.md b/docs/api-reference/v2/introduction.md new file mode 100644 index 00000000..5d31e6b2 --- /dev/null +++ b/docs/api-reference/v2/introduction.md @@ -0,0 +1,229 @@ +--- +title: Plane API v2 +description: Introduction to the Plane REST API v2 — base URL, trailing slashes, PATCH-only writes, sparse reads, ?fields= responses, problem+json errors, and a quickstart request. +keywords: plane api v2, rest api, plane api introduction, api.plane.so, plane api quickstart, openapi schema, plane developer api +--- + +# Plane API v2 + +API v2 is the current, recommended REST interface to Plane. It is resource-oriented, JSON in and JSON out, and +described by a machine-readable OpenAPI document you can generate clients from. + +Everything here is stable and actively developed. [API v1](/api-reference/v1/introduction) remains available and +supported for existing integrations, but it is in maintenance — build new integrations on v2. + +::: tip Already on v1? +The two versions run side by side, so you can migrate one resource at a time. See +[Migrating from v1](/api-reference/v2/migrating-from-v1) for the field-by-field differences. +::: + +## Base URL + +| Deployment | Base URL | +| ------------ | ---------------------------------------------------------- | +| Plane Cloud | `https://api.plane.so` | +| Self-managed | Your instance URL, for example `https://plane.example.com` | + +Every v2 path lives under `/api/v2/`, and **every URL ends with a trailing slash**. Dropping it is the single most +common first-request mistake — `/api/v2/users/me` is not the same URL as `/api/v2/users/me/`. + +The OpenAPI document advertises one `servers` entry that matches your deployment, so a generated client points at the +right host without further configuration. + +## Quickstart + +`GET /api/v2/users/me/` needs no workspace or project id, which makes it the fastest way to confirm your credentials +work. + + + + + + + + + +```json +{ + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "email": "priya@my-team.io", + "display_name": "priya", + "principal_kind": "api_key", + "scopes": [] +} +``` + + + +If that returns `200`, your key is valid. If it returns `401`, the header name or the key itself is wrong — see +[Authentication](/api-reference/v2/authentication). + +## Requests + +- **JSON only.** Send `Content-Type: application/json` on requests with a body; responses are JSON. +- **`POST` creates, `PATCH` updates.** There is no `PUT` — sending one returns `405 method_not_allowed`. +- **`PATCH` is partial.** Only the fields you send are changed. Omitting a field leaves it untouched, which is not the + same as sending `null` to clear it. +- **`DELETE` returns `204`** with an empty body. Deletes are soft. +- **Writes take ids, not nested objects.** Set a relation with its `*_id` field (`state_id`, `parent_id`, `lead_id`) or + its `*_ids` array (`assignee_ids`, `label_ids`). +- **Audit fields are read-only.** `created_at`, `created_by_id`, and their siblings are set server-side; sending them + has no effect. + +```bash +# Create +curl -X POST \ + "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/states/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"name": "In Review", "color": "#3f76ff", "group": "started"}' + +# Partial update — color changes, nothing else does +curl -X PATCH \ + "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/states/f960d3c2-8524-4a41-b8eb-055ce4be2a7f/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"color": "#ffffff"}' +``` + +## Design principles + +These five rules hold across every v2 endpoint. Learning them once means you rarely have to re-read a reference page. + +### Reads are sparse + +A read returns foreign keys as `*_id` fields and many-to-many relations as `*_ids` arrays. No related objects are +embedded by default, so response size stays predictable no matter how many relations a resource has. + +```json +{ + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"] +} +``` + +### `?fields=` trims the response further + +Pass a comma-separated field list to get only the keys you need. Unrequested keys are **omitted**, not returned as +`null`, and `id` always comes back: + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/ENG/work-items/?fields=id,name,state_id" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +An unknown field name is a `400` that suggests the closest match rather than being silently ignored. See +[Sparse fields](/api-reference/v2/sparse-fields). + +### Expansion is separate-key + +`?expand=` **adds** an object beside the id — it never replaces it. `?expand=state` gives you `state_id` _and_ a +`state` object, so code that reads `state_id` keeps working whether or not the caller asked for the expansion. + +Each resource declares its own allowlist, and `?expand=` is a separate namespace from `?fields=` — the two compose +without interacting. See [Expanding relations](/api-reference/v2/expanding-relations) for the allowed values. + +### Errors are `problem+json` + +Every failure is `application/problem+json` carrying `type`, `code` and `detail`. Branch on the stable +machine-readable `code`, falling back to the closed `type` category for codes you do not recognize — never on the +human-readable `detail`. See [Errors](/api-reference/v2/errors). + +### Path segments take ids + +A path segment addresses a resource by id, with three human-readable exceptions: the workspace **slug**, a project's +**identifier** (`…/projects/ENG/`), and a work item's **`PROJ-123`**. To look something up by name, filter the list — +`?name=In%20Progress` — rather than putting the name in the path. See +[Filtering and ordering](/api-reference/v2/filtering-and-ordering). + +### Cross-tenant ids return 404 + +A workspace, project, or record id that belongs to someone else returns `404 not_found`, never `403`. The API +does not distinguish "does not exist" from "exists but is not yours", so it never leaks the existence of a resource you +cannot see. + +::: warning A `404` is not always a typo +If a `404` surprises you on an id you are confident is real, check which workspace or project the id belongs to before +checking your spelling. +::: + +### v2 evolves additively + +Inside `v2` the contract only grows: new optional fields, new enum values, new endpoints. Removals and renames are +reserved for a future major version. Write clients that ignore unknown response fields and tolerate unrecognized enum +values, and upgrades stay uneventful. + +## What's in v2 + +88 operations across these resources. + +| Resource | Base path | Reference | +| --------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------ | +| Work items | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/` | [Overview](/api-reference/v2/work-items/overview) | +| Work item comments | `…/work-items/{work_item_id}/comments/` | [Overview](/api-reference/v2/work-item-comments/overview) | +| States | `…/projects/{project_id}/states/` | [Overview](/api-reference/v2/states/overview) | +| Labels | `…/projects/{project_id}/labels/` | [Overview](/api-reference/v2/labels/overview) | +| Cycles | `…/projects/{project_id}/cycles/` | [Overview](/api-reference/v2/cycles/overview) | +| Modules | `…/projects/{project_id}/modules/` | [Overview](/api-reference/v2/modules/overview) | +| Work item types & properties, project | `…/projects/{project_id}/work-item-types/`, `…/work-item-properties/` | [Overview](/api-reference/v2/work-item-types/overview) | +| Work item types & properties, workspace | `/api/v2/workspaces/{slug}/work-item-types/`, `…/work-item-properties/` | [Overview](/api-reference/v2/workspace-work-item-types/overview) | +| Work item property contexts | `/api/v2/workspaces/{slug}/work-item-properties/{property_id}/contexts/` | [Overview](/api-reference/v2/work-item-property-contexts/overview) | +| Members | `/api/v2/workspaces/{slug}/members/`, `…/projects/{project_id}/members/` | [Overview](/api-reference/v2/members/overview) | +| Workspace features | `/api/v2/workspaces/{slug}/features/` | [Overview](/api-reference/v2/workspace-features/overview) | +| Audit logs | `/api/v2/workspaces/{slug}/audit-logs/` | [Overview](/api-reference/v2/audit-logs/overview) | +| Users | `/api/v2/users/me/` | [Overview](/api-reference/v2/users/overview) | + +::: info Two surfaces for work item types +A workspace manages work item types either at the project level or at the workspace level — never both at once. Writing +to the wrong surface returns a `409`. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +## Machine-readable schema + +The full OpenAPI document is served live at: + +``` +GET /api/v2/schema/ +``` + +Point a generator at it to produce a typed client, or diff it between releases to see exactly what changed. It is the +authority for every field, enum, and scope in this reference. + +## Where to go next + +- [Authentication](/api-reference/v2/authentication) — API keys, OAuth 2.0, and the scope model. +- [Pagination](/api-reference/v2/pagination) — offset by default, cursor for deep traversal. +- [Filtering and ordering](/api-reference/v2/filtering-and-ordering) — query parameters on list endpoints. +- [Expanding relations](/api-reference/v2/expanding-relations) — where `?expand=` works and what it accepts. +- [Errors](/api-reference/v2/errors) — the RFC 9457 problem shape and the full code table. +- [Migrating from v1](/api-reference/v2/migrating-from-v1) — what changed and how to move. diff --git a/docs/api-reference/v2/invitations/bulk-invitations.md b/docs/api-reference/v2/invitations/bulk-invitations.md new file mode 100644 index 00000000..1be48581 --- /dev/null +++ b/docs/api-reference/v2/invitations/bulk-invitations.md @@ -0,0 +1,193 @@ +--- +title: Bulk write invitations +description: Bulk write invitations with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, bulk write invitations, invitations, members bulk +--- + +# Bulk write invitations + +
+ POST + /api/v2/workspaces/{slug}/invitations/bulk/ +
+ +
+
+ +Invitations are pending workspace membership offers. Write up to 100 invitations in one request, with a per-row result for each. + +- Send `{"items": [...]}`, at most 100 per request. Add `"upsert": true` to match existing rows on `(external_source, external_id)` instead of always creating. +- The response is always `200`. Each row reports its own outcome, so a partial failure is reported rather than silently dropped — read `succeeded`/`failed` and the per-row `status`. +- Rows are written on independent savepoints: a failing row rolls back only itself. +- `?fields=` does not apply here — the bulk response is a per-row result envelope, not a resource body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The emails. + + + + + +The message. + +Nullable. + + + + + +Role to grant. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `accepted`, `created_at`, `created_by_id`, `email`, `id`, `message`, `responded_at`, `role`. + + + +
+
+ +
+ +### Scopes + +`workspaces.members:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "accepted": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "email": "ana@example.com", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "message": "example", + "responded_at": "2026-01-14T09:22:41.478363Z", + "role": "example" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/invitations/create-invitation.md b/docs/api-reference/v2/invitations/create-invitation.md new file mode 100644 index 00000000..2c61f254 --- /dev/null +++ b/docs/api-reference/v2/invitations/create-invitation.md @@ -0,0 +1,184 @@ +--- +title: Create a invitation +description: Create a invitation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a invitation, invitations, members create +--- + +# Create a invitation + +
+ POST + /api/v2/workspaces/{slug}/invitations/ +
+ +
+
+ +Invitations are pending workspace membership offers. Create a invitation. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Email address. + + + + + +The message. + +Nullable. + + + + + +Role to grant. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `accepted`, `created_at`, `created_by_id`, `email`, `id`, `message`, `responded_at`, `role`. + + + +
+
+ +
+ +### Scopes + +`workspaces.members:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "accepted": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "email": "ana@example.com", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "message": "example", + "responded_at": "2026-01-14T09:22:41.478363Z", + "role": "example" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/invitations/delete-invitation.md b/docs/api-reference/v2/invitations/delete-invitation.md new file mode 100644 index 00000000..330eb9c7 --- /dev/null +++ b/docs/api-reference/v2/invitations/delete-invitation.md @@ -0,0 +1,148 @@ +--- +title: Delete a invitation +description: Delete a invitation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a invitation, invitations, members destroy +--- + +# Delete a invitation + +
+ DELETE + /api/v2/workspaces/{slug}/invitations/{pk}/ +
+ +
+
+ +Invitations are pending workspace membership offers. Delete a invitation. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The invitation id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `accepted`, `created_at`, `created_by_id`, `email`, `id`, `message`, `responded_at`, `role`. + + + +
+
+ +
+ +### Scopes + +`workspaces.members:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No invitation matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/invitations/get-invitation.md b/docs/api-reference/v2/invitations/get-invitation.md new file mode 100644 index 00000000..7efbe09f --- /dev/null +++ b/docs/api-reference/v2/invitations/get-invitation.md @@ -0,0 +1,155 @@ +--- +title: Get a invitation +description: Read a invitation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a invitation, invitations, members retrieve +--- + +# Get a invitation + +
+ GET + /api/v2/workspaces/{slug}/invitations/{pk}/ +
+ +
+
+ +Invitations are pending workspace membership offers. Read a single invitation by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The invitation id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `accepted`, `created_at`, `created_by_id`, `email`, `id`, `message`, `responded_at`, `role`. + + + +
+
+ +
+ +### Scopes + +`workspaces.members:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "accepted": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "email": "ana@example.com", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "message": "example", + "responded_at": "2026-01-14T09:22:41.478363Z", + "role": "example" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No invitation matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/invitations/list-invitations.md b/docs/api-reference/v2/invitations/list-invitations.md new file mode 100644 index 00000000..5b8c65d2 --- /dev/null +++ b/docs/api-reference/v2/invitations/list-invitations.md @@ -0,0 +1,215 @@ +--- +title: List invitations +description: List invitations with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list invitations, invitations, members list +--- + +# List invitations + +
+ GET + /api/v2/workspaces/{slug}/invitations/ +
+ +
+
+ +Invitations are pending workspace membership offers. List the invitations you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Filter by `accepted`. + + + + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `email`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `accepted`, `created_at`, `created_by_id`, `email`, `id`, `message`, `responded_at`, `role`. + + + +
+
+ +
+ +### Scopes + +`workspaces.members:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "accepted": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "email": "ana@example.com", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "message": "example", + "responded_at": "2026-01-14T09:22:41.478363Z", + "role": "example" + }, + { + "accepted": false, + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "email": "ana@example.com", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "message": "example", + "responded_at": "2026-01-14T09:22:41.478363Z", + "role": "example" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/invitations/overview.md b/docs/api-reference/v2/invitations/overview.md new file mode 100644 index 00000000..a61c90df --- /dev/null +++ b/docs/api-reference/v2/invitations/overview.md @@ -0,0 +1,85 @@ +--- +title: Invitations overview +description: The Plane API v2 invitation object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, invitations, invitation object +--- + +# Invitations overview + +Invitations are pending workspace membership offers. + +
+
+ +## The invitation object + +### Attributes + +- `accepted` _boolean_ + + Whether accepted. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `email` _string_ + + Email address. + +- `id` _string (uuid)_ + + Unique identifier. + +- `message` _string_ + + The message. + +- `responded_at` _string (date-time)_ + + The responded at. + +- `role` _string_ + + Role to grant. + +
+
+ + + +```json +{ + "accepted": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "email": "ana@example.com", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "message": "example", + "responded_at": "2026-01-14T09:22:41.478363Z", + "role": "example" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | --------------------------------------------- | ---------------------- | +| `GET` | `/api/v2/workspaces/{slug}/invitations/` | List invitations | +| `POST` | `/api/v2/workspaces/{slug}/invitations/` | Create a invitation | +| `POST` | `/api/v2/workspaces/{slug}/invitations/bulk/` | Bulk write invitations | +| `DELETE` | `/api/v2/workspaces/{slug}/invitations/{pk}/` | Delete a invitation | +| `GET` | `/api/v2/workspaces/{slug}/invitations/{pk}/` | Get a invitation | + +## Response shaping + +Every invitation read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/labels/bulk-labels.md b/docs/api-reference/v2/labels/bulk-labels.md new file mode 100644 index 00000000..250d9af6 --- /dev/null +++ b/docs/api-reference/v2/labels/bulk-labels.md @@ -0,0 +1,195 @@ +--- +title: Bulk write labels +description: Bulk write labels with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, bulk write labels, labels, labels bulk +--- + +# Bulk write labels + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/labels/bulk/ +
+ +
+
+ +Labels are the project's tag catalog. Write up to 100 labels in one request, with a per-row result for each. + +- Send `{"items": [...]}`, at most 100 per request. Add `"upsert": true` to match existing rows on `(external_source, external_id)` instead of always creating. +- The response is always `200`. Each row reports its own outcome, so a partial failure is reported rather than silently dropped — read `succeeded`/`failed` and the per-row `status`. +- Rows are written on independent savepoints: a failing row rolls back only itself. +- `?fields=` does not apply here — the bulk response is a per-row result envelope, not a resource body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The items. + + + + + +Reconcile each row on (external_source, external_id) instead of creating it. + + + +
+
+ +
+ +### Scopes + +`projects.labels:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "failed": 1, + "results": [null], + "succeeded": 1 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/labels/create-label.md b/docs/api-reference/v2/labels/create-label.md new file mode 100644 index 00000000..0de244cb --- /dev/null +++ b/docs/api-reference/v2/labels/create-label.md @@ -0,0 +1,240 @@ +--- +title: Create a label +description: Create a label in a Plane project with the v2 REST API. Body parameters, nesting with parent_id, name uniqueness, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create label, POST labels, parent_id, label color, external_id +--- + +# Create a label + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/labels/ +
+ +
+
+ +Add a label to a project. The new label is immediately available to every work item in that project, and to no other project. + +Label names must be unique within a project — reusing one returns `409 conflict`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project to add the label to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name for the label, unique within the project. Maximum 255 characters. + + + + + +Hex color used wherever the label is rendered, for example `#e5484d`. Maximum 255 characters. Send one if the label needs to be recognizable at a glance on a board. + + + + + +Free-form note about what the label is for and when to apply it. + + + + + +The label this one nests under, for grouping related tags such as `Billing` and `Search` beneath an `Area` label. The parent must be a label in the same project — an id from another project is a `400`, not a silent link. + +Omit it, or send `null`, for a top-level label. + + + + + +Ordering weight within the project. Lower values sort first. Assigned automatically when omitted, so send it only when you are recreating a specific order. + + + + + +Your system's identifier for this label, for sync and import correlation. Maximum 255 characters. Store it here and you can find the label later with `?external_id=`, without keeping a map of Plane ids. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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`, `id`, `name`, `parent_id`, `sort_order`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.labels:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | `name` missing or over 255 characters, or a `parent_id` that isn't a label in this project. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't create labels. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A label with this name already exists in the project. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88", + "name": "Regression", + "description": "Worked before the last release", + "color": "#e5484d", + "sort_order": 65535, + "parent_id": "2b7d5e94-3c1a-4f60-9a8d-7e1c4b0f2d35", + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "conflict", + "detail": "A label with this name already exists in the project." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/labels/delete-label.md b/docs/api-reference/v2/labels/delete-label.md new file mode 100644 index 00000000..b1668719 --- /dev/null +++ b/docs/api-reference/v2/labels/delete-label.md @@ -0,0 +1,170 @@ +--- +title: Delete a label +description: Delete a label from a Plane project with the v2 REST API. Path parameters, what happens to tagged work items, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete label, DELETE label, remove label, 204 no content +--- + +# Delete a label + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/labels/{pk}/ +
+ +
+
+ +Remove a label from a project. The label disappears from every work item that carried it — the work items themselves are not touched, and nothing else about them changes. + +A successful delete returns `204` with an empty body. Don't parse the response. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the label belongs to. + + + + + +The label id to delete. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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`, `id`, `name`, `parent_id`, `sort_order`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.labels:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | A path parameter is malformed, for example a `pk` that isn't a UUID. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't delete labels. | +| `404` | `not_found` | No such label in this project, or the workspace or project is outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The write collides with an existing record or a business rule. | +| `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. | + +Labels have no delete protection. Unlike a project's default state, no label is pinned, and a label still applied to work items deletes normally. + +
+ +::: warning Deleting is not the same as unassigning +If you only want the tag off a few work items, update those work items' `label_ids` instead. Deleting the label strips it from every work item in the project at once, and there is no undelete endpoint — you would have to recreate the label and reapply it everywhere. +::: + +::: tip Deleting twice +A repeated delete returns `404`, not `204`. Treat both as "the label is gone" when your job is idempotent. +::: + +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No label matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/labels/get-label.md b/docs/api-reference/v2/labels/get-label.md new file mode 100644 index 00000000..6d25e2dc --- /dev/null +++ b/docs/api-reference/v2/labels/get-label.md @@ -0,0 +1,173 @@ +--- +title: Get a label +description: Retrieve a single label from a Plane project with the v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get label, retrieve label, GET label by id, label object +--- + +# Get a label + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/labels/{pk}/ +
+ +
+
+ +Retrieve one label by id. Reach for this when you already hold a label id — from a work item's `label_ids`, or from a webhook payload — and need its current name, color, or parent. + +If you only know the label by name or by your own identifier, use [List labels](/api-reference/v2/labels/list-labels) with `search` or `external_id` instead. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the label belongs to. + + + + + +The label id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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`, `id`, `name`, `parent_id`, `sort_order`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.labels:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read labels. | +| `404` | `not_found` | No such label in this project, or the workspace or project is outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info A real label under the wrong project is a 404 +Labels are project-scoped. Pairing a valid label id with a different project's `project_id` returns `404`, the same response you get for an id that doesn't exist anywhere — existence outside your reach is never leaked. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88", + "name": "Regression", + "description": "Worked before the last release", + "color": "#e5484d", + "sort_order": 65535, + "parent_id": "2b7d5e94-3c1a-4f60-9a8d-7e1c4b0f2d35", + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No label matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/labels/list-labels.md b/docs/api-reference/v2/labels/list-labels.md new file mode 100644 index 00000000..67bb18ec --- /dev/null +++ b/docs/api-reference/v2/labels/list-labels.md @@ -0,0 +1,281 @@ +--- +title: List labels +description: List the labels in a Plane project with the v2 REST API. Filters, search, ordering, pagination, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list labels, label filters, parent_id, external_id, pagination, GET labels +--- + +# List labels + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/labels/ +
+ +
+
+ +Return the labels defined in a project. Use it to build a label picker, to resolve label names to ids before writing `label_ids` on a work item, or to look up a label you imported by its `external_id`. + +Results are paginated. Labels from other projects are never included — a project's labels are its own. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project whose labels you want. + + + +
+
+ +
+ +### Query Parameters + +**Filters** + +
+ + + +Return only the labels nested under this label. + +Pair it with `parent_id__isnull=true` to get the opposite view — every top-level label in the project. Use `parent_id__isnull=false` for every label that has a parent. + + + + + +Return labels whose `external_id` matches. Combine with `external_source` to resolve a record from another system to its Plane label without keeping a local id map. + + + + + +Return labels that came from this system, for example `github` or `jira`. + + + +
+ +**Search** + +
+ + + +A search term matched against the label `name`. Use it to power type-ahead in a picker rather than downloading every page and filtering client-side. + + + +
+ +**Ordering** + +
+ + + +Field to sort by. Prefix with `-` for descending. Defaults to `sort_order`. + +- `sort_order` , `-sort_order` — the project's own label ordering +- `created_at` , `-created_at` — newest or oldest first +- `id` , `-id` + +A value outside this list is not rejected — it silently falls back to the default `sort_order`. Check your spelling: a typo in `order_by` fails silently and shows up as a differently sorted page, not as an error. + + + +
+ +**Pagination** + +
+ + + +Page size. Defaults to 50, maximum 200. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read the `next` value from the response and send it back as `offset` to walk forward. + + + + + +Set to `cursor` to switch from the default offset envelope to the keyset cursor envelope, which skips the `COUNT(*)` and returns `next_cursor` and `has_more` instead of `next` and `total_count`. Send the returned `next_cursor` back as `cursor` to fetch the following page. + +Only `created_at` and `id` are cursor-eligible, because a keyset needs a strictly ordered column and `sort_order` is neither unique nor monotonic. Pair `paginate=cursor` with `order_by=created_at` or `order_by=id` — the default `sort_order` ordering is rejected with `400 ordering_not_cursor_eligible`. + +Most projects have few enough labels that the default offset envelope is all you need. + + + + + +Defaults to `true`. Set to `false` to omit `total_count` and skip the count query. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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`, `id`, `name`, `parent_id`, `sort_order`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.labels:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read labels. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "2b7d5e94-3c1a-4f60-9a8d-7e1c4b0f2d35", + "name": "Area", + "description": "Which part of the product", + "color": "#6b7280", + "sort_order": 32767, + "parent_id": null, + "external_id": null, + "external_source": null, + "created_at": "2026-01-09T11:04:18.220914Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + }, + { + "id": "9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88", + "name": "Regression", + "description": "Worked before the last release", + "color": "#e5484d", + "sort_order": 65535, + "parent_id": "2b7d5e94-3c1a-4f60-9a8d-7e1c4b0f2d35", + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next": null, + "previous": null, + "total_count": 2, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/labels/overview.md b/docs/api-reference/v2/labels/overview.md new file mode 100644 index 00000000..f05c8a34 --- /dev/null +++ b/docs/api-reference/v2/labels/overview.md @@ -0,0 +1,122 @@ +--- +title: Labels overview +description: The Plane API v2 label object. Attributes, nested labels via parent_id, name uniqueness within a project, and the label endpoints. +keywords: plane api v2, labels, label object, parent_id, nested labels, tags, work item labels +--- + +# Labels overview + +A label is a tag you attach to work items to slice a project by something the workflow doesn't already model — `bug`, `needs-design`, `customer-escalation`. A label belongs to exactly one project, so two projects can both have a `bug` label and they are separate records with separate ids. + +Work items reference labels by id through `label_ids`, and you filter work items with `label_id`. Nothing on the label object tells you which work items carry it — that relationship is read from the work item side. + +[Learn more about labels](https://docs.plane.so/core-concepts/work-items/labels) + +
+
+ +## The label object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the label. This is the value you send in a work item's `label_ids`. + +- `name` _string_ + + Display name, unique within the project. Maximum 255 characters. + +- `description` _string_ + + Free-form note about what the label is for. Useful when a team needs a convention written down next to the tag. + +- `color` _string_ + + Hex color used wherever the label is rendered, for example `#e5484d`. + +- `sort_order` _number_ + + Ordering weight within the project. Lower values sort first. Assigned automatically when you don't send one. + +- `parent_id` _string (uuid) or null_ + + The label this one nests under, letting you build groups such as an `Area` label with `Billing` and `Search` beneath it. `null` for a top-level label. + +- `external_id` , `external_source` _string or null_ + + Correlation fields for sync and import. Together they let you map a label to a record in another system and find it again later — list labels filtered by both to look one up without storing Plane ids yourself. + +- `created_at` _string (date-time)_ + + When the label was created. + +- `created_by_id` _string (uuid) or null_ + + The user who created the label. `null` for labels created by an integration or by the system. + +::: info Nesting is metadata, not inheritance +`parent_id` groups labels for display and filtering. Applying a child label to a work item does not also apply its parent. +::: + +
+
+ + + +```json +{ + "id": "9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88", + "name": "Regression", + "description": "Worked before the last release", + "color": "#e5484d", + "sort_order": 65535, + "parent_id": "2b7d5e94-3c1a-4f60-9a8d-7e1c4b0f2d35", + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------------------- | ----------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/labels/` | List labels | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/labels/` | Create a label | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/labels/bulk/` | Bulk write labels | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/labels/upsert/` | Upsert a label | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/labels/{pk}/` | Delete a label | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/labels/{pk}/` | Get a label | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/labels/{pk}/` | Update a label | + +## Names are unique per project + +A project cannot hold two labels with the same `name`. Creating a duplicate, or renaming a label onto a name already in use, returns `409 conflict`. + +If you are importing labels from another system, treat the `409` as "this already exists" rather than an error: list the project's labels filtered by `external_id` and `external_source` first, and update the match instead of creating it again. + +## Deleting labels + +`DELETE` returns `204` with an empty body. The label is removed from every work item that carried it — the work items themselves are untouched. + +::: warning Deletes are not reversible through the API +There is no undelete endpoint. If you need the association back, recreate the label and reapply it with `label_ids` on each work item. +::: + +## Changed from v1 + +- `parent` is now **`parent_id`**, on both reads and writes. +- `created_by` is now **`created_by_id`**. +- Reads no longer return `updated_at`, `updated_by`, `project`, or `workspace`. The project is already in the request path. +- Reads now return `external_id` and `external_source`, which v1 accepted on writes but did not surface on the label object. +- Lists return the offset envelope — `data`, `next`, `previous`, `total_count` — instead of v1's `results` with cursor keys. See [List labels](/api-reference/v2/labels/list-labels). +- The `expand` and `fields` query parameters are not available on labels. + +See [Migrating from v1](/api-reference/v2/migrating-from-v1) for the full list. diff --git a/docs/api-reference/v2/labels/update-label.md b/docs/api-reference/v2/labels/update-label.md new file mode 100644 index 00000000..99c7ffcf --- /dev/null +++ b/docs/api-reference/v2/labels/update-label.md @@ -0,0 +1,242 @@ +--- +title: Update a label +description: Update a label in a Plane project with the v2 REST API. Partial PATCH semantics, body parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update label, PATCH label, rename label, parent_id, label color +--- + +# Update a label + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/labels/{pk}/ +
+ +
+
+ +Change a label's name, color, description, nesting, or ordering. Every work item already carrying the label picks up the change — the label id does not move, so no work item loses its tag when you rename it. + +The update is partial. Fields you omit are left untouched, and omitting a field is not the same as sending `null`: send `"parent_id": null` to lift a nested label to the top level. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the label belongs to. + + + + + +The label id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +New display name, unique within the project. Maximum 255 characters. Renaming onto a name another label already holds returns `409 conflict`. + + + + + +Hex color used wherever the label is rendered, for example `#e5484d`. Maximum 255 characters. + + + + + +Free-form note about what the label is for. + + + + + +Move the label under a different parent. The new parent must be a label in the same project. Send `null` to make the label top-level again. + + + + + +Ordering weight within the project. Lower values sort first. + + + + + +Your system's identifier for this label, for sync and import correlation. Maximum 255 characters. Send `null` to clear it. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. Send `null` to clear it. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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`, `id`, `name`, `parent_id`, `sort_order`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.labels:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | A field over its length limit, or a `parent_id` that isn't a label in this project. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't update labels. | +| `404` | `not_found` | No such label in this project, or the workspace or project is outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | Another label in the project already uses the name you sent. | +| `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. | + +
+ +::: warning There is no PUT +`PUT` on this path returns `405 method_not_allowed`. Send only the fields you want to change with `PATCH` — a full-object write is never required, and rebuilding the object client-side risks clobbering a field someone else just changed. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88", + "name": "Regression - blocking", + "description": "Worked before the last release", + "color": "#c62828", + "sort_order": 65535, + "parent_id": "2b7d5e94-3c1a-4f60-9a8d-7e1c4b0f2d35", + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "conflict", + "detail": "A label with this name already exists in the project." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/labels/upsert-label.md b/docs/api-reference/v2/labels/upsert-label.md new file mode 100644 index 00000000..65ecc016 --- /dev/null +++ b/docs/api-reference/v2/labels/upsert-label.md @@ -0,0 +1,234 @@ +--- +title: Upsert a label +description: Upsert a label with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, upsert a label, labels, labels upsert +--- + +# Upsert a label + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/labels/upsert/ +
+ +
+
+ +Labels are the project's tag catalog. Create a label, or update the existing one that carries the same `(external_source, external_id)` pair. + +- Both `external_source` and `external_id` are required — without them there is nothing to match on and the request is a `400`. +- A created record answers `201` with `X-Plane-Upsert: created`; an updated one answers `200` with `X-Plane-Upsert: updated`. Branch on the header rather than guessing from the status. +- Upsert is safe for **sequential** importers. Two simultaneous upserts of the same key can each miss and each create, so serialize your writes per key. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. + + + + + +Free-form description. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Id of the related parent. + +Nullable. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `name`, `parent_id`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`projects.labels:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Example name", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/members/create-project-member.md b/docs/api-reference/v2/members/create-project-member.md new file mode 100644 index 00000000..7a233f89 --- /dev/null +++ b/docs/api-reference/v2/members/create-project-member.md @@ -0,0 +1,185 @@ +--- +title: Create a project member +description: Create a project member with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a project member, project members, project members create +--- + +# Create a project member + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/members/ +
+ +
+
+ +Members are the people on a workspace or project, and the role each one holds. Create a project member. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Id of the related member. + + + + + +Role to grant. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `id`, `member_id`, `role`. + + + + + +Comma-separated relations to embed: `member`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.members:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "member_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "role": "example" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/members/delete-project-member.md b/docs/api-reference/v2/members/delete-project-member.md new file mode 100644 index 00000000..0dde8d99 --- /dev/null +++ b/docs/api-reference/v2/members/delete-project-member.md @@ -0,0 +1,162 @@ +--- +title: Delete a project member +description: Delete a project member with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a project member, project members, project members destroy +--- + +# Delete a project member + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/members/{pk}/ +
+ +
+
+ +Members are the people on a workspace or project, and the role each one holds. Delete a project member. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project member id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `id`, `member_id`, `role`. + + + + + +Comma-separated relations to embed: `member`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.members:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project member matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/members/get-project-member.md b/docs/api-reference/v2/members/get-project-member.md new file mode 100644 index 00000000..b2a01510 --- /dev/null +++ b/docs/api-reference/v2/members/get-project-member.md @@ -0,0 +1,164 @@ +--- +title: Get a project member +description: Read a project member with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a project member, project members, project members retrieve +--- + +# Get a project member + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/members/{pk}/ +
+ +
+
+ +Members are the people on a workspace or project, and the role each one holds. Read a single project member by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project member id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `id`, `member_id`, `role`. + + + + + +Comma-separated relations to embed: `member`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.members:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "member_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "role": "example" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project member matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/members/get-project-role-distribution.md b/docs/api-reference/v2/members/get-project-role-distribution.md new file mode 100644 index 00000000..e8fa04b9 --- /dev/null +++ b/docs/api-reference/v2/members/get-project-role-distribution.md @@ -0,0 +1,122 @@ +--- +title: Get project role distribution +description: Get project role distribution with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get project role distribution, project role distribution, project role distribution +--- + +# Get project role distribution + +
+ GET + /api/v2/workspaces/{slug}/project-role-distribution/ +
+ +
+
+ +Members are the people on a workspace or project, and the role each one holds. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Scopes + +`workspaces.members:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "roles": [ + { + "distinct_member_count": 3, + "is_system": false, + "level": 1, + "membership_count": 3, + "name": "Example name", + "role_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team" + } + ], + "total_distinct_members": 3, + "total_memberships": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/members/list-project-members.md b/docs/api-reference/v2/members/list-project-members.md new file mode 100644 index 00000000..fb7f98dd --- /dev/null +++ b/docs/api-reference/v2/members/list-project-members.md @@ -0,0 +1,299 @@ +--- +title: List project members +description: List the members of a Plane project with the v2 REST API. Role and member filters, search, ordering, pagination, ?expand=member, OAuth scopes, and code examples. +keywords: plane api v2, list project members, project roster, member_id filter, role filter, expand member, GET project members +--- + +# List project members + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/members/ +
+ +
+
+ +Return one project's active member roster as a paginated list. Reach for this when you need the people who can actually be assigned work in a project — the workspace roster is wider, and includes members with no access to this project. + +Roles here are project roles, so the same person can come back as `member` on the workspace roster and as `contributor` on this one. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project whose roster you want. A project id from another workspace returns `404`. + + + +
+
+ +
+ +### Query Parameters + +Filters combine with `AND`. Check your spelling on `order_by` and `paginate` — neither is validated. An unrecognized `order_by` value silently falls back to the default ordering, and anything other than `paginate=cursor` silently uses offset pagination. A typo shows up as an unexpected sort order or envelope, not as an error. + +
+ + + +Return the membership for one user. Use `member_id__in` to check several at once, comma-separated. + +Pairing `?member_id=` with `?per_page=1` is the cheapest membership check there is: an empty `data` array means that user is not on this project. + + + + + +Return only members holding this role slug, for example `?role=contributor`. Use `role__in` for several roles at once, comma-separated. + +The value is a plain string, not a fixed enum — a custom role is matched by its own slug. + + + + + +A search term matched against the member's user record, so you can find someone by name or email without expanding first. + + + +
+
+ +
+ +### Expansion + +
+ + + +Comma-separated relations to embed alongside the ids: `member` (the member's user object). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `created_at` , `-created_at` — when the person was added to the project +- `id` , `-id` + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. Most project rosters fit in a single page. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read `next` from the response rather than computing offsets yourself. + + + + + +Set to `cursor` for the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead of `next` and `total_count`. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted from the response. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `id`, `member_id`, `role`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.members:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this project's roster. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info Validate assignees against this list, not the workspace one +A `member_id` that is on the workspace roster but not on this project is not a valid assignee for the project's work items. Check membership here before writing `assignee_ids`. +::: + +::: info A `409` is not a permission signal +`403` means the caller's role or scope is too narrow. `409 work_item_types_managed_at_workspace` or `work_item_types_managed_at_project` means the write belongs on the other surface, whatever the caller's role is. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "9d2f5c81-4b73-4e60-8a1f-2c9b6d3e7f04", + "member_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "role": "contributor", + "member": { + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "display_name": "Priya Raghavan", + "avatar_url": "https://assets.plane.so/avatars/16c61a3a.png", + "email": "priya@example.com" + } + }, + { + "id": "b5c07e29-1d84-4f36-9a72-6e13c8d5a0f7", + "member_id": "7f2b9e04-6c1d-4a58-9e3b-0d4c8a2f6b71", + "role": "release-manager", + "member": { + "id": "7f2b9e04-6c1d-4a58-9e3b-0d4c8a2f6b71", + "display_name": "Devansh Kapoor", + "avatar_url": null, + "email": "devansh@example.com" + } + } + ], + "next": null, + "previous": null, + "total_count": 2, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No Project matches the given query." +} +``` + + + +
+
+ +::: tip Custom roles appear as their own slug +`release-manager` above is a custom role defined by the workspace. `role` has no enum in the schema, so treat any slug you do not recognize as a role you have no rules for rather than as bad data. +::: diff --git a/docs/api-reference/v2/members/list-workspace-members.md b/docs/api-reference/v2/members/list-workspace-members.md new file mode 100644 index 00000000..8c090307 --- /dev/null +++ b/docs/api-reference/v2/members/list-workspace-members.md @@ -0,0 +1,307 @@ +--- +title: List workspace members +description: List the members of a Plane workspace with the v2 REST API. Role and member filters, search, ordering, pagination, ?expand=member, OAuth scopes, and code examples. +keywords: plane api v2, list workspace members, workspace roster, member_id filter, role filter, expand member, GET members +--- + +# List workspace members + +
+ GET + /api/v2/workspaces/{slug}/members/ +
+ +
+
+ +Return the workspace's active member roster as a paginated list. This is the endpoint that resolves a person to the `member_id` you assign work to, and the one you poll to keep a user directory in sync with Plane. + +The workspace roster is the superset of every project roster — anyone on a project is on this list too. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +Filters combine with `AND`. Check your spelling on `order_by` and `paginate` — neither is validated. An unrecognized `order_by` value silently falls back to the default ordering, and anything other than `paginate=cursor` silently uses offset pagination. A typo shows up as an unexpected sort order or envelope, not as an error. + +
+ + + +Return the membership for one user. Use the `member_id__in` variant to look several users up at once, comma-separated — `?member_id__in=16c61a3a-512a-48ac-b0be-b6b46fe6f430,7f2b9e04-6c1d-4a58-9e3b-0d4c8a2f6b71`. + +This is the cheap way to answer "is this person still in the workspace, and what is their role now?" without paging the whole roster. + + + + + +Return only members holding this role slug, for example `?role=owner`. Use `role__in` for several roles at once, comma-separated. + +The value is a plain string, not a fixed enum — custom roles are matched by their own slug. + + + + + +A search term matched against the member's user record, so you can find someone by name or email without expanding first. + + + +
+
+ +
+ +### Expansion + +
+ + + +Comma-separated relations to embed alongside the ids: `member` (the member's user object). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `created_at` , `-created_at` — when the person joined the workspace +- `id` , `-id` + +There is no ordering by name or role; sort the page client-side, or expand and sort on `display_name`. + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read `next` from the response rather than computing offsets yourself. + + + + + +Set to `cursor` for the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead of `next` and `total_count`. Worth it when you are walking a large workspace end to end. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted from the response. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `id`, `member_id`, `role`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.members:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this workspace roster. | +| `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. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info Membership id versus user id +`id` identifies the membership row; `member_id` identifies the person. Key caches and joins on `member_id` — the same user has a different `id` on every roster they appear in. See [Members overview](/api-reference/v2/members/overview). +::: + +::: info Roles gate writes, not this read +A narrow role shows up as `403` on the write you attempted. A `409 work_item_types_managed_at_workspace` or `work_item_types_managed_at_project` is not a role problem at all — see [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "c1b7e4a9-2f66-4a1d-9c3b-7d5e2f8a1b40", + "member_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "role": "owner", + "member": { + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "display_name": "Priya Raghavan", + "avatar_url": "https://assets.plane.so/avatars/16c61a3a.png", + "email": "priya@example.com" + } + }, + { + "id": "3e8a5d17-9c40-4b2f-81d6-4a7f2b9e0c53", + "member_id": "7f2b9e04-6c1d-4a58-9e3b-0d4c8a2f6b71", + "role": "member", + "member": { + "id": "7f2b9e04-6c1d-4a58-9e3b-0d4c8a2f6b71", + "display_name": "Devansh Kapoor", + "avatar_url": null, + "email": "devansh@example.com" + } + } + ], + "next": null, + "previous": null, + "total_count": 2, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "c1b7e4a9-2f66-4a1d-9c3b-7d5e2f8a1b40", + "member_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "role": "owner" + }, + { + "id": "3e8a5d17-9c40-4b2f-81d6-4a7f2b9e0c53", + "member_id": "7f2b9e04-6c1d-4a58-9e3b-0d4c8a2f6b71", + "role": "member" + } + ], + "next": null, + "previous": null, + "total_count": 2, + "pagination": { + "style": "offset" + } +} +``` + + + +
+
+ +::: tip Expanding a page costs one query, not one per row +`?expand=member` loads the users in bulk, so a 200-row page is a single extra query. Prefer one expanded page over 200 follow-up requests. +::: diff --git a/docs/api-reference/v2/members/overview.md b/docs/api-reference/v2/members/overview.md new file mode 100644 index 00000000..76940be6 --- /dev/null +++ b/docs/api-reference/v2/members/overview.md @@ -0,0 +1,138 @@ +--- +title: Members overview +description: The Plane API v2 member object. Workspace and project rosters, the role slug, membership ids versus user ids, and expanding the user with ?expand=member. +keywords: plane api v2, members, workspace members, project members, member_id, role slug, expand member, roster +--- + +# Members overview + +A member record is the link between a user and a workspace or a project. Reading a roster answers two questions: who belongs here, and in what role. + +v2 returns the **membership**, not the user. Every row is three fields — the membership's own `id`, the `member_id` of the user it points at, and the `role` that user holds in this context. Names, emails, and avatars belong to the user, and you pull them into the same response with [`?expand=member`](#expanding-the-user). + +[Learn more about members](https://docs.plane.so/core-concepts/workspaces/members) + +
+
+ +## The member object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the membership row — not for the user. The same person has one membership in the workspace and another in each project they belong to, so they have several `id` values but one `member_id`. + +- `member_id` _string (uuid)_ + + The user this membership belongs to. This is the id that lines up with `created_by_id` on other objects, `assignee_ids` on work items, and `actor_id` in [audit logs](/api-reference/v2/audit-logs/overview). Join on `member_id`, never on `id`. + +- `role` _string_ + + The member's role in this workspace or project, expressed as a slug. It is what decides whether this person can write here at all. + + The slug is context-correct: the same person can read as `member` on the workspace roster and as `contributor` on a project roster. Nullable — `null` appears only on legacy rows that predate role slugs. + +::: tip Roles are an open set, not an enum +The schema types `role` as a plain string with no enum. Built-in slugs such as `owner`, `member`, and `contributor` are joined by whatever custom roles the workspace has defined, each under its own slug. Match the slugs you care about and fall through gracefully on anything else — do not write an exhaustive `switch`. +::: + +
+
+ + + +```json +{ + "id": "c1b7e4a9-2f66-4a1d-9c3b-7d5e2f8a1b40", + "member_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "role": "member" +} +``` + + + + + +```json +{ + "id": "c1b7e4a9-2f66-4a1d-9c3b-7d5e2f8a1b40", + "member_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "role": "member", + "member": { + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "display_name": "Priya Raghavan", + "avatar_url": "https://assets.plane.so/avatars/16c61a3a.png", + "email": "priya@example.com" + } +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | --------------------------------------------------------------- | ----------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/members/` | List workspace members | +| `POST` | `/api/v2/workspaces/{slug}/members/remove/` | Remove a workspace member | +| `GET` | `/api/v2/workspaces/{slug}/project-role-distribution/` | Get project role distribution | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/members/` | List project members | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/members/` | Create a project member | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/members/{pk}/` | Delete a project member | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/members/{pk}/` | Get a project member | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/members/{pk}/` | Update a project member | + +Both endpoints return the **active** roster for their scope. The workspace list is the superset: a project member is always a workspace member too, so a `member_id` on a project roster always appears on the workspace roster as well — usually with a different `role`. + +Members are read-only in v2. There are no v2 endpoints to invite, update, or remove a member; membership changes are made in Plane or through the [v1 members endpoints](/api-reference/v1/members/overview). + +## Membership ids are not user ids + +This is the single mistake worth designing against. Given the same person: + +| Roster | `id` | `member_id` | +| ------------------------ | -------------------------------------- | -------------------------------------- | +| Workspace `my-team` | `c1b7e4a9-2f66-4a1d-9c3b-7d5e2f8a1b40` | `16c61a3a-512a-48ac-b0be-b6b46fe6f430` | +| Project `4af68566-…067b` | `9d2f5c81-4b73-4e60-8a1f-2c9b6d3e7f04` | `16c61a3a-512a-48ac-b0be-b6b46fe6f430` | + +Key your user cache on `member_id`. Use `id` only when you need to talk about the membership itself. + +## Expanding the user + +Members are one of only two v2 resources that accept `?expand=` — the other is work items. The single allowed value here is `member`. + +```bash +GET /api/v2/workspaces/my-team/members/?expand=member +``` + +Expansion is **separate-key**: `member_id` stays exactly where it was and a `member` object is added beside it. The id is never swapped for an object, so code that reads `member_id` keeps working whether or not the caller expanded. The expanded object carries `id`, `display_name`, `avatar_url`, and `email`. + +Any other value — `user`, `project`, `role` — is rejected with `400 invalid_request`. See [Expanding relations](/api-reference/v2/expanding-relations) for the full contract. + +## Roles and errors + +A role that is too narrow produces `403 forbidden` on the write you attempted. Roles never produce a `404`: a workspace or project outside your tenant returns `404 not_found` whether or not you would have had the role for it. + +::: info A `409` is never a role problem +If a write comes back `409 work_item_types_managed_at_workspace` or `work_item_types_managed_at_project`, the caller's role and scopes are fine. The workspace simply manages work item types on the other surface, and the same call succeeds there. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +## Changed from v1 + +- A member row is now the **membership**, not a flattened user. v1 returned `first_name`, `last_name`, `email`, `avatar`, `avatar_url`, and `display_name` inline; v2 returns `id`, `member_id`, and `role`, and moves the user fields behind `?expand=member`. +- `role` is a **slug string**, not the numeric level v1 returned. Compare `role == "member"`, not `role >= 15`. +- Responses are paginated envelopes rather than bare arrays. See [Pagination](/api-reference/v2/pagination). +- v2 exposes reads only. The v1 add, update, and remove member endpoints have no v2 equivalent yet. + +See [Migrating from v1](/api-reference/v2/migrating-from-v1) for the full list. + +## Related + +- [List workspace members](/api-reference/v2/members/list-workspace-members) +- [List project members](/api-reference/v2/members/list-project-members) +- [Expanding relations](/api-reference/v2/expanding-relations) +- [Get current user](/api-reference/v2/users/get-current-user) — the `member_id` your own credentials map to diff --git a/docs/api-reference/v2/members/remove-workspace-member.md b/docs/api-reference/v2/members/remove-workspace-member.md new file mode 100644 index 00000000..7d04e2bf --- /dev/null +++ b/docs/api-reference/v2/members/remove-workspace-member.md @@ -0,0 +1,145 @@ +--- +title: Remove a workspace member +description: Remove a workspace member with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, remove a workspace member, workspace members, workspace members remove +--- + +# Remove a workspace member + +
+ POST + /api/v2/workspaces/{slug}/members/remove/ +
+ +
+
+ +Members are the people on a workspace or project, and the role each one holds. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Email address. + + + + + +Whether remove seat. + + + +
+
+ +
+ +### Scopes + +`workspaces.members:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + +
+
diff --git a/docs/api-reference/v2/members/update-project-member.md b/docs/api-reference/v2/members/update-project-member.md new file mode 100644 index 00000000..d0ef514b --- /dev/null +++ b/docs/api-reference/v2/members/update-project-member.md @@ -0,0 +1,203 @@ +--- +title: Update a project member +description: Update a project member with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a project member, project members, project members partial update +--- + +# Update a project member + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/members/{pk}/ +
+ +
+
+ +Members are the people on a workspace or project, and the role each one holds. Update a project member. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project member id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Id of the related member. + + + + + +Role to grant. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `id`, `member_id`, `role`. + + + + + +Comma-separated relations to embed: `member`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.members:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "member_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "role": "example" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project member matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/migrating-from-v1.md b/docs/api-reference/v2/migrating-from-v1.md new file mode 100644 index 00000000..608cd7f7 --- /dev/null +++ b/docs/api-reference/v2/migrating-from-v1.md @@ -0,0 +1,350 @@ +--- +title: Migrating from v1 +description: A practical guide to moving an integration from the Plane REST API v1 to v2. Path and header changes, PATCH-only writes, RFC 9457 errors, sparse reads, the new pagination envelope, renamed fields, and what v2 does not cover yet. +keywords: plane api v2 migration, v1 to v2, plane rest api upgrade, is_default, description_html, custom_fields, RFC 9457, sparse reads, PATCH only +--- + +# Migrating from v1 + +v2 is a separate, additive surface. v1 keeps working, nothing is being switched off underneath you, and you can move one call at a time — the two versions can run side by side in the same integration against the same workspace. + +This page is the checklist for moving a call across. + +## At a glance + +| | v1 | v2 | +| ----------------- | -------------------------- | ----------------------------------------------------------- | +| Path | `/api/v1/…` | `/api/v2/…` | +| Auth header | `X-API-Key` | `X-Api-Key` | +| Update verb | `PUT` or `PATCH` | `PATCH` only | +| Errors | Ad-hoc JSON bodies | RFC 9457 `application/problem+json` with stable `code` | +| Relations on read | Embedded objects | `*_id` / `*_ids`, plus `?expand=` on work items and members | +| List envelope | `results` + cursor strings | `data` + `pagination.style` | +| Sparse fieldsets | `?fields=` | Not needed — reads are already sparse | + +Unchanged: the base URL (`https://api.plane.so`), the trailing slash on every path, JSON in and out, and the fine-grained OAuth scope names (`projects.work_items:read`, `projects.states:write`, and so on). + +## 1. Change the path + +`/api/v1/` becomes `/api/v2/`. Everything else about the URL — workspace slug, project id, trailing slash — works the same way. + +```diff +- https://api.plane.so/api/v1/workspaces/my-team/projects/{project_id}/work-items/ ++ https://api.plane.so/api/v2/workspaces/my-team/projects/{project_id}/work-items/ +``` + +## 2. Auth header casing + +v1 docs use `X-API-Key`; v2 standardizes on **`X-Api-Key`**. + +HTTP header names are case-insensitive, so this is cosmetic — an existing `X-API-Key` header keeps working against v2, and no client library will care. Adopt the new casing when you touch the code, not as a migration step of its own. + +OAuth is unchanged: `Authorization: Bearer `. + +## 3. `PUT` is gone + +v2 has no `PUT`. Every update is a `PATCH`, and a `PATCH` is partial — it changes only the fields you send. + +```diff +- PUT /api/v1/workspaces/my-team/projects/{project_id}/states/{pk}/ ++ PATCH /api/v2/workspaces/my-team/projects/{project_id}/states/{pk}/ +``` + +Sending `PUT` returns `405` with the code `method_not_allowed`. + +Two things follow from partial updates: + +- **You no longer have to read-modify-write.** To change one field, send one field. +- **Omitted is not the same as `null`.** Leaving a key out means "don't touch it"; sending `"target_date": null` means "clear it". + +## 4. Errors are `problem+json` + +Every v2 error is `application/problem+json` with the same three keys — `type`, `code` and `detail` — plus `errors[]` on validation failures: + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "One or more fields failed validation.", + "errors": [ + { + "field": "name", + "code": "required", + "message": "This field is required." + } + ] +} +``` + + + +**Branch on `code`.** It is the stable, machine-readable part, and it is always present. `type` is a coarse category from a closed 13-value set — use it as the fallback arm for a `code` you do not recognize. `detail` is prose that can be reworded at any time. `errors[]` appears only on `invalid_request`, and each entry carries `field`, `code` and `message`. + +::: warning The body has no `status` or `title` +If you built against an early v2 preview, note that `status` and `title` were removed from the body, and `type` changed from a `https://api.plane.so/errors/…` URI to a bare slug. Read the HTTP status from the status line. +::: + +Other behavior worth knowing while you port error handling: + +- A resource outside your tenant is `404 not_found`, never `403`. v2 does not leak existence. +- `DELETE` returns `204` with an empty body. +- Uniqueness and protected-state conflicts are `409 conflict` — a duplicate state name, deleting a project's default state, deleting a state that still holds work items. +- Throttling is `429 rate_limited`; honor `Retry-After`. + +See [Errors](/api-reference/v2/errors) for the full code list. + +## 5. Reads are sparse + +This is the largest behavioral change. v1 embedded related objects in the response; v2 returns identifiers. + +```diff +- "state": { "id": "f960d3c2-…", "name": "In Progress", "group": "started" } ++ "state_id": "f960d3c2-…" + +- "assignees": [ { "id": "16c61a3a-…", "display_name": "Priya Raghavan" } ] ++ "assignee_ids": ["16c61a3a-…"] +``` + +To-one relations come back as `_id`; to-many relations as `_ids` arrays. That makes every response a predictable, flat shape and keeps list pages cheap. + +Where you genuinely need the object, add `?expand=`. It is **separate-key**: `?expand=state` keeps `state_id` _and_ adds a `state` object beside it, so nothing you already read stops working. + +Each resource declares its own allowlist — work items expand `state`, `type`, `parent`, `assignees`, `labels`, `cycle` and `modules`; cycles expand `owned_by`; modules expand `lead` and `members`; comments expand `actor`; and so on. A resource that declares none rejects every value, and an unknown value is always a `400`. The full table is in [Expanding relations](/api-reference/v2/expanding-relations). + +::: tip You usually need fewer objects than v1 gave you +v1 embedded relations whether or not you used them. Before reaching for `?expand=`, check whether the id is all your code actually consumed — for cache keys, joins, and foreign keys it almost always is. +::: + +### `?fields=` trims further + +v1 had a `?fields=` parameter, and so does v2 — but the semantics are stricter in two ways worth porting carefully: + +- **Unrequested keys are omitted, not nulled.** In v2, absent means "not requested" and `null` means "genuinely null". Code that reads `row.target_date` expecting a key to exist needs to check presence instead. +- **Unknown names are a `400`.** v1 ignored them silently. A typo that used to return the full row now fails loudly — which is the point, since a silently-ignored field name costs you the saving without telling you. + +`id` always comes back, and `all` returns the full requestable set. Some resources also leave heavy fields such as `description_html` off list rows by default; naming the field pulls it back. See [Sparse fields](/api-reference/v2/sparse-fields). + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/ENG/work-items/?fields=id,name,state_id" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +## 6. Writes take ids + +v2 write payloads take identifiers, never nested objects: `state_id`, `parent_id`, `assignee_ids`, `label_ids`, modules' `lead_id`. An id belonging to another project or workspace is a clean `400` — it never silently links to nothing. + +Audit fields (`created_at`, `created_by_id`, and friends) are read-only. Sending them has no effect. + +## 7. The pagination envelope changed + +v1 returned `results` with cursor strings like `20:1:0`. v2 returns `data` inside an envelope that declares its own style. + +**v2 offset (the default):** + +```json +{ + "data": [], + "next": 50, + "previous": null, + "total_count": 327, + "pagination": { "style": "offset" } +} +``` + +**v2 cursor (opt in with `?paginate=cursor`):** + +```json +{ + "data": [], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { "style": "cursor" } +} +``` + +Port your paginator to read `data`, then **branch on `pagination.style`** rather than sniffing which keys are present. On the offset path `next` and `previous` are integer offsets, not tokens and not URLs. `per_page` defaults to 50 and caps at 200; `offset` caps at 10000, which is the point at which you should switch to cursor. See [Pagination](/api-reference/v2/pagination). + +## 8. Renamed and removed fields + +These are the concrete differences to grep your codebase for. + +| Resource | v1 | v2 | +| ---------- | -------------------------------------------------- | --------------------------------------------------------- | +| States | `default` | **`is_default`** | +| States | `updated_at`, `updated_by`, `project`, `workspace` | **Not returned** | +| Work items | `updated_at`, `updated_by`, `project`, `workspace` | **Not returned** | +| Work items | `description_html` on read | **Write-only** — accepted on write, not returned on read | +| Work items | — | `custom_fields` — present only on single-object responses | + +Details on the two that bite hardest: + +::: warning `description_html` is write-only on work items +You can send `description_html` on a create or update and it is stored, but it does **not** come back on any read — not on the list, not on the detail route, not in the create/update response. Code that round-trips a description through the API needs to keep its own copy of what it wrote. +::: + +::: warning `custom_fields` is absent on list responses +`custom_fields` carries a work item's custom property values, and it is present **only on single-object responses** — retrieve, create, update, upsert and the archive verbs. On the **list** path the key is **omitted entirely** (not `null`), because resolving properties per row would mean a query per work item. Asking for it there with `?fields=custom_fields` is a `400` rather than a silent empty answer. If you need property values for many items, list first and then fetch the ones you care about individually. +::: + +Also note: the state `group` enum gained `triage` alongside v1's five groups. + +## 9. Side by side: listing work items + +The same call in both versions. + +### v1 + +```bash +curl "https://api.plane.so/api/v1/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/?per_page=20&expand=state,assignees" \ + -H "X-API-Key: $PLANE_API_KEY" +``` + + + +```json +{ + "total_count": 327, + "next_cursor": "20:1:0", + "prev_cursor": "20:0:0", + "next_page_results": true, + "prev_page_results": false, + "count": 20, + "total_pages": 17, + "results": [ + { + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect", + "description": "", + "priority": "high", + "sequence_id": 118, + "state": { + "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "In Progress", + "group": "started" + }, + "assignees": [{ "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "display_name": "Priya Raghavan" }], + "labels": [], + "created_at": "2026-01-14T09:22:41.478363Z" + } + ] +} +``` + + + +### v2 + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/?per_page=20&expand=state,assignees" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + + + +```json +{ + "data": [ + { + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect", + "identifier": "PROJ-118", + "sequence_id": 118, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": null, + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": [], + "parent_id": null, + "start_date": null, + "target_date": "2026-02-02", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "state": { + "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "In Progress", + "color": "#3f76ff", + "group": "started" + }, + "assignees": [ + { + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "display_name": "Priya Raghavan", + "avatar_url": "https://assets.plane.so/avatars/16c61a3a.png", + "email": "priya@example.com" + } + ] + } + ], + "next": 20, + "previous": null, + "total_count": 327, + "pagination": { "style": "offset" } +} +``` + + + +Line up the differences: + +- `results` → `data`; the cursor strings are replaced by an integer `next` plus a `pagination.style` discriminator. +- `state` → `state_id`, and `assignees` → `assignee_ids`. The expanded `state` and `assignees` objects are **added** by `?expand=`, not substituted for the ids. +- `description` is gone from the read shape. +- New on every work item: `identifier` (`PROJ-118`), `type_id`, `is_draft`, `archived_at`, and `created_by_id`. `custom_fields` is absent here because this is a list — it appears on single-object responses. + +## 10. What is new in v2 + +Capabilities that have no v1 equivalent and are worth designing around rather than porting: + +**Work item types and custom properties.** Define types (`Bug`, `Incident`) and typed custom properties (`TEXT`, `DATETIME`, `DECIMAL`, `BOOLEAN`, `OPTION`, `RELATION`, `URL`, `EMAIL`, `FILE`, `FORMULA`), attach properties to types, and read a type's full writable schema in one call. A workspace runs these at either the project or the workspace level — start at [Work item type modes](/api-reference/v2/work-item-type-modes), because writing to the wrong surface is a `409`. + +**Property options and contexts.** `OPTION` properties carry their own managed choice lists. In workspace mode, a **property context** narrows where a workspace property applies and can override `is_required`, `is_multi`, or `default_value` for a specific slice of projects and types. See [Property options](/api-reference/v2/work-item-property-options/overview) and [Property contexts](/api-reference/v2/work-item-property-contexts/overview). + +**Workspace features.** Read and toggle workspace-level capabilities through the API instead of the UI: `GET` and `PATCH /api/v2/workspaces/{slug}/features/`. See [Workspace features](/api-reference/v2/workspace-features/overview). + +**Audit logs.** Query the workspace audit trail with filters for actor, category, event name, target, outcome, IP address, and time range — `GET /api/v2/workspaces/{slug}/audit-logs/`. See [Audit logs](/api-reference/v2/audit-logs/overview). + +**Fetch a work item by its `PROJ-123` identifier.** The human key that appears in the Plane UI is now directly addressable, without knowing the project id or resolving a UUID first: + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/work-items/PROJ-118/" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +This is the fastest way to turn something a person typed — a ticket reference in a chat message, a commit trailer, a support email — into a work item. See [Get a work item by identifier](/api-reference/v2/work-items/get-work-item-by-identifier). + +## 11. Not yet in v2 + +v2 does not cover the whole product surface. It currently covers work items and their comments, states, labels, cycles, modules, work item types and custom properties, members, workspace features, audit logs, and the current user. + +Everything else still lives in v1 — projects, pages and wiki pages, intake, teamspaces, initiatives, epics, estimates, customers, stickies, attachments and links, worklogs, work item relations, activity, milestones, and workspace invitations. So do the sub-resources and verbs of cycles and modules: adding or removing their work items, archiving, and unarchiving all remain v1-only. + +::: info Run both versions side by side +There is no requirement to migrate wholesale. Point the calls that v2 covers at `/api/v2/` and leave the rest on `/api/v1/`. The same API key authenticates both. For the v1 surface, see [the v1 introduction](/api-reference/v1/introduction). +::: + +## Migration checklist + +1. Swap `/api/v1/` for `/api/v2/` on the calls v2 covers. +2. Replace every `PUT` with `PATCH`, and trim the payload to the fields that actually change. +3. Rewrite error handling to read `code` from the problem+json body. +4. Update your paginator: read `data`, branch on `pagination.style`, treat `next` as an integer offset. +5. Replace embedded relation reads with `*_id` / `*_ids`, adding `?expand=` only where you need the object and only where it is supported. +6. Rename state `default` to `is_default`, and stop reading `updated_at`, `updated_by`, `project`, and `workspace` from state and work item responses. +7. Stop expecting `description_html` back from a work item read. +8. Stop expecting `custom_fields` on list responses — fetch the item individually when you need its property values. +9. Update your error handling to the `{type, code, detail}` shape: v2 no longer sends `status` or `title`, and `type` is a bare category slug rather than a URI. See [Errors](/api-reference/v2/errors). +10. Adopt [`?fields=`](/api-reference/v2/sparse-fields) where you only need a few keys — v1's `?fields=` silently ignored unknown names, whereas v2 returns a `400`, and v2 **omits** unrequested keys rather than nulling them. +11. Replace name-based lookups with the identity filters (`?name=`, `?key=`, `?url=`) — v2 path segments take ids, not attributes. See [Filtering and ordering](/api-reference/v2/filtering-and-ordering). +12. Confirm nothing you depend on is in the "not yet in v2" list above. + +## Related + +- [Pagination](/api-reference/v2/pagination) +- [Filtering and ordering](/api-reference/v2/filtering-and-ordering) +- [Expanding relations](/api-reference/v2/expanding-relations) +- [Work item type modes](/api-reference/v2/work-item-type-modes) +- [Errors](/api-reference/v2/errors) diff --git a/docs/api-reference/v2/milestones/bulk-milestones.md b/docs/api-reference/v2/milestones/bulk-milestones.md new file mode 100644 index 00000000..1573ae84 --- /dev/null +++ b/docs/api-reference/v2/milestones/bulk-milestones.md @@ -0,0 +1,186 @@ +--- +title: Bulk write milestones +description: Bulk write milestones with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, bulk write milestones, milestones, milestones bulk +--- + +# Bulk write milestones + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/milestones/bulk/ +
+ +
+
+ +Milestones mark dated checkpoints in a project. Write up to 100 milestones in one request, with a per-row result for each. + +- Send `{"items": [...]}`, at most 100 per request. Add `"upsert": true` to match existing rows on `(external_source, external_id)` instead of always creating. +- The response is always `200`. Each row reports its own outcome, so a partial failure is reported rather than silently dropped — read `succeeded`/`failed` and the per-row `status`. +- Rows are written on independent savepoints: a failing row rolls back only itself. +- `?fields=` does not apply here — the bulk response is a per-row result envelope, not a resource body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The items. + + + + + +Reconcile each row on (external_source, external_id) instead of creating it. + + + +
+
+ +
+ +### Scopes + +`projects.milestones:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "failed": 1, + "results": [null], + "succeeded": 1 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/milestones/create-milestone.md b/docs/api-reference/v2/milestones/create-milestone.md new file mode 100644 index 00000000..0b88b849 --- /dev/null +++ b/docs/api-reference/v2/milestones/create-milestone.md @@ -0,0 +1,208 @@ +--- +title: Create a milestone +description: Create a milestone with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a milestone, milestones, milestones create +--- + +# Create a milestone + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/milestones/ +
+ +
+
+ +Milestones mark dated checkpoints in a project. Create a milestone. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Title. + +Maximum 255 characters. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Planned due date, as `YYYY-MM-DD`. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `external_id`, `external_source`, `id`, `target_date`, `title`. + + + +
+
+ +
+ +### Scopes + +`projects.milestones:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "target_date": "2026-01-20", + "title": "Example title" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/milestones/delete-milestone.md b/docs/api-reference/v2/milestones/delete-milestone.md new file mode 100644 index 00000000..89112f7f --- /dev/null +++ b/docs/api-reference/v2/milestones/delete-milestone.md @@ -0,0 +1,154 @@ +--- +title: Delete a milestone +description: Delete a milestone with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a milestone, milestones, milestones destroy +--- + +# Delete a milestone + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/milestones/{pk}/ +
+ +
+
+ +Milestones mark dated checkpoints in a project. Delete a milestone. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The milestone id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `external_id`, `external_source`, `id`, `target_date`, `title`. + + + +
+
+ +
+ +### Scopes + +`projects.milestones:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No milestone matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/milestones/get-milestone.md b/docs/api-reference/v2/milestones/get-milestone.md new file mode 100644 index 00000000..f7f16674 --- /dev/null +++ b/docs/api-reference/v2/milestones/get-milestone.md @@ -0,0 +1,161 @@ +--- +title: Get a milestone +description: Read a milestone with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a milestone, milestones, milestones retrieve +--- + +# Get a milestone + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/milestones/{pk}/ +
+ +
+
+ +Milestones mark dated checkpoints in a project. Read a single milestone by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The milestone id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `external_id`, `external_source`, `id`, `target_date`, `title`. + + + +
+
+ +
+ +### Scopes + +`projects.milestones:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "target_date": "2026-01-20", + "title": "Example title" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No milestone matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/milestones/list-milestones.md b/docs/api-reference/v2/milestones/list-milestones.md new file mode 100644 index 00000000..a453a6ed --- /dev/null +++ b/docs/api-reference/v2/milestones/list-milestones.md @@ -0,0 +1,248 @@ +--- +title: List milestones +description: List milestones with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list milestones, milestones, milestones list +--- + +# List milestones + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/milestones/ +
+ +
+
+ +Milestones mark dated checkpoints in a project. List the milestones you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `external_id`. + + + + + +Filter by `external_source`. + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +Filter by `target_date`. + + + + + +Filter by `target_date__gte`. + + + + + +Filter by `target_date__lte`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `external_id`, `external_source`, `id`, `target_date`, `title`. + + + +
+
+ +
+ +### Scopes + +`projects.milestones:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "target_date": "2026-01-20", + "title": "Example title" + }, + { + "archived_at": null, + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "target_date": "2026-01-20", + "title": "Another example" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/milestones/manage-milestone-work-items.md b/docs/api-reference/v2/milestones/manage-milestone-work-items.md new file mode 100644 index 00000000..95d74e6d --- /dev/null +++ b/docs/api-reference/v2/milestones/manage-milestone-work-items.md @@ -0,0 +1,173 @@ +--- +title: Add or remove milestone work items +description: Add or remove milestone work items with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, add or remove milestone work items, milestone work items, milestones work items +--- + +# Add or remove milestone work items + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/milestones/{pk}/work-items/ +
+ +
+
+ +Milestones mark dated checkpoints in a project. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The milestone work item id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The add. + + + + + +The remove. + + + +
+
+ +
+ +### Scopes + +`projects.milestones:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "added": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "removed": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/milestones/overview.md b/docs/api-reference/v2/milestones/overview.md new file mode 100644 index 00000000..0cdb94ad --- /dev/null +++ b/docs/api-reference/v2/milestones/overview.md @@ -0,0 +1,88 @@ +--- +title: Milestones overview +description: The Plane API v2 milestone object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, milestones, milestone object +--- + +# Milestones overview + +Milestones mark dated checkpoints in a project. + +
+
+ +## The milestone object + +### Attributes + +- `archived_at` _string (date-time)_ + + When the record was archived, or `null` if it is active. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `target_date` _string (date)_ + + Planned due date, as `YYYY-MM-DD`. + +- `title` _string_ + + Title. + +
+
+ + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "target_date": "2026-01-20", + "title": "Example title" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ----------------------------------------------------------------------------- | ---------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/milestones/` | List milestones | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/milestones/` | Create a milestone | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/milestones/bulk/` | Bulk write milestones | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/milestones/upsert/` | Upsert a milestone | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/milestones/{pk}/` | Delete a milestone | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/milestones/{pk}/` | Get a milestone | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/milestones/{pk}/` | Update a milestone | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/milestones/{pk}/work-items/` | Add or remove milestone work items | + +## Response shaping + +Every milestone read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/milestones/update-milestone.md b/docs/api-reference/v2/milestones/update-milestone.md new file mode 100644 index 00000000..01dab587 --- /dev/null +++ b/docs/api-reference/v2/milestones/update-milestone.md @@ -0,0 +1,223 @@ +--- +title: Update a milestone +description: Update a milestone with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a milestone, milestones, milestones partial update +--- + +# Update a milestone + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/milestones/{pk}/ +
+ +
+
+ +Milestones mark dated checkpoints in a project. Update a milestone. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The milestone id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Planned due date, as `YYYY-MM-DD`. + +Nullable. + + + + + +Title. + +Maximum 255 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `external_id`, `external_source`, `id`, `target_date`, `title`. + + + +
+
+ +
+ +### Scopes + +`projects.milestones:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "target_date": "2026-01-20", + "title": "Example title" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No milestone matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/milestones/upsert-milestone.md b/docs/api-reference/v2/milestones/upsert-milestone.md new file mode 100644 index 00000000..927f5850 --- /dev/null +++ b/docs/api-reference/v2/milestones/upsert-milestone.md @@ -0,0 +1,212 @@ +--- +title: Upsert a milestone +description: Upsert a milestone with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, upsert a milestone, milestones, milestones upsert +--- + +# Upsert a milestone + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/milestones/upsert/ +
+ +
+
+ +Milestones mark dated checkpoints in a project. Create a milestone, or update the existing one that carries the same `(external_source, external_id)` pair. + +- Both `external_source` and `external_id` are required — without them there is nothing to match on and the request is a `400`. +- A created record answers `201` with `X-Plane-Upsert: created`; an updated one answers `200` with `X-Plane-Upsert: updated`. Branch on the header rather than guessing from the status. +- Upsert is safe for **sequential** importers. Two simultaneous upserts of the same key can each miss and each create, so serialize your writes per key. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Title. + +Maximum 255 characters. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Planned due date, as `YYYY-MM-DD`. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `external_id`, `external_source`, `id`, `target_date`, `title`. + + + +
+
+ +
+ +### Scopes + +`projects.milestones:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "target_date": "2026-01-20", + "title": "Example title" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/modules/bulk-modules.md b/docs/api-reference/v2/modules/bulk-modules.md new file mode 100644 index 00000000..f4a0f825 --- /dev/null +++ b/docs/api-reference/v2/modules/bulk-modules.md @@ -0,0 +1,204 @@ +--- +title: Bulk write modules +description: Bulk write modules with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, bulk write modules, modules, modules bulk +--- + +# Bulk write modules + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/modules/bulk/ +
+ +
+
+ +Modules group work items into a deliverable inside a project. Write up to 100 modules in one request, with a per-row result for each. + +- Send `{"items": [...]}`, at most 100 per request. Add `"upsert": true` to match existing rows on `(external_source, external_id)` instead of always creating. +- The response is always `200`. Each row reports its own outcome, so a partial failure is reported rather than silently dropped — read `succeeded`/`failed` and the per-row `status`. +- Rows are written on independent savepoints: a failing row rolls back only itself. +- `?fields=` does not apply here — the bulk response is a per-row result envelope, not a resource body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The items. + + + + + +Reconcile each row on (external_source, external_id) instead of creating it. + + + +
+
+ +
+ +### Scopes + +`projects.modules:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "failed": 1, + "results": [null], + "succeeded": 1 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/modules/create-module.md b/docs/api-reference/v2/modules/create-module.md new file mode 100644 index 00000000..7da8d4ca --- /dev/null +++ b/docs/api-reference/v2/modules/create-module.md @@ -0,0 +1,295 @@ +--- +title: Create a module +description: Create a module in a Plane project with the v2 REST API. Body parameters, the module status enum, lead validation, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create module, module status, lead_id, POST modules, project modules +--- + +# Create a module + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/modules/ +
+ +
+
+ +Create a module in a project. Only `name` is required — everything else can be filled in later with a `PATCH` as the +work firms up. + +Module names must be unique within a project — reusing one returns `409 conflict`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project to create the module in. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name for the module, unique within the project. Maximum 255 characters. + + + + + +Plain-text summary of what the module covers. + + + + + +Where the module sits in its lifecycle. + +- `backlog` — Captured, not yet committed to +- `planned` — Committed to but not started +- `in-progress` — Actively being worked on +- `paused` — Started, then put on hold +- `completed` — Delivered +- `cancelled` — Dropped without delivering + +Defaults to `planned` when omitted. A value outside this list is a `400 invalid_request`. + + + + + +Date the module is scheduled to begin, as `YYYY-MM-DD`. Nullable. + + + + + +Date the module is expected to land, as `YYYY-MM-DD`. Nullable, and must not be earlier than `start_date`. + + + + + +The user accountable for the module. Must be a member of this project — any other user id is rejected with a `400` +naming `lead_id`, never linked silently. Nullable. + + + + + +Ordering weight used when modules are listed. Lower values sort first. In a project that already has modules, Plane +positions the new module ahead of them on create, so send `sort_order` in a follow-up `PATCH` if you need a specific +slot. + + + + + +Free-form JSON object holding the icon Plane renders for the module. + + + + + +Your system's identifier for this module, for sync and import correlation. Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. Nullable. + + + +
+
+ +::: info Members aren't set here +`member_ids` is read-only in v2, so a module is created with no members. Module membership is not yet writable through +the v2 API. +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `archived_at`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `lead_id`, `logo_props`, `member_ids`, `name`, `sort_order`, `start_date`, `status`, `target_date`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `lead` (the module lead), `members` (the module members). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.modules:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | Missing `name`, a `status` outside the enum, a `lead_id` who isn't a project member, or a `target_date` before `start_date`. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't create modules in this project. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A module with this name already exists in the project. | +| `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. Wait for the interval in `Retry-After` and retry. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "7c1f3d90-2a64-4e58-9b0d-3fa1c7e28b45", + "name": "Billing revamp", + "description": "Rework subscription billing end to end.", + "status": "in-progress", + "start_date": "2026-01-05", + "target_date": "2026-02-27", + "lead_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "member_ids": [], + "sort_order": 65535.0, + "logo_props": {}, + "external_id": null, + "external_source": null, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "conflict", + "detail": "A module with this name already exists in the project." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/modules/delete-module.md b/docs/api-reference/v2/modules/delete-module.md new file mode 100644 index 00000000..92b85bc0 --- /dev/null +++ b/docs/api-reference/v2/modules/delete-module.md @@ -0,0 +1,174 @@ +--- +title: Delete a module +description: Delete a module from a Plane project with the v2 REST API. Path parameters, what happens to the module's work items, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete module, remove module, DELETE modules, soft delete +--- + +# Delete a module + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/ +
+ +
+
+ +Delete a module. The work items that were grouped by it stay in the project — only the grouping goes away. + +This is a soft delete: the module stops appearing in reads and its name is freed up for reuse in the project. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the module belongs to. + + + + + +The module to delete. + + + +
+
+ +::: info 204, then 404 +A successful delete returns `204` with an empty body — there is nothing to parse. A second `DELETE` on the same id +returns `404 not_found`, so a retry after a dropped connection is safe to treat as success. +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `archived_at`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `lead_id`, `logo_props`, `member_ids`, `name`, `sort_order`, `start_date`, `status`, `target_date`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `lead` (the module lead), `members` (the module members). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.modules:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't delete this module. | +| `404` | `not_found` | No such module, workspace, or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The write collides with an existing record or a business rule. | +| `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. Wait for the interval in `Retry-After` and retry. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No Module matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/modules/get-module.md b/docs/api-reference/v2/modules/get-module.md new file mode 100644 index 00000000..f49b0c60 --- /dev/null +++ b/docs/api-reference/v2/modules/get-module.md @@ -0,0 +1,186 @@ +--- +title: Get a module +description: Retrieve a single module from a Plane project with the v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get module, retrieve module, module by id, GET modules +--- + +# Get a module + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/ +
+ +
+
+ +Retrieve one module by id. The response is the same shape a list row uses, so you can refresh a single module without +re-reading the whole collection. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the module belongs to. + + + + + +The module to retrieve. + + + +
+
+ +::: info A module in another project is a 404 +`pk` is resolved inside `project_id`. A valid module id from a different project — or from another workspace — returns +`404 not_found`, not `403`. The API never reveals that a resource you can't see exists. +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `archived_at`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `lead_id`, `logo_props`, `member_ids`, `name`, `sort_order`, `start_date`, `status`, `target_date`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `lead` (the module lead), `members` (the module members). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.modules:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read modules in this project. | +| `404` | `not_found` | No such module, workspace, or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Wait for the interval in `Retry-After` and retry. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "7c1f3d90-2a64-4e58-9b0d-3fa1c7e28b45", + "name": "Billing revamp", + "description": "Rework subscription billing end to end.", + "status": "in-progress", + "start_date": "2026-01-05", + "target_date": "2026-02-27", + "lead_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "member_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430", "9d3e1f27-8b4c-4a06-95f1-2c7ea45b0d18"], + "sort_order": 65535.0, + "logo_props": {}, + "external_id": null, + "external_source": null, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No Module matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/modules/list-modules.md b/docs/api-reference/v2/modules/list-modules.md new file mode 100644 index 00000000..0b1f4fa0 --- /dev/null +++ b/docs/api-reference/v2/modules/list-modules.md @@ -0,0 +1,323 @@ +--- +title: List modules +description: List the modules in a Plane project with the v2 REST API. Filter by status, lead, and external id, search by name, order, paginate, and handle errors. +keywords: plane api v2, list modules, module status filter, lead_id, GET modules, pagination +--- + +# List modules + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/modules/ +
+ +
+
+ +List the modules in a project. Use it to build a delivery board, to find the module a sync run created, or to pull +everything a person leads. + +Results are paginated and, by default, ordered by `sort_order` — the order modules appear in the Plane UI. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project whose modules you want. + + + +
+
+ +
+ +### Query Parameters + +
+ +**Filters** + + + +Return only modules in this lifecycle position. One of `backlog`, `planned`, `in-progress`, `paused`, `completed`, or +`cancelled`. + +Use `status__in` with a comma-separated list to match several at once, for example +`?status__in=planned,in-progress`. + + + + + +Return only modules led by this user. Match is exact on the module's `lead_id`. + + + + + +Return only modules carrying this external identifier. Pair it with `external_source` to resolve a record you imported +from another system. + + + + + +Return only modules that came from this system, for example `github` or `jira`. + + + +**Search** + + + +Free-text match on the module name. + + + +**Ordering** + + + +Field to sort by. Prefix with `-` for descending. + +- `sort_order` , `-sort_order` +- `created_at` , `-created_at` +- `id` , `-id` + +Defaults to `sort_order`. + + + +**Pagination** + + + +Page size. Defaults to 50, maximum 200. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000 — go deeper with cursor pagination. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope, then follow `next_cursor` with `?cursor=`. + + + + + +Set to `false` to skip the count query and omit `total_count` from the offset envelope. Defaults to `true`. + + + +
+
+ +::: warning Cursor needs an explicit order_by +The default `sort_order` is not unique, so it can't back a stable keyset. A bare `?paginate=cursor` returns +`400 ordering_not_cursor_eligible`. Pair it with a cursor-eligible ordering — `?paginate=cursor&order_by=created_at` +— or stay on offset. +::: + +::: info Enum filters are validated +`status` and `status__in` are checked against the allowed values. A typo like `?status=in_progress` is a clean +`400 invalid_request`, not an empty result set. +::: + +Modules do not support `?expand=` — `lead_id` and `member_ids` are always returned as ids. + +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `archived_at`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `lead_id`, `logo_props`, `member_ids`, `name`, `sort_order`, `start_date`, `status`, `target_date`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `lead` (the module lead), `members` (the module members). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.modules:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read modules in this project. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Wait for the interval in `Retry-After` and retry. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "7c1f3d90-2a64-4e58-9b0d-3fa1c7e28b45", + "name": "Billing revamp", + "description": "Rework subscription billing end to end.", + "status": "in-progress", + "start_date": "2026-01-05", + "target_date": "2026-02-27", + "lead_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "member_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430", "9d3e1f27-8b4c-4a06-95f1-2c7ea45b0d18"], + "sort_order": 65535.0, + "logo_props": {}, + "external_id": null, + "external_source": null, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + }, + { + "id": "3ea77b18-5c9d-4f02-8a61-b0d4c9e5137a", + "name": "Search relevance", + "description": "", + "status": "in-progress", + "start_date": null, + "target_date": null, + "lead_id": null, + "member_ids": [], + "sort_order": 98302.5, + "logo_props": {}, + "external_id": null, + "external_source": null, + "archived_at": null, + "created_at": "2026-01-19T14:03:07.201884Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next": null, + "previous": null, + "total_count": 2, + "pagination": { "style": "offset" } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "7c1f3d90-2a64-4e58-9b0d-3fa1c7e28b45", + "name": "Billing revamp", + "description": "Rework subscription billing end to end.", + "status": "in-progress", + "start_date": "2026-01-05", + "target_date": "2026-02-27", + "lead_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "member_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430", "9d3e1f27-8b4c-4a06-95f1-2c7ea45b0d18"], + "sort_order": 65535.0, + "logo_props": {}, + "external_id": null, + "external_source": null, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { "style": "cursor" } +} +``` + + + +
+
diff --git a/docs/api-reference/v2/modules/manage-module-work-items.md b/docs/api-reference/v2/modules/manage-module-work-items.md new file mode 100644 index 00000000..2619b459 --- /dev/null +++ b/docs/api-reference/v2/modules/manage-module-work-items.md @@ -0,0 +1,173 @@ +--- +title: Add or remove module work items +description: Add or remove module work items with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, add or remove module work items, module work items, modules work items manage +--- + +# Add or remove module work items + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/work-items/ +
+ +
+
+ +Modules group work items into a deliverable inside a project. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The module work item id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The add. + + + + + +The remove. + + + +
+
+ +
+ +### Scopes + +`projects.modules:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "added": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "removed": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/modules/overview.md b/docs/api-reference/v2/modules/overview.md new file mode 100644 index 00000000..7ab35451 --- /dev/null +++ b/docs/api-reference/v2/modules/overview.md @@ -0,0 +1,178 @@ +--- +title: Modules overview +description: The Plane API v2 module object. Attributes, the module status enum, lead and member handling, name uniqueness, and how modules differ from v1. +keywords: plane api v2, modules, module object, module status, module lead, project modules +--- + +# Modules overview + +A module groups work items into a single deliverable inside one project — a launch, a migration, a redesign. Modules +belong to exactly one project and are never shared across projects. + +Where a cycle answers "what are we doing this sprint", a module answers "what does this piece of work consist of". A +work item can sit in a module and a cycle at the same time. + +[Learn more about modules](https://docs.plane.so/core-concepts/modules) + +
+
+ +## The module object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the module. + +- `name` _string_ + + Display name, unique within the project. Maximum 255 characters. + +- `description` _string_ + + Plain-text summary of what the module covers. + +- `status` _string_ + + Where the module sits in its lifecycle. One of `backlog`, `planned`, `in-progress`, `paused`, `completed`, or + `cancelled`. + +- `start_date` _string (date)_ + + Date the module is scheduled to begin, as `YYYY-MM-DD`, or `null`. + +- `target_date` _string (date)_ + + Date the module is expected to land, as `YYYY-MM-DD`, or `null`. + +- `lead_id` _string (uuid)_ + + The project member accountable for the module, or `null`. + +- `member_ids` _array of string_ + + Project members assigned to the module. Read-only in v2 — module membership is not yet writable through the v2 API. + +- `sort_order` _number_ + + Ordering weight used when modules are listed. Lower values sort first. + +- `logo_props` _any_ + + Free-form JSON object holding the icon Plane renders for the module. `{}` when no icon is set. + +- `external_id` , `external_source` _string_ + + Correlation fields for sync and import. Together they let you map a module to a record in another system and find it + again later. + +- `archived_at` _string (date-time)_ + + When the module was archived, or `null` for an active module. + +- `created_at` _string (date-time)_ + + When the module was created. + +- `created_by_id` _string (uuid)_ + + The user who created the module. + +::: info Relations are always ids +Modules do not support `?expand=`. `lead_id` and `member_ids` come back as ids — resolve them against +[project members](/api-reference/v2/members/list-project-members). +::: + +
+
+ + + +```json +{ + "id": "7c1f3d90-2a64-4e58-9b0d-3fa1c7e28b45", + "name": "Billing revamp", + "description": "Rework subscription billing end to end.", + "status": "in-progress", + "start_date": "2026-01-05", + "target_date": "2026-02-27", + "lead_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "member_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430", "9d3e1f27-8b4c-4a06-95f1-2c7ea45b0d18"], + "sort_order": 65535.0, + "logo_props": {}, + "external_id": null, + "external_source": null, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | -------------------------------------------------------------------------- | ------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/modules/` | List modules | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/modules/` | Create a module | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/modules/bulk/` | Bulk write modules | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/modules/upsert/` | Upsert a module | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/` | Delete a module | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/` | Get a module | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/` | Update a module | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/work-items/` | Add or remove module work items | + +## Status + +`status` is the module's lifecycle position and is validated on both writes and the `status` filter — a value outside +the enum is a `400 invalid_request`, never a silently ignored write or an empty list. + +| Value | Meaning | +| ------------- | ------------------------------ | +| `backlog` | Captured, not yet committed to | +| `planned` | Committed to but not started | +| `in-progress` | Actively being worked on | +| `paused` | Started, then put on hold | +| `completed` | Delivered | +| `cancelled` | Dropped without delivering | + +A module created without a `status` starts as `planned`. + +## Lead and members + +`lead_id` must be a member of the module's project. Passing a user who isn't a project member is rejected with +`400 invalid_request` naming `lead_id` — the link is never made silently. + +`member_ids` is read-only. Reads return the module's current members, but there is no v2 write path for adding or +removing them yet. + +## Dates + +`start_date` and `target_date` are plain dates (`YYYY-MM-DD`), and either can be `null`. A `target_date` earlier than +`start_date` is rejected with `400 invalid_request`. + +## Names are unique per project + +Two modules in the same project cannot share a name. A duplicate on create — or on a rename via `PATCH` — returns +`409 conflict`. + +## Not yet in v2 + +Module work-item management (listing the work items in a module, adding them, removing them) and archive/unarchive are +still only available in v1. See the [v1 module reference](/api-reference/v1/module/overview) for those routes; the v2 +endpoints above cover module CRUD only. + +## Changed from v1 + +- `lead` is now **`lead_id`**, `members` is now **`member_ids`**, and `created_by` is now **`created_by_id`**. +- `member_ids` is read-only. +- Reads no longer return `updated_at`, `updated_by`, `project`, `workspace`, `view_props`, `description_text`, or + `description_html`. +- `PUT` is gone — updates are `PATCH` and partial. + +See [Migrating from v1](/api-reference/v2/migrating-from-v1) for the full list. diff --git a/docs/api-reference/v2/modules/update-module.md b/docs/api-reference/v2/modules/update-module.md new file mode 100644 index 00000000..aa5de154 --- /dev/null +++ b/docs/api-reference/v2/modules/update-module.md @@ -0,0 +1,296 @@ +--- +title: Update a module +description: Partially update a module in a Plane project with the v2 REST API. PATCH body parameters, status transitions, lead validation, OAuth scopes, and error codes. +keywords: plane api v2, update module, patch module, module status, module lead, PATCH modules +--- + +# Update a module + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/ +
+ +
+
+ +Update a module in place — move it to `in-progress`, hand it to a new lead, push out its target date. + +The update is partial: fields you omit are untouched. Omitting a field is not the same as sending `null`, which clears +a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the module belongs to. + + + + + +The module to update. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +New display name, unique within the project. Maximum 255 characters. Renaming onto an existing module's name returns +`409 conflict`. + + + + + +Plain-text summary of what the module covers. + + + + + +Move the module to a different lifecycle position. + +- `backlog` — Captured, not yet committed to +- `planned` — Committed to but not started +- `in-progress` — Actively being worked on +- `paused` — Started, then put on hold +- `completed` — Delivered +- `cancelled` — Dropped without delivering + +Any value outside this list is a `400 invalid_request`. Transitions are unrestricted — a `completed` module can be +sent back to `in-progress`. + + + + + +Date the module is scheduled to begin, as `YYYY-MM-DD`. Send `null` to clear it. + + + + + +Date the module is expected to land, as `YYYY-MM-DD`. Send `null` to clear it. Must not be earlier than the module's +`start_date` — including the `start_date` already stored when you only send `target_date`. + + + + + +Reassign the module. Must be a member of this project — any other user id is rejected with a `400` naming `lead_id`. +Send `null` to leave the module without a lead. + + + + + +Ordering weight used when modules are listed. Lower values sort first. + + + + + +Free-form JSON object holding the icon Plane renders for the module. The value you send replaces the stored object. + + + + + +Your system's identifier for this module, for sync and import correlation. Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. Nullable. + + + +
+
+ +::: warning No PUT +v2 has no `PUT`. Sending one returns `405 method_not_allowed` — use `PATCH` with only the fields you want to change. +::: + +`member_ids`, `archived_at`, and the audit fields are read-only. Sending them has no effect. + +
+ +### Response shaping + +
+ + + +Comma-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: `archived_at`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `lead_id`, `logo_props`, `member_ids`, `name`, `sort_order`, `start_date`, `status`, `target_date`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `lead` (the module lead), `members` (the module members). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.modules:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | A `status` outside the enum, a `lead_id` who isn't a project member, or a `target_date` before `start_date`. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't edit this module. | +| `404` | `not_found` | No such module, workspace, or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | Another module in the project already uses this name. | +| `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. Wait for the interval in `Retry-After` and retry. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "7c1f3d90-2a64-4e58-9b0d-3fa1c7e28b45", + "name": "Billing revamp", + "description": "Rework subscription billing end to end.", + "status": "paused", + "start_date": "2026-01-05", + "target_date": "2026-03-13", + "lead_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "member_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430", "9d3e1f27-8b4c-4a06-95f1-2c7ea45b0d18"], + "sort_order": 65535.0, + "logo_props": {}, + "external_id": null, + "external_source": null, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "The request body failed validation.", + "errors": [ + { + "field": "lead_id", + "code": "does_not_exist", + "message": "Invalid pk \"b4d70c11-9e35-4a2f-8d6c-1f0ab3e97c52\" - object does not exist." + } + ] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/modules/upsert-module.md b/docs/api-reference/v2/modules/upsert-module.md new file mode 100644 index 00000000..7990b042 --- /dev/null +++ b/docs/api-reference/v2/modules/upsert-module.md @@ -0,0 +1,274 @@ +--- +title: Upsert a module +description: Upsert a module with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, upsert a module, modules, modules upsert +--- + +# Upsert a module + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/modules/upsert/ +
+ +
+
+ +Modules group work items into a deliverable inside a project. Create a module, or update the existing one that carries the same `(external_source, external_id)` pair. + +- Both `external_source` and `external_id` are required — without them there is nothing to match on and the request is a `400`. +- A created record answers `201` with `X-Plane-Upsert: created`; an updated one answers `200` with `X-Plane-Upsert: updated`. Branch on the header rather than guessing from the status. +- Upsert is safe for **sequential** importers. Two simultaneous upserts of the same key can each miss and each create, so serialize your writes per key. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Free-form description. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Id of the related lead. + +Nullable. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Manual ordering weight. Lower sorts first. + + + + + +Planned start date, as `YYYY-MM-DD`. + +Nullable. + + + + + +- `backlog` - Backlog +- `planned` - Planned +- `in-progress` - In Progress +- `paused` - Paused +- `completed` - Completed +- `cancelled` - Cancelled + +One of `backlog`, `planned`, `in-progress`, `paused`, `completed`, `cancelled`. + + + + + +Planned due date, as `YYYY-MM-DD`. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `lead_id`, `logo_props`, `member_ids`, `name`, `sort_order`, `start_date`, `status`, `target_date`. + + + + + +Comma-separated relations to embed: `lead`, `members`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.modules:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "name": "Example name", + "sort_order": 65535, + "start_date": "2026-01-12", + "status": "backlog", + "target_date": "2026-01-20" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/pagination.md b/docs/api-reference/v2/pagination.md new file mode 100644 index 00000000..5ca2bb9e --- /dev/null +++ b/docs/api-reference/v2/pagination.md @@ -0,0 +1,327 @@ +--- +title: Pagination +description: How Plane API v2 paginates list endpoints. Offset pagination by default, opt-in cursor pagination, the response envelopes for each, and how to page through a full result set. +keywords: plane api v2, pagination, offset pagination, cursor pagination, per_page, next_cursor, total_count, paginate cursor +--- + +# Pagination + +Every v2 list endpoint is paginated. A list response is always an **envelope object** — never a bare array — and the envelope tells you both what you got and how to get the rest. + +v2 has two pagination styles: + +| Style | How you get it | Envelope keys | Cost | Best for | +| ---------- | -------------------- | ------------------------------------------------------- | -------------------------- | ------------------------------------------------ | +| **Offset** | Default — do nothing | `data`, `next`, `previous`, `total_count`, `pagination` | Runs a `COUNT` per request | UI paging, jumping to a page, showing a total | +| **Cursor** | `?paginate=cursor` | `data`, `next_cursor`, `has_more`, `pagination` | No `COUNT` | Full exports, deep traversal, changing data sets | + +Both envelopes carry a `pagination.style` discriminator. Branch your client on that key rather than on which fields happen to be present — see [Write one loop for both styles](#write-one-loop-for-both-styles). + +## Offset pagination (default) + +Offset is what you get when you send nothing special. You control the window with `per_page` and `offset`. + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/?per_page=50&offset=0" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + + + +```json +{ + "data": [ + { + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect", + "identifier": "PROJ-118", + "sequence_id": 118, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": null, + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": [], + "parent_id": null, + "start_date": null, + "target_date": "2026-02-02", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next": 50, + "previous": null, + "total_count": 327, + "pagination": { "style": "offset" } +} +``` + + + +### Reading the envelope + +- `data` — the page of results. +- `next` — an **integer offset**, not a URL and not a token. Send it back as `?offset=` to get the following page. It is `null` on the last page. +- `previous` — the integer offset of the preceding page, or `null` when you are already at the start. +- `total_count` — how many rows match your filters across all pages. Omitted entirely when you pass `?count=false`. +- `pagination.style` — always `"offset"` here. + +Because `next` and `previous` are plain integers, you can also compute them yourself and jump straight to an arbitrary page — `?offset=200&per_page=50` is the fifth page, no traversal required. + +### Query parameters + +- `per_page` — page size. Defaults to `50`, capped at `200`. Values above the cap are clamped down rather than rejected; a non-integer value is a `400 invalid_request`. +- `offset` — rows to skip from the start of the result set. Defaults to `0`, maximum `10000`. A negative, non-integer, or over-cap `offset` is a `400 invalid_request`, and the over-cap message points you at cursor pagination. +- `count` — set `?count=false` to skip the `COUNT` query. `total_count` is then omitted from the envelope; `next` and `previous` still work. + +::: tip Turn off the count on hot paths +`total_count` costs a second database query on every request. If you are streaming a list into a job rather than rendering "327 results" in a UI, `?count=false` makes each page cheaper without changing how you traverse. +::: + +::: warning Offset stops at 10000 +`offset` is hard-capped at 10000 rows. If your result set is larger than that — a full export, a backfill, a nightly sync — switch to cursor pagination instead of trying to walk past the cap. +::: + +## Cursor pagination (opt-in) + +Cursor pagination is a keyset walk. Instead of counting rows to skip, each page hands you an opaque token that encodes where the previous page stopped. + +Opt in with `?paginate=cursor`, then follow `next_cursor` until `has_more` is `false`. + +```bash +# first page +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/?paginate=cursor&per_page=100" \ + -H "X-Api-Key: $PLANE_API_KEY" + +# every page after that — same parameters, plus the token +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/?per_page=100&cursor=b3A9MTcx" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + + + +```json +{ + "data": [ + { + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect", + "identifier": "PROJ-118", + "sequence_id": 118, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": null, + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": [], + "parent_id": null, + "start_date": null, + "target_date": "2026-02-02", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { "style": "cursor" } +} +``` + + + +### What is different + +- There is **no `total_count`** and no `count` parameter — cursor pagination never runs a `COUNT`. That is the point of it. +- There is **no `previous`**. Cursor traversal is forward-only. +- `next_cursor` is `null` and `has_more` is `false` on the last page. +- `per_page` behaves exactly as it does on the offset path: default `50`, max `200`. + +Passing `?cursor=` is enough to stay on the cursor path — you do not need to repeat `paginate=cursor` on follow-up requests. You **do** need to repeat everything else. The token encodes only the position in the walk; filters, `order_by`, and `per_page` are re-read from each request, so dropping them mid-traversal silently changes the result set you are walking. + +The token is opaque. Treat it as a string to hand straight back to the API: do not parse it, do not build one yourself, and do not persist it as a long-lived bookmark. A malformed or truncated token is a `400 invalid_request`. + +::: info Why cursor is stable +Offset pages are computed by skipping rows. If someone creates a work item while you are on page 3, everything shifts by one and you can see a row twice or miss it entirely. A cursor encodes the last row's sort key, so inserts and deletes elsewhere in the list cannot shift your window. +::: + +### Not every ordering can use a cursor + +A keyset walk needs an ordering whose key is unique and monotonic, so each resource declares an explicit allowlist of cursor-eligible `order_by` values rather than accepting any stored column. On work items the eligible values are `created_at`, `updated_at`, `sequence_id`, and `id`. + +Everything else is rejected on the cursor path. `sort_order` is a plain stored column but is not unique, so it cannot anchor a keyset. The **semantic orderings** cannot either — `order_by=priority` sorts `urgent` before `none` and `order_by=state_group` sorts by workflow meaning, and neither produces a key a cursor can resume from. + +Pairing one of those with cursor pagination returns a `400` with the stable code `ordering_not_cursor_eligible`: + + + +```json +{ + "type": "invalid_request", + "code": "ordering_not_cursor_eligible", + "detail": "This ordering can't be used with cursor pagination. Pass a cursor-safe ordering (e.g. order_by=created_at), or use the default offset pagination." +} +``` + + + +Two ways out, depending on what you actually need: + +- You need the semantic sort — drop `paginate=cursor` and use offset. +- You need the deep traversal — sort by a cursor-eligible column such as `created_at`, and sort the results yourself once you have them all. + +::: warning The check applies to the default ordering too +Eligibility is evaluated against the ordering that will actually run. If you omit `order_by`, that is the resource's own default — which is not always a cursor-eligible column. When a cursor request 400s even though you passed no `order_by`, pass an explicit cursor-eligible one such as `?order_by=created_at`. +::: + +See [Filtering and ordering](/api-reference/v2/filtering-and-ordering) for the full ordering contract and each resource's allowed `order_by` values. + +## Write one loop for both styles + +The two envelopes share the `data` key and the `pagination.style` discriminator, so a single auto-paginator can serve every list endpoint. Branch on `pagination.style` — not on `"next" in body`, which breaks the moment someone flips a call to cursor. + + + + + + + +## Choosing a style + +Reach for **offset** when a human is looking at the result: you want a total to display, you want to jump to page 7, and the list is small enough that the 10000-row cap is irrelevant. + +Reach for **cursor** when a machine is looking at the result: exports, backfills, incremental syncs, anything that walks a whole project's work items. It skips the `COUNT`, has no depth cap, and will not double-count rows if the data changes while you are traversing. + +## Making pages cheaper with `?fields=` + +Both envelopes compose with [`?fields=`](/api-reference/v2/sparse-fields), which is usually the biggest win available on +a paginated traversal — you pay for the keys you asked for, on every row of every page: + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/ENG/work-items/?fields=id,updated_at&paginate=cursor&per_page=200" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +Two things worth knowing when you combine them: + +- **The envelope keys are never filtered.** `?fields=` shapes the objects inside `data`; `next`, `total_count`, + `next_cursor`, `has_more` and `pagination` always come back. +- **Some resources leave heavy fields off list rows by default.** Pages, releases, customers, initiatives, teamspaces, + stickies, intake work items and templates omit `description_html` from collection rows unless you name it. So a plain + list is already cheaper than the object shape suggests — and `?fields=all` opts back into the full row. + +Pair `?count=false` with `?fields=` when you are draining a list and need neither the total nor most of the columns. + +## Related + +- [Filtering and ordering](/api-reference/v2/filtering-and-ordering) — narrowing a list before you page through it +- [Sparse fields](/api-reference/v2/sparse-fields) — `?fields=` for cheaper rows +- [Errors](/api-reference/v2/errors) — the full `problem+json` error contract and code list +- [Expanding relations](/api-reference/v2/expanding-relations) — `?expand=` on paginated lists diff --git a/docs/api-reference/v2/permission-schemes/get-permission-scheme.md b/docs/api-reference/v2/permission-schemes/get-permission-scheme.md new file mode 100644 index 00000000..1192debd --- /dev/null +++ b/docs/api-reference/v2/permission-schemes/get-permission-scheme.md @@ -0,0 +1,155 @@ +--- +title: Get a permission scheme +description: Read a permission scheme with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a permission scheme, permission schemes, permission schemes retrieve +--- + +# Get a permission scheme + +
+ GET + /api/v2/workspaces/{slug}/permission-schemes/{pk}/ +
+ +
+
+ +Permission schemes are the named bundles of grants a role can carry. Read a single permission scheme by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The permission scheme id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `description`, `id`, `is_system`, `name`, `namespace`, `permissions`, `slug`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`workspaces.roles:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_system": false, + "name": "Example name", + "namespace": "instance", + "permissions": ["example"], + "slug": "my-team", + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No permission scheme matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/permission-schemes/list-permission-schemes.md b/docs/api-reference/v2/permission-schemes/list-permission-schemes.md new file mode 100644 index 00000000..62e36537 --- /dev/null +++ b/docs/api-reference/v2/permission-schemes/list-permission-schemes.md @@ -0,0 +1,203 @@ +--- +title: List permission schemes +description: List permission schemes with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list permission schemes, permission schemes, permission schemes list +--- + +# List permission schemes + +
+ GET + /api/v2/workspaces/{slug}/permission-schemes/ +
+ +
+
+ +Permission schemes are the named bundles of grants a role can carry. List the permission schemes you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `description`, `id`, `is_system`, `name`, `namespace`, `permissions`, `slug`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`workspaces.roles:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_system": false, + "name": "Example name", + "namespace": "instance", + "permissions": ["example"], + "slug": "my-team", + "sort_order": 65535 + }, + { + "description": "What this is for.", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_system": false, + "name": "Another example", + "namespace": "instance", + "permissions": ["example"], + "slug": "my-team", + "sort_order": 65535 + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/permission-schemes/overview.md b/docs/api-reference/v2/permission-schemes/overview.md new file mode 100644 index 00000000..b564284a --- /dev/null +++ b/docs/api-reference/v2/permission-schemes/overview.md @@ -0,0 +1,82 @@ +--- +title: Permission schemes overview +description: The Plane API v2 permission scheme object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, permission schemes, permission scheme object +--- + +# Permission schemes overview + +Permission schemes are the named bundles of grants a role can carry. + +
+
+ +## The permission scheme object + +### Attributes + +- `description` _string_ + + Free-form description. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_system` _boolean_ + + Whether is system. + +- `name` _string_ + + Display name. + +- `namespace` _string_ + + The namespace. + +- `permissions` _array of string_ + + The permissions. + +- `slug` _string_ + + The slug. + +- `sort_order` _integer_ + + Manual ordering weight. Lower sorts first. + +
+
+ + + +```json +{ + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_system": false, + "name": "Example name", + "namespace": "instance", + "permissions": ["example"], + "slug": "my-team", + "sort_order": 65535 +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| ------ | ---------------------------------------------------- | ----------------------- | +| `GET` | `/api/v2/workspaces/{slug}/permission-schemes/` | List permission schemes | +| `GET` | `/api/v2/workspaces/{slug}/permission-schemes/{pk}/` | Get a permission scheme | + +## Response shaping + +Every permission scheme read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/permissions/get-project-permissions.md b/docs/api-reference/v2/permissions/get-project-permissions.md new file mode 100644 index 00000000..3301887e --- /dev/null +++ b/docs/api-reference/v2/permissions/get-project-permissions.md @@ -0,0 +1,132 @@ +--- +title: Get your effective project permissions +description: Read a effective permission with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get your effective project permissions, effective permissions, workspaces projects permissions me retrieve +--- + +# Get your effective project permissions + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/permissions/me/ +
+ +
+
+ +These endpoints report what the calling principal is actually allowed to do. Read a single effective permission by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Scopes + +This endpoint is exempt from fine-grained scope checks. + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "permission_grants": ["example"], + "relation": "example" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No effective permission matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/permissions/get-workspace-permissions.md b/docs/api-reference/v2/permissions/get-workspace-permissions.md new file mode 100644 index 00000000..3fb079a6 --- /dev/null +++ b/docs/api-reference/v2/permissions/get-workspace-permissions.md @@ -0,0 +1,123 @@ +--- +title: Get your effective workspace permissions +description: Read a effective permission with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get your effective workspace permissions, effective permissions, workspaces permissions me retrieve +--- + +# Get your effective workspace permissions + +
+ GET + /api/v2/workspaces/{slug}/permissions/me/ +
+ +
+
+ +These endpoints report what the calling principal is actually allowed to do. Read a single effective permission by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Scopes + +This endpoint is exempt from fine-grained scope checks. + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "permission_grants": ["example"], + "relation": "example" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No effective permission matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/permissions/overview.md b/docs/api-reference/v2/permissions/overview.md new file mode 100644 index 00000000..482eb08b --- /dev/null +++ b/docs/api-reference/v2/permissions/overview.md @@ -0,0 +1,52 @@ +--- +title: Permissions overview +description: The Plane API v2 permission object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, permissions, permission object +--- + +# Permissions overview + +These endpoints report what the calling principal is actually allowed to do. + +
+
+ +## The permission object + +### Attributes + +- `permission_grants` _array of string_ + + The permission grants. + +- `relation` _string_ + + The relation. + +
+
+ + + +```json +{ + "permission_grants": ["example"], + "relation": "example" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| ------ | ----------------------------------------------------------------- | ---------------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/permissions/me/` | Get your effective workspace permissions | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/permissions/me/` | Get your effective project permissions | + +## Response shaping + +Every permission read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/project-automations/create-automation-edge.md b/docs/api-reference/v2/project-automations/create-automation-edge.md new file mode 100644 index 00000000..e7cbd280 --- /dev/null +++ b/docs/api-reference/v2/project-automations/create-automation-edge.md @@ -0,0 +1,197 @@ +--- +title: Create a automation edge +description: Create a automation edge with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a automation edge, automation edges, project automation edges create +--- + +# Create a automation edge + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/edges/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Create a automation edge. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Id of the related source node. + + + + + +Id of the related target node. + + + + + +Order for evaluation when multiple edges from same node + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `execution_order`, `id`, `source_node_id`, `target_node_id`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "execution_order": 1, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "source_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/create-automation-node.md b/docs/api-reference/v2/project-automations/create-automation-node.md new file mode 100644 index 00000000..4c6bb658 --- /dev/null +++ b/docs/api-reference/v2/project-automations/create-automation-node.md @@ -0,0 +1,229 @@ +--- +title: Create a automation node +description: Create a automation node with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a automation node, automation nodes, project automation nodes create +--- + +# Create a automation node + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Create a automation node. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Name of the handler class (e.g., 'record_created', 'send_email') + +Maximum 100 characters. + + + + + +Display name for the node + +Maximum 255 characters. + + + + + +Type of node: trigger, action, or condition + +- `trigger` - Trigger +- `action` - Action +- `condition` - Condition + +One of `trigger`, `action`, `condition`. + + + + + +Node-specific configuration and parameters + + + + + +Whether the rule is switched on. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `config`, `created_at`, `created_by_id`, `handler_name`, `id`, `is_enabled`, `last_triggered_at`, `name`, `next_scheduled_at`, `node_type`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "config": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "handler_name": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "last_triggered_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "next_scheduled_at": "2026-01-14T09:22:41.478363Z", + "node_type": null, + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/create-project-automation.md b/docs/api-reference/v2/project-automations/create-project-automation.md new file mode 100644 index 00000000..8d00724e --- /dev/null +++ b/docs/api-reference/v2/project-automations/create-project-automation.md @@ -0,0 +1,219 @@ +--- +title: Create a project automation +description: Create a project automation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a project automation, project automations, project automations create +--- + +# Create a project automation + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/automations/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Create a project automation. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Human-readable automation name + +Maximum 255 characters. + + + + + +The scope defines on what entity this automation runs on (e.g., WorkItem, Cycle, Module) + +Maximum 50 characters. + + + + + +Automation description + + + + + +Ids of the projects to associate. Replaces the current set. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `bot_user_id`, `created_at`, `created_by_id`, `current_version_id`, `description`, `id`, `is_enabled`, `is_global`, `last_run_at`, `name`, `project_ids`, `run_count`, `scope`, `status`, `updated_at`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "bot_user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "current_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "is_global": false, + "last_run_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "run_count": 3, + "scope": "example", + "status": "draft", + "updated_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/delete-automation-edge.md b/docs/api-reference/v2/project-automations/delete-automation-edge.md new file mode 100644 index 00000000..1640eb5b --- /dev/null +++ b/docs/api-reference/v2/project-automations/delete-automation-edge.md @@ -0,0 +1,160 @@ +--- +title: Delete a automation edge +description: Delete a automation edge with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a automation edge, automation edges, project automation edges destroy +--- + +# Delete a automation edge + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/edges/{pk}/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Delete a automation edge. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + + + +The automation edge id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `execution_order`, `id`, `source_node_id`, `target_node_id`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation edge matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/delete-automation-node.md b/docs/api-reference/v2/project-automations/delete-automation-node.md new file mode 100644 index 00000000..5ed0e319 --- /dev/null +++ b/docs/api-reference/v2/project-automations/delete-automation-node.md @@ -0,0 +1,160 @@ +--- +title: Delete a automation node +description: Delete a automation node with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a automation node, automation nodes, project automation nodes destroy +--- + +# Delete a automation node + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/{pk}/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Delete a automation node. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + + + +The automation node id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `config`, `created_at`, `created_by_id`, `handler_name`, `id`, `is_enabled`, `last_triggered_at`, `name`, `next_scheduled_at`, `node_type`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation node matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/delete-project-automation.md b/docs/api-reference/v2/project-automations/delete-project-automation.md new file mode 100644 index 00000000..bfd5410b --- /dev/null +++ b/docs/api-reference/v2/project-automations/delete-project-automation.md @@ -0,0 +1,154 @@ +--- +title: Delete a project automation +description: Delete a project automation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a project automation, project automations, project automations destroy +--- + +# Delete a project automation + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{pk}/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Delete a project automation. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project automation id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `bot_user_id`, `created_at`, `created_by_id`, `current_version_id`, `description`, `id`, `is_enabled`, `is_global`, `last_run_at`, `name`, `project_ids`, `run_count`, `scope`, `status`, `updated_at`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project automation matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/get-automation-activity.md b/docs/api-reference/v2/project-automations/get-automation-activity.md new file mode 100644 index 00000000..86e2e770 --- /dev/null +++ b/docs/api-reference/v2/project-automations/get-automation-activity.md @@ -0,0 +1,176 @@ +--- +title: Get a automation activity +description: Read a automation activity with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a automation activity, automation activities, project automation activities retrieve +--- + +# Get a automation activity + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/activities/{pk}/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Read a single automation activity by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + + + +The automation activity id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `actor_id`, `automation_edge_id`, `automation_id`, `automation_node_id`, `automation_run_id`, `automation_scope`, `automation_version_id`, `created_at`, `epoch`, `field`, `id`, `new_identifier`, `new_value`, `node_execution_id`, `old_identifier`, `old_value`, `verb`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "actor_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_edge_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_run_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_scope": "example", + "automation_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "epoch": 1, + "field": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "new_identifier": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "new_value": "example", + "node_execution_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_identifier": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_value": "example", + "verb": "example" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation activity matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/get-automation-edge.md b/docs/api-reference/v2/project-automations/get-automation-edge.md new file mode 100644 index 00000000..ac2e582e --- /dev/null +++ b/docs/api-reference/v2/project-automations/get-automation-edge.md @@ -0,0 +1,167 @@ +--- +title: Get a automation edge +description: Read a automation edge with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a automation edge, automation edges, project automation edges retrieve +--- + +# Get a automation edge + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/edges/{pk}/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Read a single automation edge by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + + + +The automation edge id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `execution_order`, `id`, `source_node_id`, `target_node_id`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "execution_order": 1, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "source_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation edge matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/get-automation-node.md b/docs/api-reference/v2/project-automations/get-automation-node.md new file mode 100644 index 00000000..3139c92a --- /dev/null +++ b/docs/api-reference/v2/project-automations/get-automation-node.md @@ -0,0 +1,171 @@ +--- +title: Get a automation node +description: Read a automation node with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a automation node, automation nodes, project automation nodes retrieve +--- + +# Get a automation node + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/{pk}/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Read a single automation node by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + + + +The automation node id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `config`, `created_at`, `created_by_id`, `handler_name`, `id`, `is_enabled`, `last_triggered_at`, `name`, `next_scheduled_at`, `node_type`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "config": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "handler_name": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "last_triggered_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "next_scheduled_at": "2026-01-14T09:22:41.478363Z", + "node_type": null, + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation node matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/get-project-automation.md b/docs/api-reference/v2/project-automations/get-project-automation.md new file mode 100644 index 00000000..b7016993 --- /dev/null +++ b/docs/api-reference/v2/project-automations/get-project-automation.md @@ -0,0 +1,168 @@ +--- +title: Get a project automation +description: Read a project automation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a project automation, project automations, project automations retrieve +--- + +# Get a project automation + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{pk}/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Read a single project automation by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project automation id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `bot_user_id`, `created_at`, `created_by_id`, `current_version_id`, `description`, `id`, `is_enabled`, `is_global`, `last_run_at`, `name`, `project_ids`, `run_count`, `scope`, `status`, `updated_at`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "bot_user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "current_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "is_global": false, + "last_run_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "run_count": 3, + "scope": "example", + "status": "draft", + "updated_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project automation matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/list-automation-activities.md b/docs/api-reference/v2/project-automations/list-automation-activities.md new file mode 100644 index 00000000..0fd3edf1 --- /dev/null +++ b/docs/api-reference/v2/project-automations/list-automation-activities.md @@ -0,0 +1,229 @@ +--- +title: List automation activities +description: List automation activities with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list automation activities, automation activities, project automation activities list +--- + +# List automation activities + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/activities/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. List the automation activities you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `created_at__gt`. + + + + + +Filter by `field`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +Filter by `verb`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `actor_id`, `automation_edge_id`, `automation_id`, `automation_node_id`, `automation_run_id`, `automation_scope`, `automation_version_id`, `created_at`, `epoch`, `field`, `id`, `new_identifier`, `new_value`, `node_execution_id`, `old_identifier`, `old_value`, `verb`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "actor_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_edge_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_run_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_scope": "example", + "automation_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "epoch": 1, + "field": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "new_identifier": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "new_value": "example", + "node_execution_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_identifier": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_value": "example", + "verb": "example" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/list-automation-edges.md b/docs/api-reference/v2/project-automations/list-automation-edges.md new file mode 100644 index 00000000..6dc333f8 --- /dev/null +++ b/docs/api-reference/v2/project-automations/list-automation-edges.md @@ -0,0 +1,224 @@ +--- +title: List automation edges +description: List automation edges with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list automation edges, automation edges, project automation edges list +--- + +# List automation edges + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/edges/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. List the automation edges you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +Filter by `source_node_id`. + + + + + +Filter by `target_node_id`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `execution_order`, `id`, `source_node_id`, `target_node_id`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "execution_order": 1, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "source_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "execution_order": 1, + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "source_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/list-automation-nodes.md b/docs/api-reference/v2/project-automations/list-automation-nodes.md new file mode 100644 index 00000000..5ad53b06 --- /dev/null +++ b/docs/api-reference/v2/project-automations/list-automation-nodes.md @@ -0,0 +1,250 @@ +--- +title: List automation nodes +description: List automation nodes with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list automation nodes, automation nodes, project automation nodes list +--- + +# List automation nodes + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. List the automation nodes you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `handler_name`. + + + + + +Filter by `is_enabled`. + + + + + +Filter by `name`. + + + + + +Filter by `node_type`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `config`, `created_at`, `created_by_id`, `handler_name`, `id`, `is_enabled`, `last_triggered_at`, `name`, `next_scheduled_at`, `node_type`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "config": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "handler_name": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "last_triggered_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "next_scheduled_at": "2026-01-14T09:22:41.478363Z", + "node_type": null, + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "config": null, + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "handler_name": "example", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_enabled": false, + "last_triggered_at": "2026-01-14T09:22:41.478363Z", + "name": "Another example", + "next_scheduled_at": "2026-01-14T09:22:41.478363Z", + "node_type": null, + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/list-project-automations.md b/docs/api-reference/v2/project-automations/list-project-automations.md new file mode 100644 index 00000000..1e174bbf --- /dev/null +++ b/docs/api-reference/v2/project-automations/list-project-automations.md @@ -0,0 +1,239 @@ +--- +title: List project automations +description: List project automations with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list project automations, project automations, project automations list +--- + +# List project automations + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/automations/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. List the project automations you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `is_enabled`. + + + + + +Filter by `is_global`. + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +Filter by `scope`. + + + + + +A search term. + + + + + +Filter by `status`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `bot_user_id`, `created_at`, `created_by_id`, `current_version_id`, `description`, `id`, `is_enabled`, `is_global`, `last_run_at`, `name`, `project_ids`, `run_count`, `scope`, `status`, `updated_at`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "bot_user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "current_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "is_global": false, + "last_run_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "run_count": 3, + "scope": "example", + "status": "draft", + "updated_at": "2026-01-14T09:22:41.478363Z" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/overview.md b/docs/api-reference/v2/project-automations/overview.md new file mode 100644 index 00000000..9302263c --- /dev/null +++ b/docs/api-reference/v2/project-automations/overview.md @@ -0,0 +1,144 @@ +--- +title: Project automations overview +description: The Plane API v2 project automation object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, project automations, project automation object +--- + +# Project automations overview + +Project automations are rule graphs that run inside one project. + +
+
+ +## The project automation object + +### Attributes + +- `actor_id` _string (uuid)_ + + The related actor. + +- `automation_edge_id` _string (uuid)_ + + The related automation edge. + +- `automation_id` _string (uuid)_ + + The related automation. + +- `automation_node_id` _string (uuid)_ + + The related automation node. + +- `automation_run_id` _string (uuid)_ + + The related automation run. + +- `automation_scope` _string_ + + The automation scope. + +- `automation_version_id` _string (uuid)_ + + The related automation version. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `epoch` _number_ + + The epoch. + +- `field` _string_ + + The field. + +- `id` _string (uuid)_ + + Unique identifier. + +- `new_identifier` _string (uuid)_ + + The new identifier. + +- `new_value` _string_ + + The new value. + +- `node_execution_id` _string (uuid)_ + + The related node execution. + +- `old_identifier` _string (uuid)_ + + The old identifier. + +- `old_value` _string_ + + The old value. + +- `verb` _string_ + + The verb. + +
+
+ + + +```json +{ + "actor_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_edge_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_run_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_scope": "example", + "automation_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "epoch": 1, + "field": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "new_identifier": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "new_value": "example", + "node_execution_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_identifier": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_value": "example", + "verb": "example" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/` | List project automations | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/` | Create a project automation | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/activities/` | List automation activities | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/activities/{pk}/` | Get a automation activity | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/edges/` | List automation edges | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/edges/` | Create a automation edge | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/edges/{pk}/` | Delete a automation edge | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/edges/{pk}/` | Get a automation edge | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/edges/{pk}/` | Update a automation edge | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/` | List automation nodes | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/` | Create a automation node | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/{pk}/` | Delete a automation node | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/{pk}/` | Get a automation node | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/{pk}/` | Update a automation node | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/{pk}/regenerate-webhook-secret/` | Regenerate an automation node webhook secret | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{pk}/` | Delete a project automation | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{pk}/` | Get a project automation | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{pk}/` | Update a project automation | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/automations/{pk}/status/` | Enable or disable a project automation | + +## Response shaping + +Every project automation read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/project-automations/regenerate-node-webhook-secret.md b/docs/api-reference/v2/project-automations/regenerate-node-webhook-secret.md new file mode 100644 index 00000000..aa1193d3 --- /dev/null +++ b/docs/api-reference/v2/project-automations/regenerate-node-webhook-secret.md @@ -0,0 +1,135 @@ +--- +title: Regenerate an automation node webhook secret +description: Regenerate an automation node webhook secret with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, regenerate an automation node webhook secret, automation nodes, project automation nodes regenerate webhook secret +--- + +# Regenerate an automation node webhook secret + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/{pk}/regenerate-webhook-secret/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + + + +The automation node id. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "secret": "example" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/set-project-automation-status.md b/docs/api-reference/v2/project-automations/set-project-automation-status.md new file mode 100644 index 00000000..db95df37 --- /dev/null +++ b/docs/api-reference/v2/project-automations/set-project-automation-status.md @@ -0,0 +1,151 @@ +--- +title: Enable or disable a project automation +description: Enable or disable a project automation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, enable or disable a project automation, project automations, project automations status +--- + +# Enable or disable a project automation + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{pk}/status/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project automation id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Whether the rule is switched on. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + +
+
diff --git a/docs/api-reference/v2/project-automations/update-automation-edge.md b/docs/api-reference/v2/project-automations/update-automation-edge.md new file mode 100644 index 00000000..db2e937d --- /dev/null +++ b/docs/api-reference/v2/project-automations/update-automation-edge.md @@ -0,0 +1,215 @@ +--- +title: Update a automation edge +description: Update a automation edge with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a automation edge, automation edges, project automation edges partial update +--- + +# Update a automation edge + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/edges/{pk}/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Update a automation edge. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + + + +The automation edge id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Order for evaluation when multiple edges from same node + + + + + +Id of the related source node. + + + + + +Id of the related target node. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `execution_order`, `id`, `source_node_id`, `target_node_id`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "execution_order": 1, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "source_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation edge matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/update-automation-node.md b/docs/api-reference/v2/project-automations/update-automation-node.md new file mode 100644 index 00000000..8fcbfc4e --- /dev/null +++ b/docs/api-reference/v2/project-automations/update-automation-node.md @@ -0,0 +1,241 @@ +--- +title: Update a automation node +description: Update a automation node with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a automation node, automation nodes, project automation nodes partial update +--- + +# Update a automation node + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/{pk}/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Update a automation node. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The automation the resource belongs to. + + + + + +The automation node id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Node-specific configuration and parameters + + + + + +Name of the handler class (e.g., 'record_created', 'send_email') + +Maximum 100 characters. + + + + + +Whether the rule is switched on. + + + + + +Display name for the node + +Maximum 255 characters. + + + + + +Type of node: trigger, action, or condition + +- `trigger` - Trigger +- `action` - Action +- `condition` - Condition + +One of `trigger`, `action`, `condition`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `config`, `created_at`, `created_by_id`, `handler_name`, `id`, `is_enabled`, `last_triggered_at`, `name`, `next_scheduled_at`, `node_type`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "config": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "handler_name": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "last_triggered_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "next_scheduled_at": "2026-01-14T09:22:41.478363Z", + "node_type": null, + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation node matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-automations/update-project-automation.md b/docs/api-reference/v2/project-automations/update-project-automation.md new file mode 100644 index 00000000..e3f75fca --- /dev/null +++ b/docs/api-reference/v2/project-automations/update-project-automation.md @@ -0,0 +1,234 @@ +--- +title: Update a project automation +description: Update a project automation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a project automation, project automations, project automations partial update +--- + +# Update a project automation + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/automations/{pk}/ +
+ +
+
+ +Project automations are rule graphs that run inside one project. Update a project automation. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project automation id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Automation description + + + + + +Human-readable automation name + +Maximum 255 characters. + + + + + +Ids of the projects to associate. Replaces the current set. + + + + + +The scope defines on what entity this automation runs on (e.g., WorkItem, Cycle, Module) + +Maximum 50 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `bot_user_id`, `created_at`, `created_by_id`, `current_version_id`, `description`, `id`, `is_enabled`, `is_global`, `last_run_at`, `name`, `project_ids`, `run_count`, `scope`, `status`, `updated_at`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "bot_user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "current_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "is_global": false, + "last_run_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "run_count": 3, + "scope": "example", + "status": "draft", + "updated_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project automation matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-features/get-project-features.md b/docs/api-reference/v2/project-features/get-project-features.md new file mode 100644 index 00000000..c48cd6ba --- /dev/null +++ b/docs/api-reference/v2/project-features/get-project-features.md @@ -0,0 +1,137 @@ +--- +title: Get project features +description: Read a project feature with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get project features, project features, project features retrieve +--- + +# Get project features + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/features/ +
+ +
+
+ +Project features are the per-project toggles for optional Plane surfaces. Read a single project feature by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Scopes + +`projects.features:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "is_automated_cycle_enabled": false, + "is_epic_enabled": false, + "is_manually_start_end_cycles_enabled": false, + "is_milestone_enabled": false, + "is_parallel_cycles_enabled": false, + "is_project_updates_enabled": false, + "is_workflow_enabled": false +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project feature matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-features/overview.md b/docs/api-reference/v2/project-features/overview.md new file mode 100644 index 00000000..9d6e972f --- /dev/null +++ b/docs/api-reference/v2/project-features/overview.md @@ -0,0 +1,77 @@ +--- +title: Project features overview +description: The Plane API v2 project feature object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, project features, project feature object +--- + +# Project features overview + +Project features are the per-project toggles for optional Plane surfaces. + +
+
+ +## The project feature object + +### Attributes + +- `is_automated_cycle_enabled` _boolean_ + + Whether is automated cycle enabled. + +- `is_epic_enabled` _boolean_ + + Whether is epic enabled. + +- `is_manually_start_end_cycles_enabled` _boolean_ + + Whether is manually start end cycles enabled. + +- `is_milestone_enabled` _boolean_ + + Whether is milestone enabled. + +- `is_parallel_cycles_enabled` _boolean_ + + Whether is parallel cycles enabled. + +- `is_project_updates_enabled` _boolean_ + + Whether is project updates enabled. + +- `is_workflow_enabled` _boolean_ + + Whether is workflow enabled. + +
+
+ + + +```json +{ + "is_automated_cycle_enabled": false, + "is_epic_enabled": false, + "is_manually_start_end_cycles_enabled": false, + "is_milestone_enabled": false, + "is_parallel_cycles_enabled": false, + "is_project_updates_enabled": false, + "is_workflow_enabled": false +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| ------- | ----------------------------------------------------------- | ----------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/features/` | Get project features | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/features/` | Update project features | + +## Response shaping + +Every project feature read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/project-features/update-project-features.md b/docs/api-reference/v2/project-features/update-project-features.md new file mode 100644 index 00000000..ec17fcd4 --- /dev/null +++ b/docs/api-reference/v2/project-features/update-project-features.md @@ -0,0 +1,197 @@ +--- +title: Update project features +description: Update project features with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update project features, project features, project features update +--- + +# Update project features + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/features/ +
+ +
+
+ +Project features are the per-project toggles for optional Plane surfaces. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Whether is automated cycle enabled. + + + + + +Whether is epic enabled. + + + + + +Whether is manually start end cycles enabled. + + + + + +Whether is milestone enabled. + + + + + +Whether is parallel cycles enabled. + + + + + +Whether is project updates enabled. + + + + + +Whether is workflow enabled. + + + +
+
+ +
+ +### Scopes + +`projects.features:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "is_automated_cycle_enabled": false, + "is_epic_enabled": false, + "is_manually_start_end_cycles_enabled": false, + "is_milestone_enabled": false, + "is_parallel_cycles_enabled": false, + "is_project_updates_enabled": false, + "is_workflow_enabled": false +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-pages/create-project-page.md b/docs/api-reference/v2/project-pages/create-project-page.md new file mode 100644 index 00000000..cf6a5afb --- /dev/null +++ b/docs/api-reference/v2/project-pages/create-project-page.md @@ -0,0 +1,285 @@ +--- +title: Create a project page +description: Create a project page with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a project page, project pages, project pages create +--- + +# Create a project page + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/pages/ +
+ +
+
+ +Project pages are the documents that live inside a project. Create a project page. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + + + + + +Who can see this. + + + + + +The archived at. + +Nullable. + + + + + +Id of the related collection. + +Nullable. + + + + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Prevents further edits to the content. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Id of the related parent. + +Nullable. + + + + + +Manual ordering weight. Lower sorts first. + + + + + +Editor-owned layout descriptor. Pass back what you read. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `collection_id`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `external_id`, `external_source`, `id`, `is_global`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `parent_id`, `sort_order`, `view_props`. + + + + + +Comma-separated relations to embed: `owned_by`, `parent`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.pages:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "archived_at": null, + "collection_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_global": false, + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535, + "view_props": null +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/project-pages/delete-project-page.md b/docs/api-reference/v2/project-pages/delete-project-page.md new file mode 100644 index 00000000..8f4bf650 --- /dev/null +++ b/docs/api-reference/v2/project-pages/delete-project-page.md @@ -0,0 +1,162 @@ +--- +title: Delete a project page +description: Delete a project page with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a project page, project pages, project pages destroy +--- + +# Delete a project page + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/pages/{pk}/ +
+ +
+
+ +Project pages are the documents that live inside a project. Delete a project page. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project page id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `collection_id`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `external_id`, `external_source`, `id`, `is_global`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `parent_id`, `sort_order`, `view_props`. + + + + + +Comma-separated relations to embed: `owned_by`, `parent`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.pages:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project page matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-pages/get-project-page.md b/docs/api-reference/v2/project-pages/get-project-page.md new file mode 100644 index 00000000..a337fdd0 --- /dev/null +++ b/docs/api-reference/v2/project-pages/get-project-page.md @@ -0,0 +1,180 @@ +--- +title: Get a project page +description: Read a project page with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a project page, project pages, project pages retrieve +--- + +# Get a project page + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/pages/{pk}/ +
+ +
+
+ +Project pages are the documents that live inside a project. Read a single project page by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project page id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `collection_id`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `external_id`, `external_source`, `id`, `is_global`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `parent_id`, `sort_order`, `view_props`. + + + + + +Comma-separated relations to embed: `owned_by`, `parent`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.pages:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "archived_at": null, + "collection_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_global": false, + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535, + "view_props": null +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project page matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-pages/list-project-pages.md b/docs/api-reference/v2/project-pages/list-project-pages.md new file mode 100644 index 00000000..2f55036c --- /dev/null +++ b/docs/api-reference/v2/project-pages/list-project-pages.md @@ -0,0 +1,284 @@ +--- +title: List project pages +description: List project pages with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list project pages, project pages, project pages list +--- + +# List project pages + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/pages/ +
+ +
+
+ +Project pages are the documents that live inside a project. List the project pages you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +- `0` - Public +- `1` - Private + +One of `0`, `1`. + + + + + +Filter by `collection_id`. + + + + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `external_id`. + + + + + +Filter by `external_source`. + + + + + +Filter by `is_global`. + + + + + +Filter by `is_locked`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Filter by `owned_by_id`. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Filter by `parent_id`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +- `all` - All +- `public` - Public +- `private` - Private +- `shared` - Shared +- `archived` - Archived + +One of `all`, `archived`, `private`, `public`, `shared`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `collection_id`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `external_id`, `external_source`, `id`, `is_global`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `parent_id`, `sort_order`, `view_props`. + + + + + +Comma-separated relations to embed: `owned_by`, `parent`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.pages:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "access": 0, + "archived_at": null, + "collection_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_global": false, + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535, + "view_props": null + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/project-pages/overview.md b/docs/api-reference/v2/project-pages/overview.md new file mode 100644 index 00000000..72d118a8 --- /dev/null +++ b/docs/api-reference/v2/project-pages/overview.md @@ -0,0 +1,140 @@ +--- +title: Project pages overview +description: The Plane API v2 project page object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, project pages, project page object +--- + +# Project pages overview + +Project pages are the documents that live inside a project. + +
+
+ +## The project page object + +### Attributes + +- `access` _integer_ + + Who can see this. + +- `archived_at` _string (date-time)_ + + When the record was archived, or `null` if it is active. + +- `collection_id` _string_ + + The related collection. + +- `color` _string_ + + Hex color used wherever this is rendered, for example `#3f76ff`. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description_html` _string_ + + Rich-text body as HTML. This is the field the Plane editor round-trips. + +- `description_stripped` _string_ + + The description stripped. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_global` _boolean_ + + Whether this lives at the workspace level rather than inside a project. + +- `is_locked` _boolean_ + + Prevents further edits to the content. + +- `logo_props` _string_ + + Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +- `name` _string_ + + Display name. + +- `owned_by_id` _string (uuid)_ + + The related owned by. + +- `parent_id` _string (uuid)_ + + The related parent. + +- `sort_order` _number_ + + Manual ordering weight. Lower sorts first. + +- `view_props` _string_ + + Editor-owned layout descriptor. Pass back what you read. + +
+
+ + + +```json +{ + "access": 0, + "archived_at": null, + "collection_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_global": false, + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535, + "view_props": null +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------------- | --------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/pages/` | List project pages | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/pages/` | Create a project page | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/pages/{pk}/` | Delete a project page | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/pages/{pk}/` | Get a project page | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/pages/{pk}/` | Update a project page | + +## Response shaping + +Every project page read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/project-pages/update-project-page.md b/docs/api-reference/v2/project-pages/update-project-page.md new file mode 100644 index 00000000..ae35762b --- /dev/null +++ b/docs/api-reference/v2/project-pages/update-project-page.md @@ -0,0 +1,300 @@ +--- +title: Update a project page +description: Update a project page with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a project page, project pages, project pages partial update +--- + +# Update a project page + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/pages/{pk}/ +
+ +
+
+ +Project pages are the documents that live inside a project. Update a project page. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project page id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Who can see this. + + + + + +The archived at. + +Nullable. + + + + + +Id of the related collection. + +Nullable. + + + + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Prevents further edits to the content. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Display name. + + + + + +Id of the related parent. + +Nullable. + + + + + +Manual ordering weight. Lower sorts first. + + + + + +Editor-owned layout descriptor. Pass back what you read. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `collection_id`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `external_id`, `external_source`, `id`, `is_global`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `parent_id`, `sort_order`, `view_props`. + + + + + +Comma-separated relations to embed: `owned_by`, `parent`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.pages:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "archived_at": null, + "collection_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_global": false, + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535, + "view_props": null +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project page matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-views/create-project-view.md b/docs/api-reference/v2/project-views/create-project-view.md new file mode 100644 index 00000000..7a142b42 --- /dev/null +++ b/docs/api-reference/v2/project-views/create-project-view.md @@ -0,0 +1,254 @@ +--- +title: Create a project view +description: Create a project view with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a project view, project views, project views create +--- + +# Create a project view + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/views/ +
+ +
+
+ +Project views are saved filter-and-layout combinations for a project. Create a project view. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Who can see this. + + + + + +Free-form description. + + + + + +Saved display options — grouping, ordering and layout. + + + + + +The display properties. + + + + + +Saved filter set, in the same shape the list endpoints accept. + + + + + +Prevents further edits to the content. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +The pql filters. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `created_at`, `created_by_id`, `description`, `display_filters`, `display_properties`, `filters`, `id`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `pql_filters`, `query`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.views:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "display_filters": null, + "display_properties": null, + "filters": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "pql_filters": null, + "query": null, + "sort_order": 65535 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-views/delete-project-view.md b/docs/api-reference/v2/project-views/delete-project-view.md new file mode 100644 index 00000000..47077065 --- /dev/null +++ b/docs/api-reference/v2/project-views/delete-project-view.md @@ -0,0 +1,162 @@ +--- +title: Delete a project view +description: Delete a project view with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a project view, project views, project views destroy +--- + +# Delete a project view + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/views/{pk}/ +
+ +
+
+ +Project views are saved filter-and-layout combinations for a project. Delete a project view. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project view id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `created_at`, `created_by_id`, `description`, `display_filters`, `display_properties`, `filters`, `id`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `pql_filters`, `query`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.views:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project view matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-views/get-project-view.md b/docs/api-reference/v2/project-views/get-project-view.md new file mode 100644 index 00000000..7e3a00a2 --- /dev/null +++ b/docs/api-reference/v2/project-views/get-project-view.md @@ -0,0 +1,177 @@ +--- +title: Get a project view +description: Read a project view with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a project view, project views, project views retrieve +--- + +# Get a project view + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/views/{pk}/ +
+ +
+
+ +Project views are saved filter-and-layout combinations for a project. Read a single project view by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project view id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `created_at`, `created_by_id`, `description`, `display_filters`, `display_properties`, `filters`, `id`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `pql_filters`, `query`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.views:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "display_filters": null, + "display_properties": null, + "filters": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "pql_filters": null, + "query": null, + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project view matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-views/list-project-views.md b/docs/api-reference/v2/project-views/list-project-views.md new file mode 100644 index 00000000..e03a5c8d --- /dev/null +++ b/docs/api-reference/v2/project-views/list-project-views.md @@ -0,0 +1,245 @@ +--- +title: List project views +description: List project views with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list project views, project views, project views list +--- + +# List project views + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/views/ +
+ +
+
+ +Project views are saved filter-and-layout combinations for a project. List the project views you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +- `0` - Private +- `1` - Public + +One of `0`, `1`. + + + + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `is_locked`. + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Filter by `owned_by_id`. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `created_at`, `created_by_id`, `description`, `display_filters`, `display_properties`, `filters`, `id`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `pql_filters`, `query`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.views:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "access": 0, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "display_filters": null, + "display_properties": null, + "filters": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "pql_filters": null, + "query": null, + "sort_order": 65535 + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-views/overview.md b/docs/api-reference/v2/project-views/overview.md new file mode 100644 index 00000000..535c4da5 --- /dev/null +++ b/docs/api-reference/v2/project-views/overview.md @@ -0,0 +1,125 @@ +--- +title: Project views overview +description: The Plane API v2 project view object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, project views, project view object +--- + +# Project views overview + +Project views are saved filter-and-layout combinations for a project. + +
+
+ +## The project view object + +### Attributes + +- `access` _integer_ + + Who can see this. + +- `archived_at` _string (date-time)_ + + When the record was archived, or `null` if it is active. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description` _string_ + + Free-form description. + +- `display_filters` _string_ + + Saved display options — grouping, ordering and layout. + +- `display_properties` _string_ + + The display properties. + +- `filters` _string_ + + Saved filter set, in the same shape the list endpoints accept. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_locked` _boolean_ + + Prevents further edits to the content. + +- `logo_props` _string_ + + Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +- `name` _string_ + + Display name. + +- `owned_by_id` _string (uuid)_ + + The related owned by. + +- `pql_filters` _string_ + + The pql filters. + +- `query` _string_ + + The query. + +- `sort_order` _number_ + + Manual ordering weight. Lower sorts first. + +
+
+ + + +```json +{ + "access": 0, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "display_filters": null, + "display_properties": null, + "filters": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "pql_filters": null, + "query": null, + "sort_order": 65535 +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------------- | --------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/views/` | List project views | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/views/` | Create a project view | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/views/{pk}/` | Delete a project view | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/views/{pk}/` | Get a project view | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/views/{pk}/` | Update a project view | + +## Response shaping + +Every project view read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/project-views/update-project-view.md b/docs/api-reference/v2/project-views/update-project-view.md new file mode 100644 index 00000000..08aa74e0 --- /dev/null +++ b/docs/api-reference/v2/project-views/update-project-view.md @@ -0,0 +1,269 @@ +--- +title: Update a project view +description: Update a project view with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a project view, project views, project views partial update +--- + +# Update a project view + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/views/{pk}/ +
+ +
+
+ +Project views are saved filter-and-layout combinations for a project. Update a project view. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project view id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Who can see this. + + + + + +Free-form description. + + + + + +Saved display options — grouping, ordering and layout. + + + + + +The display properties. + + + + + +Saved filter set, in the same shape the list endpoints accept. + + + + + +Prevents further edits to the content. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Display name. + +Maximum 255 characters. + + + + + +The pql filters. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `created_at`, `created_by_id`, `description`, `display_filters`, `display_properties`, `filters`, `id`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `pql_filters`, `query`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.views:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "display_filters": null, + "display_properties": null, + "filters": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "pql_filters": null, + "query": null, + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project view matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-work-item-templates/create-project-work-item-template.md b/docs/api-reference/v2/project-work-item-templates/create-project-work-item-template.md new file mode 100644 index 00000000..e80f01d0 --- /dev/null +++ b/docs/api-reference/v2/project-work-item-templates/create-project-work-item-template.md @@ -0,0 +1,246 @@ +--- +title: Create a project work item template +description: Create a project work item template with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a project work item template, project work item templates, project work item templates create +--- + +# Create a project work item template + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-templates/ +
+ +
+
+ +Project work item templates pre-fill new work items in one project. Create a project work item template. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Seed payload used on create/update and nested under read `template_data`. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Whether is published. + + + + + +The short description. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `is_published`, `name`, `short_description`, `short_id`, `slug`, `template_data`, `template_type`. + + + +
+
+ +
+ +### Scopes + +`templates.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_published": false, + "name": "Example name", + "short_description": "example", + "short_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team", + "template_data": {}, + "template_type": "example" +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/project-work-item-templates/delete-project-work-item-template.md b/docs/api-reference/v2/project-work-item-templates/delete-project-work-item-template.md new file mode 100644 index 00000000..fa3e1511 --- /dev/null +++ b/docs/api-reference/v2/project-work-item-templates/delete-project-work-item-template.md @@ -0,0 +1,154 @@ +--- +title: Delete a project work item template +description: Delete a project work item template with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a project work item template, project work item templates, project work item templates destroy +--- + +# Delete a project work item template + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-templates/{pk}/ +
+ +
+
+ +Project work item templates pre-fill new work items in one project. Delete a project work item template. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project work item template id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `is_published`, `name`, `short_description`, `short_id`, `slug`, `template_data`, `template_type`. + + + +
+
+ +
+ +### Scopes + +`templates.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project work item template matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-work-item-templates/get-project-work-item-template.md b/docs/api-reference/v2/project-work-item-templates/get-project-work-item-template.md new file mode 100644 index 00000000..38fda240 --- /dev/null +++ b/docs/api-reference/v2/project-work-item-templates/get-project-work-item-template.md @@ -0,0 +1,164 @@ +--- +title: Get a project work item template +description: Read a project work item template with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a project work item template, project work item templates, project work item templates retrieve +--- + +# Get a project work item template + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-templates/{pk}/ +
+ +
+
+ +Project work item templates pre-fill new work items in one project. Read a single project work item template by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project work item template id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `is_published`, `name`, `short_description`, `short_id`, `slug`, `template_data`, `template_type`. + + + +
+
+ +
+ +### Scopes + +`templates.work_items:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_published": false, + "name": "Example name", + "short_description": "example", + "short_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team", + "template_data": {}, + "template_type": "example" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project work item template matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-work-item-templates/list-project-work-item-templates.md b/docs/api-reference/v2/project-work-item-templates/list-project-work-item-templates.md new file mode 100644 index 00000000..2312bf8a --- /dev/null +++ b/docs/api-reference/v2/project-work-item-templates/list-project-work-item-templates.md @@ -0,0 +1,230 @@ +--- +title: List project work item templates +description: List project work item templates with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list project work item templates, project work item templates, project work item templates list +--- + +# List project work item templates + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-templates/ +
+ +
+
+ +Project work item templates pre-fill new work items in one project. List the project work item templates you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `is_published`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +Filter by `short_id`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `is_published`, `name`, `short_description`, `short_id`, `slug`, `template_data`, `template_type`. + + + +
+
+ +
+ +### Scopes + +`templates.work_items:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_published": false, + "name": "Example name", + "short_description": "example", + "short_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team", + "template_data": {}, + "template_type": "example" + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_published": false, + "name": "Another example", + "short_description": "example", + "short_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team", + "template_data": {}, + "template_type": "example" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/project-work-item-templates/overview.md b/docs/api-reference/v2/project-work-item-templates/overview.md new file mode 100644 index 00000000..6e47ebca --- /dev/null +++ b/docs/api-reference/v2/project-work-item-templates/overview.md @@ -0,0 +1,101 @@ +--- +title: Project work item templates overview +description: The Plane API v2 project work item template object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, project work item templates, project work item template object +--- + +# Project work item templates overview + +Project work item templates pre-fill new work items in one project. + +
+
+ +## The project work item template object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description_html` _string_ + + Rich-text body as HTML. This is the field the Plane editor round-trips. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_published` _boolean_ + + Whether is published. + +- `name` _string_ + + Display name. + +- `short_description` _string_ + + The short description. + +- `short_id` _string_ + + The related short. + +- `slug` _string_ + + The slug. + +- `template_data` _object_ + + The template data. + +- `template_type` _string_ + + The template type. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_published": false, + "name": "Example name", + "short_description": "example", + "short_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team", + "template_data": {}, + "template_type": "example" +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------------------------------- | ----------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-templates/` | List project work item templates | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-templates/` | Create a project work item template | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-templates/{pk}/` | Delete a project work item template | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-templates/{pk}/` | Get a project work item template | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-templates/{pk}/` | Update a project work item template | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-templates/{pk}/use/` | Create a work item from a template | + +## Response shaping + +Every project work item template read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/project-work-item-templates/update-project-work-item-template.md b/docs/api-reference/v2/project-work-item-templates/update-project-work-item-template.md new file mode 100644 index 00000000..0375b327 --- /dev/null +++ b/docs/api-reference/v2/project-work-item-templates/update-project-work-item-template.md @@ -0,0 +1,228 @@ +--- +title: Update a project work item template +description: Update a project work item template with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a project work item template, project work item templates, project work item templates partial update +--- + +# Update a project work item template + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-templates/{pk}/ +
+ +
+
+ +Project work item templates pre-fill new work items in one project. Update a project work item template. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The project work item template id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Whether is published. + + + + + +Display name. + +Maximum 255 characters. + + + + + +The short description. + +Nullable. + + + + + +Seed payload used on create/update and nested under read `template_data`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `is_published`, `name`, `short_description`, `short_id`, `slug`, `template_data`, `template_type`. + + + +
+
+ +
+ +### Scopes + +`templates.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_published": false, + "name": "Example name", + "short_description": "example", + "short_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team", + "template_data": {}, + "template_type": "example" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project work item template matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/project-work-item-templates/use-work-item-template.md b/docs/api-reference/v2/project-work-item-templates/use-work-item-template.md new file mode 100644 index 00000000..29a45b0c --- /dev/null +++ b/docs/api-reference/v2/project-work-item-templates/use-work-item-template.md @@ -0,0 +1,208 @@ +--- +title: Create a work item from a template +description: Create a work item from a template with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a work item from a template, work items from template, work items use +--- + +# Create a work item from a template + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-templates/{pk}/use/ +
+ +
+
+ +Project work item templates pre-fill new work items in one project. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item from template id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + + + + + +Id of the related project. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `assignee_ids`, `created_at`, `created_by_id`, `custom_fields`, `cycle_id`, `id`, `identifier`, `is_draft`, `label_ids`, `module_ids`, `name`, `parent_id`, `priority`, `project_id`, `sequence_id`, `start_date`, `state_id`, `target_date`, `type_id`. + + + + + +Comma-separated relations to embed: `assignees`, `cycle`, `labels`, `modules`, `parent`, `state`, `type`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`templates.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "assignee_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "custom_fields": {}, + "cycle_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "identifier": "PROJ", + "is_draft": false, + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "module_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "name": "Example name", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "priority": "high", + "project_id": "4af68566-94a4-4eb3-94aa-50dc9427067b", + "sequence_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "start_date": "2026-01-12", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20", + "type_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/projects/archive-project.md b/docs/api-reference/v2/projects/archive-project.md new file mode 100644 index 00000000..067d3320 --- /dev/null +++ b/docs/api-reference/v2/projects/archive-project.md @@ -0,0 +1,119 @@ +--- +title: Archive a project +description: Archive a project with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, archive a project, projects, projects archive +--- + +# Archive a project + +
+ POST + /api/v2/workspaces/{slug}/projects/{pk}/archive/ +
+ +
+
+ +Projects are the main container for work items in a workspace. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project id. + + + +
+
+ +
+ +### Scopes + +`projects:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + +
+
diff --git a/docs/api-reference/v2/projects/bulk-projects.md b/docs/api-reference/v2/projects/bulk-projects.md new file mode 100644 index 00000000..07068884 --- /dev/null +++ b/docs/api-reference/v2/projects/bulk-projects.md @@ -0,0 +1,252 @@ +--- +title: Bulk write projects +description: Bulk write projects with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, bulk write projects, projects, projects bulk +--- + +# Bulk write projects + +
+ POST + /api/v2/workspaces/{slug}/projects/bulk/ +
+ +
+
+ +Projects are the main container for work items in a workspace. Write up to 100 projects in one request, with a per-row result for each. + +- Send `{"items": [...]}`, at most 100 per request. Add `"upsert": true` to match existing rows on `(external_source, external_id)` instead of always creating. +- The response is always `200`. Each row reports its own outcome, so a partial failure is reported rather than silently dropped — read `succeeded`/`failed` and the per-row `status`. +- Rows are written on independent savepoints: a failing row rolls back only itself. +- `?fields=` does not apply here — the bulk response is a per-row result envelope, not a resource body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The items. + + + + + +Reconcile each row on (external_source, external_id) instead of creating it. + + + +
+
+ +
+ +### Scopes + +`projects:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "failed": 1, + "results": [null], + "succeeded": 1 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/projects/create-project.md b/docs/api-reference/v2/projects/create-project.md new file mode 100644 index 00000000..737b2094 --- /dev/null +++ b/docs/api-reference/v2/projects/create-project.md @@ -0,0 +1,846 @@ +--- +title: Create a project +description: Create a project with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a project, projects, projects create +--- + +# Create a project + +
+ POST + /api/v2/workspaces/{slug}/projects/ +
+ +
+
+ +Projects are the main container for work items in a workspace. Create a project. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Short project key used to prefix work item numbers, for example `ENG` in `ENG-142`. + +Maximum 255 characters. + + + + + +Display name. + +Maximum 255 characters. + + + + + +The archive in. + + + + + +The close in. + + + + + +URL of the cover image. + +Nullable. + + + + + +Whether cycle view. + + + + + +Id of the related default assignee. + +Nullable. + + + + + +Id of the related default state. + +Nullable. + + + + + +Free-form description. + + + + + +Emoji shown alongside the name. + +Maximum 255 characters. Nullable. + + + + + +Id of the related estimate. + +Nullable. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Whether guest view all features. + + + + + +The icon prop. + +Nullable. + + + + + +Whether intake view. + + + + + +Whether is issue type enabled. + + + + + +Whether is time tracking enabled. + + + + + +Whether issue views view. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Whether module view. + + + + + +Project visibility: `0` is private to members, `2` is visible to the whole workspace. + + + + + +Whether page view. + + + + + +- `none` - None +- `low` - Low +- `medium` - Medium +- `high` - High +- `urgent` - Urgent + +One of `none`, `low`, `medium`, `high`, `urgent`. + + + + + +Id of the related project lead. + +Nullable. + + + + + +Planned start date, as `YYYY-MM-DD`. + +Nullable. + + + + + +Id of the related state. + +Nullable. + + + + + +Planned due date, as `YYYY-MM-DD`. + +Nullable. + + + + + +- `Africa/Abidjan` - Africa/Abidjan +- `Africa/Accra` - Africa/Accra +- `Africa/Addis_Ababa` - Africa/Addis_Ababa +- `Africa/Algiers` - Africa/Algiers +- `Africa/Asmara` - Africa/Asmara +- `Africa/Bamako` - Africa/Bamako +- `Africa/Bangui` - Africa/Bangui +- `Africa/Banjul` - Africa/Banjul +- `Africa/Bissau` - Africa/Bissau +- `Africa/Blantyre` - Africa/Blantyre +- `Africa/Brazzaville` - Africa/Brazzaville +- `Africa/Bujumbura` - Africa/Bujumbura +- `Africa/Cairo` - Africa/Cairo +- `Africa/Casablanca` - Africa/Casablanca +- `Africa/Ceuta` - Africa/Ceuta +- `Africa/Conakry` - Africa/Conakry +- `Africa/Dakar` - Africa/Dakar +- `Africa/Dar_es_Salaam` - Africa/Dar_es_Salaam +- `Africa/Djibouti` - Africa/Djibouti +- `Africa/Douala` - Africa/Douala +- `Africa/El_Aaiun` - Africa/El_Aaiun +- `Africa/Freetown` - Africa/Freetown +- `Africa/Gaborone` - Africa/Gaborone +- `Africa/Harare` - Africa/Harare +- `Africa/Johannesburg` - Africa/Johannesburg +- `Africa/Juba` - Africa/Juba +- `Africa/Kampala` - Africa/Kampala +- `Africa/Khartoum` - Africa/Khartoum +- `Africa/Kigali` - Africa/Kigali +- `Africa/Kinshasa` - Africa/Kinshasa +- `Africa/Lagos` - Africa/Lagos +- `Africa/Libreville` - Africa/Libreville +- `Africa/Lome` - Africa/Lome +- `Africa/Luanda` - Africa/Luanda +- `Africa/Lubumbashi` - Africa/Lubumbashi +- `Africa/Lusaka` - Africa/Lusaka +- `Africa/Malabo` - Africa/Malabo +- `Africa/Maputo` - Africa/Maputo +- `Africa/Maseru` - Africa/Maseru +- `Africa/Mbabane` - Africa/Mbabane +- `Africa/Mogadishu` - Africa/Mogadishu +- `Africa/Monrovia` - Africa/Monrovia +- `Africa/Nairobi` - Africa/Nairobi +- `Africa/Ndjamena` - Africa/Ndjamena +- `Africa/Niamey` - Africa/Niamey +- `Africa/Nouakchott` - Africa/Nouakchott +- `Africa/Ouagadougou` - Africa/Ouagadougou +- `Africa/Porto-Novo` - Africa/Porto-Novo +- `Africa/Sao_Tome` - Africa/Sao_Tome +- `Africa/Tripoli` - Africa/Tripoli +- `Africa/Tunis` - Africa/Tunis +- `Africa/Windhoek` - Africa/Windhoek +- `America/Adak` - America/Adak +- `America/Anchorage` - America/Anchorage +- `America/Anguilla` - America/Anguilla +- `America/Antigua` - America/Antigua +- `America/Araguaina` - America/Araguaina +- `America/Argentina/Buenos_Aires` - America/Argentina/Buenos_Aires +- `America/Argentina/Catamarca` - America/Argentina/Catamarca +- `America/Argentina/Cordoba` - America/Argentina/Cordoba +- `America/Argentina/Jujuy` - America/Argentina/Jujuy +- `America/Argentina/La_Rioja` - America/Argentina/La_Rioja +- `America/Argentina/Mendoza` - America/Argentina/Mendoza +- `America/Argentina/Rio_Gallegos` - America/Argentina/Rio_Gallegos +- `America/Argentina/Salta` - America/Argentina/Salta +- `America/Argentina/San_Juan` - America/Argentina/San_Juan +- `America/Argentina/San_Luis` - America/Argentina/San_Luis +- `America/Argentina/Tucuman` - America/Argentina/Tucuman +- `America/Argentina/Ushuaia` - America/Argentina/Ushuaia +- `America/Aruba` - America/Aruba +- `America/Asuncion` - America/Asuncion +- `America/Atikokan` - America/Atikokan +- `America/Bahia` - America/Bahia +- `America/Bahia_Banderas` - America/Bahia_Banderas +- `America/Barbados` - America/Barbados +- `America/Belem` - America/Belem +- `America/Belize` - America/Belize +- `America/Blanc-Sablon` - America/Blanc-Sablon +- `America/Boa_Vista` - America/Boa_Vista +- `America/Bogota` - America/Bogota +- `America/Boise` - America/Boise +- `America/Cambridge_Bay` - America/Cambridge_Bay +- `America/Campo_Grande` - America/Campo_Grande +- `America/Cancun` - America/Cancun +- `America/Caracas` - America/Caracas +- `America/Cayenne` - America/Cayenne +- `America/Cayman` - America/Cayman +- `America/Chicago` - America/Chicago +- `America/Chihuahua` - America/Chihuahua +- `America/Ciudad_Juarez` - America/Ciudad_Juarez +- `America/Costa_Rica` - America/Costa_Rica +- `America/Creston` - America/Creston +- `America/Cuiaba` - America/Cuiaba +- `America/Curacao` - America/Curacao +- `America/Danmarkshavn` - America/Danmarkshavn +- `America/Dawson` - America/Dawson +- `America/Dawson_Creek` - America/Dawson_Creek +- `America/Denver` - America/Denver +- `America/Detroit` - America/Detroit +- `America/Dominica` - America/Dominica +- `America/Edmonton` - America/Edmonton +- `America/Eirunepe` - America/Eirunepe +- `America/El_Salvador` - America/El_Salvador +- `America/Fort_Nelson` - America/Fort_Nelson +- `America/Fortaleza` - America/Fortaleza +- `America/Glace_Bay` - America/Glace_Bay +- `America/Goose_Bay` - America/Goose_Bay +- `America/Grand_Turk` - America/Grand_Turk +- `America/Grenada` - America/Grenada +- `America/Guadeloupe` - America/Guadeloupe +- `America/Guatemala` - America/Guatemala +- `America/Guayaquil` - America/Guayaquil +- `America/Guyana` - America/Guyana +- `America/Halifax` - America/Halifax +- `America/Havana` - America/Havana +- `America/Hermosillo` - America/Hermosillo +- `America/Indiana/Indianapolis` - America/Indiana/Indianapolis +- `America/Indiana/Knox` - America/Indiana/Knox +- `America/Indiana/Marengo` - America/Indiana/Marengo +- `America/Indiana/Petersburg` - America/Indiana/Petersburg +- `America/Indiana/Tell_City` - America/Indiana/Tell_City +- `America/Indiana/Vevay` - America/Indiana/Vevay +- `America/Indiana/Vincennes` - America/Indiana/Vincennes +- `America/Indiana/Winamac` - America/Indiana/Winamac +- `America/Inuvik` - America/Inuvik +- `America/Iqaluit` - America/Iqaluit +- `America/Jamaica` - America/Jamaica +- `America/Juneau` - America/Juneau +- `America/Kentucky/Louisville` - America/Kentucky/Louisville +- `America/Kentucky/Monticello` - America/Kentucky/Monticello +- `America/Kralendijk` - America/Kralendijk +- `America/La_Paz` - America/La_Paz +- `America/Lima` - America/Lima +- `America/Los_Angeles` - America/Los_Angeles +- `America/Lower_Princes` - America/Lower_Princes +- `America/Maceio` - America/Maceio +- `America/Managua` - America/Managua +- `America/Manaus` - America/Manaus +- `America/Marigot` - America/Marigot +- `America/Martinique` - America/Martinique +- `America/Matamoros` - America/Matamoros +- `America/Mazatlan` - America/Mazatlan +- `America/Menominee` - America/Menominee +- `America/Merida` - America/Merida +- `America/Metlakatla` - America/Metlakatla +- `America/Mexico_City` - America/Mexico_City +- `America/Miquelon` - America/Miquelon +- `America/Moncton` - America/Moncton +- `America/Monterrey` - America/Monterrey +- `America/Montevideo` - America/Montevideo +- `America/Montserrat` - America/Montserrat +- `America/Nassau` - America/Nassau +- `America/New_York` - America/New_York +- `America/Nome` - America/Nome +- `America/Noronha` - America/Noronha +- `America/North_Dakota/Beulah` - America/North_Dakota/Beulah +- `America/North_Dakota/Center` - America/North_Dakota/Center +- `America/North_Dakota/New_Salem` - America/North_Dakota/New_Salem +- `America/Nuuk` - America/Nuuk +- `America/Ojinaga` - America/Ojinaga +- `America/Panama` - America/Panama +- `America/Paramaribo` - America/Paramaribo +- `America/Phoenix` - America/Phoenix +- `America/Port-au-Prince` - America/Port-au-Prince +- `America/Port_of_Spain` - America/Port_of_Spain +- `America/Porto_Velho` - America/Porto_Velho +- `America/Puerto_Rico` - America/Puerto_Rico +- `America/Punta_Arenas` - America/Punta_Arenas +- `America/Rankin_Inlet` - America/Rankin_Inlet +- `America/Recife` - America/Recife +- `America/Regina` - America/Regina +- `America/Resolute` - America/Resolute +- `America/Rio_Branco` - America/Rio_Branco +- `America/Santarem` - America/Santarem +- `America/Santiago` - America/Santiago +- `America/Santo_Domingo` - America/Santo_Domingo +- `America/Sao_Paulo` - America/Sao_Paulo +- `America/Scoresbysund` - America/Scoresbysund +- `America/Sitka` - America/Sitka +- `America/St_Barthelemy` - America/St_Barthelemy +- `America/St_Johns` - America/St_Johns +- `America/St_Kitts` - America/St_Kitts +- `America/St_Lucia` - America/St_Lucia +- `America/St_Thomas` - America/St_Thomas +- `America/St_Vincent` - America/St_Vincent +- `America/Swift_Current` - America/Swift_Current +- `America/Tegucigalpa` - America/Tegucigalpa +- `America/Thule` - America/Thule +- `America/Tijuana` - America/Tijuana +- `America/Toronto` - America/Toronto +- `America/Tortola` - America/Tortola +- `America/Vancouver` - America/Vancouver +- `America/Whitehorse` - America/Whitehorse +- `America/Winnipeg` - America/Winnipeg +- `America/Yakutat` - America/Yakutat +- `Antarctica/Casey` - Antarctica/Casey +- `Antarctica/Davis` - Antarctica/Davis +- `Antarctica/DumontDUrville` - Antarctica/DumontDUrville +- `Antarctica/Macquarie` - Antarctica/Macquarie +- `Antarctica/Mawson` - Antarctica/Mawson +- `Antarctica/McMurdo` - Antarctica/McMurdo +- `Antarctica/Palmer` - Antarctica/Palmer +- `Antarctica/Rothera` - Antarctica/Rothera +- `Antarctica/Syowa` - Antarctica/Syowa +- `Antarctica/Troll` - Antarctica/Troll +- `Antarctica/Vostok` - Antarctica/Vostok +- `Arctic/Longyearbyen` - Arctic/Longyearbyen +- `Asia/Aden` - Asia/Aden +- `Asia/Almaty` - Asia/Almaty +- `Asia/Amman` - Asia/Amman +- `Asia/Anadyr` - Asia/Anadyr +- `Asia/Aqtau` - Asia/Aqtau +- `Asia/Aqtobe` - Asia/Aqtobe +- `Asia/Ashgabat` - Asia/Ashgabat +- `Asia/Atyrau` - Asia/Atyrau +- `Asia/Baghdad` - Asia/Baghdad +- `Asia/Bahrain` - Asia/Bahrain +- `Asia/Baku` - Asia/Baku +- `Asia/Bangkok` - Asia/Bangkok +- `Asia/Barnaul` - Asia/Barnaul +- `Asia/Beirut` - Asia/Beirut +- `Asia/Bishkek` - Asia/Bishkek +- `Asia/Brunei` - Asia/Brunei +- `Asia/Chita` - Asia/Chita +- `Asia/Choibalsan` - Asia/Choibalsan +- `Asia/Colombo` - Asia/Colombo +- `Asia/Damascus` - Asia/Damascus +- `Asia/Dhaka` - Asia/Dhaka +- `Asia/Dili` - Asia/Dili +- `Asia/Dubai` - Asia/Dubai +- `Asia/Dushanbe` - Asia/Dushanbe +- `Asia/Famagusta` - Asia/Famagusta +- `Asia/Gaza` - Asia/Gaza +- `Asia/Hebron` - Asia/Hebron +- `Asia/Ho_Chi_Minh` - Asia/Ho_Chi_Minh +- `Asia/Hong_Kong` - Asia/Hong_Kong +- `Asia/Hovd` - Asia/Hovd +- `Asia/Irkutsk` - Asia/Irkutsk +- `Asia/Jakarta` - Asia/Jakarta +- `Asia/Jayapura` - Asia/Jayapura +- `Asia/Jerusalem` - Asia/Jerusalem +- `Asia/Kabul` - Asia/Kabul +- `Asia/Kamchatka` - Asia/Kamchatka +- `Asia/Karachi` - Asia/Karachi +- `Asia/Kathmandu` - Asia/Kathmandu +- `Asia/Khandyga` - Asia/Khandyga +- `Asia/Kolkata` - Asia/Kolkata +- `Asia/Krasnoyarsk` - Asia/Krasnoyarsk +- `Asia/Kuala_Lumpur` - Asia/Kuala_Lumpur +- `Asia/Kuching` - Asia/Kuching +- `Asia/Kuwait` - Asia/Kuwait +- `Asia/Macau` - Asia/Macau +- `Asia/Magadan` - Asia/Magadan +- `Asia/Makassar` - Asia/Makassar +- `Asia/Manila` - Asia/Manila +- `Asia/Muscat` - Asia/Muscat +- `Asia/Nicosia` - Asia/Nicosia +- `Asia/Novokuznetsk` - Asia/Novokuznetsk +- `Asia/Novosibirsk` - Asia/Novosibirsk +- `Asia/Omsk` - Asia/Omsk +- `Asia/Oral` - Asia/Oral +- `Asia/Phnom_Penh` - Asia/Phnom_Penh +- `Asia/Pontianak` - Asia/Pontianak +- `Asia/Pyongyang` - Asia/Pyongyang +- `Asia/Qatar` - Asia/Qatar +- `Asia/Qostanay` - Asia/Qostanay +- `Asia/Qyzylorda` - Asia/Qyzylorda +- `Asia/Riyadh` - Asia/Riyadh +- `Asia/Sakhalin` - Asia/Sakhalin +- `Asia/Samarkand` - Asia/Samarkand +- `Asia/Seoul` - Asia/Seoul +- `Asia/Shanghai` - Asia/Shanghai +- `Asia/Singapore` - Asia/Singapore +- `Asia/Srednekolymsk` - Asia/Srednekolymsk +- `Asia/Taipei` - Asia/Taipei +- `Asia/Tashkent` - Asia/Tashkent +- `Asia/Tbilisi` - Asia/Tbilisi +- `Asia/Tehran` - Asia/Tehran +- `Asia/Thimphu` - Asia/Thimphu +- `Asia/Tokyo` - Asia/Tokyo +- `Asia/Tomsk` - Asia/Tomsk +- `Asia/Ulaanbaatar` - Asia/Ulaanbaatar +- `Asia/Urumqi` - Asia/Urumqi +- `Asia/Ust-Nera` - Asia/Ust-Nera +- `Asia/Vientiane` - Asia/Vientiane +- `Asia/Vladivostok` - Asia/Vladivostok +- `Asia/Yakutsk` - Asia/Yakutsk +- `Asia/Yangon` - Asia/Yangon +- `Asia/Yekaterinburg` - Asia/Yekaterinburg +- `Asia/Yerevan` - Asia/Yerevan +- `Atlantic/Azores` - Atlantic/Azores +- `Atlantic/Bermuda` - Atlantic/Bermuda +- `Atlantic/Canary` - Atlantic/Canary +- `Atlantic/Cape_Verde` - Atlantic/Cape_Verde +- `Atlantic/Faroe` - Atlantic/Faroe +- `Atlantic/Madeira` - Atlantic/Madeira +- `Atlantic/Reykjavik` - Atlantic/Reykjavik +- `Atlantic/South_Georgia` - Atlantic/South_Georgia +- `Atlantic/St_Helena` - Atlantic/St_Helena +- `Atlantic/Stanley` - Atlantic/Stanley +- `Australia/Adelaide` - Australia/Adelaide +- `Australia/Brisbane` - Australia/Brisbane +- `Australia/Broken_Hill` - Australia/Broken_Hill +- `Australia/Darwin` - Australia/Darwin +- `Australia/Eucla` - Australia/Eucla +- `Australia/Hobart` - Australia/Hobart +- `Australia/Lindeman` - Australia/Lindeman +- `Australia/Lord_Howe` - Australia/Lord_Howe +- `Australia/Melbourne` - Australia/Melbourne +- `Australia/Perth` - Australia/Perth +- `Australia/Sydney` - Australia/Sydney +- `Canada/Atlantic` - Canada/Atlantic +- `Canada/Central` - Canada/Central +- `Canada/Eastern` - Canada/Eastern +- `Canada/Mountain` - Canada/Mountain +- `Canada/Newfoundland` - Canada/Newfoundland +- `Canada/Pacific` - Canada/Pacific +- `Europe/Amsterdam` - Europe/Amsterdam +- `Europe/Andorra` - Europe/Andorra +- `Europe/Astrakhan` - Europe/Astrakhan +- `Europe/Athens` - Europe/Athens +- `Europe/Belgrade` - Europe/Belgrade +- `Europe/Berlin` - Europe/Berlin +- `Europe/Bratislava` - Europe/Bratislava +- `Europe/Brussels` - Europe/Brussels +- `Europe/Bucharest` - Europe/Bucharest +- `Europe/Budapest` - Europe/Budapest +- `Europe/Busingen` - Europe/Busingen +- `Europe/Chisinau` - Europe/Chisinau +- `Europe/Copenhagen` - Europe/Copenhagen +- `Europe/Dublin` - Europe/Dublin +- `Europe/Gibraltar` - Europe/Gibraltar +- `Europe/Guernsey` - Europe/Guernsey +- `Europe/Helsinki` - Europe/Helsinki +- `Europe/Isle_of_Man` - Europe/Isle_of_Man +- `Europe/Istanbul` - Europe/Istanbul +- `Europe/Jersey` - Europe/Jersey +- `Europe/Kaliningrad` - Europe/Kaliningrad +- `Europe/Kirov` - Europe/Kirov +- `Europe/Kyiv` - Europe/Kyiv +- `Europe/Lisbon` - Europe/Lisbon +- `Europe/Ljubljana` - Europe/Ljubljana +- `Europe/London` - Europe/London +- `Europe/Luxembourg` - Europe/Luxembourg +- `Europe/Madrid` - Europe/Madrid +- `Europe/Malta` - Europe/Malta +- `Europe/Mariehamn` - Europe/Mariehamn +- `Europe/Minsk` - Europe/Minsk +- `Europe/Monaco` - Europe/Monaco +- `Europe/Moscow` - Europe/Moscow +- `Europe/Oslo` - Europe/Oslo +- `Europe/Paris` - Europe/Paris +- `Europe/Podgorica` - Europe/Podgorica +- `Europe/Prague` - Europe/Prague +- `Europe/Riga` - Europe/Riga +- `Europe/Rome` - Europe/Rome +- `Europe/Samara` - Europe/Samara +- `Europe/San_Marino` - Europe/San_Marino +- `Europe/Sarajevo` - Europe/Sarajevo +- `Europe/Saratov` - Europe/Saratov +- `Europe/Simferopol` - Europe/Simferopol +- `Europe/Skopje` - Europe/Skopje +- `Europe/Sofia` - Europe/Sofia +- `Europe/Stockholm` - Europe/Stockholm +- `Europe/Tallinn` - Europe/Tallinn +- `Europe/Tirane` - Europe/Tirane +- `Europe/Ulyanovsk` - Europe/Ulyanovsk +- `Europe/Vaduz` - Europe/Vaduz +- `Europe/Vatican` - Europe/Vatican +- `Europe/Vienna` - Europe/Vienna +- `Europe/Vilnius` - Europe/Vilnius +- `Europe/Volgograd` - Europe/Volgograd +- `Europe/Warsaw` - Europe/Warsaw +- `Europe/Zagreb` - Europe/Zagreb +- `Europe/Zurich` - Europe/Zurich +- `GMT` - GMT +- `Indian/Antananarivo` - Indian/Antananarivo +- `Indian/Chagos` - Indian/Chagos +- `Indian/Christmas` - Indian/Christmas +- `Indian/Cocos` - Indian/Cocos +- `Indian/Comoro` - Indian/Comoro +- `Indian/Kerguelen` - Indian/Kerguelen +- `Indian/Mahe` - Indian/Mahe +- `Indian/Maldives` - Indian/Maldives +- `Indian/Mauritius` - Indian/Mauritius +- `Indian/Mayotte` - Indian/Mayotte +- `Indian/Reunion` - Indian/Reunion +- `Pacific/Apia` - Pacific/Apia +- `Pacific/Auckland` - Pacific/Auckland +- `Pacific/Bougainville` - Pacific/Bougainville +- `Pacific/Chatham` - Pacific/Chatham +- `Pacific/Chuuk` - Pacific/Chuuk +- `Pacific/Easter` - Pacific/Easter +- `Pacific/Efate` - Pacific/Efate +- `Pacific/Fakaofo` - Pacific/Fakaofo +- `Pacific/Fiji` - Pacific/Fiji +- `Pacific/Funafuti` - Pacific/Funafuti +- `Pacific/Galapagos` - Pacific/Galapagos +- `Pacific/Gambier` - Pacific/Gambier +- `Pacific/Guadalcanal` - Pacific/Guadalcanal +- `Pacific/Guam` - Pacific/Guam +- `Pacific/Honolulu` - Pacific/Honolulu +- `Pacific/Kanton` - Pacific/Kanton +- `Pacific/Kiritimati` - Pacific/Kiritimati +- `Pacific/Kosrae` - Pacific/Kosrae +- `Pacific/Kwajalein` - Pacific/Kwajalein +- `Pacific/Majuro` - Pacific/Majuro +- `Pacific/Marquesas` - Pacific/Marquesas +- `Pacific/Midway` - Pacific/Midway +- `Pacific/Nauru` - Pacific/Nauru +- `Pacific/Niue` - Pacific/Niue +- `Pacific/Norfolk` - Pacific/Norfolk +- `Pacific/Noumea` - Pacific/Noumea +- `Pacific/Pago_Pago` - Pacific/Pago_Pago +- `Pacific/Palau` - Pacific/Palau +- `Pacific/Pitcairn` - Pacific/Pitcairn +- `Pacific/Pohnpei` - Pacific/Pohnpei +- `Pacific/Port_Moresby` - Pacific/Port_Moresby +- `Pacific/Rarotonga` - Pacific/Rarotonga +- `Pacific/Saipan` - Pacific/Saipan +- `Pacific/Tahiti` - Pacific/Tahiti +- `Pacific/Tarawa` - Pacific/Tarawa +- `Pacific/Tongatapu` - Pacific/Tongatapu +- `Pacific/Wake` - Pacific/Wake +- `Pacific/Wallis` - Pacific/Wallis +- `US/Alaska` - US/Alaska +- `US/Arizona` - US/Arizona +- `US/Central` - US/Central +- `US/Eastern` - US/Eastern +- `US/Hawaii` - US/Hawaii +- `US/Mountain` - US/Mountain +- `US/Pacific` - US/Pacific +- `UTC` - UTC + +One of `Africa/Abidjan`, `Africa/Accra`, `Africa/Addis_Ababa`, `Africa/Algiers`, `Africa/Asmara`, `Africa/Bamako`, `Africa/Bangui`, `Africa/Banjul`, `Africa/Bissau`, `Africa/Blantyre`, `Africa/Brazzaville`, `Africa/Bujumbura`, `Africa/Cairo`, `Africa/Casablanca`, `Africa/Ceuta`, `Africa/Conakry`, `Africa/Dakar`, `Africa/Dar_es_Salaam`, `Africa/Djibouti`, `Africa/Douala`, `Africa/El_Aaiun`, `Africa/Freetown`, `Africa/Gaborone`, `Africa/Harare`, `Africa/Johannesburg`, `Africa/Juba`, `Africa/Kampala`, `Africa/Khartoum`, `Africa/Kigali`, `Africa/Kinshasa`, `Africa/Lagos`, `Africa/Libreville`, `Africa/Lome`, `Africa/Luanda`, `Africa/Lubumbashi`, `Africa/Lusaka`, `Africa/Malabo`, `Africa/Maputo`, `Africa/Maseru`, `Africa/Mbabane`, `Africa/Mogadishu`, `Africa/Monrovia`, `Africa/Nairobi`, `Africa/Ndjamena`, `Africa/Niamey`, `Africa/Nouakchott`, `Africa/Ouagadougou`, `Africa/Porto-Novo`, `Africa/Sao_Tome`, `Africa/Tripoli`, `Africa/Tunis`, `Africa/Windhoek`, `America/Adak`, `America/Anchorage`, `America/Anguilla`, `America/Antigua`, `America/Araguaina`, `America/Argentina/Buenos_Aires`, `America/Argentina/Catamarca`, `America/Argentina/Cordoba`, `America/Argentina/Jujuy`, `America/Argentina/La_Rioja`, `America/Argentina/Mendoza`, `America/Argentina/Rio_Gallegos`, `America/Argentina/Salta`, `America/Argentina/San_Juan`, `America/Argentina/San_Luis`, `America/Argentina/Tucuman`, `America/Argentina/Ushuaia`, `America/Aruba`, `America/Asuncion`, `America/Atikokan`, `America/Bahia`, `America/Bahia_Banderas`, `America/Barbados`, `America/Belem`, `America/Belize`, `America/Blanc-Sablon`, `America/Boa_Vista`, `America/Bogota`, `America/Boise`, `America/Cambridge_Bay`, `America/Campo_Grande`, `America/Cancun`, `America/Caracas`, `America/Cayenne`, `America/Cayman`, `America/Chicago`, `America/Chihuahua`, `America/Ciudad_Juarez`, `America/Costa_Rica`, `America/Creston`, `America/Cuiaba`, `America/Curacao`, `America/Danmarkshavn`, `America/Dawson`, `America/Dawson_Creek`, `America/Denver`, `America/Detroit`, `America/Dominica`, `America/Edmonton`, `America/Eirunepe`, `America/El_Salvador`, `America/Fort_Nelson`, `America/Fortaleza`, `America/Glace_Bay`, `America/Goose_Bay`, `America/Grand_Turk`, `America/Grenada`, `America/Guadeloupe`, `America/Guatemala`, `America/Guayaquil`, `America/Guyana`, `America/Halifax`, `America/Havana`, `America/Hermosillo`, `America/Indiana/Indianapolis`, `America/Indiana/Knox`, `America/Indiana/Marengo`, `America/Indiana/Petersburg`, `America/Indiana/Tell_City`, `America/Indiana/Vevay`, `America/Indiana/Vincennes`, `America/Indiana/Winamac`, `America/Inuvik`, `America/Iqaluit`, `America/Jamaica`, `America/Juneau`, `America/Kentucky/Louisville`, `America/Kentucky/Monticello`, `America/Kralendijk`, `America/La_Paz`, `America/Lima`, `America/Los_Angeles`, `America/Lower_Princes`, `America/Maceio`, `America/Managua`, `America/Manaus`, `America/Marigot`, `America/Martinique`, `America/Matamoros`, `America/Mazatlan`, `America/Menominee`, `America/Merida`, `America/Metlakatla`, `America/Mexico_City`, `America/Miquelon`, `America/Moncton`, `America/Monterrey`, `America/Montevideo`, `America/Montserrat`, `America/Nassau`, `America/New_York`, `America/Nome`, `America/Noronha`, `America/North_Dakota/Beulah`, `America/North_Dakota/Center`, `America/North_Dakota/New_Salem`, `America/Nuuk`, `America/Ojinaga`, `America/Panama`, `America/Paramaribo`, `America/Phoenix`, `America/Port-au-Prince`, `America/Port_of_Spain`, `America/Porto_Velho`, `America/Puerto_Rico`, `America/Punta_Arenas`, `America/Rankin_Inlet`, `America/Recife`, `America/Regina`, `America/Resolute`, `America/Rio_Branco`, `America/Santarem`, `America/Santiago`, `America/Santo_Domingo`, `America/Sao_Paulo`, `America/Scoresbysund`, `America/Sitka`, `America/St_Barthelemy`, `America/St_Johns`, `America/St_Kitts`, `America/St_Lucia`, `America/St_Thomas`, `America/St_Vincent`, `America/Swift_Current`, `America/Tegucigalpa`, `America/Thule`, `America/Tijuana`, `America/Toronto`, `America/Tortola`, `America/Vancouver`, `America/Whitehorse`, `America/Winnipeg`, `America/Yakutat`, `Antarctica/Casey`, `Antarctica/Davis`, `Antarctica/DumontDUrville`, `Antarctica/Macquarie`, `Antarctica/Mawson`, `Antarctica/McMurdo`, `Antarctica/Palmer`, `Antarctica/Rothera`, `Antarctica/Syowa`, `Antarctica/Troll`, `Antarctica/Vostok`, `Arctic/Longyearbyen`, `Asia/Aden`, `Asia/Almaty`, `Asia/Amman`, `Asia/Anadyr`, `Asia/Aqtau`, `Asia/Aqtobe`, `Asia/Ashgabat`, `Asia/Atyrau`, `Asia/Baghdad`, `Asia/Bahrain`, `Asia/Baku`, `Asia/Bangkok`, `Asia/Barnaul`, `Asia/Beirut`, `Asia/Bishkek`, `Asia/Brunei`, `Asia/Chita`, `Asia/Choibalsan`, `Asia/Colombo`, `Asia/Damascus`, `Asia/Dhaka`, `Asia/Dili`, `Asia/Dubai`, `Asia/Dushanbe`, `Asia/Famagusta`, `Asia/Gaza`, `Asia/Hebron`, `Asia/Ho_Chi_Minh`, `Asia/Hong_Kong`, `Asia/Hovd`, `Asia/Irkutsk`, `Asia/Jakarta`, `Asia/Jayapura`, `Asia/Jerusalem`, `Asia/Kabul`, `Asia/Kamchatka`, `Asia/Karachi`, `Asia/Kathmandu`, `Asia/Khandyga`, `Asia/Kolkata`, `Asia/Krasnoyarsk`, `Asia/Kuala_Lumpur`, `Asia/Kuching`, `Asia/Kuwait`, `Asia/Macau`, `Asia/Magadan`, `Asia/Makassar`, `Asia/Manila`, `Asia/Muscat`, `Asia/Nicosia`, `Asia/Novokuznetsk`, `Asia/Novosibirsk`, `Asia/Omsk`, `Asia/Oral`, `Asia/Phnom_Penh`, `Asia/Pontianak`, `Asia/Pyongyang`, `Asia/Qatar`, `Asia/Qostanay`, `Asia/Qyzylorda`, `Asia/Riyadh`, `Asia/Sakhalin`, `Asia/Samarkand`, `Asia/Seoul`, `Asia/Shanghai`, `Asia/Singapore`, `Asia/Srednekolymsk`, `Asia/Taipei`, `Asia/Tashkent`, `Asia/Tbilisi`, `Asia/Tehran`, `Asia/Thimphu`, `Asia/Tokyo`, `Asia/Tomsk`, `Asia/Ulaanbaatar`, `Asia/Urumqi`, `Asia/Ust-Nera`, `Asia/Vientiane`, `Asia/Vladivostok`, `Asia/Yakutsk`, `Asia/Yangon`, `Asia/Yekaterinburg`, `Asia/Yerevan`, `Atlantic/Azores`, `Atlantic/Bermuda`, `Atlantic/Canary`, `Atlantic/Cape_Verde`, `Atlantic/Faroe`, `Atlantic/Madeira`, `Atlantic/Reykjavik`, `Atlantic/South_Georgia`, `Atlantic/St_Helena`, `Atlantic/Stanley`, `Australia/Adelaide`, `Australia/Brisbane`, `Australia/Broken_Hill`, `Australia/Darwin`, `Australia/Eucla`, `Australia/Hobart`, `Australia/Lindeman`, `Australia/Lord_Howe`, `Australia/Melbourne`, `Australia/Perth`, `Australia/Sydney`, `Canada/Atlantic`, `Canada/Central`, `Canada/Eastern`, `Canada/Mountain`, `Canada/Newfoundland`, `Canada/Pacific`, `Europe/Amsterdam`, `Europe/Andorra`, `Europe/Astrakhan`, `Europe/Athens`, `Europe/Belgrade`, `Europe/Berlin`, `Europe/Bratislava`, `Europe/Brussels`, `Europe/Bucharest`, `Europe/Budapest`, `Europe/Busingen`, `Europe/Chisinau`, `Europe/Copenhagen`, `Europe/Dublin`, `Europe/Gibraltar`, `Europe/Guernsey`, `Europe/Helsinki`, `Europe/Isle_of_Man`, `Europe/Istanbul`, `Europe/Jersey`, `Europe/Kaliningrad`, `Europe/Kirov`, `Europe/Kyiv`, `Europe/Lisbon`, `Europe/Ljubljana`, `Europe/London`, `Europe/Luxembourg`, `Europe/Madrid`, `Europe/Malta`, `Europe/Mariehamn`, `Europe/Minsk`, `Europe/Monaco`, `Europe/Moscow`, `Europe/Oslo`, `Europe/Paris`, `Europe/Podgorica`, `Europe/Prague`, `Europe/Riga`, `Europe/Rome`, `Europe/Samara`, `Europe/San_Marino`, `Europe/Sarajevo`, `Europe/Saratov`, `Europe/Simferopol`, `Europe/Skopje`, `Europe/Sofia`, `Europe/Stockholm`, `Europe/Tallinn`, `Europe/Tirane`, `Europe/Ulyanovsk`, `Europe/Vaduz`, `Europe/Vatican`, `Europe/Vienna`, `Europe/Vilnius`, `Europe/Volgograd`, `Europe/Warsaw`, `Europe/Zagreb`, `Europe/Zurich`, `GMT`, `Indian/Antananarivo`, `Indian/Chagos`, `Indian/Christmas`, `Indian/Cocos`, `Indian/Comoro`, `Indian/Kerguelen`, `Indian/Mahe`, `Indian/Maldives`, `Indian/Mauritius`, `Indian/Mayotte`, `Indian/Reunion`, `Pacific/Apia`, `Pacific/Auckland`, `Pacific/Bougainville`, `Pacific/Chatham`, `Pacific/Chuuk`, `Pacific/Easter`, `Pacific/Efate`, `Pacific/Fakaofo`, `Pacific/Fiji`, `Pacific/Funafuti`, `Pacific/Galapagos`, `Pacific/Gambier`, `Pacific/Guadalcanal`, `Pacific/Guam`, `Pacific/Honolulu`, `Pacific/Kanton`, `Pacific/Kiritimati`, `Pacific/Kosrae`, `Pacific/Kwajalein`, `Pacific/Majuro`, `Pacific/Marquesas`, `Pacific/Midway`, `Pacific/Nauru`, `Pacific/Niue`, `Pacific/Norfolk`, `Pacific/Noumea`, `Pacific/Pago_Pago`, `Pacific/Palau`, `Pacific/Pitcairn`, `Pacific/Pohnpei`, `Pacific/Port_Moresby`, `Pacific/Rarotonga`, `Pacific/Saipan`, `Pacific/Tahiti`, `Pacific/Tarawa`, `Pacific/Tongatapu`, `Pacific/Wake`, `Pacific/Wallis`, `US/Alaska`, `US/Arizona`, `US/Central`, `US/Eastern`, `US/Hawaii`, `US/Mountain`, `US/Pacific`, `UTC`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archive_in`, `archived_at`, `close_in`, `cover_image`, `cover_image_url`, `created_at`, `created_by_id`, `cycle_view`, `default_assignee_id`, `default_state_id`, `description`, `emoji`, `estimate_id`, `external_id`, `external_source`, `guest_view_all_features`, `icon_prop`, `id`, `identifier`, `intake_view`, `is_issue_type_enabled`, `is_time_tracking_enabled`, `issue_views_view`, `logo_props`, `module_view`, `name`, `network`, `page_view`, `priority`, `project_lead_id`, `start_date`, `state_id`, `target_date`, `timezone`. + + + + + +Comma-separated relations to embed: `default_assignee`, `project_lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archive_in": 1, + "archived_at": null, + "close_in": 1, + "cover_image": "https://example.com/cover.png", + "cover_image_url": "https://example.com", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "cycle_view": false, + "default_assignee_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "default_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "emoji": "1f680", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "guest_view_all_features": false, + "icon_prop": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "identifier": "PROJ", + "intake_view": false, + "is_issue_type_enabled": false, + "is_time_tracking_enabled": false, + "issue_views_view": false, + "logo_props": null, + "module_view": false, + "name": "Example name", + "network": 0, + "page_view": false, + "priority": "high", + "project_lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "start_date": "2026-01-12", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20", + "timezone": "Africa/Abidjan" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/projects/delete-project.md b/docs/api-reference/v2/projects/delete-project.md new file mode 100644 index 00000000..5378da90 --- /dev/null +++ b/docs/api-reference/v2/projects/delete-project.md @@ -0,0 +1,156 @@ +--- +title: Delete a project +description: Delete a project with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a project, projects, projects destroy +--- + +# Delete a project + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{pk}/ +
+ +
+
+ +Projects are the main container for work items in a workspace. Delete a project. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archive_in`, `archived_at`, `close_in`, `cover_image`, `cover_image_url`, `created_at`, `created_by_id`, `cycle_view`, `default_assignee_id`, `default_state_id`, `description`, `emoji`, `estimate_id`, `external_id`, `external_source`, `guest_view_all_features`, `icon_prop`, `id`, `identifier`, `intake_view`, `is_issue_type_enabled`, `is_time_tracking_enabled`, `issue_views_view`, `logo_props`, `module_view`, `name`, `network`, `page_view`, `priority`, `project_lead_id`, `start_date`, `state_id`, `target_date`, `timezone`. + + + + + +Comma-separated relations to embed: `default_assignee`, `project_lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/projects/get-project-summary.md b/docs/api-reference/v2/projects/get-project-summary.md new file mode 100644 index 00000000..e3aca831 --- /dev/null +++ b/docs/api-reference/v2/projects/get-project-summary.md @@ -0,0 +1,137 @@ +--- +title: Get a project summary +description: Get a project summary with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a project summary, project summary, projects summary +--- + +# Get a project summary + +
+ GET + /api/v2/workspaces/{slug}/projects/{pk}/summary/ +
+ +
+
+ +Projects are the main container for work items in a workspace. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project summary id. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Comma-separated count keys to include. Allowed: members, states, labels, cycles, modules, issues, intakes, work_item_types, work_item_properties, pages. Omitting returns all. Unknown keys are a 400. + + + +
+
+ +
+ +### Scopes + +`projects:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "counts": {}, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "identifier": "PROJ", + "name": "Example name" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/projects/get-project.md b/docs/api-reference/v2/projects/get-project.md new file mode 100644 index 00000000..e5b25823 --- /dev/null +++ b/docs/api-reference/v2/projects/get-project.md @@ -0,0 +1,189 @@ +--- +title: Get a project +description: Read a project with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a project, projects, projects retrieve +--- + +# Get a project + +
+ GET + /api/v2/workspaces/{slug}/projects/{pk}/ +
+ +
+
+ +Projects are the main container for work items in a workspace. Read a single project by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archive_in`, `archived_at`, `close_in`, `cover_image`, `cover_image_url`, `created_at`, `created_by_id`, `cycle_view`, `default_assignee_id`, `default_state_id`, `description`, `emoji`, `estimate_id`, `external_id`, `external_source`, `guest_view_all_features`, `icon_prop`, `id`, `identifier`, `intake_view`, `is_issue_type_enabled`, `is_time_tracking_enabled`, `issue_views_view`, `logo_props`, `module_view`, `name`, `network`, `page_view`, `priority`, `project_lead_id`, `start_date`, `state_id`, `target_date`, `timezone`. + + + + + +Comma-separated relations to embed: `default_assignee`, `project_lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archive_in": 1, + "archived_at": null, + "close_in": 1, + "cover_image": "https://example.com/cover.png", + "cover_image_url": "https://example.com", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "cycle_view": false, + "default_assignee_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "default_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "emoji": "1f680", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "guest_view_all_features": false, + "icon_prop": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "identifier": "PROJ", + "intake_view": false, + "is_issue_type_enabled": false, + "is_time_tracking_enabled": false, + "issue_views_view": false, + "logo_props": null, + "module_view": false, + "name": "Example name", + "network": 0, + "page_view": false, + "priority": "high", + "project_lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "start_date": "2026-01-12", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20", + "timezone": "Africa/Abidjan" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/projects/list-projects.md b/docs/api-reference/v2/projects/list-projects.md new file mode 100644 index 00000000..f9236822 --- /dev/null +++ b/docs/api-reference/v2/projects/list-projects.md @@ -0,0 +1,299 @@ +--- +title: List projects +description: List projects with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list projects, projects, projects list +--- + +# List projects + +
+ GET + /api/v2/workspaces/{slug}/projects/ +
+ +
+
+ +Projects are the main container for work items in a workspace. List the projects you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `external_id`. + + + + + +Filter by `external_source`. + + + + + +Filter by `identifier`. + + + + + +Filter by `include_archived`. + + + + + +Filter by `is_archived`. + + + + + +Filter by `key`. + + + + + +Filter by `name`. + + + + + +Filter by `network`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +- `none` - None +- `low` - Low +- `medium` - Medium +- `high` - High +- `urgent` - Urgent + +One of `high`, `low`, `medium`, `none`, `urgent`. + + + + + +Multiple values may be separated by commas. + +- `none` - None +- `low` - Low +- `medium` - Medium +- `high` - High +- `urgent` - Urgent + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archive_in`, `archived_at`, `close_in`, `cover_image`, `cover_image_url`, `created_at`, `created_by_id`, `cycle_view`, `default_assignee_id`, `default_state_id`, `description`, `emoji`, `estimate_id`, `external_id`, `external_source`, `guest_view_all_features`, `icon_prop`, `id`, `identifier`, `intake_view`, `is_issue_type_enabled`, `is_time_tracking_enabled`, `issue_views_view`, `logo_props`, `module_view`, `name`, `network`, `page_view`, `priority`, `project_lead_id`, `start_date`, `state_id`, `target_date`, `timezone`. + + + + + +Comma-separated relations to embed: `default_assignee`, `project_lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "archive_in": 1, + "archived_at": null, + "close_in": 1, + "cover_image": "https://example.com/cover.png", + "cover_image_url": "https://example.com", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "cycle_view": false, + "default_assignee_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "default_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "emoji": "1f680", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "guest_view_all_features": false, + "icon_prop": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "identifier": "PROJ", + "intake_view": false, + "is_issue_type_enabled": false, + "is_time_tracking_enabled": false, + "issue_views_view": false, + "logo_props": null, + "module_view": false, + "name": "Example name", + "network": 0, + "page_view": false, + "priority": "high", + "project_lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "start_date": "2026-01-12", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20", + "timezone": "Africa/Abidjan" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/projects/overview.md b/docs/api-reference/v2/projects/overview.md new file mode 100644 index 00000000..01ceb75a --- /dev/null +++ b/docs/api-reference/v2/projects/overview.md @@ -0,0 +1,220 @@ +--- +title: Projects overview +description: The Plane API v2 project object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, projects, project object +--- + +# Projects overview + +Projects are the main container for work items in a workspace. + +
+
+ +## The project object + +### Attributes + +- `archive_in` _integer_ + + The archive in. + +- `archived_at` _string (date-time)_ + + When the record was archived, or `null` if it is active. + +- `close_in` _integer_ + + The close in. + +- `cover_image` _string_ + + URL of the cover image. + +- `cover_image_url` _string_ + + The cover image url. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `cycle_view` _boolean_ + + Whether cycle view. + +- `default_assignee_id` _string (uuid)_ + + The related default assignee. + +- `default_state_id` _string (uuid)_ + + The related default state. + +- `description` _string_ + + Free-form description. + +- `emoji` _string_ + + Emoji shown alongside the name. + +- `estimate_id` _string (uuid)_ + + The related estimate. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `guest_view_all_features` _boolean_ + + Whether guest view all features. + +- `icon_prop` _string_ + + The icon prop. + +- `id` _string (uuid)_ + + Unique identifier. + +- `identifier` _string_ + + Short project key used to prefix work item numbers, for example `ENG` in `ENG-142`. + +- `intake_view` _boolean_ + + Whether intake view. + +- `is_issue_type_enabled` _boolean_ + + Whether is issue type enabled. + +- `is_time_tracking_enabled` _boolean_ + + Whether is time tracking enabled. + +- `issue_views_view` _boolean_ + + Whether issue views view. + +- `logo_props` _string_ + + Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +- `module_view` _boolean_ + + Whether module view. + +- `name` _string_ + + Display name. + +- `network` _integer_ + + Project visibility: `0` is private to members, `2` is visible to the whole workspace. + +- `page_view` _boolean_ + + Whether page view. + +- `priority` _string_ + + Urgency of the work item. + +- `project_lead_id` _string (uuid)_ + + The related project lead. + +- `start_date` _string (date-time)_ + + Planned start date, as `YYYY-MM-DD`. + +- `state_id` _string (uuid)_ + + The related state. + +- `target_date` _string (date-time)_ + + Planned due date, as `YYYY-MM-DD`. + +- `timezone` _string_ + + IANA timezone name, for example `America/New_York`. + +
+
+ + + +```json +{ + "archive_in": 1, + "archived_at": null, + "close_in": 1, + "cover_image": "https://example.com/cover.png", + "cover_image_url": "https://example.com", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "cycle_view": false, + "default_assignee_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "default_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "emoji": "1f680", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "guest_view_all_features": false, + "icon_prop": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "identifier": "PROJ", + "intake_view": false, + "is_issue_type_enabled": false, + "is_time_tracking_enabled": false, + "issue_views_view": false, + "logo_props": null, + "module_view": false, + "name": "Example name", + "network": 0, + "page_view": false, + "priority": "high", + "project_lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "start_date": "2026-01-12", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20", + "timezone": "Africa/Abidjan" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------- | --------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/` | List projects | +| `POST` | `/api/v2/workspaces/{slug}/projects/` | Create a project | +| `POST` | `/api/v2/workspaces/{slug}/projects/bulk/` | Bulk write projects | +| `POST` | `/api/v2/workspaces/{slug}/projects/upsert/` | Upsert a project | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{pk}/` | Delete a project | +| `GET` | `/api/v2/workspaces/{slug}/projects/{pk}/` | Get a project | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{pk}/` | Update a project | +| `POST` | `/api/v2/workspaces/{slug}/projects/{pk}/archive/` | Archive a project | +| `GET` | `/api/v2/workspaces/{slug}/projects/{pk}/summary/` | Get a project summary | +| `POST` | `/api/v2/workspaces/{slug}/projects/{pk}/unarchive/` | Unarchive a project | + +## Response shaping + +Every project read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/projects/unarchive-project.md b/docs/api-reference/v2/projects/unarchive-project.md new file mode 100644 index 00000000..1b04a795 --- /dev/null +++ b/docs/api-reference/v2/projects/unarchive-project.md @@ -0,0 +1,119 @@ +--- +title: Unarchive a project +description: Unarchive a project with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, unarchive a project, projects, projects unarchive +--- + +# Unarchive a project + +
+ POST + /api/v2/workspaces/{slug}/projects/{pk}/unarchive/ +
+ +
+
+ +Projects are the main container for work items in a workspace. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project id. + + + +
+
+ +
+ +### Scopes + +`projects:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + +
+
diff --git a/docs/api-reference/v2/projects/update-project.md b/docs/api-reference/v2/projects/update-project.md new file mode 100644 index 00000000..5d92230f --- /dev/null +++ b/docs/api-reference/v2/projects/update-project.md @@ -0,0 +1,861 @@ +--- +title: Update a project +description: Update a project with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a project, projects, projects partial update +--- + +# Update a project + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{pk}/ +
+ +
+
+ +Projects are the main container for work items in a workspace. Update a project. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The archive in. + + + + + +The close in. + + + + + +URL of the cover image. + +Nullable. + + + + + +Whether cycle view. + + + + + +Id of the related default assignee. + +Nullable. + + + + + +Id of the related default state. + +Nullable. + + + + + +Free-form description. + + + + + +Emoji shown alongside the name. + +Maximum 255 characters. Nullable. + + + + + +Id of the related estimate. + +Nullable. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Whether guest view all features. + + + + + +The icon prop. + +Nullable. + + + + + +Short project key used to prefix work item numbers, for example `ENG` in `ENG-142`. + +Maximum 255 characters. + + + + + +Whether intake view. + + + + + +Whether is issue type enabled. + + + + + +Whether is time tracking enabled. + + + + + +Whether issue views view. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Whether module view. + + + + + +Display name. + +Maximum 255 characters. + + + + + +Project visibility: `0` is private to members, `2` is visible to the whole workspace. + + + + + +Whether page view. + + + + + +- `none` - None +- `low` - Low +- `medium` - Medium +- `high` - High +- `urgent` - Urgent + +One of `none`, `low`, `medium`, `high`, `urgent`. + + + + + +Id of the related project lead. + +Nullable. + + + + + +Planned start date, as `YYYY-MM-DD`. + +Nullable. + + + + + +Id of the related state. + +Nullable. + + + + + +Planned due date, as `YYYY-MM-DD`. + +Nullable. + + + + + +- `Africa/Abidjan` - Africa/Abidjan +- `Africa/Accra` - Africa/Accra +- `Africa/Addis_Ababa` - Africa/Addis_Ababa +- `Africa/Algiers` - Africa/Algiers +- `Africa/Asmara` - Africa/Asmara +- `Africa/Bamako` - Africa/Bamako +- `Africa/Bangui` - Africa/Bangui +- `Africa/Banjul` - Africa/Banjul +- `Africa/Bissau` - Africa/Bissau +- `Africa/Blantyre` - Africa/Blantyre +- `Africa/Brazzaville` - Africa/Brazzaville +- `Africa/Bujumbura` - Africa/Bujumbura +- `Africa/Cairo` - Africa/Cairo +- `Africa/Casablanca` - Africa/Casablanca +- `Africa/Ceuta` - Africa/Ceuta +- `Africa/Conakry` - Africa/Conakry +- `Africa/Dakar` - Africa/Dakar +- `Africa/Dar_es_Salaam` - Africa/Dar_es_Salaam +- `Africa/Djibouti` - Africa/Djibouti +- `Africa/Douala` - Africa/Douala +- `Africa/El_Aaiun` - Africa/El_Aaiun +- `Africa/Freetown` - Africa/Freetown +- `Africa/Gaborone` - Africa/Gaborone +- `Africa/Harare` - Africa/Harare +- `Africa/Johannesburg` - Africa/Johannesburg +- `Africa/Juba` - Africa/Juba +- `Africa/Kampala` - Africa/Kampala +- `Africa/Khartoum` - Africa/Khartoum +- `Africa/Kigali` - Africa/Kigali +- `Africa/Kinshasa` - Africa/Kinshasa +- `Africa/Lagos` - Africa/Lagos +- `Africa/Libreville` - Africa/Libreville +- `Africa/Lome` - Africa/Lome +- `Africa/Luanda` - Africa/Luanda +- `Africa/Lubumbashi` - Africa/Lubumbashi +- `Africa/Lusaka` - Africa/Lusaka +- `Africa/Malabo` - Africa/Malabo +- `Africa/Maputo` - Africa/Maputo +- `Africa/Maseru` - Africa/Maseru +- `Africa/Mbabane` - Africa/Mbabane +- `Africa/Mogadishu` - Africa/Mogadishu +- `Africa/Monrovia` - Africa/Monrovia +- `Africa/Nairobi` - Africa/Nairobi +- `Africa/Ndjamena` - Africa/Ndjamena +- `Africa/Niamey` - Africa/Niamey +- `Africa/Nouakchott` - Africa/Nouakchott +- `Africa/Ouagadougou` - Africa/Ouagadougou +- `Africa/Porto-Novo` - Africa/Porto-Novo +- `Africa/Sao_Tome` - Africa/Sao_Tome +- `Africa/Tripoli` - Africa/Tripoli +- `Africa/Tunis` - Africa/Tunis +- `Africa/Windhoek` - Africa/Windhoek +- `America/Adak` - America/Adak +- `America/Anchorage` - America/Anchorage +- `America/Anguilla` - America/Anguilla +- `America/Antigua` - America/Antigua +- `America/Araguaina` - America/Araguaina +- `America/Argentina/Buenos_Aires` - America/Argentina/Buenos_Aires +- `America/Argentina/Catamarca` - America/Argentina/Catamarca +- `America/Argentina/Cordoba` - America/Argentina/Cordoba +- `America/Argentina/Jujuy` - America/Argentina/Jujuy +- `America/Argentina/La_Rioja` - America/Argentina/La_Rioja +- `America/Argentina/Mendoza` - America/Argentina/Mendoza +- `America/Argentina/Rio_Gallegos` - America/Argentina/Rio_Gallegos +- `America/Argentina/Salta` - America/Argentina/Salta +- `America/Argentina/San_Juan` - America/Argentina/San_Juan +- `America/Argentina/San_Luis` - America/Argentina/San_Luis +- `America/Argentina/Tucuman` - America/Argentina/Tucuman +- `America/Argentina/Ushuaia` - America/Argentina/Ushuaia +- `America/Aruba` - America/Aruba +- `America/Asuncion` - America/Asuncion +- `America/Atikokan` - America/Atikokan +- `America/Bahia` - America/Bahia +- `America/Bahia_Banderas` - America/Bahia_Banderas +- `America/Barbados` - America/Barbados +- `America/Belem` - America/Belem +- `America/Belize` - America/Belize +- `America/Blanc-Sablon` - America/Blanc-Sablon +- `America/Boa_Vista` - America/Boa_Vista +- `America/Bogota` - America/Bogota +- `America/Boise` - America/Boise +- `America/Cambridge_Bay` - America/Cambridge_Bay +- `America/Campo_Grande` - America/Campo_Grande +- `America/Cancun` - America/Cancun +- `America/Caracas` - America/Caracas +- `America/Cayenne` - America/Cayenne +- `America/Cayman` - America/Cayman +- `America/Chicago` - America/Chicago +- `America/Chihuahua` - America/Chihuahua +- `America/Ciudad_Juarez` - America/Ciudad_Juarez +- `America/Costa_Rica` - America/Costa_Rica +- `America/Creston` - America/Creston +- `America/Cuiaba` - America/Cuiaba +- `America/Curacao` - America/Curacao +- `America/Danmarkshavn` - America/Danmarkshavn +- `America/Dawson` - America/Dawson +- `America/Dawson_Creek` - America/Dawson_Creek +- `America/Denver` - America/Denver +- `America/Detroit` - America/Detroit +- `America/Dominica` - America/Dominica +- `America/Edmonton` - America/Edmonton +- `America/Eirunepe` - America/Eirunepe +- `America/El_Salvador` - America/El_Salvador +- `America/Fort_Nelson` - America/Fort_Nelson +- `America/Fortaleza` - America/Fortaleza +- `America/Glace_Bay` - America/Glace_Bay +- `America/Goose_Bay` - America/Goose_Bay +- `America/Grand_Turk` - America/Grand_Turk +- `America/Grenada` - America/Grenada +- `America/Guadeloupe` - America/Guadeloupe +- `America/Guatemala` - America/Guatemala +- `America/Guayaquil` - America/Guayaquil +- `America/Guyana` - America/Guyana +- `America/Halifax` - America/Halifax +- `America/Havana` - America/Havana +- `America/Hermosillo` - America/Hermosillo +- `America/Indiana/Indianapolis` - America/Indiana/Indianapolis +- `America/Indiana/Knox` - America/Indiana/Knox +- `America/Indiana/Marengo` - America/Indiana/Marengo +- `America/Indiana/Petersburg` - America/Indiana/Petersburg +- `America/Indiana/Tell_City` - America/Indiana/Tell_City +- `America/Indiana/Vevay` - America/Indiana/Vevay +- `America/Indiana/Vincennes` - America/Indiana/Vincennes +- `America/Indiana/Winamac` - America/Indiana/Winamac +- `America/Inuvik` - America/Inuvik +- `America/Iqaluit` - America/Iqaluit +- `America/Jamaica` - America/Jamaica +- `America/Juneau` - America/Juneau +- `America/Kentucky/Louisville` - America/Kentucky/Louisville +- `America/Kentucky/Monticello` - America/Kentucky/Monticello +- `America/Kralendijk` - America/Kralendijk +- `America/La_Paz` - America/La_Paz +- `America/Lima` - America/Lima +- `America/Los_Angeles` - America/Los_Angeles +- `America/Lower_Princes` - America/Lower_Princes +- `America/Maceio` - America/Maceio +- `America/Managua` - America/Managua +- `America/Manaus` - America/Manaus +- `America/Marigot` - America/Marigot +- `America/Martinique` - America/Martinique +- `America/Matamoros` - America/Matamoros +- `America/Mazatlan` - America/Mazatlan +- `America/Menominee` - America/Menominee +- `America/Merida` - America/Merida +- `America/Metlakatla` - America/Metlakatla +- `America/Mexico_City` - America/Mexico_City +- `America/Miquelon` - America/Miquelon +- `America/Moncton` - America/Moncton +- `America/Monterrey` - America/Monterrey +- `America/Montevideo` - America/Montevideo +- `America/Montserrat` - America/Montserrat +- `America/Nassau` - America/Nassau +- `America/New_York` - America/New_York +- `America/Nome` - America/Nome +- `America/Noronha` - America/Noronha +- `America/North_Dakota/Beulah` - America/North_Dakota/Beulah +- `America/North_Dakota/Center` - America/North_Dakota/Center +- `America/North_Dakota/New_Salem` - America/North_Dakota/New_Salem +- `America/Nuuk` - America/Nuuk +- `America/Ojinaga` - America/Ojinaga +- `America/Panama` - America/Panama +- `America/Paramaribo` - America/Paramaribo +- `America/Phoenix` - America/Phoenix +- `America/Port-au-Prince` - America/Port-au-Prince +- `America/Port_of_Spain` - America/Port_of_Spain +- `America/Porto_Velho` - America/Porto_Velho +- `America/Puerto_Rico` - America/Puerto_Rico +- `America/Punta_Arenas` - America/Punta_Arenas +- `America/Rankin_Inlet` - America/Rankin_Inlet +- `America/Recife` - America/Recife +- `America/Regina` - America/Regina +- `America/Resolute` - America/Resolute +- `America/Rio_Branco` - America/Rio_Branco +- `America/Santarem` - America/Santarem +- `America/Santiago` - America/Santiago +- `America/Santo_Domingo` - America/Santo_Domingo +- `America/Sao_Paulo` - America/Sao_Paulo +- `America/Scoresbysund` - America/Scoresbysund +- `America/Sitka` - America/Sitka +- `America/St_Barthelemy` - America/St_Barthelemy +- `America/St_Johns` - America/St_Johns +- `America/St_Kitts` - America/St_Kitts +- `America/St_Lucia` - America/St_Lucia +- `America/St_Thomas` - America/St_Thomas +- `America/St_Vincent` - America/St_Vincent +- `America/Swift_Current` - America/Swift_Current +- `America/Tegucigalpa` - America/Tegucigalpa +- `America/Thule` - America/Thule +- `America/Tijuana` - America/Tijuana +- `America/Toronto` - America/Toronto +- `America/Tortola` - America/Tortola +- `America/Vancouver` - America/Vancouver +- `America/Whitehorse` - America/Whitehorse +- `America/Winnipeg` - America/Winnipeg +- `America/Yakutat` - America/Yakutat +- `Antarctica/Casey` - Antarctica/Casey +- `Antarctica/Davis` - Antarctica/Davis +- `Antarctica/DumontDUrville` - Antarctica/DumontDUrville +- `Antarctica/Macquarie` - Antarctica/Macquarie +- `Antarctica/Mawson` - Antarctica/Mawson +- `Antarctica/McMurdo` - Antarctica/McMurdo +- `Antarctica/Palmer` - Antarctica/Palmer +- `Antarctica/Rothera` - Antarctica/Rothera +- `Antarctica/Syowa` - Antarctica/Syowa +- `Antarctica/Troll` - Antarctica/Troll +- `Antarctica/Vostok` - Antarctica/Vostok +- `Arctic/Longyearbyen` - Arctic/Longyearbyen +- `Asia/Aden` - Asia/Aden +- `Asia/Almaty` - Asia/Almaty +- `Asia/Amman` - Asia/Amman +- `Asia/Anadyr` - Asia/Anadyr +- `Asia/Aqtau` - Asia/Aqtau +- `Asia/Aqtobe` - Asia/Aqtobe +- `Asia/Ashgabat` - Asia/Ashgabat +- `Asia/Atyrau` - Asia/Atyrau +- `Asia/Baghdad` - Asia/Baghdad +- `Asia/Bahrain` - Asia/Bahrain +- `Asia/Baku` - Asia/Baku +- `Asia/Bangkok` - Asia/Bangkok +- `Asia/Barnaul` - Asia/Barnaul +- `Asia/Beirut` - Asia/Beirut +- `Asia/Bishkek` - Asia/Bishkek +- `Asia/Brunei` - Asia/Brunei +- `Asia/Chita` - Asia/Chita +- `Asia/Choibalsan` - Asia/Choibalsan +- `Asia/Colombo` - Asia/Colombo +- `Asia/Damascus` - Asia/Damascus +- `Asia/Dhaka` - Asia/Dhaka +- `Asia/Dili` - Asia/Dili +- `Asia/Dubai` - Asia/Dubai +- `Asia/Dushanbe` - Asia/Dushanbe +- `Asia/Famagusta` - Asia/Famagusta +- `Asia/Gaza` - Asia/Gaza +- `Asia/Hebron` - Asia/Hebron +- `Asia/Ho_Chi_Minh` - Asia/Ho_Chi_Minh +- `Asia/Hong_Kong` - Asia/Hong_Kong +- `Asia/Hovd` - Asia/Hovd +- `Asia/Irkutsk` - Asia/Irkutsk +- `Asia/Jakarta` - Asia/Jakarta +- `Asia/Jayapura` - Asia/Jayapura +- `Asia/Jerusalem` - Asia/Jerusalem +- `Asia/Kabul` - Asia/Kabul +- `Asia/Kamchatka` - Asia/Kamchatka +- `Asia/Karachi` - Asia/Karachi +- `Asia/Kathmandu` - Asia/Kathmandu +- `Asia/Khandyga` - Asia/Khandyga +- `Asia/Kolkata` - Asia/Kolkata +- `Asia/Krasnoyarsk` - Asia/Krasnoyarsk +- `Asia/Kuala_Lumpur` - Asia/Kuala_Lumpur +- `Asia/Kuching` - Asia/Kuching +- `Asia/Kuwait` - Asia/Kuwait +- `Asia/Macau` - Asia/Macau +- `Asia/Magadan` - Asia/Magadan +- `Asia/Makassar` - Asia/Makassar +- `Asia/Manila` - Asia/Manila +- `Asia/Muscat` - Asia/Muscat +- `Asia/Nicosia` - Asia/Nicosia +- `Asia/Novokuznetsk` - Asia/Novokuznetsk +- `Asia/Novosibirsk` - Asia/Novosibirsk +- `Asia/Omsk` - Asia/Omsk +- `Asia/Oral` - Asia/Oral +- `Asia/Phnom_Penh` - Asia/Phnom_Penh +- `Asia/Pontianak` - Asia/Pontianak +- `Asia/Pyongyang` - Asia/Pyongyang +- `Asia/Qatar` - Asia/Qatar +- `Asia/Qostanay` - Asia/Qostanay +- `Asia/Qyzylorda` - Asia/Qyzylorda +- `Asia/Riyadh` - Asia/Riyadh +- `Asia/Sakhalin` - Asia/Sakhalin +- `Asia/Samarkand` - Asia/Samarkand +- `Asia/Seoul` - Asia/Seoul +- `Asia/Shanghai` - Asia/Shanghai +- `Asia/Singapore` - Asia/Singapore +- `Asia/Srednekolymsk` - Asia/Srednekolymsk +- `Asia/Taipei` - Asia/Taipei +- `Asia/Tashkent` - Asia/Tashkent +- `Asia/Tbilisi` - Asia/Tbilisi +- `Asia/Tehran` - Asia/Tehran +- `Asia/Thimphu` - Asia/Thimphu +- `Asia/Tokyo` - Asia/Tokyo +- `Asia/Tomsk` - Asia/Tomsk +- `Asia/Ulaanbaatar` - Asia/Ulaanbaatar +- `Asia/Urumqi` - Asia/Urumqi +- `Asia/Ust-Nera` - Asia/Ust-Nera +- `Asia/Vientiane` - Asia/Vientiane +- `Asia/Vladivostok` - Asia/Vladivostok +- `Asia/Yakutsk` - Asia/Yakutsk +- `Asia/Yangon` - Asia/Yangon +- `Asia/Yekaterinburg` - Asia/Yekaterinburg +- `Asia/Yerevan` - Asia/Yerevan +- `Atlantic/Azores` - Atlantic/Azores +- `Atlantic/Bermuda` - Atlantic/Bermuda +- `Atlantic/Canary` - Atlantic/Canary +- `Atlantic/Cape_Verde` - Atlantic/Cape_Verde +- `Atlantic/Faroe` - Atlantic/Faroe +- `Atlantic/Madeira` - Atlantic/Madeira +- `Atlantic/Reykjavik` - Atlantic/Reykjavik +- `Atlantic/South_Georgia` - Atlantic/South_Georgia +- `Atlantic/St_Helena` - Atlantic/St_Helena +- `Atlantic/Stanley` - Atlantic/Stanley +- `Australia/Adelaide` - Australia/Adelaide +- `Australia/Brisbane` - Australia/Brisbane +- `Australia/Broken_Hill` - Australia/Broken_Hill +- `Australia/Darwin` - Australia/Darwin +- `Australia/Eucla` - Australia/Eucla +- `Australia/Hobart` - Australia/Hobart +- `Australia/Lindeman` - Australia/Lindeman +- `Australia/Lord_Howe` - Australia/Lord_Howe +- `Australia/Melbourne` - Australia/Melbourne +- `Australia/Perth` - Australia/Perth +- `Australia/Sydney` - Australia/Sydney +- `Canada/Atlantic` - Canada/Atlantic +- `Canada/Central` - Canada/Central +- `Canada/Eastern` - Canada/Eastern +- `Canada/Mountain` - Canada/Mountain +- `Canada/Newfoundland` - Canada/Newfoundland +- `Canada/Pacific` - Canada/Pacific +- `Europe/Amsterdam` - Europe/Amsterdam +- `Europe/Andorra` - Europe/Andorra +- `Europe/Astrakhan` - Europe/Astrakhan +- `Europe/Athens` - Europe/Athens +- `Europe/Belgrade` - Europe/Belgrade +- `Europe/Berlin` - Europe/Berlin +- `Europe/Bratislava` - Europe/Bratislava +- `Europe/Brussels` - Europe/Brussels +- `Europe/Bucharest` - Europe/Bucharest +- `Europe/Budapest` - Europe/Budapest +- `Europe/Busingen` - Europe/Busingen +- `Europe/Chisinau` - Europe/Chisinau +- `Europe/Copenhagen` - Europe/Copenhagen +- `Europe/Dublin` - Europe/Dublin +- `Europe/Gibraltar` - Europe/Gibraltar +- `Europe/Guernsey` - Europe/Guernsey +- `Europe/Helsinki` - Europe/Helsinki +- `Europe/Isle_of_Man` - Europe/Isle_of_Man +- `Europe/Istanbul` - Europe/Istanbul +- `Europe/Jersey` - Europe/Jersey +- `Europe/Kaliningrad` - Europe/Kaliningrad +- `Europe/Kirov` - Europe/Kirov +- `Europe/Kyiv` - Europe/Kyiv +- `Europe/Lisbon` - Europe/Lisbon +- `Europe/Ljubljana` - Europe/Ljubljana +- `Europe/London` - Europe/London +- `Europe/Luxembourg` - Europe/Luxembourg +- `Europe/Madrid` - Europe/Madrid +- `Europe/Malta` - Europe/Malta +- `Europe/Mariehamn` - Europe/Mariehamn +- `Europe/Minsk` - Europe/Minsk +- `Europe/Monaco` - Europe/Monaco +- `Europe/Moscow` - Europe/Moscow +- `Europe/Oslo` - Europe/Oslo +- `Europe/Paris` - Europe/Paris +- `Europe/Podgorica` - Europe/Podgorica +- `Europe/Prague` - Europe/Prague +- `Europe/Riga` - Europe/Riga +- `Europe/Rome` - Europe/Rome +- `Europe/Samara` - Europe/Samara +- `Europe/San_Marino` - Europe/San_Marino +- `Europe/Sarajevo` - Europe/Sarajevo +- `Europe/Saratov` - Europe/Saratov +- `Europe/Simferopol` - Europe/Simferopol +- `Europe/Skopje` - Europe/Skopje +- `Europe/Sofia` - Europe/Sofia +- `Europe/Stockholm` - Europe/Stockholm +- `Europe/Tallinn` - Europe/Tallinn +- `Europe/Tirane` - Europe/Tirane +- `Europe/Ulyanovsk` - Europe/Ulyanovsk +- `Europe/Vaduz` - Europe/Vaduz +- `Europe/Vatican` - Europe/Vatican +- `Europe/Vienna` - Europe/Vienna +- `Europe/Vilnius` - Europe/Vilnius +- `Europe/Volgograd` - Europe/Volgograd +- `Europe/Warsaw` - Europe/Warsaw +- `Europe/Zagreb` - Europe/Zagreb +- `Europe/Zurich` - Europe/Zurich +- `GMT` - GMT +- `Indian/Antananarivo` - Indian/Antananarivo +- `Indian/Chagos` - Indian/Chagos +- `Indian/Christmas` - Indian/Christmas +- `Indian/Cocos` - Indian/Cocos +- `Indian/Comoro` - Indian/Comoro +- `Indian/Kerguelen` - Indian/Kerguelen +- `Indian/Mahe` - Indian/Mahe +- `Indian/Maldives` - Indian/Maldives +- `Indian/Mauritius` - Indian/Mauritius +- `Indian/Mayotte` - Indian/Mayotte +- `Indian/Reunion` - Indian/Reunion +- `Pacific/Apia` - Pacific/Apia +- `Pacific/Auckland` - Pacific/Auckland +- `Pacific/Bougainville` - Pacific/Bougainville +- `Pacific/Chatham` - Pacific/Chatham +- `Pacific/Chuuk` - Pacific/Chuuk +- `Pacific/Easter` - Pacific/Easter +- `Pacific/Efate` - Pacific/Efate +- `Pacific/Fakaofo` - Pacific/Fakaofo +- `Pacific/Fiji` - Pacific/Fiji +- `Pacific/Funafuti` - Pacific/Funafuti +- `Pacific/Galapagos` - Pacific/Galapagos +- `Pacific/Gambier` - Pacific/Gambier +- `Pacific/Guadalcanal` - Pacific/Guadalcanal +- `Pacific/Guam` - Pacific/Guam +- `Pacific/Honolulu` - Pacific/Honolulu +- `Pacific/Kanton` - Pacific/Kanton +- `Pacific/Kiritimati` - Pacific/Kiritimati +- `Pacific/Kosrae` - Pacific/Kosrae +- `Pacific/Kwajalein` - Pacific/Kwajalein +- `Pacific/Majuro` - Pacific/Majuro +- `Pacific/Marquesas` - Pacific/Marquesas +- `Pacific/Midway` - Pacific/Midway +- `Pacific/Nauru` - Pacific/Nauru +- `Pacific/Niue` - Pacific/Niue +- `Pacific/Norfolk` - Pacific/Norfolk +- `Pacific/Noumea` - Pacific/Noumea +- `Pacific/Pago_Pago` - Pacific/Pago_Pago +- `Pacific/Palau` - Pacific/Palau +- `Pacific/Pitcairn` - Pacific/Pitcairn +- `Pacific/Pohnpei` - Pacific/Pohnpei +- `Pacific/Port_Moresby` - Pacific/Port_Moresby +- `Pacific/Rarotonga` - Pacific/Rarotonga +- `Pacific/Saipan` - Pacific/Saipan +- `Pacific/Tahiti` - Pacific/Tahiti +- `Pacific/Tarawa` - Pacific/Tarawa +- `Pacific/Tongatapu` - Pacific/Tongatapu +- `Pacific/Wake` - Pacific/Wake +- `Pacific/Wallis` - Pacific/Wallis +- `US/Alaska` - US/Alaska +- `US/Arizona` - US/Arizona +- `US/Central` - US/Central +- `US/Eastern` - US/Eastern +- `US/Hawaii` - US/Hawaii +- `US/Mountain` - US/Mountain +- `US/Pacific` - US/Pacific +- `UTC` - UTC + +One of `Africa/Abidjan`, `Africa/Accra`, `Africa/Addis_Ababa`, `Africa/Algiers`, `Africa/Asmara`, `Africa/Bamako`, `Africa/Bangui`, `Africa/Banjul`, `Africa/Bissau`, `Africa/Blantyre`, `Africa/Brazzaville`, `Africa/Bujumbura`, `Africa/Cairo`, `Africa/Casablanca`, `Africa/Ceuta`, `Africa/Conakry`, `Africa/Dakar`, `Africa/Dar_es_Salaam`, `Africa/Djibouti`, `Africa/Douala`, `Africa/El_Aaiun`, `Africa/Freetown`, `Africa/Gaborone`, `Africa/Harare`, `Africa/Johannesburg`, `Africa/Juba`, `Africa/Kampala`, `Africa/Khartoum`, `Africa/Kigali`, `Africa/Kinshasa`, `Africa/Lagos`, `Africa/Libreville`, `Africa/Lome`, `Africa/Luanda`, `Africa/Lubumbashi`, `Africa/Lusaka`, `Africa/Malabo`, `Africa/Maputo`, `Africa/Maseru`, `Africa/Mbabane`, `Africa/Mogadishu`, `Africa/Monrovia`, `Africa/Nairobi`, `Africa/Ndjamena`, `Africa/Niamey`, `Africa/Nouakchott`, `Africa/Ouagadougou`, `Africa/Porto-Novo`, `Africa/Sao_Tome`, `Africa/Tripoli`, `Africa/Tunis`, `Africa/Windhoek`, `America/Adak`, `America/Anchorage`, `America/Anguilla`, `America/Antigua`, `America/Araguaina`, `America/Argentina/Buenos_Aires`, `America/Argentina/Catamarca`, `America/Argentina/Cordoba`, `America/Argentina/Jujuy`, `America/Argentina/La_Rioja`, `America/Argentina/Mendoza`, `America/Argentina/Rio_Gallegos`, `America/Argentina/Salta`, `America/Argentina/San_Juan`, `America/Argentina/San_Luis`, `America/Argentina/Tucuman`, `America/Argentina/Ushuaia`, `America/Aruba`, `America/Asuncion`, `America/Atikokan`, `America/Bahia`, `America/Bahia_Banderas`, `America/Barbados`, `America/Belem`, `America/Belize`, `America/Blanc-Sablon`, `America/Boa_Vista`, `America/Bogota`, `America/Boise`, `America/Cambridge_Bay`, `America/Campo_Grande`, `America/Cancun`, `America/Caracas`, `America/Cayenne`, `America/Cayman`, `America/Chicago`, `America/Chihuahua`, `America/Ciudad_Juarez`, `America/Costa_Rica`, `America/Creston`, `America/Cuiaba`, `America/Curacao`, `America/Danmarkshavn`, `America/Dawson`, `America/Dawson_Creek`, `America/Denver`, `America/Detroit`, `America/Dominica`, `America/Edmonton`, `America/Eirunepe`, `America/El_Salvador`, `America/Fort_Nelson`, `America/Fortaleza`, `America/Glace_Bay`, `America/Goose_Bay`, `America/Grand_Turk`, `America/Grenada`, `America/Guadeloupe`, `America/Guatemala`, `America/Guayaquil`, `America/Guyana`, `America/Halifax`, `America/Havana`, `America/Hermosillo`, `America/Indiana/Indianapolis`, `America/Indiana/Knox`, `America/Indiana/Marengo`, `America/Indiana/Petersburg`, `America/Indiana/Tell_City`, `America/Indiana/Vevay`, `America/Indiana/Vincennes`, `America/Indiana/Winamac`, `America/Inuvik`, `America/Iqaluit`, `America/Jamaica`, `America/Juneau`, `America/Kentucky/Louisville`, `America/Kentucky/Monticello`, `America/Kralendijk`, `America/La_Paz`, `America/Lima`, `America/Los_Angeles`, `America/Lower_Princes`, `America/Maceio`, `America/Managua`, `America/Manaus`, `America/Marigot`, `America/Martinique`, `America/Matamoros`, `America/Mazatlan`, `America/Menominee`, `America/Merida`, `America/Metlakatla`, `America/Mexico_City`, `America/Miquelon`, `America/Moncton`, `America/Monterrey`, `America/Montevideo`, `America/Montserrat`, `America/Nassau`, `America/New_York`, `America/Nome`, `America/Noronha`, `America/North_Dakota/Beulah`, `America/North_Dakota/Center`, `America/North_Dakota/New_Salem`, `America/Nuuk`, `America/Ojinaga`, `America/Panama`, `America/Paramaribo`, `America/Phoenix`, `America/Port-au-Prince`, `America/Port_of_Spain`, `America/Porto_Velho`, `America/Puerto_Rico`, `America/Punta_Arenas`, `America/Rankin_Inlet`, `America/Recife`, `America/Regina`, `America/Resolute`, `America/Rio_Branco`, `America/Santarem`, `America/Santiago`, `America/Santo_Domingo`, `America/Sao_Paulo`, `America/Scoresbysund`, `America/Sitka`, `America/St_Barthelemy`, `America/St_Johns`, `America/St_Kitts`, `America/St_Lucia`, `America/St_Thomas`, `America/St_Vincent`, `America/Swift_Current`, `America/Tegucigalpa`, `America/Thule`, `America/Tijuana`, `America/Toronto`, `America/Tortola`, `America/Vancouver`, `America/Whitehorse`, `America/Winnipeg`, `America/Yakutat`, `Antarctica/Casey`, `Antarctica/Davis`, `Antarctica/DumontDUrville`, `Antarctica/Macquarie`, `Antarctica/Mawson`, `Antarctica/McMurdo`, `Antarctica/Palmer`, `Antarctica/Rothera`, `Antarctica/Syowa`, `Antarctica/Troll`, `Antarctica/Vostok`, `Arctic/Longyearbyen`, `Asia/Aden`, `Asia/Almaty`, `Asia/Amman`, `Asia/Anadyr`, `Asia/Aqtau`, `Asia/Aqtobe`, `Asia/Ashgabat`, `Asia/Atyrau`, `Asia/Baghdad`, `Asia/Bahrain`, `Asia/Baku`, `Asia/Bangkok`, `Asia/Barnaul`, `Asia/Beirut`, `Asia/Bishkek`, `Asia/Brunei`, `Asia/Chita`, `Asia/Choibalsan`, `Asia/Colombo`, `Asia/Damascus`, `Asia/Dhaka`, `Asia/Dili`, `Asia/Dubai`, `Asia/Dushanbe`, `Asia/Famagusta`, `Asia/Gaza`, `Asia/Hebron`, `Asia/Ho_Chi_Minh`, `Asia/Hong_Kong`, `Asia/Hovd`, `Asia/Irkutsk`, `Asia/Jakarta`, `Asia/Jayapura`, `Asia/Jerusalem`, `Asia/Kabul`, `Asia/Kamchatka`, `Asia/Karachi`, `Asia/Kathmandu`, `Asia/Khandyga`, `Asia/Kolkata`, `Asia/Krasnoyarsk`, `Asia/Kuala_Lumpur`, `Asia/Kuching`, `Asia/Kuwait`, `Asia/Macau`, `Asia/Magadan`, `Asia/Makassar`, `Asia/Manila`, `Asia/Muscat`, `Asia/Nicosia`, `Asia/Novokuznetsk`, `Asia/Novosibirsk`, `Asia/Omsk`, `Asia/Oral`, `Asia/Phnom_Penh`, `Asia/Pontianak`, `Asia/Pyongyang`, `Asia/Qatar`, `Asia/Qostanay`, `Asia/Qyzylorda`, `Asia/Riyadh`, `Asia/Sakhalin`, `Asia/Samarkand`, `Asia/Seoul`, `Asia/Shanghai`, `Asia/Singapore`, `Asia/Srednekolymsk`, `Asia/Taipei`, `Asia/Tashkent`, `Asia/Tbilisi`, `Asia/Tehran`, `Asia/Thimphu`, `Asia/Tokyo`, `Asia/Tomsk`, `Asia/Ulaanbaatar`, `Asia/Urumqi`, `Asia/Ust-Nera`, `Asia/Vientiane`, `Asia/Vladivostok`, `Asia/Yakutsk`, `Asia/Yangon`, `Asia/Yekaterinburg`, `Asia/Yerevan`, `Atlantic/Azores`, `Atlantic/Bermuda`, `Atlantic/Canary`, `Atlantic/Cape_Verde`, `Atlantic/Faroe`, `Atlantic/Madeira`, `Atlantic/Reykjavik`, `Atlantic/South_Georgia`, `Atlantic/St_Helena`, `Atlantic/Stanley`, `Australia/Adelaide`, `Australia/Brisbane`, `Australia/Broken_Hill`, `Australia/Darwin`, `Australia/Eucla`, `Australia/Hobart`, `Australia/Lindeman`, `Australia/Lord_Howe`, `Australia/Melbourne`, `Australia/Perth`, `Australia/Sydney`, `Canada/Atlantic`, `Canada/Central`, `Canada/Eastern`, `Canada/Mountain`, `Canada/Newfoundland`, `Canada/Pacific`, `Europe/Amsterdam`, `Europe/Andorra`, `Europe/Astrakhan`, `Europe/Athens`, `Europe/Belgrade`, `Europe/Berlin`, `Europe/Bratislava`, `Europe/Brussels`, `Europe/Bucharest`, `Europe/Budapest`, `Europe/Busingen`, `Europe/Chisinau`, `Europe/Copenhagen`, `Europe/Dublin`, `Europe/Gibraltar`, `Europe/Guernsey`, `Europe/Helsinki`, `Europe/Isle_of_Man`, `Europe/Istanbul`, `Europe/Jersey`, `Europe/Kaliningrad`, `Europe/Kirov`, `Europe/Kyiv`, `Europe/Lisbon`, `Europe/Ljubljana`, `Europe/London`, `Europe/Luxembourg`, `Europe/Madrid`, `Europe/Malta`, `Europe/Mariehamn`, `Europe/Minsk`, `Europe/Monaco`, `Europe/Moscow`, `Europe/Oslo`, `Europe/Paris`, `Europe/Podgorica`, `Europe/Prague`, `Europe/Riga`, `Europe/Rome`, `Europe/Samara`, `Europe/San_Marino`, `Europe/Sarajevo`, `Europe/Saratov`, `Europe/Simferopol`, `Europe/Skopje`, `Europe/Sofia`, `Europe/Stockholm`, `Europe/Tallinn`, `Europe/Tirane`, `Europe/Ulyanovsk`, `Europe/Vaduz`, `Europe/Vatican`, `Europe/Vienna`, `Europe/Vilnius`, `Europe/Volgograd`, `Europe/Warsaw`, `Europe/Zagreb`, `Europe/Zurich`, `GMT`, `Indian/Antananarivo`, `Indian/Chagos`, `Indian/Christmas`, `Indian/Cocos`, `Indian/Comoro`, `Indian/Kerguelen`, `Indian/Mahe`, `Indian/Maldives`, `Indian/Mauritius`, `Indian/Mayotte`, `Indian/Reunion`, `Pacific/Apia`, `Pacific/Auckland`, `Pacific/Bougainville`, `Pacific/Chatham`, `Pacific/Chuuk`, `Pacific/Easter`, `Pacific/Efate`, `Pacific/Fakaofo`, `Pacific/Fiji`, `Pacific/Funafuti`, `Pacific/Galapagos`, `Pacific/Gambier`, `Pacific/Guadalcanal`, `Pacific/Guam`, `Pacific/Honolulu`, `Pacific/Kanton`, `Pacific/Kiritimati`, `Pacific/Kosrae`, `Pacific/Kwajalein`, `Pacific/Majuro`, `Pacific/Marquesas`, `Pacific/Midway`, `Pacific/Nauru`, `Pacific/Niue`, `Pacific/Norfolk`, `Pacific/Noumea`, `Pacific/Pago_Pago`, `Pacific/Palau`, `Pacific/Pitcairn`, `Pacific/Pohnpei`, `Pacific/Port_Moresby`, `Pacific/Rarotonga`, `Pacific/Saipan`, `Pacific/Tahiti`, `Pacific/Tarawa`, `Pacific/Tongatapu`, `Pacific/Wake`, `Pacific/Wallis`, `US/Alaska`, `US/Arizona`, `US/Central`, `US/Eastern`, `US/Hawaii`, `US/Mountain`, `US/Pacific`, `UTC`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archive_in`, `archived_at`, `close_in`, `cover_image`, `cover_image_url`, `created_at`, `created_by_id`, `cycle_view`, `default_assignee_id`, `default_state_id`, `description`, `emoji`, `estimate_id`, `external_id`, `external_source`, `guest_view_all_features`, `icon_prop`, `id`, `identifier`, `intake_view`, `is_issue_type_enabled`, `is_time_tracking_enabled`, `issue_views_view`, `logo_props`, `module_view`, `name`, `network`, `page_view`, `priority`, `project_lead_id`, `start_date`, `state_id`, `target_date`, `timezone`. + + + + + +Comma-separated relations to embed: `default_assignee`, `project_lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archive_in": 1, + "archived_at": null, + "close_in": 1, + "cover_image": "https://example.com/cover.png", + "cover_image_url": "https://example.com", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "cycle_view": false, + "default_assignee_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "default_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "emoji": "1f680", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "guest_view_all_features": false, + "icon_prop": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "identifier": "PROJ", + "intake_view": false, + "is_issue_type_enabled": false, + "is_time_tracking_enabled": false, + "issue_views_view": false, + "logo_props": null, + "module_view": false, + "name": "Example name", + "network": 0, + "page_view": false, + "priority": "high", + "project_lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "start_date": "2026-01-12", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20", + "timezone": "Africa/Abidjan" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No project matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/projects/upsert-project.md b/docs/api-reference/v2/projects/upsert-project.md new file mode 100644 index 00000000..ee5de396 --- /dev/null +++ b/docs/api-reference/v2/projects/upsert-project.md @@ -0,0 +1,850 @@ +--- +title: Upsert a project +description: Upsert a project with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, upsert a project, projects, projects upsert +--- + +# Upsert a project + +
+ POST + /api/v2/workspaces/{slug}/projects/upsert/ +
+ +
+
+ +Projects are the main container for work items in a workspace. Create a project, or update the existing one that carries the same `(external_source, external_id)` pair. + +- Both `external_source` and `external_id` are required — without them there is nothing to match on and the request is a `400`. +- A created record answers `201` with `X-Plane-Upsert: created`; an updated one answers `200` with `X-Plane-Upsert: updated`. Branch on the header rather than guessing from the status. +- Upsert is safe for **sequential** importers. Two simultaneous upserts of the same key can each miss and each create, so serialize your writes per key. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Short project key used to prefix work item numbers, for example `ENG` in `ENG-142`. + +Maximum 255 characters. + + + + + +Display name. + +Maximum 255 characters. + + + + + +The archive in. + + + + + +The close in. + + + + + +URL of the cover image. + +Nullable. + + + + + +Whether cycle view. + + + + + +Id of the related default assignee. + +Nullable. + + + + + +Id of the related default state. + +Nullable. + + + + + +Free-form description. + + + + + +Emoji shown alongside the name. + +Maximum 255 characters. Nullable. + + + + + +Id of the related estimate. + +Nullable. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Whether guest view all features. + + + + + +The icon prop. + +Nullable. + + + + + +Whether intake view. + + + + + +Whether is issue type enabled. + + + + + +Whether is time tracking enabled. + + + + + +Whether issue views view. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Whether module view. + + + + + +Project visibility: `0` is private to members, `2` is visible to the whole workspace. + + + + + +Whether page view. + + + + + +- `none` - None +- `low` - Low +- `medium` - Medium +- `high` - High +- `urgent` - Urgent + +One of `none`, `low`, `medium`, `high`, `urgent`. + + + + + +Id of the related project lead. + +Nullable. + + + + + +Planned start date, as `YYYY-MM-DD`. + +Nullable. + + + + + +Id of the related state. + +Nullable. + + + + + +Planned due date, as `YYYY-MM-DD`. + +Nullable. + + + + + +- `Africa/Abidjan` - Africa/Abidjan +- `Africa/Accra` - Africa/Accra +- `Africa/Addis_Ababa` - Africa/Addis_Ababa +- `Africa/Algiers` - Africa/Algiers +- `Africa/Asmara` - Africa/Asmara +- `Africa/Bamako` - Africa/Bamako +- `Africa/Bangui` - Africa/Bangui +- `Africa/Banjul` - Africa/Banjul +- `Africa/Bissau` - Africa/Bissau +- `Africa/Blantyre` - Africa/Blantyre +- `Africa/Brazzaville` - Africa/Brazzaville +- `Africa/Bujumbura` - Africa/Bujumbura +- `Africa/Cairo` - Africa/Cairo +- `Africa/Casablanca` - Africa/Casablanca +- `Africa/Ceuta` - Africa/Ceuta +- `Africa/Conakry` - Africa/Conakry +- `Africa/Dakar` - Africa/Dakar +- `Africa/Dar_es_Salaam` - Africa/Dar_es_Salaam +- `Africa/Djibouti` - Africa/Djibouti +- `Africa/Douala` - Africa/Douala +- `Africa/El_Aaiun` - Africa/El_Aaiun +- `Africa/Freetown` - Africa/Freetown +- `Africa/Gaborone` - Africa/Gaborone +- `Africa/Harare` - Africa/Harare +- `Africa/Johannesburg` - Africa/Johannesburg +- `Africa/Juba` - Africa/Juba +- `Africa/Kampala` - Africa/Kampala +- `Africa/Khartoum` - Africa/Khartoum +- `Africa/Kigali` - Africa/Kigali +- `Africa/Kinshasa` - Africa/Kinshasa +- `Africa/Lagos` - Africa/Lagos +- `Africa/Libreville` - Africa/Libreville +- `Africa/Lome` - Africa/Lome +- `Africa/Luanda` - Africa/Luanda +- `Africa/Lubumbashi` - Africa/Lubumbashi +- `Africa/Lusaka` - Africa/Lusaka +- `Africa/Malabo` - Africa/Malabo +- `Africa/Maputo` - Africa/Maputo +- `Africa/Maseru` - Africa/Maseru +- `Africa/Mbabane` - Africa/Mbabane +- `Africa/Mogadishu` - Africa/Mogadishu +- `Africa/Monrovia` - Africa/Monrovia +- `Africa/Nairobi` - Africa/Nairobi +- `Africa/Ndjamena` - Africa/Ndjamena +- `Africa/Niamey` - Africa/Niamey +- `Africa/Nouakchott` - Africa/Nouakchott +- `Africa/Ouagadougou` - Africa/Ouagadougou +- `Africa/Porto-Novo` - Africa/Porto-Novo +- `Africa/Sao_Tome` - Africa/Sao_Tome +- `Africa/Tripoli` - Africa/Tripoli +- `Africa/Tunis` - Africa/Tunis +- `Africa/Windhoek` - Africa/Windhoek +- `America/Adak` - America/Adak +- `America/Anchorage` - America/Anchorage +- `America/Anguilla` - America/Anguilla +- `America/Antigua` - America/Antigua +- `America/Araguaina` - America/Araguaina +- `America/Argentina/Buenos_Aires` - America/Argentina/Buenos_Aires +- `America/Argentina/Catamarca` - America/Argentina/Catamarca +- `America/Argentina/Cordoba` - America/Argentina/Cordoba +- `America/Argentina/Jujuy` - America/Argentina/Jujuy +- `America/Argentina/La_Rioja` - America/Argentina/La_Rioja +- `America/Argentina/Mendoza` - America/Argentina/Mendoza +- `America/Argentina/Rio_Gallegos` - America/Argentina/Rio_Gallegos +- `America/Argentina/Salta` - America/Argentina/Salta +- `America/Argentina/San_Juan` - America/Argentina/San_Juan +- `America/Argentina/San_Luis` - America/Argentina/San_Luis +- `America/Argentina/Tucuman` - America/Argentina/Tucuman +- `America/Argentina/Ushuaia` - America/Argentina/Ushuaia +- `America/Aruba` - America/Aruba +- `America/Asuncion` - America/Asuncion +- `America/Atikokan` - America/Atikokan +- `America/Bahia` - America/Bahia +- `America/Bahia_Banderas` - America/Bahia_Banderas +- `America/Barbados` - America/Barbados +- `America/Belem` - America/Belem +- `America/Belize` - America/Belize +- `America/Blanc-Sablon` - America/Blanc-Sablon +- `America/Boa_Vista` - America/Boa_Vista +- `America/Bogota` - America/Bogota +- `America/Boise` - America/Boise +- `America/Cambridge_Bay` - America/Cambridge_Bay +- `America/Campo_Grande` - America/Campo_Grande +- `America/Cancun` - America/Cancun +- `America/Caracas` - America/Caracas +- `America/Cayenne` - America/Cayenne +- `America/Cayman` - America/Cayman +- `America/Chicago` - America/Chicago +- `America/Chihuahua` - America/Chihuahua +- `America/Ciudad_Juarez` - America/Ciudad_Juarez +- `America/Costa_Rica` - America/Costa_Rica +- `America/Creston` - America/Creston +- `America/Cuiaba` - America/Cuiaba +- `America/Curacao` - America/Curacao +- `America/Danmarkshavn` - America/Danmarkshavn +- `America/Dawson` - America/Dawson +- `America/Dawson_Creek` - America/Dawson_Creek +- `America/Denver` - America/Denver +- `America/Detroit` - America/Detroit +- `America/Dominica` - America/Dominica +- `America/Edmonton` - America/Edmonton +- `America/Eirunepe` - America/Eirunepe +- `America/El_Salvador` - America/El_Salvador +- `America/Fort_Nelson` - America/Fort_Nelson +- `America/Fortaleza` - America/Fortaleza +- `America/Glace_Bay` - America/Glace_Bay +- `America/Goose_Bay` - America/Goose_Bay +- `America/Grand_Turk` - America/Grand_Turk +- `America/Grenada` - America/Grenada +- `America/Guadeloupe` - America/Guadeloupe +- `America/Guatemala` - America/Guatemala +- `America/Guayaquil` - America/Guayaquil +- `America/Guyana` - America/Guyana +- `America/Halifax` - America/Halifax +- `America/Havana` - America/Havana +- `America/Hermosillo` - America/Hermosillo +- `America/Indiana/Indianapolis` - America/Indiana/Indianapolis +- `America/Indiana/Knox` - America/Indiana/Knox +- `America/Indiana/Marengo` - America/Indiana/Marengo +- `America/Indiana/Petersburg` - America/Indiana/Petersburg +- `America/Indiana/Tell_City` - America/Indiana/Tell_City +- `America/Indiana/Vevay` - America/Indiana/Vevay +- `America/Indiana/Vincennes` - America/Indiana/Vincennes +- `America/Indiana/Winamac` - America/Indiana/Winamac +- `America/Inuvik` - America/Inuvik +- `America/Iqaluit` - America/Iqaluit +- `America/Jamaica` - America/Jamaica +- `America/Juneau` - America/Juneau +- `America/Kentucky/Louisville` - America/Kentucky/Louisville +- `America/Kentucky/Monticello` - America/Kentucky/Monticello +- `America/Kralendijk` - America/Kralendijk +- `America/La_Paz` - America/La_Paz +- `America/Lima` - America/Lima +- `America/Los_Angeles` - America/Los_Angeles +- `America/Lower_Princes` - America/Lower_Princes +- `America/Maceio` - America/Maceio +- `America/Managua` - America/Managua +- `America/Manaus` - America/Manaus +- `America/Marigot` - America/Marigot +- `America/Martinique` - America/Martinique +- `America/Matamoros` - America/Matamoros +- `America/Mazatlan` - America/Mazatlan +- `America/Menominee` - America/Menominee +- `America/Merida` - America/Merida +- `America/Metlakatla` - America/Metlakatla +- `America/Mexico_City` - America/Mexico_City +- `America/Miquelon` - America/Miquelon +- `America/Moncton` - America/Moncton +- `America/Monterrey` - America/Monterrey +- `America/Montevideo` - America/Montevideo +- `America/Montserrat` - America/Montserrat +- `America/Nassau` - America/Nassau +- `America/New_York` - America/New_York +- `America/Nome` - America/Nome +- `America/Noronha` - America/Noronha +- `America/North_Dakota/Beulah` - America/North_Dakota/Beulah +- `America/North_Dakota/Center` - America/North_Dakota/Center +- `America/North_Dakota/New_Salem` - America/North_Dakota/New_Salem +- `America/Nuuk` - America/Nuuk +- `America/Ojinaga` - America/Ojinaga +- `America/Panama` - America/Panama +- `America/Paramaribo` - America/Paramaribo +- `America/Phoenix` - America/Phoenix +- `America/Port-au-Prince` - America/Port-au-Prince +- `America/Port_of_Spain` - America/Port_of_Spain +- `America/Porto_Velho` - America/Porto_Velho +- `America/Puerto_Rico` - America/Puerto_Rico +- `America/Punta_Arenas` - America/Punta_Arenas +- `America/Rankin_Inlet` - America/Rankin_Inlet +- `America/Recife` - America/Recife +- `America/Regina` - America/Regina +- `America/Resolute` - America/Resolute +- `America/Rio_Branco` - America/Rio_Branco +- `America/Santarem` - America/Santarem +- `America/Santiago` - America/Santiago +- `America/Santo_Domingo` - America/Santo_Domingo +- `America/Sao_Paulo` - America/Sao_Paulo +- `America/Scoresbysund` - America/Scoresbysund +- `America/Sitka` - America/Sitka +- `America/St_Barthelemy` - America/St_Barthelemy +- `America/St_Johns` - America/St_Johns +- `America/St_Kitts` - America/St_Kitts +- `America/St_Lucia` - America/St_Lucia +- `America/St_Thomas` - America/St_Thomas +- `America/St_Vincent` - America/St_Vincent +- `America/Swift_Current` - America/Swift_Current +- `America/Tegucigalpa` - America/Tegucigalpa +- `America/Thule` - America/Thule +- `America/Tijuana` - America/Tijuana +- `America/Toronto` - America/Toronto +- `America/Tortola` - America/Tortola +- `America/Vancouver` - America/Vancouver +- `America/Whitehorse` - America/Whitehorse +- `America/Winnipeg` - America/Winnipeg +- `America/Yakutat` - America/Yakutat +- `Antarctica/Casey` - Antarctica/Casey +- `Antarctica/Davis` - Antarctica/Davis +- `Antarctica/DumontDUrville` - Antarctica/DumontDUrville +- `Antarctica/Macquarie` - Antarctica/Macquarie +- `Antarctica/Mawson` - Antarctica/Mawson +- `Antarctica/McMurdo` - Antarctica/McMurdo +- `Antarctica/Palmer` - Antarctica/Palmer +- `Antarctica/Rothera` - Antarctica/Rothera +- `Antarctica/Syowa` - Antarctica/Syowa +- `Antarctica/Troll` - Antarctica/Troll +- `Antarctica/Vostok` - Antarctica/Vostok +- `Arctic/Longyearbyen` - Arctic/Longyearbyen +- `Asia/Aden` - Asia/Aden +- `Asia/Almaty` - Asia/Almaty +- `Asia/Amman` - Asia/Amman +- `Asia/Anadyr` - Asia/Anadyr +- `Asia/Aqtau` - Asia/Aqtau +- `Asia/Aqtobe` - Asia/Aqtobe +- `Asia/Ashgabat` - Asia/Ashgabat +- `Asia/Atyrau` - Asia/Atyrau +- `Asia/Baghdad` - Asia/Baghdad +- `Asia/Bahrain` - Asia/Bahrain +- `Asia/Baku` - Asia/Baku +- `Asia/Bangkok` - Asia/Bangkok +- `Asia/Barnaul` - Asia/Barnaul +- `Asia/Beirut` - Asia/Beirut +- `Asia/Bishkek` - Asia/Bishkek +- `Asia/Brunei` - Asia/Brunei +- `Asia/Chita` - Asia/Chita +- `Asia/Choibalsan` - Asia/Choibalsan +- `Asia/Colombo` - Asia/Colombo +- `Asia/Damascus` - Asia/Damascus +- `Asia/Dhaka` - Asia/Dhaka +- `Asia/Dili` - Asia/Dili +- `Asia/Dubai` - Asia/Dubai +- `Asia/Dushanbe` - Asia/Dushanbe +- `Asia/Famagusta` - Asia/Famagusta +- `Asia/Gaza` - Asia/Gaza +- `Asia/Hebron` - Asia/Hebron +- `Asia/Ho_Chi_Minh` - Asia/Ho_Chi_Minh +- `Asia/Hong_Kong` - Asia/Hong_Kong +- `Asia/Hovd` - Asia/Hovd +- `Asia/Irkutsk` - Asia/Irkutsk +- `Asia/Jakarta` - Asia/Jakarta +- `Asia/Jayapura` - Asia/Jayapura +- `Asia/Jerusalem` - Asia/Jerusalem +- `Asia/Kabul` - Asia/Kabul +- `Asia/Kamchatka` - Asia/Kamchatka +- `Asia/Karachi` - Asia/Karachi +- `Asia/Kathmandu` - Asia/Kathmandu +- `Asia/Khandyga` - Asia/Khandyga +- `Asia/Kolkata` - Asia/Kolkata +- `Asia/Krasnoyarsk` - Asia/Krasnoyarsk +- `Asia/Kuala_Lumpur` - Asia/Kuala_Lumpur +- `Asia/Kuching` - Asia/Kuching +- `Asia/Kuwait` - Asia/Kuwait +- `Asia/Macau` - Asia/Macau +- `Asia/Magadan` - Asia/Magadan +- `Asia/Makassar` - Asia/Makassar +- `Asia/Manila` - Asia/Manila +- `Asia/Muscat` - Asia/Muscat +- `Asia/Nicosia` - Asia/Nicosia +- `Asia/Novokuznetsk` - Asia/Novokuznetsk +- `Asia/Novosibirsk` - Asia/Novosibirsk +- `Asia/Omsk` - Asia/Omsk +- `Asia/Oral` - Asia/Oral +- `Asia/Phnom_Penh` - Asia/Phnom_Penh +- `Asia/Pontianak` - Asia/Pontianak +- `Asia/Pyongyang` - Asia/Pyongyang +- `Asia/Qatar` - Asia/Qatar +- `Asia/Qostanay` - Asia/Qostanay +- `Asia/Qyzylorda` - Asia/Qyzylorda +- `Asia/Riyadh` - Asia/Riyadh +- `Asia/Sakhalin` - Asia/Sakhalin +- `Asia/Samarkand` - Asia/Samarkand +- `Asia/Seoul` - Asia/Seoul +- `Asia/Shanghai` - Asia/Shanghai +- `Asia/Singapore` - Asia/Singapore +- `Asia/Srednekolymsk` - Asia/Srednekolymsk +- `Asia/Taipei` - Asia/Taipei +- `Asia/Tashkent` - Asia/Tashkent +- `Asia/Tbilisi` - Asia/Tbilisi +- `Asia/Tehran` - Asia/Tehran +- `Asia/Thimphu` - Asia/Thimphu +- `Asia/Tokyo` - Asia/Tokyo +- `Asia/Tomsk` - Asia/Tomsk +- `Asia/Ulaanbaatar` - Asia/Ulaanbaatar +- `Asia/Urumqi` - Asia/Urumqi +- `Asia/Ust-Nera` - Asia/Ust-Nera +- `Asia/Vientiane` - Asia/Vientiane +- `Asia/Vladivostok` - Asia/Vladivostok +- `Asia/Yakutsk` - Asia/Yakutsk +- `Asia/Yangon` - Asia/Yangon +- `Asia/Yekaterinburg` - Asia/Yekaterinburg +- `Asia/Yerevan` - Asia/Yerevan +- `Atlantic/Azores` - Atlantic/Azores +- `Atlantic/Bermuda` - Atlantic/Bermuda +- `Atlantic/Canary` - Atlantic/Canary +- `Atlantic/Cape_Verde` - Atlantic/Cape_Verde +- `Atlantic/Faroe` - Atlantic/Faroe +- `Atlantic/Madeira` - Atlantic/Madeira +- `Atlantic/Reykjavik` - Atlantic/Reykjavik +- `Atlantic/South_Georgia` - Atlantic/South_Georgia +- `Atlantic/St_Helena` - Atlantic/St_Helena +- `Atlantic/Stanley` - Atlantic/Stanley +- `Australia/Adelaide` - Australia/Adelaide +- `Australia/Brisbane` - Australia/Brisbane +- `Australia/Broken_Hill` - Australia/Broken_Hill +- `Australia/Darwin` - Australia/Darwin +- `Australia/Eucla` - Australia/Eucla +- `Australia/Hobart` - Australia/Hobart +- `Australia/Lindeman` - Australia/Lindeman +- `Australia/Lord_Howe` - Australia/Lord_Howe +- `Australia/Melbourne` - Australia/Melbourne +- `Australia/Perth` - Australia/Perth +- `Australia/Sydney` - Australia/Sydney +- `Canada/Atlantic` - Canada/Atlantic +- `Canada/Central` - Canada/Central +- `Canada/Eastern` - Canada/Eastern +- `Canada/Mountain` - Canada/Mountain +- `Canada/Newfoundland` - Canada/Newfoundland +- `Canada/Pacific` - Canada/Pacific +- `Europe/Amsterdam` - Europe/Amsterdam +- `Europe/Andorra` - Europe/Andorra +- `Europe/Astrakhan` - Europe/Astrakhan +- `Europe/Athens` - Europe/Athens +- `Europe/Belgrade` - Europe/Belgrade +- `Europe/Berlin` - Europe/Berlin +- `Europe/Bratislava` - Europe/Bratislava +- `Europe/Brussels` - Europe/Brussels +- `Europe/Bucharest` - Europe/Bucharest +- `Europe/Budapest` - Europe/Budapest +- `Europe/Busingen` - Europe/Busingen +- `Europe/Chisinau` - Europe/Chisinau +- `Europe/Copenhagen` - Europe/Copenhagen +- `Europe/Dublin` - Europe/Dublin +- `Europe/Gibraltar` - Europe/Gibraltar +- `Europe/Guernsey` - Europe/Guernsey +- `Europe/Helsinki` - Europe/Helsinki +- `Europe/Isle_of_Man` - Europe/Isle_of_Man +- `Europe/Istanbul` - Europe/Istanbul +- `Europe/Jersey` - Europe/Jersey +- `Europe/Kaliningrad` - Europe/Kaliningrad +- `Europe/Kirov` - Europe/Kirov +- `Europe/Kyiv` - Europe/Kyiv +- `Europe/Lisbon` - Europe/Lisbon +- `Europe/Ljubljana` - Europe/Ljubljana +- `Europe/London` - Europe/London +- `Europe/Luxembourg` - Europe/Luxembourg +- `Europe/Madrid` - Europe/Madrid +- `Europe/Malta` - Europe/Malta +- `Europe/Mariehamn` - Europe/Mariehamn +- `Europe/Minsk` - Europe/Minsk +- `Europe/Monaco` - Europe/Monaco +- `Europe/Moscow` - Europe/Moscow +- `Europe/Oslo` - Europe/Oslo +- `Europe/Paris` - Europe/Paris +- `Europe/Podgorica` - Europe/Podgorica +- `Europe/Prague` - Europe/Prague +- `Europe/Riga` - Europe/Riga +- `Europe/Rome` - Europe/Rome +- `Europe/Samara` - Europe/Samara +- `Europe/San_Marino` - Europe/San_Marino +- `Europe/Sarajevo` - Europe/Sarajevo +- `Europe/Saratov` - Europe/Saratov +- `Europe/Simferopol` - Europe/Simferopol +- `Europe/Skopje` - Europe/Skopje +- `Europe/Sofia` - Europe/Sofia +- `Europe/Stockholm` - Europe/Stockholm +- `Europe/Tallinn` - Europe/Tallinn +- `Europe/Tirane` - Europe/Tirane +- `Europe/Ulyanovsk` - Europe/Ulyanovsk +- `Europe/Vaduz` - Europe/Vaduz +- `Europe/Vatican` - Europe/Vatican +- `Europe/Vienna` - Europe/Vienna +- `Europe/Vilnius` - Europe/Vilnius +- `Europe/Volgograd` - Europe/Volgograd +- `Europe/Warsaw` - Europe/Warsaw +- `Europe/Zagreb` - Europe/Zagreb +- `Europe/Zurich` - Europe/Zurich +- `GMT` - GMT +- `Indian/Antananarivo` - Indian/Antananarivo +- `Indian/Chagos` - Indian/Chagos +- `Indian/Christmas` - Indian/Christmas +- `Indian/Cocos` - Indian/Cocos +- `Indian/Comoro` - Indian/Comoro +- `Indian/Kerguelen` - Indian/Kerguelen +- `Indian/Mahe` - Indian/Mahe +- `Indian/Maldives` - Indian/Maldives +- `Indian/Mauritius` - Indian/Mauritius +- `Indian/Mayotte` - Indian/Mayotte +- `Indian/Reunion` - Indian/Reunion +- `Pacific/Apia` - Pacific/Apia +- `Pacific/Auckland` - Pacific/Auckland +- `Pacific/Bougainville` - Pacific/Bougainville +- `Pacific/Chatham` - Pacific/Chatham +- `Pacific/Chuuk` - Pacific/Chuuk +- `Pacific/Easter` - Pacific/Easter +- `Pacific/Efate` - Pacific/Efate +- `Pacific/Fakaofo` - Pacific/Fakaofo +- `Pacific/Fiji` - Pacific/Fiji +- `Pacific/Funafuti` - Pacific/Funafuti +- `Pacific/Galapagos` - Pacific/Galapagos +- `Pacific/Gambier` - Pacific/Gambier +- `Pacific/Guadalcanal` - Pacific/Guadalcanal +- `Pacific/Guam` - Pacific/Guam +- `Pacific/Honolulu` - Pacific/Honolulu +- `Pacific/Kanton` - Pacific/Kanton +- `Pacific/Kiritimati` - Pacific/Kiritimati +- `Pacific/Kosrae` - Pacific/Kosrae +- `Pacific/Kwajalein` - Pacific/Kwajalein +- `Pacific/Majuro` - Pacific/Majuro +- `Pacific/Marquesas` - Pacific/Marquesas +- `Pacific/Midway` - Pacific/Midway +- `Pacific/Nauru` - Pacific/Nauru +- `Pacific/Niue` - Pacific/Niue +- `Pacific/Norfolk` - Pacific/Norfolk +- `Pacific/Noumea` - Pacific/Noumea +- `Pacific/Pago_Pago` - Pacific/Pago_Pago +- `Pacific/Palau` - Pacific/Palau +- `Pacific/Pitcairn` - Pacific/Pitcairn +- `Pacific/Pohnpei` - Pacific/Pohnpei +- `Pacific/Port_Moresby` - Pacific/Port_Moresby +- `Pacific/Rarotonga` - Pacific/Rarotonga +- `Pacific/Saipan` - Pacific/Saipan +- `Pacific/Tahiti` - Pacific/Tahiti +- `Pacific/Tarawa` - Pacific/Tarawa +- `Pacific/Tongatapu` - Pacific/Tongatapu +- `Pacific/Wake` - Pacific/Wake +- `Pacific/Wallis` - Pacific/Wallis +- `US/Alaska` - US/Alaska +- `US/Arizona` - US/Arizona +- `US/Central` - US/Central +- `US/Eastern` - US/Eastern +- `US/Hawaii` - US/Hawaii +- `US/Mountain` - US/Mountain +- `US/Pacific` - US/Pacific +- `UTC` - UTC + +One of `Africa/Abidjan`, `Africa/Accra`, `Africa/Addis_Ababa`, `Africa/Algiers`, `Africa/Asmara`, `Africa/Bamako`, `Africa/Bangui`, `Africa/Banjul`, `Africa/Bissau`, `Africa/Blantyre`, `Africa/Brazzaville`, `Africa/Bujumbura`, `Africa/Cairo`, `Africa/Casablanca`, `Africa/Ceuta`, `Africa/Conakry`, `Africa/Dakar`, `Africa/Dar_es_Salaam`, `Africa/Djibouti`, `Africa/Douala`, `Africa/El_Aaiun`, `Africa/Freetown`, `Africa/Gaborone`, `Africa/Harare`, `Africa/Johannesburg`, `Africa/Juba`, `Africa/Kampala`, `Africa/Khartoum`, `Africa/Kigali`, `Africa/Kinshasa`, `Africa/Lagos`, `Africa/Libreville`, `Africa/Lome`, `Africa/Luanda`, `Africa/Lubumbashi`, `Africa/Lusaka`, `Africa/Malabo`, `Africa/Maputo`, `Africa/Maseru`, `Africa/Mbabane`, `Africa/Mogadishu`, `Africa/Monrovia`, `Africa/Nairobi`, `Africa/Ndjamena`, `Africa/Niamey`, `Africa/Nouakchott`, `Africa/Ouagadougou`, `Africa/Porto-Novo`, `Africa/Sao_Tome`, `Africa/Tripoli`, `Africa/Tunis`, `Africa/Windhoek`, `America/Adak`, `America/Anchorage`, `America/Anguilla`, `America/Antigua`, `America/Araguaina`, `America/Argentina/Buenos_Aires`, `America/Argentina/Catamarca`, `America/Argentina/Cordoba`, `America/Argentina/Jujuy`, `America/Argentina/La_Rioja`, `America/Argentina/Mendoza`, `America/Argentina/Rio_Gallegos`, `America/Argentina/Salta`, `America/Argentina/San_Juan`, `America/Argentina/San_Luis`, `America/Argentina/Tucuman`, `America/Argentina/Ushuaia`, `America/Aruba`, `America/Asuncion`, `America/Atikokan`, `America/Bahia`, `America/Bahia_Banderas`, `America/Barbados`, `America/Belem`, `America/Belize`, `America/Blanc-Sablon`, `America/Boa_Vista`, `America/Bogota`, `America/Boise`, `America/Cambridge_Bay`, `America/Campo_Grande`, `America/Cancun`, `America/Caracas`, `America/Cayenne`, `America/Cayman`, `America/Chicago`, `America/Chihuahua`, `America/Ciudad_Juarez`, `America/Costa_Rica`, `America/Creston`, `America/Cuiaba`, `America/Curacao`, `America/Danmarkshavn`, `America/Dawson`, `America/Dawson_Creek`, `America/Denver`, `America/Detroit`, `America/Dominica`, `America/Edmonton`, `America/Eirunepe`, `America/El_Salvador`, `America/Fort_Nelson`, `America/Fortaleza`, `America/Glace_Bay`, `America/Goose_Bay`, `America/Grand_Turk`, `America/Grenada`, `America/Guadeloupe`, `America/Guatemala`, `America/Guayaquil`, `America/Guyana`, `America/Halifax`, `America/Havana`, `America/Hermosillo`, `America/Indiana/Indianapolis`, `America/Indiana/Knox`, `America/Indiana/Marengo`, `America/Indiana/Petersburg`, `America/Indiana/Tell_City`, `America/Indiana/Vevay`, `America/Indiana/Vincennes`, `America/Indiana/Winamac`, `America/Inuvik`, `America/Iqaluit`, `America/Jamaica`, `America/Juneau`, `America/Kentucky/Louisville`, `America/Kentucky/Monticello`, `America/Kralendijk`, `America/La_Paz`, `America/Lima`, `America/Los_Angeles`, `America/Lower_Princes`, `America/Maceio`, `America/Managua`, `America/Manaus`, `America/Marigot`, `America/Martinique`, `America/Matamoros`, `America/Mazatlan`, `America/Menominee`, `America/Merida`, `America/Metlakatla`, `America/Mexico_City`, `America/Miquelon`, `America/Moncton`, `America/Monterrey`, `America/Montevideo`, `America/Montserrat`, `America/Nassau`, `America/New_York`, `America/Nome`, `America/Noronha`, `America/North_Dakota/Beulah`, `America/North_Dakota/Center`, `America/North_Dakota/New_Salem`, `America/Nuuk`, `America/Ojinaga`, `America/Panama`, `America/Paramaribo`, `America/Phoenix`, `America/Port-au-Prince`, `America/Port_of_Spain`, `America/Porto_Velho`, `America/Puerto_Rico`, `America/Punta_Arenas`, `America/Rankin_Inlet`, `America/Recife`, `America/Regina`, `America/Resolute`, `America/Rio_Branco`, `America/Santarem`, `America/Santiago`, `America/Santo_Domingo`, `America/Sao_Paulo`, `America/Scoresbysund`, `America/Sitka`, `America/St_Barthelemy`, `America/St_Johns`, `America/St_Kitts`, `America/St_Lucia`, `America/St_Thomas`, `America/St_Vincent`, `America/Swift_Current`, `America/Tegucigalpa`, `America/Thule`, `America/Tijuana`, `America/Toronto`, `America/Tortola`, `America/Vancouver`, `America/Whitehorse`, `America/Winnipeg`, `America/Yakutat`, `Antarctica/Casey`, `Antarctica/Davis`, `Antarctica/DumontDUrville`, `Antarctica/Macquarie`, `Antarctica/Mawson`, `Antarctica/McMurdo`, `Antarctica/Palmer`, `Antarctica/Rothera`, `Antarctica/Syowa`, `Antarctica/Troll`, `Antarctica/Vostok`, `Arctic/Longyearbyen`, `Asia/Aden`, `Asia/Almaty`, `Asia/Amman`, `Asia/Anadyr`, `Asia/Aqtau`, `Asia/Aqtobe`, `Asia/Ashgabat`, `Asia/Atyrau`, `Asia/Baghdad`, `Asia/Bahrain`, `Asia/Baku`, `Asia/Bangkok`, `Asia/Barnaul`, `Asia/Beirut`, `Asia/Bishkek`, `Asia/Brunei`, `Asia/Chita`, `Asia/Choibalsan`, `Asia/Colombo`, `Asia/Damascus`, `Asia/Dhaka`, `Asia/Dili`, `Asia/Dubai`, `Asia/Dushanbe`, `Asia/Famagusta`, `Asia/Gaza`, `Asia/Hebron`, `Asia/Ho_Chi_Minh`, `Asia/Hong_Kong`, `Asia/Hovd`, `Asia/Irkutsk`, `Asia/Jakarta`, `Asia/Jayapura`, `Asia/Jerusalem`, `Asia/Kabul`, `Asia/Kamchatka`, `Asia/Karachi`, `Asia/Kathmandu`, `Asia/Khandyga`, `Asia/Kolkata`, `Asia/Krasnoyarsk`, `Asia/Kuala_Lumpur`, `Asia/Kuching`, `Asia/Kuwait`, `Asia/Macau`, `Asia/Magadan`, `Asia/Makassar`, `Asia/Manila`, `Asia/Muscat`, `Asia/Nicosia`, `Asia/Novokuznetsk`, `Asia/Novosibirsk`, `Asia/Omsk`, `Asia/Oral`, `Asia/Phnom_Penh`, `Asia/Pontianak`, `Asia/Pyongyang`, `Asia/Qatar`, `Asia/Qostanay`, `Asia/Qyzylorda`, `Asia/Riyadh`, `Asia/Sakhalin`, `Asia/Samarkand`, `Asia/Seoul`, `Asia/Shanghai`, `Asia/Singapore`, `Asia/Srednekolymsk`, `Asia/Taipei`, `Asia/Tashkent`, `Asia/Tbilisi`, `Asia/Tehran`, `Asia/Thimphu`, `Asia/Tokyo`, `Asia/Tomsk`, `Asia/Ulaanbaatar`, `Asia/Urumqi`, `Asia/Ust-Nera`, `Asia/Vientiane`, `Asia/Vladivostok`, `Asia/Yakutsk`, `Asia/Yangon`, `Asia/Yekaterinburg`, `Asia/Yerevan`, `Atlantic/Azores`, `Atlantic/Bermuda`, `Atlantic/Canary`, `Atlantic/Cape_Verde`, `Atlantic/Faroe`, `Atlantic/Madeira`, `Atlantic/Reykjavik`, `Atlantic/South_Georgia`, `Atlantic/St_Helena`, `Atlantic/Stanley`, `Australia/Adelaide`, `Australia/Brisbane`, `Australia/Broken_Hill`, `Australia/Darwin`, `Australia/Eucla`, `Australia/Hobart`, `Australia/Lindeman`, `Australia/Lord_Howe`, `Australia/Melbourne`, `Australia/Perth`, `Australia/Sydney`, `Canada/Atlantic`, `Canada/Central`, `Canada/Eastern`, `Canada/Mountain`, `Canada/Newfoundland`, `Canada/Pacific`, `Europe/Amsterdam`, `Europe/Andorra`, `Europe/Astrakhan`, `Europe/Athens`, `Europe/Belgrade`, `Europe/Berlin`, `Europe/Bratislava`, `Europe/Brussels`, `Europe/Bucharest`, `Europe/Budapest`, `Europe/Busingen`, `Europe/Chisinau`, `Europe/Copenhagen`, `Europe/Dublin`, `Europe/Gibraltar`, `Europe/Guernsey`, `Europe/Helsinki`, `Europe/Isle_of_Man`, `Europe/Istanbul`, `Europe/Jersey`, `Europe/Kaliningrad`, `Europe/Kirov`, `Europe/Kyiv`, `Europe/Lisbon`, `Europe/Ljubljana`, `Europe/London`, `Europe/Luxembourg`, `Europe/Madrid`, `Europe/Malta`, `Europe/Mariehamn`, `Europe/Minsk`, `Europe/Monaco`, `Europe/Moscow`, `Europe/Oslo`, `Europe/Paris`, `Europe/Podgorica`, `Europe/Prague`, `Europe/Riga`, `Europe/Rome`, `Europe/Samara`, `Europe/San_Marino`, `Europe/Sarajevo`, `Europe/Saratov`, `Europe/Simferopol`, `Europe/Skopje`, `Europe/Sofia`, `Europe/Stockholm`, `Europe/Tallinn`, `Europe/Tirane`, `Europe/Ulyanovsk`, `Europe/Vaduz`, `Europe/Vatican`, `Europe/Vienna`, `Europe/Vilnius`, `Europe/Volgograd`, `Europe/Warsaw`, `Europe/Zagreb`, `Europe/Zurich`, `GMT`, `Indian/Antananarivo`, `Indian/Chagos`, `Indian/Christmas`, `Indian/Cocos`, `Indian/Comoro`, `Indian/Kerguelen`, `Indian/Mahe`, `Indian/Maldives`, `Indian/Mauritius`, `Indian/Mayotte`, `Indian/Reunion`, `Pacific/Apia`, `Pacific/Auckland`, `Pacific/Bougainville`, `Pacific/Chatham`, `Pacific/Chuuk`, `Pacific/Easter`, `Pacific/Efate`, `Pacific/Fakaofo`, `Pacific/Fiji`, `Pacific/Funafuti`, `Pacific/Galapagos`, `Pacific/Gambier`, `Pacific/Guadalcanal`, `Pacific/Guam`, `Pacific/Honolulu`, `Pacific/Kanton`, `Pacific/Kiritimati`, `Pacific/Kosrae`, `Pacific/Kwajalein`, `Pacific/Majuro`, `Pacific/Marquesas`, `Pacific/Midway`, `Pacific/Nauru`, `Pacific/Niue`, `Pacific/Norfolk`, `Pacific/Noumea`, `Pacific/Pago_Pago`, `Pacific/Palau`, `Pacific/Pitcairn`, `Pacific/Pohnpei`, `Pacific/Port_Moresby`, `Pacific/Rarotonga`, `Pacific/Saipan`, `Pacific/Tahiti`, `Pacific/Tarawa`, `Pacific/Tongatapu`, `Pacific/Wake`, `Pacific/Wallis`, `US/Alaska`, `US/Arizona`, `US/Central`, `US/Eastern`, `US/Hawaii`, `US/Mountain`, `US/Pacific`, `UTC`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archive_in`, `archived_at`, `close_in`, `cover_image`, `cover_image_url`, `created_at`, `created_by_id`, `cycle_view`, `default_assignee_id`, `default_state_id`, `description`, `emoji`, `estimate_id`, `external_id`, `external_source`, `guest_view_all_features`, `icon_prop`, `id`, `identifier`, `intake_view`, `is_issue_type_enabled`, `is_time_tracking_enabled`, `issue_views_view`, `logo_props`, `module_view`, `name`, `network`, `page_view`, `priority`, `project_lead_id`, `start_date`, `state_id`, `target_date`, `timezone`. + + + + + +Comma-separated relations to embed: `default_assignee`, `project_lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archive_in": 1, + "archived_at": null, + "close_in": 1, + "cover_image": "https://example.com/cover.png", + "cover_image_url": "https://example.com", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "cycle_view": false, + "default_assignee_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "default_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "emoji": "1f680", + "estimate_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "guest_view_all_features": false, + "icon_prop": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "identifier": "PROJ", + "intake_view": false, + "is_issue_type_enabled": false, + "is_time_tracking_enabled": false, + "issue_views_view": false, + "logo_props": null, + "module_view": false, + "name": "Example name", + "network": 0, + "page_view": false, + "priority": "high", + "project_lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "start_date": "2026-01-12", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20", + "timezone": "Africa/Abidjan" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-comments/create-release-comment.md b/docs/api-reference/v2/release-comments/create-release-comment.md new file mode 100644 index 00000000..28d2bd66 --- /dev/null +++ b/docs/api-reference/v2/release-comments/create-release-comment.md @@ -0,0 +1,195 @@ +--- +title: Create a release comment +description: Create a release comment with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a release comment, release comments, release comments create +--- + +# Create a release comment + +
+ POST + /api/v2/workspaces/{slug}/releases/{release_id}/comments/ +
+ +
+
+ +Release comments are the discussion thread on a release. Create a release comment. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release the resource belongs to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The comment html. + + + + + +Whether is resolved. + + + + + +Id of the related parent. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `comment_html`, `comment_id`, `created_at`, `created_by_id`, `edited_at`, `id`, `is_hidden`, `is_resolved`, `parent_id`, `release_id`. + + + +
+
+ +
+ +### Scopes + +`releases.comments:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "comment_html": "example", + "comment_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "edited_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_hidden": false, + "is_resolved": false, + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-comments/delete-release-comment.md b/docs/api-reference/v2/release-comments/delete-release-comment.md new file mode 100644 index 00000000..1ff79dbb --- /dev/null +++ b/docs/api-reference/v2/release-comments/delete-release-comment.md @@ -0,0 +1,154 @@ +--- +title: Delete a release comment +description: Delete a release comment with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a release comment, release comments, release comments destroy +--- + +# Delete a release comment + +
+ DELETE + /api/v2/workspaces/{slug}/releases/{release_id}/comments/{pk}/ +
+ +
+
+ +Release comments are the discussion thread on a release. Delete a release comment. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release the resource belongs to. + + + + + +The release comment id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `comment_html`, `comment_id`, `created_at`, `created_by_id`, `edited_at`, `id`, `is_hidden`, `is_resolved`, `parent_id`, `release_id`. + + + +
+
+ +
+ +### Scopes + +`releases.comments:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release comment matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-comments/get-release-comment.md b/docs/api-reference/v2/release-comments/get-release-comment.md new file mode 100644 index 00000000..1b70f4e3 --- /dev/null +++ b/docs/api-reference/v2/release-comments/get-release-comment.md @@ -0,0 +1,163 @@ +--- +title: Get a release comment +description: Read a release comment with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a release comment, release comments, release comments retrieve +--- + +# Get a release comment + +
+ GET + /api/v2/workspaces/{slug}/releases/{release_id}/comments/{pk}/ +
+ +
+
+ +Release comments are the discussion thread on a release. Read a single release comment by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release the resource belongs to. + + + + + +The release comment id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `comment_html`, `comment_id`, `created_at`, `created_by_id`, `edited_at`, `id`, `is_hidden`, `is_resolved`, `parent_id`, `release_id`. + + + +
+
+ +
+ +### Scopes + +`releases.comments:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "comment_html": "example", + "comment_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "edited_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_hidden": false, + "is_resolved": false, + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release comment matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-comments/list-release-comments.md b/docs/api-reference/v2/release-comments/list-release-comments.md new file mode 100644 index 00000000..eba81546 --- /dev/null +++ b/docs/api-reference/v2/release-comments/list-release-comments.md @@ -0,0 +1,228 @@ +--- +title: List release comments +description: List release comments with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list release comments, release comments, release comments list +--- + +# List release comments + +
+ GET + /api/v2/workspaces/{slug}/releases/{release_id}/comments/ +
+ +
+
+ +Release comments are the discussion thread on a release. List the release comments you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release the resource belongs to. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `is_resolved`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Filter by `parent_id`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `comment_html`, `comment_id`, `created_at`, `created_by_id`, `edited_at`, `id`, `is_hidden`, `is_resolved`, `parent_id`, `release_id`. + + + +
+
+ +
+ +### Scopes + +`releases.comments:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "comment_html": "example", + "comment_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "edited_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_hidden": false, + "is_resolved": false, + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "comment_html": "example", + "comment_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "edited_at": "2026-01-14T09:22:41.478363Z", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_hidden": false, + "is_resolved": false, + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-comments/overview.md b/docs/api-reference/v2/release-comments/overview.md new file mode 100644 index 00000000..b0aa70de --- /dev/null +++ b/docs/api-reference/v2/release-comments/overview.md @@ -0,0 +1,95 @@ +--- +title: Release comments overview +description: The Plane API v2 release comment object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, release comments, release comment object +--- + +# Release comments overview + +Release comments are the discussion thread on a release. + +
+
+ +## The release comment object + +### Attributes + +- `comment_html` _string_ + + The comment html. + +- `comment_id` _string (uuid)_ + + The related comment. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `edited_at` _string (date-time)_ + + The edited at. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_hidden` _boolean_ + + Whether is hidden. + +- `is_resolved` _boolean_ + + Whether is resolved. + +- `parent_id` _string (uuid)_ + + The related parent. + +- `release_id` _string (uuid)_ + + The related release. + +
+
+ + + +```json +{ + "comment_html": "example", + "comment_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "edited_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_hidden": false, + "is_resolved": false, + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------------------- | ------------------------ | +| `GET` | `/api/v2/workspaces/{slug}/releases/{release_id}/comments/` | List release comments | +| `POST` | `/api/v2/workspaces/{slug}/releases/{release_id}/comments/` | Create a release comment | +| `DELETE` | `/api/v2/workspaces/{slug}/releases/{release_id}/comments/{pk}/` | Delete a release comment | +| `GET` | `/api/v2/workspaces/{slug}/releases/{release_id}/comments/{pk}/` | Get a release comment | +| `PATCH` | `/api/v2/workspaces/{slug}/releases/{release_id}/comments/{pk}/` | Update a release comment | + +## Response shaping + +Every release comment read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/release-comments/update-release-comment.md b/docs/api-reference/v2/release-comments/update-release-comment.md new file mode 100644 index 00000000..c3ef4249 --- /dev/null +++ b/docs/api-reference/v2/release-comments/update-release-comment.md @@ -0,0 +1,213 @@ +--- +title: Update a release comment +description: Update a release comment with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a release comment, release comments, release comments partial update +--- + +# Update a release comment + +
+ PATCH + /api/v2/workspaces/{slug}/releases/{release_id}/comments/{pk}/ +
+ +
+
+ +Release comments are the discussion thread on a release. Update a release comment. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release the resource belongs to. + + + + + +The release comment id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The comment html. + + + + + +Whether is resolved. + + + + + +Id of the related parent. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `comment_html`, `comment_id`, `created_at`, `created_by_id`, `edited_at`, `id`, `is_hidden`, `is_resolved`, `parent_id`, `release_id`. + + + +
+
+ +
+ +### Scopes + +`releases.comments:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "comment_html": "example", + "comment_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "edited_at": "2026-01-14T09:22:41.478363Z", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_hidden": false, + "is_resolved": false, + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release comment matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-labels/create-release-label.md b/docs/api-reference/v2/release-labels/create-release-label.md new file mode 100644 index 00000000..736725b4 --- /dev/null +++ b/docs/api-reference/v2/release-labels/create-release-label.md @@ -0,0 +1,184 @@ +--- +title: Create a release label +description: Create a release label with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a release label, release labels, release labels create +--- + +# Create a release label + +
+ POST + /api/v2/workspaces/{slug}/releases/labels/ +
+ +
+
+ +Release labels are the workspace-level label catalog for releases. Create a release label. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `id`, `name`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`releases.labels:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Example name", + "sort_order": 65535 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-labels/delete-release-label.md b/docs/api-reference/v2/release-labels/delete-release-label.md new file mode 100644 index 00000000..20022c9e --- /dev/null +++ b/docs/api-reference/v2/release-labels/delete-release-label.md @@ -0,0 +1,148 @@ +--- +title: Delete a release label +description: Delete a release label with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a release label, release labels, release labels destroy +--- + +# Delete a release label + +
+ DELETE + /api/v2/workspaces/{slug}/releases/labels/{pk}/ +
+ +
+
+ +Release labels are the workspace-level label catalog for releases. Delete a release label. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release label id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `id`, `name`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`releases.labels:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release label matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-labels/get-release-label.md b/docs/api-reference/v2/release-labels/get-release-label.md new file mode 100644 index 00000000..222aee35 --- /dev/null +++ b/docs/api-reference/v2/release-labels/get-release-label.md @@ -0,0 +1,153 @@ +--- +title: Get a release label +description: Read a release label with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a release label, release labels, release labels retrieve +--- + +# Get a release label + +
+ GET + /api/v2/workspaces/{slug}/releases/labels/{pk}/ +
+ +
+
+ +Release labels are the workspace-level label catalog for releases. Read a single release label by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release label id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `id`, `name`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`releases.labels:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Example name", + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release label matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-labels/list-release-labels.md b/docs/api-reference/v2/release-labels/list-release-labels.md new file mode 100644 index 00000000..9855a4e0 --- /dev/null +++ b/docs/api-reference/v2/release-labels/list-release-labels.md @@ -0,0 +1,205 @@ +--- +title: List release labels +description: List release labels with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list release labels, release labels, release labels list +--- + +# List release labels + +
+ GET + /api/v2/workspaces/{slug}/releases/labels/ +
+ +
+
+ +Release labels are the workspace-level label catalog for releases. List the release labels you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `id`, `name`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`releases.labels:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Example name", + "sort_order": 65535 + }, + { + "color": "#3f76ff", + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Another example", + "sort_order": 65535 + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-labels/overview.md b/docs/api-reference/v2/release-labels/overview.md new file mode 100644 index 00000000..6fb091ce --- /dev/null +++ b/docs/api-reference/v2/release-labels/overview.md @@ -0,0 +1,75 @@ +--- +title: Release labels overview +description: The Plane API v2 release label object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, release labels, release label object +--- + +# Release labels overview + +Release labels are the workspace-level label catalog for releases. + +
+
+ +## The release label object + +### Attributes + +- `color` _string_ + + Hex color used wherever this is rendered, for example `#3f76ff`. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `id` _string (uuid)_ + + Unique identifier. + +- `name` _string_ + + Display name. + +- `sort_order` _integer_ + + Manual ordering weight. Lower sorts first. + +
+
+ + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Example name", + "sort_order": 65535 +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------- | ---------------------- | +| `GET` | `/api/v2/workspaces/{slug}/releases/labels/` | List release labels | +| `POST` | `/api/v2/workspaces/{slug}/releases/labels/` | Create a release label | +| `DELETE` | `/api/v2/workspaces/{slug}/releases/labels/{pk}/` | Delete a release label | +| `GET` | `/api/v2/workspaces/{slug}/releases/labels/{pk}/` | Get a release label | +| `PATCH` | `/api/v2/workspaces/{slug}/releases/labels/{pk}/` | Update a release label | + +## Response shaping + +Every release label read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/release-labels/update-release-label.md b/docs/api-reference/v2/release-labels/update-release-label.md new file mode 100644 index 00000000..6d7a418b --- /dev/null +++ b/docs/api-reference/v2/release-labels/update-release-label.md @@ -0,0 +1,205 @@ +--- +title: Update a release label +description: Update a release label with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a release label, release labels, release labels partial update +--- + +# Update a release label + +
+ PATCH + /api/v2/workspaces/{slug}/releases/labels/{pk}/ +
+ +
+
+ +Release labels are the workspace-level label catalog for releases. Update a release label. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release label id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. + + + + + +Display name. + +Maximum 255 characters. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `id`, `name`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`releases.labels:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "Example name", + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release label matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-links/create-release-link.md b/docs/api-reference/v2/release-links/create-release-link.md new file mode 100644 index 00000000..db01286b --- /dev/null +++ b/docs/api-reference/v2/release-links/create-release-link.md @@ -0,0 +1,194 @@ +--- +title: Create a release link +description: Create a release link with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a release link, release links, release links create +--- + +# Create a release link + +
+ POST + /api/v2/workspaces/{slug}/releases/{release_id}/links/ +
+ +
+
+ +Release links attach external URLs to a release. Create a release link. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release the resource belongs to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Title. + +Maximum 255 characters. + + + + + +Target URL. + +Maximum 200 characters. + + + + + +The metadata. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `metadata`, `release_id`, `title`, `url`. + + + +
+
+ +
+ +### Scopes + +`releases.links:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "metadata": null, + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "title": "Example title", + "url": "https://example.com/spec" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-links/delete-release-link.md b/docs/api-reference/v2/release-links/delete-release-link.md new file mode 100644 index 00000000..e715db35 --- /dev/null +++ b/docs/api-reference/v2/release-links/delete-release-link.md @@ -0,0 +1,154 @@ +--- +title: Delete a release link +description: Delete a release link with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a release link, release links, release links destroy +--- + +# Delete a release link + +
+ DELETE + /api/v2/workspaces/{slug}/releases/{release_id}/links/{pk}/ +
+ +
+
+ +Release links attach external URLs to a release. Delete a release link. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release the resource belongs to. + + + + + +The release link id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `metadata`, `release_id`, `title`, `url`. + + + +
+
+ +
+ +### Scopes + +`releases.links:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release link matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-links/get-release-link.md b/docs/api-reference/v2/release-links/get-release-link.md new file mode 100644 index 00000000..5649ecff --- /dev/null +++ b/docs/api-reference/v2/release-links/get-release-link.md @@ -0,0 +1,160 @@ +--- +title: Get a release link +description: Read a release link with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a release link, release links, release links retrieve +--- + +# Get a release link + +
+ GET + /api/v2/workspaces/{slug}/releases/{release_id}/links/{pk}/ +
+ +
+
+ +Release links attach external URLs to a release. Read a single release link by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release the resource belongs to. + + + + + +The release link id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `metadata`, `release_id`, `title`, `url`. + + + +
+
+ +
+ +### Scopes + +`releases.links:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "metadata": null, + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "title": "Example title", + "url": "https://example.com/spec" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release link matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-links/list-release-links.md b/docs/api-reference/v2/release-links/list-release-links.md new file mode 100644 index 00000000..05200298 --- /dev/null +++ b/docs/api-reference/v2/release-links/list-release-links.md @@ -0,0 +1,210 @@ +--- +title: List release links +description: List release links with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list release links, release links, release links list +--- + +# List release links + +
+ GET + /api/v2/workspaces/{slug}/releases/{release_id}/links/ +
+ +
+
+ +Release links attach external URLs to a release. List the release links you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release the resource belongs to. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `metadata`, `release_id`, `title`, `url`. + + + +
+
+ +
+ +### Scopes + +`releases.links:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "metadata": null, + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "title": "Example title", + "url": "https://example.com/spec" + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "metadata": null, + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "title": "Another example", + "url": "https://example.com/spec" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-links/overview.md b/docs/api-reference/v2/release-links/overview.md new file mode 100644 index 00000000..8bac7219 --- /dev/null +++ b/docs/api-reference/v2/release-links/overview.md @@ -0,0 +1,80 @@ +--- +title: Release links overview +description: The Plane API v2 release link object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, release links, release link object +--- + +# Release links overview + +Release links attach external URLs to a release. + +
+
+ +## The release link object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `id` _string (uuid)_ + + Unique identifier. + +- `metadata` _string_ + + The metadata. + +- `release_id` _string (uuid)_ + + The related release. + +- `title` _string_ + + Title. + +- `url` _string (uri)_ + + Target URL. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "metadata": null, + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "title": "Example title", + "url": "https://example.com/spec" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------------- | --------------------- | +| `GET` | `/api/v2/workspaces/{slug}/releases/{release_id}/links/` | List release links | +| `POST` | `/api/v2/workspaces/{slug}/releases/{release_id}/links/` | Create a release link | +| `DELETE` | `/api/v2/workspaces/{slug}/releases/{release_id}/links/{pk}/` | Delete a release link | +| `GET` | `/api/v2/workspaces/{slug}/releases/{release_id}/links/{pk}/` | Get a release link | +| `PATCH` | `/api/v2/workspaces/{slug}/releases/{release_id}/links/{pk}/` | Update a release link | + +## Response shaping + +Every release link read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/release-links/update-release-link.md b/docs/api-reference/v2/release-links/update-release-link.md new file mode 100644 index 00000000..9a15e66e --- /dev/null +++ b/docs/api-reference/v2/release-links/update-release-link.md @@ -0,0 +1,212 @@ +--- +title: Update a release link +description: Update a release link with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a release link, release links, release links partial update +--- + +# Update a release link + +
+ PATCH + /api/v2/workspaces/{slug}/releases/{release_id}/links/{pk}/ +
+ +
+
+ +Release links attach external URLs to a release. Update a release link. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release the resource belongs to. + + + + + +The release link id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The metadata. + + + + + +Title. + +Maximum 255 characters. + + + + + +Target URL. + +Maximum 200 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `metadata`, `release_id`, `title`, `url`. + + + +
+
+ +
+ +### Scopes + +`releases.links:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "metadata": null, + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "title": "Example title", + "url": "https://example.com/spec" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release link matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-tags/create-release-tag.md b/docs/api-reference/v2/release-tags/create-release-tag.md new file mode 100644 index 00000000..52844046 --- /dev/null +++ b/docs/api-reference/v2/release-tags/create-release-tag.md @@ -0,0 +1,198 @@ +--- +title: Create a release tag +description: Create a release tag with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a release tag, release tags, release tags create +--- + +# Create a release tag + +
+ POST + /api/v2/workspaces/{slug}/releases/tags/ +
+ +
+
+ +Release tags are the workspace-level tag catalog for releases. Create a release tag. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Version string, for example `1.4.0`. + +Maximum 255 characters. + + + + + +The commit hash. + +Maximum 255 characters. Nullable. + + + + + +Free-form description. + +Nullable. + + + + + +The git tag. + +Maximum 255 characters. Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `commit_hash`, `created_at`, `created_by_id`, `description`, `git_tag`, `id`, `version`. + + + +
+
+ +
+ +### Scopes + +`releases.tags:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "commit_hash": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "git_tag": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "version": "1.4.0" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-tags/delete-release-tag.md b/docs/api-reference/v2/release-tags/delete-release-tag.md new file mode 100644 index 00000000..b3b7a4e6 --- /dev/null +++ b/docs/api-reference/v2/release-tags/delete-release-tag.md @@ -0,0 +1,148 @@ +--- +title: Delete a release tag +description: Delete a release tag with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a release tag, release tags, release tags destroy +--- + +# Delete a release tag + +
+ DELETE + /api/v2/workspaces/{slug}/releases/tags/{pk}/ +
+ +
+
+ +Release tags are the workspace-level tag catalog for releases. Delete a release tag. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release tag id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `commit_hash`, `created_at`, `created_by_id`, `description`, `git_tag`, `id`, `version`. + + + +
+
+ +
+ +### Scopes + +`releases.tags:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release tag matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-tags/get-release-tag.md b/docs/api-reference/v2/release-tags/get-release-tag.md new file mode 100644 index 00000000..2e15bda6 --- /dev/null +++ b/docs/api-reference/v2/release-tags/get-release-tag.md @@ -0,0 +1,154 @@ +--- +title: Get a release tag +description: Read a release tag with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a release tag, release tags, release tags retrieve +--- + +# Get a release tag + +
+ GET + /api/v2/workspaces/{slug}/releases/tags/{pk}/ +
+ +
+
+ +Release tags are the workspace-level tag catalog for releases. Read a single release tag by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release tag id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `commit_hash`, `created_at`, `created_by_id`, `description`, `git_tag`, `id`, `version`. + + + +
+
+ +
+ +### Scopes + +`releases.tags:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "commit_hash": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "git_tag": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "version": "1.4.0" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release tag matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-tags/list-release-tags.md b/docs/api-reference/v2/release-tags/list-release-tags.md new file mode 100644 index 00000000..d3f388a1 --- /dev/null +++ b/docs/api-reference/v2/release-tags/list-release-tags.md @@ -0,0 +1,207 @@ +--- +title: List release tags +description: List release tags with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list release tags, release tags, release tags list +--- + +# List release tags + +
+ GET + /api/v2/workspaces/{slug}/releases/tags/ +
+ +
+
+ +Release tags are the workspace-level tag catalog for releases. List the release tags you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +Filter by `version`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `commit_hash`, `created_at`, `created_by_id`, `description`, `git_tag`, `id`, `version`. + + + +
+
+ +
+ +### Scopes + +`releases.tags:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "commit_hash": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "git_tag": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "version": "1.4.0" + }, + { + "commit_hash": "example", + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "git_tag": "example", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "version": "1.4.0" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/release-tags/overview.md b/docs/api-reference/v2/release-tags/overview.md new file mode 100644 index 00000000..51e1c384 --- /dev/null +++ b/docs/api-reference/v2/release-tags/overview.md @@ -0,0 +1,80 @@ +--- +title: Release tags overview +description: The Plane API v2 release tag object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, release tags, release tag object +--- + +# Release tags overview + +Release tags are the workspace-level tag catalog for releases. + +
+
+ +## The release tag object + +### Attributes + +- `commit_hash` _string_ + + The commit hash. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description` _string_ + + Free-form description. + +- `git_tag` _string_ + + The git tag. + +- `id` _string (uuid)_ + + Unique identifier. + +- `version` _string_ + + Version string, for example `1.4.0`. + +
+
+ + + +```json +{ + "commit_hash": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "git_tag": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "version": "1.4.0" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ----------------------------------------------- | -------------------- | +| `GET` | `/api/v2/workspaces/{slug}/releases/tags/` | List release tags | +| `POST` | `/api/v2/workspaces/{slug}/releases/tags/` | Create a release tag | +| `DELETE` | `/api/v2/workspaces/{slug}/releases/tags/{pk}/` | Delete a release tag | +| `GET` | `/api/v2/workspaces/{slug}/releases/tags/{pk}/` | Get a release tag | +| `PATCH` | `/api/v2/workspaces/{slug}/releases/tags/{pk}/` | Update a release tag | + +## Response shaping + +Every release tag read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/release-tags/update-release-tag.md b/docs/api-reference/v2/release-tags/update-release-tag.md new file mode 100644 index 00000000..95604182 --- /dev/null +++ b/docs/api-reference/v2/release-tags/update-release-tag.md @@ -0,0 +1,216 @@ +--- +title: Update a release tag +description: Update a release tag with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a release tag, release tags, release tags partial update +--- + +# Update a release tag + +
+ PATCH + /api/v2/workspaces/{slug}/releases/tags/{pk}/ +
+ +
+
+ +Release tags are the workspace-level tag catalog for releases. Update a release tag. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release tag id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The commit hash. + +Maximum 255 characters. Nullable. + + + + + +Free-form description. + +Nullable. + + + + + +The git tag. + +Maximum 255 characters. Nullable. + + + + + +Version string, for example `1.4.0`. + +Maximum 255 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `commit_hash`, `created_at`, `created_by_id`, `description`, `git_tag`, `id`, `version`. + + + +
+
+ +
+ +### Scopes + +`releases.tags:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "commit_hash": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "git_tag": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "version": "1.4.0" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release tag matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/releases/create-release.md b/docs/api-reference/v2/releases/create-release.md new file mode 100644 index 00000000..31bd8386 --- /dev/null +++ b/docs/api-reference/v2/releases/create-release.md @@ -0,0 +1,273 @@ +--- +title: Create a release +description: Create a release with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a release, releases, releases create +--- + +# Create a release + +
+ POST + /api/v2/workspaces/{slug}/releases/ +
+ +
+
+ +Releases group shipped work and carry a changelog. Create a release. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +The description json. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Whether is latest. + + + + + +Whether is prerelease. + + + + + +Id of the related lead. + +Nullable. + + + + + +The release date. + +Nullable. + + + + + +- `unreleased` - Unreleased +- `released` - Released +- `cancelled` - Cancelled + +One of `unreleased`, `released`, `cancelled`. + + + + + +Id of the related tag. + +Nullable. + + + + + +Planned due date, as `YYYY-MM-DD`. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `description_id`, `external_id`, `external_source`, `id`, `is_latest`, `is_prerelease`, `label_ids`, `lead_id`, `name`, `release_date`, `status`, `tag_id`, `target_date`. + + + + + +Comma-separated relations to embed: `lead`, `tag`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`releases:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_latest": false, + "is_prerelease": false, + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "Example name", + "release_date": "2026-01-20", + "status": "unreleased", + "tag_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20" +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/releases/delete-release.md b/docs/api-reference/v2/releases/delete-release.md new file mode 100644 index 00000000..4f954458 --- /dev/null +++ b/docs/api-reference/v2/releases/delete-release.md @@ -0,0 +1,156 @@ +--- +title: Delete a release +description: Delete a release with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a release, releases, releases destroy +--- + +# Delete a release + +
+ DELETE + /api/v2/workspaces/{slug}/releases/{pk}/ +
+ +
+
+ +Releases group shipped work and carry a changelog. Delete a release. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `description_id`, `external_id`, `external_source`, `id`, `is_latest`, `is_prerelease`, `label_ids`, `lead_id`, `name`, `release_date`, `status`, `tag_id`, `target_date`. + + + + + +Comma-separated relations to embed: `lead`, `tag`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`releases:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/releases/get-release-changelog.md b/docs/api-reference/v2/releases/get-release-changelog.md new file mode 100644 index 00000000..a9d112a3 --- /dev/null +++ b/docs/api-reference/v2/releases/get-release-changelog.md @@ -0,0 +1,135 @@ +--- +title: Get a release changelog +description: Read a release changelog with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a release changelog, release changelog, releases changelog retrieve +--- + +# Get a release changelog + +
+ GET + /api/v2/workspaces/{slug}/releases/{pk}/changelog/ +
+ +
+
+ +Releases group shipped work and carry a changelog. Read a single release changelog by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release changelog id. + + + +
+
+ +
+ +### Scopes + +`releases:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "changelog_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description_html": "

Details go here.

", + "description_json": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release changelog matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/releases/get-release.md b/docs/api-reference/v2/releases/get-release.md new file mode 100644 index 00000000..32a16c8d --- /dev/null +++ b/docs/api-reference/v2/releases/get-release.md @@ -0,0 +1,171 @@ +--- +title: Get a release +description: Read a release with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a release, releases, releases retrieve +--- + +# Get a release + +
+ GET + /api/v2/workspaces/{slug}/releases/{pk}/ +
+ +
+
+ +Releases group shipped work and carry a changelog. Read a single release by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `description_id`, `external_id`, `external_source`, `id`, `is_latest`, `is_prerelease`, `label_ids`, `lead_id`, `name`, `release_date`, `status`, `tag_id`, `target_date`. + + + + + +Comma-separated relations to embed: `lead`, `tag`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`releases:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_latest": false, + "is_prerelease": false, + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "Example name", + "release_date": "2026-01-20", + "status": "unreleased", + "tag_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/releases/list-releases.md b/docs/api-reference/v2/releases/list-releases.md new file mode 100644 index 00000000..824a4055 --- /dev/null +++ b/docs/api-reference/v2/releases/list-releases.md @@ -0,0 +1,271 @@ +--- +title: List releases +description: List releases with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list releases, releases, releases list +--- + +# List releases + +
+ GET + /api/v2/workspaces/{slug}/releases/ +
+ +
+
+ +Releases group shipped work and carry a changelog. List the releases you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `is_latest`. + + + + + +Filter by `is_prerelease`. + + + + + +Filter by `lead_id`. + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +Filter by `release_date`. + + + + + +A search term. + + + + + +- `unreleased` - Unreleased +- `released` - Released +- `cancelled` - Cancelled + +One of `cancelled`, `released`, `unreleased`. + + + + + +Multiple values may be separated by commas. + +- `unreleased` - Unreleased +- `released` - Released +- `cancelled` - Cancelled + + + + + +Filter by `tag_id`. + + + + + +Filter by `target_date`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `description_id`, `external_id`, `external_source`, `id`, `is_latest`, `is_prerelease`, `label_ids`, `lead_id`, `name`, `release_date`, `status`, `tag_id`, `target_date`. + + + + + +Comma-separated relations to embed: `lead`, `tag`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`releases:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_latest": false, + "is_prerelease": false, + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "Example name", + "release_date": "2026-01-20", + "status": "unreleased", + "tag_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/releases/manage-release-labels.md b/docs/api-reference/v2/releases/manage-release-labels.md new file mode 100644 index 00000000..ac8caabf --- /dev/null +++ b/docs/api-reference/v2/releases/manage-release-labels.md @@ -0,0 +1,167 @@ +--- +title: Add or remove release labels +description: Add or remove release labels with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, add or remove release labels, release labels, releases labels +--- + +# Add or remove release labels + +
+ POST + /api/v2/workspaces/{slug}/releases/{pk}/labels/ +
+ +
+
+ +Releases group shipped work and carry a changelog. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release label id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The add. + + + + + +The remove. + + + +
+
+ +
+ +### Scopes + +`releases:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "added": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "removed": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/releases/manage-release-work-items.md b/docs/api-reference/v2/releases/manage-release-work-items.md new file mode 100644 index 00000000..be8f15ba --- /dev/null +++ b/docs/api-reference/v2/releases/manage-release-work-items.md @@ -0,0 +1,167 @@ +--- +title: Add or remove release work items +description: Add or remove release work items with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, add or remove release work items, release work items, releases work items +--- + +# Add or remove release work items + +
+ POST + /api/v2/workspaces/{slug}/releases/{pk}/work-items/ +
+ +
+
+ +Releases group shipped work and carry a changelog. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release work item id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The add. + + + + + +The remove. + + + +
+
+ +
+ +### Scopes + +`releases:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "added": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "removed": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/releases/overview.md b/docs/api-reference/v2/releases/overview.md new file mode 100644 index 00000000..1f3fb706 --- /dev/null +++ b/docs/api-reference/v2/releases/overview.md @@ -0,0 +1,129 @@ +--- +title: Releases overview +description: The Plane API v2 release object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, releases, release object +--- + +# Releases overview + +Releases group shipped work and carry a changelog. + +
+
+ +## The release object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description_html` _string_ + + Rich-text body as HTML. This is the field the Plane editor round-trips. + +- `description_id` _string (uuid)_ + + The related description. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_latest` _boolean_ + + Whether is latest. + +- `is_prerelease` _boolean_ + + Whether is prerelease. + +- `label_ids` _array of string_ + + Ids of the associated labels. + +- `lead_id` _string (uuid)_ + + The related lead. + +- `name` _string_ + + Display name. + +- `release_date` _string (date)_ + + The release date. + +- `status` _string_ + + The status. + +- `tag_id` _string (uuid)_ + + The related tag. + +- `target_date` _string (date)_ + + Planned due date, as `YYYY-MM-DD`. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_latest": false, + "is_prerelease": false, + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "Example name", + "release_date": "2026-01-20", + "status": "unreleased", + "tag_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20" +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ----------------------------------------------------- | -------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/releases/` | List releases | +| `POST` | `/api/v2/workspaces/{slug}/releases/` | Create a release | +| `DELETE` | `/api/v2/workspaces/{slug}/releases/{pk}/` | Delete a release | +| `GET` | `/api/v2/workspaces/{slug}/releases/{pk}/` | Get a release | +| `PATCH` | `/api/v2/workspaces/{slug}/releases/{pk}/` | Update a release | +| `GET` | `/api/v2/workspaces/{slug}/releases/{pk}/changelog/` | Get a release changelog | +| `PATCH` | `/api/v2/workspaces/{slug}/releases/{pk}/changelog/` | Update a release changelog | +| `POST` | `/api/v2/workspaces/{slug}/releases/{pk}/labels/` | Add or remove release labels | +| `POST` | `/api/v2/workspaces/{slug}/releases/{pk}/work-items/` | Add or remove release work items | + +## Response shaping + +Every release read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/releases/update-release-changelog.md b/docs/api-reference/v2/releases/update-release-changelog.md new file mode 100644 index 00000000..f2f2814d --- /dev/null +++ b/docs/api-reference/v2/releases/update-release-changelog.md @@ -0,0 +1,174 @@ +--- +title: Update a release changelog +description: Update a release changelog with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a release changelog, release changelog, releases changelog partial update +--- + +# Update a release changelog + +
+ PATCH + /api/v2/workspaces/{slug}/releases/{pk}/changelog/ +
+ +
+
+ +Releases group shipped work and carry a changelog. Update a release changelog. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release changelog id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +The description json. + + + +
+
+ +
+ +### Scopes + +`releases:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "changelog_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description_html": "

Details go here.

", + "description_json": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "release_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release changelog matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/releases/update-release.md b/docs/api-reference/v2/releases/update-release.md new file mode 100644 index 00000000..f8d7ae7e --- /dev/null +++ b/docs/api-reference/v2/releases/update-release.md @@ -0,0 +1,291 @@ +--- +title: Update a release +description: Update a release with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a release, releases, releases partial update +--- + +# Update a release + +
+ PATCH + /api/v2/workspaces/{slug}/releases/{pk}/ +
+ +
+
+ +Releases group shipped work and carry a changelog. Update a release. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The release id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +The description json. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Whether is latest. + + + + + +Whether is prerelease. + + + + + +Id of the related lead. + +Nullable. + + + + + +Display name. + +Maximum 255 characters. + + + + + +The release date. + +Nullable. + + + + + +- `unreleased` - Unreleased +- `released` - Released +- `cancelled` - Cancelled + +One of `unreleased`, `released`, `cancelled`. + + + + + +Id of the related tag. + +Nullable. + + + + + +Planned due date, as `YYYY-MM-DD`. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `description_id`, `external_id`, `external_source`, `id`, `is_latest`, `is_prerelease`, `label_ids`, `lead_id`, `name`, `release_date`, `status`, `tag_id`, `target_date`. + + + + + +Comma-separated relations to embed: `lead`, `tag`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`releases:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_latest": false, + "is_prerelease": false, + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "Example name", + "release_date": "2026-01-20", + "status": "unreleased", + "tag_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No release matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/roles/get-role.md b/docs/api-reference/v2/roles/get-role.md new file mode 100644 index 00000000..a5e9f59f --- /dev/null +++ b/docs/api-reference/v2/roles/get-role.md @@ -0,0 +1,155 @@ +--- +title: Get a role +description: Read a role with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a role, roles, roles retrieve +--- + +# Get a role + +
+ GET + /api/v2/workspaces/{slug}/roles/{pk}/ +
+ +
+
+ +Roles are the named permission levels a member can hold. Read a single role by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The role id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `description`, `id`, `is_system`, `level`, `name`, `namespace`, `slug`, `status`. + + + +
+
+ +
+ +### Scopes + +`workspaces.roles:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_system": false, + "level": 1, + "name": "Example name", + "namespace": null, + "slug": "my-team", + "status": "active" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No role matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/roles/list-roles.md b/docs/api-reference/v2/roles/list-roles.md new file mode 100644 index 00000000..6350f00d --- /dev/null +++ b/docs/api-reference/v2/roles/list-roles.md @@ -0,0 +1,227 @@ +--- +title: List roles +description: List roles with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list roles, roles, roles list +--- + +# List roles + +
+ GET + /api/v2/workspaces/{slug}/roles/ +
+ +
+
+ +Roles are the named permission levels a member can hold. List the roles you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `is_system`. + + + + + +Whether this role is for instance, workspace, or project-level resources + +- `instance` - Instance +- `workspace` - Workspace +- `project` - Project + +One of `instance`, `project`, `workspace`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +Filter by `slug`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `description`, `id`, `is_system`, `level`, `name`, `namespace`, `slug`, `status`. + + + +
+
+ +
+ +### Scopes + +`workspaces.roles:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_system": false, + "level": 1, + "name": "Example name", + "namespace": null, + "slug": "my-team", + "status": "active" + }, + { + "description": "What this is for.", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_system": false, + "level": 1, + "name": "Another example", + "namespace": null, + "slug": "my-team", + "status": "active" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/roles/overview.md b/docs/api-reference/v2/roles/overview.md new file mode 100644 index 00000000..5cb987cc --- /dev/null +++ b/docs/api-reference/v2/roles/overview.md @@ -0,0 +1,82 @@ +--- +title: Roles overview +description: The Plane API v2 role object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, roles, role object +--- + +# Roles overview + +Roles are the named permission levels a member can hold. + +
+
+ +## The role object + +### Attributes + +- `description` _string_ + + Free-form description. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_system` _boolean_ + + Whether is system. + +- `level` _integer_ + + The level. + +- `name` _string_ + + Display name. + +- `namespace` _string_ + + The namespace. + +- `slug` _string_ + + The slug. + +- `status` _string_ + + The status. + +
+
+ + + +```json +{ + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_system": false, + "level": 1, + "name": "Example name", + "namespace": null, + "slug": "my-team", + "status": "active" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| ------ | --------------------------------------- | ----------- | +| `GET` | `/api/v2/workspaces/{slug}/roles/` | List roles | +| `GET` | `/api/v2/workspaces/{slug}/roles/{pk}/` | Get a role | + +## Response shaping + +Every role read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/sparse-fields.md b/docs/api-reference/v2/sparse-fields.md new file mode 100644 index 00000000..a6da2b22 --- /dev/null +++ b/docs/api-reference/v2/sparse-fields.md @@ -0,0 +1,233 @@ +--- +title: Sparse fields +description: Use ?fields= to return only the keys you need from the Plane v2 REST API. Omit semantics, the all token, deferred and detail-only fields, and how it composes with ?expand=. +keywords: plane api v2, sparse fields, fields parameter, partial response, omit fields, all token, detail_only, deferred_on_list, token cost +--- + +# Sparse fields + +Every v2 endpoint whose response is a resource body accepts `?fields=` — a comma-separated list of the keys you +actually want. + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/ENG/work-items/?fields=id,name,state_id" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +```json +{ + "data": [ + { + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect loop", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "id": "3b7d9e40-1c62-4a85-b0f3-9d5c2e6a8471", + "name": "Rate limit the invite endpoint", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 50, + "previous": null, + "total_count": 327, + "pagination": { "style": "offset" } +} +``` + +A default work item row is about twenty keys. If you only need three, `?fields=` is the difference between paying for +twenty and paying for three — multiplied by up to 200 rows a page, on every call. + +## Omit, not null + +Unrequested keys are **absent from the JSON**. They are not returned as `null`. + +This is the whole design decision, and it is worth being precise about, because it makes one thing possible that +null-filling does not: telling apart _"I didn't ask for this"_ from _"this is genuinely null"_. + +| Response | Means | +| --------------------- | ------------------------------------------ | +| key absent | you didn't request it | +| `"target_date": null` | you requested it and it is genuinely unset | + +Null-filling would also save you almost nothing. On a sparse row the key names are most of the payload, so a nulled +twenty-field row still ships twenty key names. Omitting is what actually reduces the response. + +::: tip Unrequested fields are never computed +Omitted keys are dropped before serialization, so a field that costs a query or a computation to produce is not just +excluded from the response — it never runs. `?fields=` is a latency win as well as a payload win. +::: + +## `id` always comes back + +You never have to ask for `id`, and you cannot get rid of it: + +``` +?fields=name → { "id": "…", "name": "…" } +``` + +It is the one shape guarantee, so a client can always correlate a row back to its resource. (A handful of +parent-addressed singletons — project features, for instance — have no `id` of their own. Those return exactly what you +asked for.) + +## `all` + +`all` is a reserved token meaning "every requestable field for this response shape". + +``` +?fields=all → the full body +?fields=all,name → the full body; `all` wins and `name` is redundant +``` + +Use it when you want everything _including_ fields that a list would normally leave out — see deferred fields below. + +## What a list leaves out by default + +Two Meta-level rules shape the default list row, so that the naive call — no parameters at all — isn't the expensive +one. + +### Deferred fields + +Unbounded-size fields like `description_html` are **omitted from collection rows by default**. Naming one pulls it +back: + +``` +GET …/pages/ → rows without description_html +GET …/pages/?fields=id,name,description_html → rows with it +GET …/pages/?fields=all → rows with it +``` + +Deferral is a default, not a ban. It affects collections only — a single-object response is never deferred, so +`GET …/pages/{id}/` always includes `description_html`. + +Resources that defer something today: customers, customer requests, initiatives, intake work items, pages (project and +workspace), releases, stickies, teamspaces, and work item templates. Templates defer `template_data` as well, since it +embeds HTML too. + +### Detail-only fields + +A detail-only field is never on a collection row, and **cannot be requested there**: + +``` +GET …/work-items/?fields=id,custom_fields +``` + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "One or more fields failed validation.", + "errors": [ + { + "field": "fields", + "code": "invalid", + "message": "custom_fields is not available on collection responses; retrieve the work item." + } + ] +} +``` + +Work item `custom_fields` is the only such field today. Populating it requires a per-item property resolution that the +list path can't afford, so rather than return a misleading `null` on every row, the key is absent on collections and +honest on detail. See [List work items](/api-reference/v2/work-items/list-work-items). + +## Where it applies + +`?fields=` works on every response rendered by a resource read serializer: + +- list and retrieve +- `POST` and `PATCH` echoes +- `upsert` +- custom list actions + +It does **not** apply to: + +- `APIView` endpoints — [current user](/api-reference/v2/users/get-current-user), + [workspace features](/api-reference/v2/workspace-features/get-workspace-features), + [effective permissions](/api-reference/v2/permissions/get-workspace-permissions), + [artifacts](/api-reference/v2/artifacts/overview) +- `bulk` writes, whose response is a per-row result envelope rather than a resource body +- bespoke bodies such as upload credentials and grouped relation reads +- resources served by a compact `Lite` serializer, such as work item property options and type-property attachments + +The reference page for each endpoint lists its requestable fields when the parameter is available. The OpenAPI document +enumerates them too, so a generated SDK or MCP tool schema constrains the value up front rather than discovering it from +a `400`. + +## Typos fail loudly + +An unknown field name is a `400` — never a silently-ignored parameter. The message names the closest match and +enumerates the valid set, so a client can correct itself in one round trip: + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "One or more fields failed validation.", + "errors": [ + { + "field": "fields", + "code": "invalid", + "message": "Unknown field(s): titel — did you mean 'title'? Valid fields: all, id, name, identifier, …" + } + ] +} +``` + +Strictness is deliberate here. If a typo were ignored, you would get the full row back, notice nothing, and lose the +saving invisibly. + +An empty value (`?fields=`) is not an error — it simply applies no filtering. + +## Composing with `?expand=` + +`?fields=` and `?expand=` are **separate namespaces**, and they do not interact. + +``` +?fields=id,name&expand=state → { "id": "…", "name": "…", "state": { … } } +``` + +`state_id` is a field; `state` is an expandable relation. Filtering fields never drops an expansion, and expanding never +replaces an id with an object. + +That means naming a relation in `?fields=` is a `400`, not an implicit expand — and the error tells you which parameter +you wanted: + +> `'state'` is an expandable relation, not a response field — use `?expand=state` to embed the object, or +> `?fields=state_id` for just its id. + +The reverse is symmetric: `?expand=state_id` points you back at `?fields=state_id`. See +[Expanding relations](/api-reference/v2/expanding-relations). + +## The one-call identity lookup + +`?fields=` composes with the identity filters to make name→id resolution cheap — useful when you hold a human-readable +name and need the UUID: + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/ENG/states/?name=In%20Progress&fields=id" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +```json +{ + "data": [{ "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" }], + "next": null, + "previous": null, + "total_count": 1, + "pagination": { "style": "offset" } +} +``` + +Because it's a list, duplicates are visible as two rows rather than hidden behind a guess. See +[Filtering and ordering](/api-reference/v2/filtering-and-ordering). + +## Notes for SDK authors + +Read components in the OpenAPI document declare `id` as their only required property. That is truthful rather than +lax: under omit semantics any other key genuinely may be absent, and a generated type that marks fields required would +be lying. + +If you are generating a typed client, the ergonomic move is to narrow the return type from the requested field list — +`list({ fields: ["id", "name"] })` yielding `Pick[]`. That requires a generator that can +express const-generic narrowing. diff --git a/docs/api-reference/v2/states/bulk-states.md b/docs/api-reference/v2/states/bulk-states.md new file mode 100644 index 00000000..8662c11f --- /dev/null +++ b/docs/api-reference/v2/states/bulk-states.md @@ -0,0 +1,198 @@ +--- +title: Bulk write states +description: Bulk write states with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, bulk write states, states, states bulk +--- + +# Bulk write states + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/states/bulk/ +
+ +
+
+ +States are the workflow steps a work item moves through. Write up to 100 states in one request, with a per-row result for each. + +- Send `{"items": [...]}`, at most 100 per request. Add `"upsert": true` to match existing rows on `(external_source, external_id)` instead of always creating. +- The response is always `200`. Each row reports its own outcome, so a partial failure is reported rather than silently dropped — read `succeeded`/`failed` and the per-row `status`. +- Rows are written on independent savepoints: a failing row rolls back only itself. +- `?fields=` does not apply here — the bulk response is a per-row result envelope, not a resource body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The items. + + + + + +Reconcile each row on (external_source, external_id) instead of creating it. + + + +
+
+ +
+ +### Scopes + +`projects.states:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "failed": 1, + "results": [null], + "succeeded": 1 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/states/create-state.md b/docs/api-reference/v2/states/create-state.md new file mode 100644 index 00000000..2b2ad7fa --- /dev/null +++ b/docs/api-reference/v2/states/create-state.md @@ -0,0 +1,255 @@ +--- +title: Create a state +description: Create a workflow state in a Plane project with the v2 REST API. Body parameters, workflow groups, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create state, workflow state, state group, POST states +--- + +# Create a state + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/states/ +
+ +
+
+ +Add a workflow state to a project. The new state is appended to the project's workflow and becomes immediately selectable on work items. + +State names must be unique within a project — reusing one returns `409 conflict`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project to add the state to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name for the state, unique within the project. Maximum 255 characters. + + + + + +Hex color used wherever the state is rendered, for example `#3f76ff`. Maximum 255 characters. + + + + + +The workflow group the state belongs to. Determines how the state is treated by boards, charts, and cycle progress. + +- `backlog` — Not yet scheduled +- `unstarted` — Scheduled but not begun +- `started` — Actively in progress +- `completed` — Finished successfully +- `cancelled` — Closed without completion +- `triage` — Awaiting intake review + +Defaults to `backlog` when omitted. + + + + + +Free-form description of what the state means in this workflow. + + + + + +Ordering weight within the project. Lower values sort first. Assigned automatically when omitted. + + + + + +Make this the project's default state — where work items land when no `state_id` is supplied. Setting it clears the flag on the previous default. + + + + + +Your system's identifier for this state, for sync and import correlation. Maximum 255 characters. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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`, `is_triage`, `name`, `sequence`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.states:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | Missing `name`/`color`, or a `group` outside the enum. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't create states. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A state with this name already exists in the project. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "name": "In Review", + "description": "Awaiting code review", + "color": "#3f76ff", + "group": "started", + "sequence": 45000, + "is_default": false, + "is_triage": false, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "conflict", + "detail": "A state with this name already exists in this project." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/states/delete-state.md b/docs/api-reference/v2/states/delete-state.md new file mode 100644 index 00000000..79441def --- /dev/null +++ b/docs/api-reference/v2/states/delete-state.md @@ -0,0 +1,185 @@ +--- +title: Delete a state +description: Delete a workflow state from a Plane project with the v2 REST API. Protected default states, states holding work items, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete state, remove workflow state, default state conflict, 409 conflict, DELETE states +--- + +# Delete a state + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/states/{pk}/ +
+ +
+
+ +Remove a state from a project's workflow. A successful delete returns `204` with an empty body. + +Two conditions block a delete, and you have to clear the condition before the state will go — see [Before you delete](#before-you-delete). + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the state belongs to. + + + + + +The id of the state to delete. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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`, `is_triage`, `name`, `sequence`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.states:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't delete states. | +| `404` | `not_found` | No such state, project, or workspace — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The state still holds work items. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "conflict", + "code": "conflict", + "detail": "The default state cannot be deleted." +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "conflict", + "detail": "This state still has work items in it." +} +``` + + + +
+
+ +## Before you delete + +Both protected cases return `409 conflict`, so branch on the `detail` only for messaging — the fix differs: + +- **The project's default state.** Every project needs somewhere for work items to land when no `state_id` is supplied. Promote another state with [Update a state](/api-reference/v2/states/update-state) and `"is_default": true`, which demotes the current default, then delete it. +- **A state that still holds work items.** Deleting it would leave those work items without a status. Move them to another state first — filter the project's work items by this state, `PATCH` each one to the replacement state, then retry the delete. + +A safe teardown is therefore: reassign work items, hand off the default flag if this state has it, delete. + +::: tip Deletes are soft +The state stops appearing in the API and in Plane, but the row is retained. Treat the `204` as final for integration purposes — the states API has no restore operation. +::: diff --git a/docs/api-reference/v2/states/get-state.md b/docs/api-reference/v2/states/get-state.md new file mode 100644 index 00000000..3ae3ff61 --- /dev/null +++ b/docs/api-reference/v2/states/get-state.md @@ -0,0 +1,178 @@ +--- +title: Get a state +description: Retrieve a single workflow state from a Plane project with the v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get state, retrieve state, workflow state, state group, GET state by id +--- + +# Get a state + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/states/{pk}/ +
+ +
+
+ +Retrieve one state by id. Reach for this when you already hold a `state_id` — from a work item, a webhook payload, or a stored mapping — and need its current name, color, or group. + +Checking `group` before you act is the reliable way to tell whether a work item is finished: a state named `Done` in one project and `Shipped` in another both report `group: "completed"`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the state belongs to. A state id from a different project returns `404`, even inside the same workspace. + + + + + +The id of the state to retrieve. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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`, `is_triage`, `name`, `sequence`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.states:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this project's states. | +| `404` | `not_found` | No such state, project, or workspace — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info The response is flat +The state object has no nested records. `created_by_id` is returned as an id, so fetch the user separately if you need their name. +::: + +::: info Existence is never leaked +A state outside your tenant returns `404`, not `403`. If you are resolving a cached id and get a `404`, re-read the project's workflow with [List states](/api-reference/v2/states/list-states) instead of retrying. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "In Progress", + "description": "Actively being worked on", + "color": "#3f76ff", + "group": "started", + "sequence": 25000, + "is_default": false, + "is_triage": false, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No State matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/states/list-states.md b/docs/api-reference/v2/states/list-states.md new file mode 100644 index 00000000..bf7fa5a4 --- /dev/null +++ b/docs/api-reference/v2/states/list-states.md @@ -0,0 +1,323 @@ +--- +title: List states +description: List the workflow states in a Plane project with the v2 REST API. Filters, search, ordering, pagination, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list states, workflow states, state group filter, pagination, GET states +--- + +# List states + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/states/ +
+ +
+
+ +Return the states in a project as a paginated list. This is how you resolve a state name to the `state_id` you need when creating or moving a work item, and how you build a state picker that stays in sync with the project's workflow. + +Results are scoped to a single project — states are never shared across projects, so there is no workspace-wide state list. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project whose states you want to list. + + + +
+
+ +
+ +### Query Parameters + +Filters combine with `AND`. The enum-backed _filters_ `group` and `group__in` are validated: an unrecognized value is rejected with a `400 invalid_request` instead of quietly returning an empty list, so a typo surfaces immediately rather than looking like "no results". `order_by` and `paginate` are not validated — an unrecognized `order_by` silently falls back to the default ordering and anything other than `paginate=cursor` silently uses offset pagination, so check your spelling there. + +
+ + + +Return only states in this workflow group. One of `backlog`, `unstarted`, `started`, `completed`, `cancelled`, or `triage`. + +Use the `group__in` variant to match several groups at once, passing them comma-separated — `?group__in=started,completed`. Filtering by group is the portable way to ask "what counts as in progress here", because every project names its states differently but the groups are fixed. + + + + + +Return only the project's default state (`true`) or only the non-default states (`false`). Pairing `?is_default=true` with `?per_page=1` is the cheapest way to find where new work items will land. + + + + + +Return states whose `external_id` matches exactly. Use it to find the state you previously created for a record in another system. + + + + + +Return states that came from a particular system, for example `github` or `jira`. Combine it with `external_id` — an `external_id` is only unique within its source. + + + + + +A search term matched against the state name. + + + +
+
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `sequence` , `-sequence` — the project's own workflow order +- `created_at` , `-created_at` — when each state was added +- `id` , `-id` + +Order by `sequence` when you are rendering the workflow to a user; it is the order the project itself uses. + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. Most projects have fewer than 20 states, so one page is usually the whole workflow. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read the `next` value from the response rather than computing offsets yourself. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead of `next` and `total_count`. Omit it for the default offset envelope. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted from the response. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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`, `is_triage`, `name`, `sequence`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.states:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this project's states. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48", + "name": "Backlog", + "description": "Not yet scheduled", + "color": "#8b8d98", + "group": "backlog", + "sequence": 15000, + "is_default": true, + "is_triage": false, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + }, + { + "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "In Progress", + "description": "Actively being worked on", + "color": "#3f76ff", + "group": "started", + "sequence": 25000, + "is_default": false, + "is_triage": false, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.512907Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + }, + { + "id": "7b1e9d40-3c86-4f2a-9a5d-8e2b0c47d613", + "name": "Done", + "description": "Shipped", + "color": "#26a05f", + "group": "completed", + "sequence": 35000, + "is_default": false, + "is_triage": false, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.548221Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next": null, + "previous": null, + "total_count": 3, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48", + "name": "Backlog", + "description": "Not yet scheduled", + "color": "#8b8d98", + "group": "backlog", + "sequence": 15000, + "is_default": true, + "is_triage": false, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + + + +
+
+ +::: tip Read the group, not the name +A project can rename `In Progress` to `Building` at any time, and reporting keeps working because boards and burndowns read `group`. Write integrations the same way: match on `group`, and treat `name` as a label for humans. +::: diff --git a/docs/api-reference/v2/states/overview.md b/docs/api-reference/v2/states/overview.md new file mode 100644 index 00000000..7f7a5d80 --- /dev/null +++ b/docs/api-reference/v2/states/overview.md @@ -0,0 +1,122 @@ +--- +title: States overview +description: The Plane API v2 state object. Attributes, workflow groups, the default and triage states, and how states gate work item transitions. +keywords: plane api v2, states, workflow states, state groups, backlog, triage, work item status +--- + +# States overview + +A state is a step in a project's workflow. Every work item sits in exactly one state, and a state belongs to exactly one project — states are never shared across projects. + +Each state is assigned to a **group**, which is what Plane uses to reason about progress. Boards, charts, and cycle burndowns read the group, not the state name, so a project can rename `In Progress` to `Building` without breaking any reporting. + +[Learn more about states](https://docs.plane.so/core-concepts/work-items/states) + +
+
+ +## The state object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the state. + +- `name` _string_ + + Display name, unique within the project. Maximum 255 characters. + +- `description` _string_ + + Free-form description of what the state means in this workflow. + +- `color` _string_ + + Hex color used wherever the state is rendered, for example `#3f76ff`. + +- `group` _string_ + + The workflow group this state belongs to. One of `backlog`, `unstarted`, `started`, `completed`, `cancelled`, or `triage`. + +- `sequence` _number_ + + Ordering weight within the project. Lower values sort first. + +- `is_default` _boolean_ + + Whether new work items land in this state when no `state_id` is supplied. Exactly one state per project is the default. + +- `is_triage` _boolean_ + + Whether this is the project's triage state, used by intake. Read-only — a triage state is created and managed by Plane. + +- `external_id` , `external_source` _string_ + + Correlation fields for sync and import. Together they let you map a state to a record in another system and find it again later. + +- `created_at` _string (date-time)_ + + When the state was created. + +- `created_by_id` _string (uuid)_ + + The user who created the state. + +::: tip Group drives behavior, name does not +Filtering work items by `state_group=started` is stable across projects even when each project names its in-progress state differently. Filtering by `state_id` is exact but project-specific. +::: + +
+
+ + + +```json +{ + "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "In Progress", + "description": "Actively being worked on", + "color": "#3f76ff", + "group": "started", + "sequence": 25000, + "is_default": false, + "is_triage": false, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------------------- | ----------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/states/` | List states | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/states/` | Create a state | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/states/bulk/` | Bulk write states | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/states/upsert/` | Upsert a state | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/states/{pk}/` | Delete a state | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/states/{pk}/` | Get a state | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/states/{pk}/` | Update a state | + +## Deleting states + +Two deletes are rejected with `409 conflict`: + +- The project's **default state** cannot be deleted. Mark another state as default first. +- A state that **still holds work items** cannot be deleted. Move those work items to another state first. + +## Changed from v1 + +- `default` is now **`is_default`**. +- Reads no longer return `updated_at`, `updated_by`, `project`, or `workspace`. +- `group` accepts `triage` in addition to the five v1 groups. + +See [Migrating from v1](/api-reference/v2/migrating-from-v1) for the full list. diff --git a/docs/api-reference/v2/states/update-state.md b/docs/api-reference/v2/states/update-state.md new file mode 100644 index 00000000..812ccbe4 --- /dev/null +++ b/docs/api-reference/v2/states/update-state.md @@ -0,0 +1,270 @@ +--- +title: Update a state +description: Update a workflow state in a Plane project with the v2 REST API. Partial PATCH semantics, body parameters, the default state flag, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update state, patch state, workflow state, is_default, state group, PATCH states +--- + +# Update a state + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/states/{pk}/ +
+ +
+
+ +Change a state's name, color, description, group, position, or default flag. Work items already sitting in the state stay where they are — you are editing the state itself, not moving anything. + +`PATCH` is partial. Send only the fields you want to change; anything you omit keeps its current value. Omitting a field is not the same as sending `null`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the state belongs to. + + + + + +The id of the state to update. + + + +
+
+ +
+ +### Body Parameters + +Every field is optional — send the subset you are changing. + +
+ + + +New display name, unique within the project. Maximum 255 characters. Renaming is safe for reporting: boards and charts key off `group`, not `name`. + + + + + +Hex color used wherever the state is rendered, for example `#3f76ff`. Maximum 255 characters. + + + + + +Free-form description of what the state means in this workflow. + + + + + +Move the state to a different workflow group. This changes how every work item in the state is counted by boards, charts, and cycle progress, so switching a state from `started` to `completed` retroactively changes what those work items report as. + +- `backlog` — Not yet scheduled +- `unstarted` — Scheduled but not begun +- `started` — Actively in progress +- `completed` — Finished successfully +- `cancelled` — Closed without completion +- `triage` — Awaiting intake review + + + + + +Ordering weight within the project. Lower values sort first. Set it to reposition the state in the workflow. + + + + + +Make this the project's default state — where work items land when no `state_id` is supplied. A project has exactly one default, so setting this to `true` clears the flag on the state that held it. + + + + + +Your system's identifier for this state, for sync and import correlation. Maximum 255 characters. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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`, `is_triage`, `name`, `sequence`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.states:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | A `group` outside the enum, or a field over its length limit. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't update states. | +| `404` | `not_found` | No such state, project, or workspace — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | Another state in the project already uses this name. | +| `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. | + +
+ +::: warning Setting is_default moves the flag +A project has exactly one default state, so promoting a state with `"is_default": true` demotes whichever state held the flag before. To change which state is the default, promote the new one — that single request is the whole operation. +::: + +::: info is_triage is not writable +`is_triage` is read-only. Plane creates and manages the triage state used by intake, so it isn't accepted in the request body. You can still assign the `triage` group to a state you own. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "Building", + "description": "Actively being worked on", + "color": "#f5a623", + "group": "started", + "sequence": 25000, + "is_default": false, + "is_triage": false, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "The request body failed validation.", + "errors": [ + { + "field": "group", + "code": "invalid_choice", + "message": "\"in_review\" is not a valid choice." + } + ] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/states/upsert-state.md b/docs/api-reference/v2/states/upsert-state.md new file mode 100644 index 00000000..416528ab --- /dev/null +++ b/docs/api-reference/v2/states/upsert-state.md @@ -0,0 +1,250 @@ +--- +title: Upsert a state +description: Upsert a state with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, upsert a state, states, states upsert +--- + +# Upsert a state + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/states/upsert/ +
+ +
+
+ +States are the workflow steps a work item moves through. Create a state, or update the existing one that carries the same `(external_source, external_id)` pair. + +- Both `external_source` and `external_id` are required — without them there is nothing to match on and the request is a `400`. +- A created record answers `201` with `X-Plane-Upsert: created`; an updated one answers `200` with `X-Plane-Upsert: updated`. Branch on the header rather than guessing from the status. +- Upsert is safe for **sequential** importers. Two simultaneous upserts of the same key can each miss and each create, so serialize your writes per key. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. + + + + + +Display name. + +Maximum 255 characters. + + + + + +Free-form description. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +- `backlog` - Backlog +- `unstarted` - Unstarted +- `started` - Started +- `completed` - Completed +- `cancelled` - Cancelled +- `triage` - Triage + +One of `backlog`, `unstarted`, `started`, `completed`, `cancelled`, `triage`. + + + + + +Make this the default for its parent. Setting it clears the flag on the previous default. + + + + + +Ordering weight. Lower sorts first. Assigned automatically when omitted. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `group`, `id`, `is_default`, `is_triage`, `name`, `sequence`. + + + +
+
+ +
+ +### Scopes + +`projects.states:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "group": "started", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_default": false, + "is_triage": false, + "name": "Example name", + "sequence": 65535 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/stickies/create-sticky.md b/docs/api-reference/v2/stickies/create-sticky.md new file mode 100644 index 00000000..fd8a5055 --- /dev/null +++ b/docs/api-reference/v2/stickies/create-sticky.md @@ -0,0 +1,209 @@ +--- +title: Create a sticky +description: Create a sticky with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a sticky, stickies, stickies create +--- + +# Create a sticky + +
+ POST + /api/v2/workspaces/{slug}/stickies/ +
+ +
+
+ +Stickies are private notes owned by the calling user. Create a sticky. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The background color. + +Maximum 255 characters. Nullable. + + + + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. Nullable. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Display name. + +Nullable. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `background_color`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `id`, `logo_props`, `name`, `owner_id`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`stickies:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "background_color": "example", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_props": null, + "name": "Example name", + "owner_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535 +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/stickies/delete-sticky.md b/docs/api-reference/v2/stickies/delete-sticky.md new file mode 100644 index 00000000..ba82aa88 --- /dev/null +++ b/docs/api-reference/v2/stickies/delete-sticky.md @@ -0,0 +1,148 @@ +--- +title: Delete a sticky +description: Delete a sticky with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a sticky, stickies, stickies destroy +--- + +# Delete a sticky + +
+ DELETE + /api/v2/workspaces/{slug}/stickies/{pk}/ +
+ +
+
+ +Stickies are private notes owned by the calling user. Delete a sticky. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The sticky id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `background_color`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `id`, `logo_props`, `name`, `owner_id`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`stickies:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No sticky matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/stickies/get-sticky.md b/docs/api-reference/v2/stickies/get-sticky.md new file mode 100644 index 00000000..39a8015e --- /dev/null +++ b/docs/api-reference/v2/stickies/get-sticky.md @@ -0,0 +1,158 @@ +--- +title: Get a sticky +description: Read a sticky with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a sticky, stickies, stickies retrieve +--- + +# Get a sticky + +
+ GET + /api/v2/workspaces/{slug}/stickies/{pk}/ +
+ +
+
+ +Stickies are private notes owned by the calling user. Read a single sticky by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The sticky id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `background_color`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `id`, `logo_props`, `name`, `owner_id`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`stickies:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "background_color": "example", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_props": null, + "name": "Example name", + "owner_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535 +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No sticky matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/stickies/list-stickies.md b/docs/api-reference/v2/stickies/list-stickies.md new file mode 100644 index 00000000..559418be --- /dev/null +++ b/docs/api-reference/v2/stickies/list-stickies.md @@ -0,0 +1,221 @@ +--- +title: List stickies +description: List stickies with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list stickies, stickies, stickies list +--- + +# List stickies + +
+ GET + /api/v2/workspaces/{slug}/stickies/ +
+ +
+
+ +Stickies are private notes owned by the calling user. List the stickies you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Filter by `color`. + + + + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Filter by `owner_id`. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `background_color`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `id`, `logo_props`, `name`, `owner_id`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`stickies:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "background_color": "example", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_props": null, + "name": "Example name", + "owner_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535 + }, + { + "background_color": "example", + "color": "#3f76ff", + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "logo_props": null, + "name": "Another example", + "owner_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535 + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/stickies/overview.md b/docs/api-reference/v2/stickies/overview.md new file mode 100644 index 00000000..0b37597f --- /dev/null +++ b/docs/api-reference/v2/stickies/overview.md @@ -0,0 +1,100 @@ +--- +title: Stickies overview +description: The Plane API v2 sticky object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, stickies, sticky object +--- + +# Stickies overview + +Stickies are private notes owned by the calling user. + +
+
+ +## The sticky object + +### Attributes + +- `background_color` _string_ + + The background color. + +- `color` _string_ + + Hex color used wherever this is rendered, for example `#3f76ff`. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description_html` _string_ + + Rich-text body as HTML. This is the field the Plane editor round-trips. + +- `description_stripped` _string_ + + The description stripped. + +- `id` _string (uuid)_ + + Unique identifier. + +- `logo_props` _string_ + + Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +- `name` _string_ + + Display name. + +- `owner_id` _string (uuid)_ + + The related owner. + +- `sort_order` _number_ + + Manual ordering weight. Lower sorts first. + +
+
+ + + +```json +{ + "background_color": "example", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_props": null, + "name": "Example name", + "owner_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535 +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------ | --------------- | +| `GET` | `/api/v2/workspaces/{slug}/stickies/` | List stickies | +| `POST` | `/api/v2/workspaces/{slug}/stickies/` | Create a sticky | +| `DELETE` | `/api/v2/workspaces/{slug}/stickies/{pk}/` | Delete a sticky | +| `GET` | `/api/v2/workspaces/{slug}/stickies/{pk}/` | Get a sticky | +| `PATCH` | `/api/v2/workspaces/{slug}/stickies/{pk}/` | Update a sticky | + +## Response shaping + +Every sticky read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/stickies/update-sticky.md b/docs/api-reference/v2/stickies/update-sticky.md new file mode 100644 index 00000000..34f3d4ec --- /dev/null +++ b/docs/api-reference/v2/stickies/update-sticky.md @@ -0,0 +1,230 @@ +--- +title: Update a sticky +description: Update a sticky with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a sticky, stickies, stickies partial update +--- + +# Update a sticky + +
+ PATCH + /api/v2/workspaces/{slug}/stickies/{pk}/ +
+ +
+
+ +Stickies are private notes owned by the calling user. Update a sticky. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The sticky id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The background color. + +Maximum 255 characters. Nullable. + + + + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. Nullable. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Display name. + +Nullable. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `background_color`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `id`, `logo_props`, `name`, `owner_id`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`stickies:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "background_color": "example", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logo_props": null, + "name": "Example name", + "owner_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535 +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No sticky matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/teamspaces/create-teamspace.md b/docs/api-reference/v2/teamspaces/create-teamspace.md new file mode 100644 index 00000000..c9db26fd --- /dev/null +++ b/docs/api-reference/v2/teamspaces/create-teamspace.md @@ -0,0 +1,218 @@ +--- +title: Create a teamspace +description: Create a teamspace with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a teamspace, teamspaces, teamspaces create +--- + +# Create a teamspace + +
+ POST + /api/v2/workspaces/{slug}/teamspaces/ +
+ +
+
+ +Teamspaces group members and projects under one team. Create a teamspace. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Id of the related lead. + +Nullable. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +Nullable. + + + + + +Ids of the members to associate. Replaces the current set. + + + + + +Ids of the projects to associate. Replaces the current set. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `lead_id`, `logo_props`, `member_ids`, `name`, `project_ids`. + + + + + +Comma-separated relations to embed: `lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`teamspaces:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]] +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/teamspaces/delete-teamspace.md b/docs/api-reference/v2/teamspaces/delete-teamspace.md new file mode 100644 index 00000000..ed5217e2 --- /dev/null +++ b/docs/api-reference/v2/teamspaces/delete-teamspace.md @@ -0,0 +1,156 @@ +--- +title: Delete a teamspace +description: Delete a teamspace with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a teamspace, teamspaces, teamspaces destroy +--- + +# Delete a teamspace + +
+ DELETE + /api/v2/workspaces/{slug}/teamspaces/{pk}/ +
+ +
+
+ +Teamspaces group members and projects under one team. Delete a teamspace. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The teamspace id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `lead_id`, `logo_props`, `member_ids`, `name`, `project_ids`. + + + + + +Comma-separated relations to embed: `lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`teamspaces:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No teamspace matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/teamspaces/get-teamspace.md b/docs/api-reference/v2/teamspaces/get-teamspace.md new file mode 100644 index 00000000..d2fa0863 --- /dev/null +++ b/docs/api-reference/v2/teamspaces/get-teamspace.md @@ -0,0 +1,164 @@ +--- +title: Get a teamspace +description: Read a teamspace with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a teamspace, teamspaces, teamspaces retrieve +--- + +# Get a teamspace + +
+ GET + /api/v2/workspaces/{slug}/teamspaces/{pk}/ +
+ +
+
+ +Teamspaces group members and projects under one team. Read a single teamspace by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The teamspace id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `lead_id`, `logo_props`, `member_ids`, `name`, `project_ids`. + + + + + +Comma-separated relations to embed: `lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`teamspaces:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]] +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No teamspace matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/teamspaces/list-teamspaces.md b/docs/api-reference/v2/teamspaces/list-teamspaces.md new file mode 100644 index 00000000..98474608 --- /dev/null +++ b/docs/api-reference/v2/teamspaces/list-teamspaces.md @@ -0,0 +1,225 @@ +--- +title: List teamspaces +description: List teamspaces with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list teamspaces, teamspaces, teamspaces list +--- + +# List teamspaces + +
+ GET + /api/v2/workspaces/{slug}/teamspaces/ +
+ +
+
+ +Teamspaces group members and projects under one team. List the teamspaces you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `lead_id`. + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `lead_id`, `logo_props`, `member_ids`, `name`, `project_ids`. + + + + + +Comma-separated relations to embed: `lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`teamspaces:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]] + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "name": "Another example", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]] + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/teamspaces/overview.md b/docs/api-reference/v2/teamspaces/overview.md new file mode 100644 index 00000000..4c4c039b --- /dev/null +++ b/docs/api-reference/v2/teamspaces/overview.md @@ -0,0 +1,90 @@ +--- +title: Teamspaces overview +description: The Plane API v2 teamspace object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, teamspaces, teamspace object +--- + +# Teamspaces overview + +Teamspaces group members and projects under one team. + +
+
+ +## The teamspace object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description_html` _string_ + + Rich-text body as HTML. This is the field the Plane editor round-trips. + +- `id` _string (uuid)_ + + Unique identifier. + +- `lead_id` _string (uuid)_ + + The related lead. + +- `logo_props` _string_ + + Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +- `member_ids` _array of string_ + + Ids of the associated members. + +- `name` _string_ + + Display name. + +- `project_ids` _array of string_ + + Ids of the associated projects. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]] +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | -------------------------------------------- | ------------------ | +| `GET` | `/api/v2/workspaces/{slug}/teamspaces/` | List teamspaces | +| `POST` | `/api/v2/workspaces/{slug}/teamspaces/` | Create a teamspace | +| `DELETE` | `/api/v2/workspaces/{slug}/teamspaces/{pk}/` | Delete a teamspace | +| `GET` | `/api/v2/workspaces/{slug}/teamspaces/{pk}/` | Get a teamspace | +| `PATCH` | `/api/v2/workspaces/{slug}/teamspaces/{pk}/` | Update a teamspace | + +## Response shaping + +Every teamspace read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/teamspaces/update-teamspace.md b/docs/api-reference/v2/teamspaces/update-teamspace.md new file mode 100644 index 00000000..d4ef83af --- /dev/null +++ b/docs/api-reference/v2/teamspaces/update-teamspace.md @@ -0,0 +1,236 @@ +--- +title: Update a teamspace +description: Update a teamspace with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a teamspace, teamspaces, teamspaces partial update +--- + +# Update a teamspace + +
+ PATCH + /api/v2/workspaces/{slug}/teamspaces/{pk}/ +
+ +
+
+ +Teamspaces group members and projects under one team. Update a teamspace. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The teamspace id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Id of the related lead. + +Nullable. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +Nullable. + + + + + +Ids of the members to associate. Replaces the current set. + + + + + +Display name. + +Maximum 255 characters. + + + + + +Ids of the projects to associate. Replaces the current set. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `lead_id`, `logo_props`, `member_ids`, `name`, `project_ids`. + + + + + +Comma-separated relations to embed: `lead`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`teamspaces:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "lead_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "logo_props": null, + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]] +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No teamspace matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/user-assets/confirm-user-asset.md b/docs/api-reference/v2/user-assets/confirm-user-asset.md new file mode 100644 index 00000000..5df13f29 --- /dev/null +++ b/docs/api-reference/v2/user-assets/confirm-user-asset.md @@ -0,0 +1,153 @@ +--- +title: Confirm a user asset upload +description: Update a user asset with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, confirm a user asset upload, user assets, user assets partial update +--- + +# Confirm a user asset upload + +
+ PATCH + /api/v2/users/me/assets/{pk}/ +
+ +
+
+ +User assets are the calling user's own uploads, such as an avatar or cover image. Update a user asset. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The user asset id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `entity_type`, `id`, `is_uploaded`, `name`, `size`, `user_id`. + + + +
+
+ +
+ +### Scopes + +`assets:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "entity_type": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1, + "user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No user asset matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/user-assets/create-user-asset.md b/docs/api-reference/v2/user-assets/create-user-asset.md new file mode 100644 index 00000000..e119e8cd --- /dev/null +++ b/docs/api-reference/v2/user-assets/create-user-asset.md @@ -0,0 +1,182 @@ +--- +title: Create a user asset upload +description: Create a user asset with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a user asset upload, user assets, user assets create +--- + +# Create a user asset upload + +
+ POST + /api/v2/users/me/assets/ +
+ +
+
+ +User assets are the calling user's own uploads, such as an avatar or cover image. Create a user asset. + +
+ +### Body Parameters + +
+ + + +- `USER_AVATAR` - USER_AVATAR +- `USER_COVER` - USER_COVER + +One of `USER_AVATAR`, `USER_COVER`. + + + + + +Display name. + + + + + +The size. + + + + + +The type. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `entity_type`, `id`, `is_uploaded`, `name`, `size`, `user_id`. + + + +
+
+ +
+ +### Scopes + +`assets:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "entity_type": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1, + "user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/user-assets/delete-user-asset.md b/docs/api-reference/v2/user-assets/delete-user-asset.md new file mode 100644 index 00000000..abf33f9b --- /dev/null +++ b/docs/api-reference/v2/user-assets/delete-user-asset.md @@ -0,0 +1,139 @@ +--- +title: Delete a user asset +description: Delete a user asset with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a user asset, user assets, user assets destroy +--- + +# Delete a user asset + +
+ DELETE + /api/v2/users/me/assets/{pk}/ +
+ +
+
+ +User assets are the calling user's own uploads, such as an avatar or cover image. Delete a user asset. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The user asset id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `entity_type`, `id`, `is_uploaded`, `name`, `size`, `user_id`. + + + +
+
+ +
+ +### Scopes + +`assets:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No user asset matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/user-assets/get-user-asset.md b/docs/api-reference/v2/user-assets/get-user-asset.md new file mode 100644 index 00000000..fd310dee --- /dev/null +++ b/docs/api-reference/v2/user-assets/get-user-asset.md @@ -0,0 +1,149 @@ +--- +title: Get a user asset +description: Read a user asset with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a user asset, user assets, user assets retrieve +--- + +# Get a user asset + +
+ GET + /api/v2/users/me/assets/{pk}/ +
+ +
+
+ +User assets are the calling user's own uploads, such as an avatar or cover image. Read a single user asset by id. + +
+ +### Path Parameters + +
+ + + +The user asset id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `entity_type`, `id`, `is_uploaded`, `name`, `size`, `user_id`. + + + +
+
+ +
+ +### Scopes + +`assets:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "entity_type": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1, + "user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No user asset matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/user-assets/list-user-assets.md b/docs/api-reference/v2/user-assets/list-user-assets.md new file mode 100644 index 00000000..408934c1 --- /dev/null +++ b/docs/api-reference/v2/user-assets/list-user-assets.md @@ -0,0 +1,188 @@ +--- +title: List user assets +description: List user assets with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list user assets, user assets, user assets list +--- + +# List user assets + +
+ GET + /api/v2/users/me/assets/ +
+ +
+
+ +User assets are the calling user's own uploads, such as an avatar or cover image. List the user assets you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `entity_type`, `id`, `is_uploaded`, `name`, `size`, `user_id`. + + + +
+
+ +
+ +### Scopes + +`assets:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "entity_type": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1, + "user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "entity_type": "example", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_uploaded": false, + "name": "Another example", + "size": 1, + "user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/user-assets/overview.md b/docs/api-reference/v2/user-assets/overview.md new file mode 100644 index 00000000..0c4bc4f2 --- /dev/null +++ b/docs/api-reference/v2/user-assets/overview.md @@ -0,0 +1,100 @@ +--- +title: User assets overview +description: The Plane API v2 user asset object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, user assets, user asset object +--- + +# User assets overview + +User assets are the calling user's own uploads, such as an avatar or cover image. + +
+
+ +## The user asset object + +### Attributes + +- `asset_url` _string_ + + The asset url. + +- `attributes` _string_ + + The attributes. + +- `content_type` _string_ + + The content type. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `entity_type` _string_ + + The entity type. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_uploaded` _boolean_ + + Whether is uploaded. + +- `name` _string_ + + Display name. + +- `size` _number_ + + The size. + +- `user_id` _string (uuid)_ + + The related user. + +
+
+ + + +```json +{ + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "entity_type": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1, + "user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------- | --------------------------- | +| `GET` | `/api/v2/users/me/assets/` | List user assets | +| `POST` | `/api/v2/users/me/assets/` | Create a user asset upload | +| `DELETE` | `/api/v2/users/me/assets/{pk}/` | Delete a user asset | +| `GET` | `/api/v2/users/me/assets/{pk}/` | Get a user asset | +| `PATCH` | `/api/v2/users/me/assets/{pk}/` | Confirm a user asset upload | + +## Response shaping + +Every user asset read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/users/get-current-user.md b/docs/api-reference/v2/users/get-current-user.md new file mode 100644 index 00000000..7d24f163 --- /dev/null +++ b/docs/api-reference/v2/users/get-current-user.md @@ -0,0 +1,176 @@ +--- +title: Get current user +description: Verify Plane API v2 credentials with GET /api/v2/users/me/. Returns the calling principal — id, display name, email, principal_kind, and granted OAuth scopes. Errors and code examples. +keywords: plane api v2, get current user, users me, whoami, verify api key, principal_kind, oauth scopes, debug 401, debug 403 +--- + +# Get current user + +
+ GET + /api/v2/users/me/ +
+ +
+
+ +Return the principal behind the credentials you just sent: which user it acts as, whether it is an OAuth token or an API key, and which scopes it carries. + +Make this your first call against a new key or a freshly authorized OAuth app. A `200` proves the credential is accepted and shows you exactly what it can do, before you spend a debugging session on an endpoint that was never in scope. + +
+ +### Path Parameters + +None. The path is fixed — there is no `{slug}` and no user id, because the endpoint describes the credential rather than a workspace or a person you choose. + +
+ +
+ +### Query Parameters + +None. No filters, no pagination, and no `?expand=`. + +
+ +
+ +### Scopes + +No fine-grained scope is required — the endpoint declares none, so there is no `users:*` scope to ask for. + +An API key reaches it unconditionally. An OAuth token still needs to carry a read scope; a token granted only write scopes is rejected with `403`. In practice any token issued for real work already qualifies, which is what makes this a reliable probe when you are unsure what your credential can reach. + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials — a wrong key, the wrong header, or an expired or revoked OAuth token. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | The credential is valid but carries no read scope — an OAuth token granted write scopes only. | +| `404` | `not_found` | The calling principal could not be resolved to a user record. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: tip Split `401` from `403` before you debug anything else +A `401` here means the credential is wrong, so no other endpoint will work either. A `200` here plus a `403` elsewhere means the credential is fine and the **scope or role** is too narrow for that specific endpoint — compare its documented scope against the `scopes` array in this response. +::: + +::: info Send the key as `X-Api-Key` +v2 reads the API key from `X-Api-Key`, or an OAuth token from `Authorization: Bearer `. v1's `X-API-Key` casing is a common cause of a `401` here when porting a v1 client. See [Authentication](/api-reference/v2/authentication). +::: + +::: info A `409` is never about this endpoint's answer +If your scopes look right and a write still returns `409 work_item_types_managed_at_workspace` or `work_item_types_managed_at_project`, stop looking at credentials. The workspace manages work item types on the other surface and the same call succeeds there. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "display_name": "Priya Raghavan", + "email": "priya@example.com", + "principal_kind": "oauth", + "scopes": ["projects.work_items:read", "projects.work_items:write", "projects.states:read", "workspaces.members:read"] +} +``` + + + + + +```json +{ + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "display_name": "Priya Raghavan", + "email": "priya@example.com", + "principal_kind": "api_key", + "scopes": [] +} +``` + + + + + +```json +{ + "type": "unauthorized", + "code": "unauthorized", + "detail": "Authentication credentials were not provided or are invalid." +} +``` + + + +
+
+ +## Reading the response + +- **`principal_kind` tells you which credential model you are in.** `oauth` means an application token acting for a user, `api_key` means a personal key, `other` covers anything else the server authenticates. +- **`scopes` is the OAuth grant.** For an OAuth token it lists the fine-grained scopes the user consented to; check it against the scope named on the endpoint you are calling. An empty array means no fine-grained scopes are attached to this credential. +- **`id` is the identity your writes will carry.** It shows up later as `created_by_id` on objects you create and as `actor_id` in [audit logs](/api-reference/v2/audit-logs/overview). +- **The response says nothing about roles.** Scopes cap what a token may attempt; the user's role in each workspace and project decides whether it succeeds. A `403` with the right scope listed here is a role problem. + +## Related + +- [Users overview](/api-reference/v2/users/overview) +- [Authentication](/api-reference/v2/authentication) +- [Members overview](/api-reference/v2/members/overview) — the roster where this `id` appears as `member_id` +- [Errors](/api-reference/v2/errors) — the full code list to branch on diff --git a/docs/api-reference/v2/users/overview.md b/docs/api-reference/v2/users/overview.md new file mode 100644 index 00000000..12de37d7 --- /dev/null +++ b/docs/api-reference/v2/users/overview.md @@ -0,0 +1,101 @@ +--- +title: Users overview +description: The Plane API v2 users resource. GET /api/v2/users/me/ echoes the calling principal — its kind, identity, and granted OAuth scopes — and is the first call to make when verifying credentials. +keywords: plane api v2, users, users me, whoami, principal_kind, oauth scopes, api key, auth debugging, verify credentials +--- + +# Users overview + +The users resource has exactly one endpoint, and it answers one question: **who am I to this API right now?** + +`GET /api/v2/users/me/` echoes the calling principal back at you — the kind of credential you presented, the identity it resolves to, and, for OAuth tokens, the scopes it was actually granted. It is the natural first call after wiring up credentials, and the first thing to run when a request is failing and you are not sure why. + +
+
+ +## The current user object + +### Attributes + +- `id` _string (uuid)_ + + The user this credential acts as. This is the same id you see as `member_id` on the [member rosters](/api-reference/v2/members/overview), `created_by_id` on objects you create, and `actor_id` in [audit logs](/api-reference/v2/audit-logs/overview) — so it is how you confirm that the trail you are looking at is yours. + +- `display_name` _string_ + + The user's display name. + +- `email` _string (email)_ + + The user's email address. Useful for confirming you are on the account you meant to be on, especially across staging and production keys. + +- `principal_kind` _string_ + + What kind of credential is calling. One of: + - `oauth` — an OAuth access token issued to an application + - `api_key` — a personal API key sent in `X-Api-Key` + - `other` — anything else the server recognizes as authenticated + +- `scopes` _array of string_ + + The scopes this credential carries. For an OAuth token these are the fine-grained scopes the user consented to, for example `projects.work_items:read`. Compare this list against the scope named on the endpoint you are calling before you conclude that a `403` is a role problem. + +
+
+ + + +```json +{ + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "display_name": "Priya Raghavan", + "email": "priya@example.com", + "principal_kind": "oauth", + "scopes": ["projects.work_items:read", "projects.work_items:write", "projects.states:read", "workspaces.members:read"] +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| ------ | ------------------- | ---------------------------- | +| `GET` | `/api/v2/users/me/` | Return the calling principal | + +The path has no workspace slug: it describes the credential, not a workspace. There is no `/users/{id}/` in v2 — to look other people up, read a workspace or project roster with [`?expand=member`](/api-reference/v2/members/list-workspace-members). + +## Debugging auth with it + +This endpoint declares no fine-grained scope requirement — an API key reaches it unconditionally, and an OAuth token needs only a read scope. That makes it a clean way to split failures apart: + +| Symptom | What `GET /users/me/` tells you | +| ------------------------------------------------- | ------------------------------------------------------------------------------------------------- | +| Every call returns `401` | If this returns `401` too, the credential itself is wrong — bad key, wrong header, expired token. | +| One call returns `403`, others work | Compare `scopes` against the scope that endpoint documents. A missing scope is the usual cause. | +| Writes land under an unexpected author | Check `id` and `email` — you are probably holding a different account's key than you think. | +| Not sure whether an integration is OAuth or a key | Read `principal_kind`. | + +::: tip A `409` is not an auth failure +If `scopes` contains the write scope for the endpoint and the call still fails with `409 work_item_types_managed_at_workspace` or `work_item_types_managed_at_project`, the credential is fine. The workspace manages work item types on the other surface, and the same request succeeds there. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +::: info Scopes are a ceiling, not a grant +A token can only do what its scopes allow **and** what the underlying user's role allows. A broad scope on a member-level account is still limited by that member's role, so an empty-handed `403` with the right scope listed here points at the role, not the token. +::: + +## Changed from v1 + +- The response is now about the **principal**, not just the person. `principal_kind` and `scopes` are new, and they are what make the endpoint useful for debugging OAuth apps. +- `first_name`, `last_name`, `avatar`, and `avatar_url` are no longer returned. `display_name` and `email` remain. + +See [Migrating from v1](/api-reference/v2/migrating-from-v1) for the full list. + +## Related + +- [Get current user](/api-reference/v2/users/get-current-user) +- [Authentication](/api-reference/v2/authentication) — how to present an API key or OAuth token +- [Members overview](/api-reference/v2/members/overview) — resolve other people from a roster diff --git a/docs/api-reference/v2/webhook-logs/get-webhook-log.md b/docs/api-reference/v2/webhook-logs/get-webhook-log.md new file mode 100644 index 00000000..ec96d2a6 --- /dev/null +++ b/docs/api-reference/v2/webhook-logs/get-webhook-log.md @@ -0,0 +1,167 @@ +--- +title: Get a webhook log +description: Read a webhook log with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a webhook log, webhook logs, webhook logs retrieve +--- + +# Get a webhook log + +
+ GET + /api/v2/workspaces/{slug}/webhook-logs/{webhook_id}/{pk}/ +
+ +
+
+ +Webhook logs are the delivery history for one webhook. Read a single webhook log by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The webhook whose delivery log you're reading. + + + + + +The webhook log id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `duration_ms`, `error_message`, `event_type`, `id`, `request_body`, `request_headers`, `request_method`, `response_body`, `response_headers`, `response_status`, `retry_count`, `status_text`, `webhook_id`. + + + +
+
+ +
+ +### Scopes + +`webhooks:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "duration_ms": 1, + "error_message": "example", + "event_type": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "request_body": "example", + "request_headers": "example", + "request_method": "example", + "response_body": "example", + "response_headers": "example", + "response_status": "example", + "retry_count": 3, + "status_text": "example", + "webhook_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No webhook log matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/webhook-logs/list-webhook-logs.md b/docs/api-reference/v2/webhook-logs/list-webhook-logs.md new file mode 100644 index 00000000..5da23f4c --- /dev/null +++ b/docs/api-reference/v2/webhook-logs/list-webhook-logs.md @@ -0,0 +1,218 @@ +--- +title: List webhook logs +description: List webhook logs with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list webhook logs, webhook logs, webhook logs list +--- + +# List webhook logs + +
+ GET + /api/v2/workspaces/{slug}/webhook-logs/{webhook_id}/ +
+ +
+
+ +Webhook logs are the delivery history for one webhook. List the webhook logs you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The webhook whose delivery log you're reading. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `duration_ms`, `error_message`, `event_type`, `id`, `request_body`, `request_headers`, `request_method`, `response_body`, `response_headers`, `response_status`, `retry_count`, `status_text`, `webhook_id`. + + + +
+
+ +
+ +### Scopes + +`webhooks:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "duration_ms": 1, + "error_message": "example", + "event_type": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "request_body": "example", + "request_headers": "example", + "request_method": "example", + "response_body": "example", + "response_headers": "example", + "response_status": "example", + "retry_count": 3, + "status_text": "example", + "webhook_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "duration_ms": 1, + "error_message": "example", + "event_type": "example", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "request_body": "example", + "request_headers": "example", + "request_method": "example", + "response_body": "example", + "response_headers": "example", + "response_status": "example", + "retry_count": 3, + "status_text": "example", + "webhook_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/webhook-logs/overview.md b/docs/api-reference/v2/webhook-logs/overview.md new file mode 100644 index 00000000..78ee46fd --- /dev/null +++ b/docs/api-reference/v2/webhook-logs/overview.md @@ -0,0 +1,112 @@ +--- +title: Webhook logs overview +description: The Plane API v2 webhook log object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, webhook logs, webhook log object +--- + +# Webhook logs overview + +Webhook logs are the delivery history for one webhook. + +
+
+ +## The webhook log object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `duration_ms` _integer_ + + The duration ms. + +- `error_message` _string_ + + The error message. + +- `event_type` _string_ + + The event type. + +- `id` _string (uuid)_ + + Unique identifier. + +- `request_body` _string_ + + The request body. + +- `request_headers` _string_ + + The request headers. + +- `request_method` _string_ + + The request method. + +- `response_body` _string_ + + The response body. + +- `response_headers` _string_ + + The response headers. + +- `response_status` _string_ + + The response status. + +- `retry_count` _integer_ + + How many retrys are attached. + +- `status_text` _string_ + + The status text. + +- `webhook_id` _string (uuid)_ + + The related webhook. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "duration_ms": 1, + "error_message": "example", + "event_type": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "request_body": "example", + "request_headers": "example", + "request_method": "example", + "response_body": "example", + "response_headers": "example", + "response_status": "example", + "retry_count": 3, + "status_text": "example", + "webhook_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| ------ | ----------------------------------------------------------- | ----------------- | +| `GET` | `/api/v2/workspaces/{slug}/webhook-logs/{webhook_id}/` | List webhook logs | +| `GET` | `/api/v2/workspaces/{slug}/webhook-logs/{webhook_id}/{pk}/` | Get a webhook log | + +## Response shaping + +Every webhook log read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/webhooks/create-webhook.md b/docs/api-reference/v2/webhooks/create-webhook.md new file mode 100644 index 00000000..e55e6872 --- /dev/null +++ b/docs/api-reference/v2/webhooks/create-webhook.md @@ -0,0 +1,208 @@ +--- +title: Create a webhook +description: Create a webhook with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a webhook, webhooks, webhooks create +--- + +# Create a webhook + +
+ POST + /api/v2/workspaces/{slug}/webhooks/ +
+ +
+
+ +Webhooks push Plane events to a URL you control. Create a webhook. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +- `application/json` - application/json +- `application/x-www-form-urlencoded` - application/x-www-form-urlencoded + +One of `application/json`, `application/x-www-form-urlencoded`. + + + + + +Whether the record is active. + + + + + +Display name. + +Maximum 255 characters. Nullable. + + + + + +The scopes. + + + + + +Target URL. + + + + + +Version string, for example `1.4.0`. + +Maximum 10 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `content_type`, `created_at`, `created_by_id`, `id`, `is_active`, `name`, `scopes`, `url`, `version`. + + + +
+
+ +
+ +### Scopes + +`webhooks:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "content_type": "application/json", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "name": "Example name", + "scopes": ["example"], + "url": "https://example.com/spec", + "version": "1.4.0" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/webhooks/delete-webhook.md b/docs/api-reference/v2/webhooks/delete-webhook.md new file mode 100644 index 00000000..c83b9c18 --- /dev/null +++ b/docs/api-reference/v2/webhooks/delete-webhook.md @@ -0,0 +1,148 @@ +--- +title: Delete a webhook +description: Delete a webhook with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a webhook, webhooks, webhooks destroy +--- + +# Delete a webhook + +
+ DELETE + /api/v2/workspaces/{slug}/webhooks/{pk}/ +
+ +
+
+ +Webhooks push Plane events to a URL you control. Delete a webhook. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The webhook id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `content_type`, `created_at`, `created_by_id`, `id`, `is_active`, `name`, `scopes`, `url`, `version`. + + + +
+
+ +
+ +### Scopes + +`webhooks:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No webhook matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/webhooks/get-webhook.md b/docs/api-reference/v2/webhooks/get-webhook.md new file mode 100644 index 00000000..fde7d7d6 --- /dev/null +++ b/docs/api-reference/v2/webhooks/get-webhook.md @@ -0,0 +1,156 @@ +--- +title: Get a webhook +description: Read a webhook with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a webhook, webhooks, webhooks retrieve +--- + +# Get a webhook + +
+ GET + /api/v2/workspaces/{slug}/webhooks/{pk}/ +
+ +
+
+ +Webhooks push Plane events to a URL you control. Read a single webhook by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The webhook id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `content_type`, `created_at`, `created_by_id`, `id`, `is_active`, `name`, `scopes`, `url`, `version`. + + + +
+
+ +
+ +### Scopes + +`webhooks:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "content_type": "application/json", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "name": "Example name", + "scopes": ["example"], + "url": "https://example.com/spec", + "version": "1.4.0" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No webhook matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/webhooks/list-webhooks.md b/docs/api-reference/v2/webhooks/list-webhooks.md new file mode 100644 index 00000000..6a9fcbda --- /dev/null +++ b/docs/api-reference/v2/webhooks/list-webhooks.md @@ -0,0 +1,223 @@ +--- +title: List webhooks +description: List webhooks with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list webhooks, webhooks, webhooks list +--- + +# List webhooks + +
+ GET + /api/v2/workspaces/{slug}/webhooks/ +
+ +
+
+ +Webhooks push Plane events to a URL you control. List the webhooks you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `is_active`. + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +Filter by `url`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `content_type`, `created_at`, `created_by_id`, `id`, `is_active`, `name`, `scopes`, `url`, `version`. + + + +
+
+ +
+ +### Scopes + +`webhooks:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "content_type": "application/json", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "name": "Example name", + "scopes": ["example"], + "url": "https://example.com/spec", + "version": "1.4.0" + }, + { + "content_type": "application/json", + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_active": true, + "name": "Another example", + "scopes": ["example"], + "url": "https://example.com/spec", + "version": "1.4.0" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/webhooks/overview.md b/docs/api-reference/v2/webhooks/overview.md new file mode 100644 index 00000000..e4a59a44 --- /dev/null +++ b/docs/api-reference/v2/webhooks/overview.md @@ -0,0 +1,91 @@ +--- +title: Webhooks overview +description: The Plane API v2 webhook object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, webhooks, webhook object +--- + +# Webhooks overview + +Webhooks push Plane events to a URL you control. + +
+
+ +## The webhook object + +### Attributes + +- `content_type` _string_ + + The content type. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_active` _boolean_ + + Whether the record is active. + +- `name` _string_ + + Display name. + +- `scopes` _array of string_ + + The scopes. + +- `url` _string (uri)_ + + Target URL. + +- `version` _string_ + + Version string, for example `1.4.0`. + +
+
+ + + +```json +{ + "content_type": "application/json", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "name": "Example name", + "scopes": ["example"], + "url": "https://example.com/spec", + "version": "1.4.0" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ----------------------------------------------------- | --------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/webhooks/` | List webhooks | +| `POST` | `/api/v2/workspaces/{slug}/webhooks/` | Create a webhook | +| `DELETE` | `/api/v2/workspaces/{slug}/webhooks/{pk}/` | Delete a webhook | +| `GET` | `/api/v2/workspaces/{slug}/webhooks/{pk}/` | Get a webhook | +| `PATCH` | `/api/v2/workspaces/{slug}/webhooks/{pk}/` | Update a webhook | +| `POST` | `/api/v2/workspaces/{slug}/webhooks/{pk}/regenerate/` | Regenerate a webhook secret | + +## Response shaping + +Every webhook read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/webhooks/regenerate-webhook-secret.md b/docs/api-reference/v2/webhooks/regenerate-webhook-secret.md new file mode 100644 index 00000000..021f0d03 --- /dev/null +++ b/docs/api-reference/v2/webhooks/regenerate-webhook-secret.md @@ -0,0 +1,149 @@ +--- +title: Regenerate a webhook secret +description: Regenerate a webhook secret with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, regenerate a webhook secret, webhooks, webhooks regenerate +--- + +# Regenerate a webhook secret + +
+ POST + /api/v2/workspaces/{slug}/webhooks/{pk}/regenerate/ +
+ +
+
+ +Webhooks push Plane events to a URL you control. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The webhook id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `content_type`, `created_at`, `created_by_id`, `id`, `is_active`, `name`, `scopes`, `secret_key`, `url`, `version`. + + + +
+
+ +
+ +### Scopes + +`webhooks:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "content_type": "application/json", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "name": "Example name", + "scopes": ["example"], + "secret_key": "example", + "url": "https://example.com/spec", + "version": "1.4.0" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/webhooks/update-webhook.md b/docs/api-reference/v2/webhooks/update-webhook.md new file mode 100644 index 00000000..92e6fbb3 --- /dev/null +++ b/docs/api-reference/v2/webhooks/update-webhook.md @@ -0,0 +1,229 @@ +--- +title: Update a webhook +description: Update a webhook with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a webhook, webhooks, webhooks partial update +--- + +# Update a webhook + +
+ PATCH + /api/v2/workspaces/{slug}/webhooks/{pk}/ +
+ +
+
+ +Webhooks push Plane events to a URL you control. Update a webhook. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The webhook id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +- `application/json` - application/json +- `application/x-www-form-urlencoded` - application/x-www-form-urlencoded + +One of `application/json`, `application/x-www-form-urlencoded`. + + + + + +Whether the record is active. + + + + + +Display name. + +Maximum 255 characters. Nullable. + + + + + +The scopes. + + + + + +Target URL. + + + + + +Version string, for example `1.4.0`. + +Maximum 10 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `content_type`, `created_at`, `created_by_id`, `id`, `is_active`, `name`, `scopes`, `url`, `version`. + + + +
+
+ +
+ +### Scopes + +`webhooks:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "content_type": "application/json", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "name": "Example name", + "scopes": ["example"], + "url": "https://example.com/spec", + "version": "1.4.0" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No webhook matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-activities/get-activity.md b/docs/api-reference/v2/work-item-activities/get-activity.md new file mode 100644 index 00000000..8dcd20bf --- /dev/null +++ b/docs/api-reference/v2/work-item-activities/get-activity.md @@ -0,0 +1,183 @@ +--- +title: Get a activity +description: Read a activity with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a activity, activities, activities retrieve +--- + +# Get a activity + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/activities/{pk}/ +
+ +
+
+ +Activities are the append-only audit trail on a work item. Read a single activity by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + + + +The activity id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `actor_id`, `comment`, `created_at`, `duration`, `epoch`, `external_id`, `external_source`, `field`, `id`, `issue_comment_id`, `new_identifier_id`, `new_value`, `old_identifier_id`, `old_value`, `verb`, `work_item_id`. + + + + + +Comma-separated relations to embed: `actor`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.activities:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "actor_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "comment": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "duration": 90, + "epoch": 1, + "external_id": null, + "external_source": null, + "field": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "issue_comment_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "new_identifier_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "new_value": "example", + "old_identifier_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_value": "example", + "verb": "example", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No activity matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-activities/list-activities.md b/docs/api-reference/v2/work-item-activities/list-activities.md new file mode 100644 index 00000000..3e983dd8 --- /dev/null +++ b/docs/api-reference/v2/work-item-activities/list-activities.md @@ -0,0 +1,254 @@ +--- +title: List activities +description: List activities with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list activities, activities, activities list +--- + +# List activities + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/activities/ +
+ +
+
+ +Activities are the append-only audit trail on a work item. List the activities you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Filter by `actor_id`. + + + + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `created_at__gte`. + + + + + +Filter by `created_at__lte`. + + + + + +Filter by `field`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +Filter by `verb`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `actor_id`, `comment`, `created_at`, `duration`, `epoch`, `external_id`, `external_source`, `field`, `id`, `issue_comment_id`, `new_identifier_id`, `new_value`, `old_identifier_id`, `old_value`, `verb`, `work_item_id`. + + + + + +Comma-separated relations to embed: `actor`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.activities:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "actor_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "comment": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "duration": 90, + "epoch": 1, + "external_id": null, + "external_source": null, + "field": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "issue_comment_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "new_identifier_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "new_value": "example", + "old_identifier_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_value": "example", + "verb": "example", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-activities/overview.md b/docs/api-reference/v2/work-item-activities/overview.md new file mode 100644 index 00000000..2048bb22 --- /dev/null +++ b/docs/api-reference/v2/work-item-activities/overview.md @@ -0,0 +1,122 @@ +--- +title: Work item activities overview +description: The Plane API v2 activity object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, work item activities, activity object +--- + +# Work item activities overview + +Activities are the append-only audit trail on a work item. + +
+
+ +## The activity object + +### Attributes + +- `actor_id` _string (uuid)_ + + The related actor. + +- `comment` _string_ + + The comment. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `duration` _integer_ + + Time logged, in minutes. + +- `epoch` _number_ + + The epoch. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `field` _string_ + + The field. + +- `id` _string (uuid)_ + + Unique identifier. + +- `issue_comment_id` _string (uuid)_ + + The related issue comment. + +- `new_identifier_id` _string (uuid)_ + + The related new identifier. + +- `new_value` _string_ + + The new value. + +- `old_identifier_id` _string (uuid)_ + + The related old identifier. + +- `old_value` _string_ + + The old value. + +- `verb` _string_ + + The verb. + +- `work_item_id` _string (uuid)_ + + The related work item. + +
+
+ + + +```json +{ + "actor_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "comment": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "duration": 90, + "epoch": 1, + "external_id": null, + "external_source": null, + "field": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "issue_comment_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "new_identifier_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "new_value": "example", + "old_identifier_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_value": "example", + "verb": "example", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| ------ | -------------------------------------------------------------------------------------------- | --------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/activities/` | List activities | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/activities/{pk}/` | Get a activity | + +## Response shaping + +Every activity read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/work-item-attachments/confirm-attachment-upload.md b/docs/api-reference/v2/work-item-attachments/confirm-attachment-upload.md new file mode 100644 index 00000000..6bee5d77 --- /dev/null +++ b/docs/api-reference/v2/work-item-attachments/confirm-attachment-upload.md @@ -0,0 +1,201 @@ +--- +title: Confirm a work item attachment upload +description: Update a attachment with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, confirm a work item attachment upload, attachments, attachments partial update +--- + +# Confirm a work item attachment upload + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/attachments/{pk}/ +
+ +
+
+ +Attachments are files uploaded against a work item. Update a attachment. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + + + +The attachment id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Whether is uploaded. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `external_id`, `external_source`, `id`, `is_uploaded`, `name`, `size`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.attachments:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1, + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No attachment matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-attachments/create-attachment-upload.md b/docs/api-reference/v2/work-item-attachments/create-attachment-upload.md new file mode 100644 index 00000000..cb13ef50 --- /dev/null +++ b/docs/api-reference/v2/work-item-attachments/create-attachment-upload.md @@ -0,0 +1,223 @@ +--- +title: Create a work item attachment upload +description: Create a attachment with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a work item attachment upload, attachments, attachments create +--- + +# Create a work item attachment upload + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/attachments/ +
+ +
+
+ +Attachments are files uploaded against a work item. Create a attachment. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + + + + + +The size. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Nullable. + + + + + +The type. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `external_id`, `external_source`, `id`, `is_uploaded`, `name`, `size`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.attachments:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1, + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-attachments/delete-attachment.md b/docs/api-reference/v2/work-item-attachments/delete-attachment.md new file mode 100644 index 00000000..9b7892e8 --- /dev/null +++ b/docs/api-reference/v2/work-item-attachments/delete-attachment.md @@ -0,0 +1,160 @@ +--- +title: Delete a attachment +description: Delete a attachment with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a attachment, attachments, attachments destroy +--- + +# Delete a attachment + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/attachments/{pk}/ +
+ +
+
+ +Attachments are files uploaded against a work item. Delete a attachment. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + + + +The attachment id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `external_id`, `external_source`, `id`, `is_uploaded`, `name`, `size`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.attachments:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No attachment matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-attachments/get-attachment.md b/docs/api-reference/v2/work-item-attachments/get-attachment.md new file mode 100644 index 00000000..df524ed1 --- /dev/null +++ b/docs/api-reference/v2/work-item-attachments/get-attachment.md @@ -0,0 +1,171 @@ +--- +title: Get a attachment +description: Read a attachment with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a attachment, attachments, attachments retrieve +--- + +# Get a attachment + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/attachments/{pk}/ +
+ +
+
+ +Attachments are files uploaded against a work item. Read a single attachment by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + + + +The attachment id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `external_id`, `external_source`, `id`, `is_uploaded`, `name`, `size`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.attachments:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1, + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No attachment matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-attachments/list-attachments.md b/docs/api-reference/v2/work-item-attachments/list-attachments.md new file mode 100644 index 00000000..fd1e2e1b --- /dev/null +++ b/docs/api-reference/v2/work-item-attachments/list-attachments.md @@ -0,0 +1,238 @@ +--- +title: List attachments +description: List attachments with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list attachments, attachments, attachments list +--- + +# List attachments + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/attachments/ +
+ +
+
+ +Attachments are files uploaded against a work item. List the attachments you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `external_id`. + + + + + +Filter by `external_source`. + + + + + +Filter by `is_uploaded`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `external_id`, `external_source`, `id`, `is_uploaded`, `name`, `size`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.attachments:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1, + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_uploaded": false, + "name": "Another example", + "size": 1, + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-attachments/overview.md b/docs/api-reference/v2/work-item-attachments/overview.md new file mode 100644 index 00000000..332b06f7 --- /dev/null +++ b/docs/api-reference/v2/work-item-attachments/overview.md @@ -0,0 +1,105 @@ +--- +title: Work item attachments overview +description: The Plane API v2 attachment object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, work item attachments, attachment object +--- + +# Work item attachments overview + +Attachments are files uploaded against a work item. + +
+
+ +## The attachment object + +### Attributes + +- `asset_url` _string_ + + The asset url. + +- `attributes` _string_ + + The attributes. + +- `content_type` _string_ + + The content type. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_uploaded` _boolean_ + + Whether is uploaded. + +- `name` _string_ + + Display name. + +- `size` _number_ + + The size. + +- `work_item_id` _string (uuid)_ + + The related work item. + +
+
+ + + +```json +{ + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1, + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | --------------------------------------------------------------------------------------------- | ------------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/attachments/` | List attachments | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/attachments/` | Create a work item attachment upload | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/attachments/{pk}/` | Delete a attachment | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/attachments/{pk}/` | Get a attachment | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/attachments/{pk}/` | Confirm a work item attachment upload | + +## Response shaping + +Every attachment read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/work-item-comments/bulk-comments.md b/docs/api-reference/v2/work-item-comments/bulk-comments.md new file mode 100644 index 00000000..a0f2c0d8 --- /dev/null +++ b/docs/api-reference/v2/work-item-comments/bulk-comments.md @@ -0,0 +1,192 @@ +--- +title: Bulk write comments +description: Bulk write comments with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, bulk write comments, comments, work item comments bulk +--- + +# Bulk write comments + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/bulk/ +
+ +
+
+ +Comments are the discussion thread on a work item. Write up to 100 comments in one request, with a per-row result for each. + +- Send `{"items": [...]}`, at most 100 per request. Add `"upsert": true` to match existing rows on `(external_source, external_id)` instead of always creating. +- The response is always `200`. Each row reports its own outcome, so a partial failure is reported rather than silently dropped — read `succeeded`/`failed` and the per-row `status`. +- Rows are written on independent savepoints: a failing row rolls back only itself. +- `?fields=` does not apply here — the bulk response is a per-row result envelope, not a resource body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The items. + + + + + +Reconcile each row on (external_source, external_id) instead of creating it. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.comments:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "failed": 1, + "results": [null], + "succeeded": 1 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-comments/create-comment.md b/docs/api-reference/v2/work-item-comments/create-comment.md new file mode 100644 index 00000000..dd88b9c8 --- /dev/null +++ b/docs/api-reference/v2/work-item-comments/create-comment.md @@ -0,0 +1,254 @@ +--- +title: Create a comment +description: Post a comment on a Plane work item with the v2 REST API. HTML bodies, INTERNAL and EXTERNAL access, external ids, OAuth scopes, errors, and code examples. +keywords: plane api v2, create work item comment, POST comments, comment_html, comment access, append-only comments +--- + +# Create a comment + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/ +
+ +
+
+ +Post a comment on a work item. The comment is attached to the work item in the path and appears in its +discussion immediately. + +The author is taken from the credentials on the request and returned as `actor_id` — you cannot post a comment +on behalf of another user. + +::: info Comments are not deduplicated +Nothing about a comment's body or `external_id` identifies it uniquely, so sending the same `comment_html` +twice creates two comments. If your integration must post a comment at most once, send an `external_id` and +look for it first with `GET …/comments/?external_id=…&external_source=…`. +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + + + +The project the work item belongs to. + + + + + +The work item to comment on. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The comment body, as HTML — for example `

Deployed the fix to staging.

`. Plane derives the plain-text +`comment_stripped` from it server-side, which is what search matches. + +
+ + + +Visibility of the comment. + +- `INTERNAL` — visible to the project team +- `EXTERNAL` — marked as visible outside the team, for example on a published project + +Omit it to take the server's default. Change it later with a `PATCH`. + + + + + +Your system's identifier for this comment, for sync and import correlation. Maximum 255 characters. Accepts +`null`. + + + + + +The system `external_id` came from, for example `github` or `zendesk`. Maximum 255 characters. Accepts `null`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `access`, `actor_id`, `comment_html`, `comment_stripped`, `created_at`, `created_by_id`, `edited_at`, `external_id`, `external_source`, `id`, `work_item_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `actor` (the comment author). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.comments:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | Missing `comment_html`, or an `access` value outside the enum. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't comment on this work item. | +| `404` | `not_found` | No such workspace, project, or work item, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The write conflicts with the current state of the work item. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "c1f7a3d9-2b64-4f80-9c1a-3d5e8b2a6c47", + "work_item_id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "comment_html": "

Deployed the fix to staging. Please re-test.

", + "comment_stripped": "Deployed the fix to staging. Please re-test.", + "access": "INTERNAL", + "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "edited_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + +
+ + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "The request body failed validation.", + "errors": [ + { + "field": "comment_html", + "code": "required", + "message": "This field is required." + } + ] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-comments/delete-comment.md b/docs/api-reference/v2/work-item-comments/delete-comment.md new file mode 100644 index 00000000..ccabc594 --- /dev/null +++ b/docs/api-reference/v2/work-item-comments/delete-comment.md @@ -0,0 +1,184 @@ +--- +title: Delete a comment +description: Delete a comment from a Plane work item with the v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete work item comment, DELETE comment, remove comment, 204 no content +--- + +# Delete a comment + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/{pk}/ +
+ +
+
+ +Remove a comment from a work item. On success the response is `204 No Content` with an empty body — there is +nothing to parse, so branch on the status code. + +The delete is a soft delete: the comment stops appearing in every read, including lists and its own detail +route, and a follow-up `GET` on the same id returns `404 not_found`. + +::: warning Deleting a comment is not reversible through the API +There is no restore endpoint, so confirm with the user before deleting. Deleting the parent work item is the +usual alternative when you want the whole thread to disappear at once. +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + + + +The project the work item belongs to. + + + + + +The work item the comment is attached to. + + + + + +The comment to delete. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `access`, `actor_id`, `comment_html`, `comment_stripped`, `created_at`, `created_by_id`, `edited_at`, `external_id`, `external_source`, `id`, `work_item_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `actor` (the comment author). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.comments:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't delete this comment. | +| `404` | `not_found` | No such comment, it belongs to another work item, or it's already deleted. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The delete conflicts with the current state of the comment. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No comment matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-comments/get-comment.md b/docs/api-reference/v2/work-item-comments/get-comment.md new file mode 100644 index 00000000..289d5d9f --- /dev/null +++ b/docs/api-reference/v2/work-item-comments/get-comment.md @@ -0,0 +1,188 @@ +--- +title: Get a comment +description: Retrieve a single comment on a Plane work item with the v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get work item comment, GET comment, comment by id, comment_html +--- + +# Get a comment + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/{pk}/ +
+ +
+
+ +Retrieve one comment by id. Use it to re-read a comment you created or updated, or to check `edited_at` before +overwriting a body you did not write. + +The comment is looked up **within the work item in the path**. A comment id that belongs to a different work +item is not reachable here and returns `404 not_found`, the same as an id that does not exist. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + + + +The project the work item belongs to. + + + + + +The work item the comment is attached to. + + + + + +The comment to retrieve. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `access`, `actor_id`, `comment_html`, `comment_stripped`, `created_at`, `created_by_id`, `edited_at`, `external_id`, `external_source`, `id`, `work_item_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `actor` (the comment author). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.comments:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this work item. | +| `404` | `not_found` | No such comment, it belongs to another work item, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "c1f7a3d9-2b64-4f80-9c1a-3d5e8b2a6c47", + "work_item_id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "comment_html": "

Deployed the fix to staging. Please re-test.

", + "comment_stripped": "Deployed the fix to staging. Please re-test.", + "access": "INTERNAL", + "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "edited_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No comment matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-comments/list-comments.md b/docs/api-reference/v2/work-item-comments/list-comments.md new file mode 100644 index 00000000..921c148f --- /dev/null +++ b/docs/api-reference/v2/work-item-comments/list-comments.md @@ -0,0 +1,326 @@ +--- +title: List comments +description: List the comments on a Plane work item with the v2 REST API. Access filter, external id lookup, search, ordering, pagination, scopes, and code examples. +keywords: plane api v2, list work item comments, GET comments, comment access filter, comment search, comment pagination +--- + +# List comments + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/ +
+ +
+
+ +Return the comments on one work item. Comments are scoped to their parent work item, so this is +the only way to read a work item's discussion — reach for it when rendering a work item detail view or mirroring +a thread into another system. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + + + +The project the work item belongs to. + + + + + +The work item whose comments you want. Comments never span work items, so this narrows the result set by +itself. + + + +
+
+ +
+ +### Query Parameters + +**Filters** + +
+ + + +Return only comments with this visibility. + +- `INTERNAL` — visible to the project team +- `EXTERNAL` — marked as visible outside the team + + + + + +Return comments carrying this identifier from your system. `external_id` is not a unique key, so this can match +more than one comment — pair it with `external_source` and handle a multi-row result. + + + + + +Return comments that came from this system, for example `github` or `zendesk`. + + + +
+ +**Search** + +
+ + + +Match comments against their plain-text body (`comment_stripped`), so HTML markup in `comment_html` never +affects whether a term hits. + + + +
+ +**Ordering** + +
+ + + +Field to sort by. Prefix with `-` for descending. Send it explicitly whenever order matters — a thread you +render should not depend on the server's unstated default. + +- `created_at` — oldest first +- `-created_at` — newest first +- `id` +- `-id` + + + +
+ +**Pagination** + +
+ + + +Page size. Defaults to 50, maximum 200. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000 — for deeper traversal switch to cursor +pagination. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope instead of the default offset envelope. The response +then carries `next_cursor` and `has_more`; send the value of `next_cursor` back as `?cursor=` to fetch the next +page. See [Pagination](/api-reference/v2/pagination) for the full envelope. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` and omit `total_count` from the offset envelope. + + + +
+ +::: info `access` is validated, `order_by` is not +`access` is checked against its allowed values — an unrecognized value is rejected as a `400 invalid_request`, so you +never get a silently empty list back. `order_by` is not checked: an unrecognized value falls back to the default +ordering, so check your spelling there because a typo shows up as an unexpected sort order rather than an error. +::: + +
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `access`, `actor_id`, `comment_html`, `comment_stripped`, `created_at`, `created_by_id`, `edited_at`, `external_id`, `external_source`, `id`, `work_item_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `actor` (the comment author). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.comments:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this work item. | +| `404` | `not_found` | No such workspace, project, or work item, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "c1f7a3d9-2b64-4f80-9c1a-3d5e8b2a6c47", + "work_item_id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "comment_html": "

Deployed the fix to staging. Please re-test.

", + "comment_stripped": "Deployed the fix to staging. Please re-test.", + "access": "INTERNAL", + "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "edited_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + }, + { + "id": "a2e04c77-3f19-4d5b-8a6e-91b0c7d2e845", + "work_item_id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "comment_html": "

Re-tested on staging, the login flow is clean now.

", + "comment_stripped": "Re-tested on staging, the login flow is clean now.", + "access": "INTERNAL", + "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": "ZD-88412", + "external_source": "zendesk", + "edited_at": "2026-01-15T11:04:02.115740Z", + "created_at": "2026-01-15T10:58:19.902314Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next": null, + "previous": null, + "total_count": 2, + "pagination": { + "style": "offset" + } +} +``` + +
+ + + +```json +{ + "data": [ + { + "id": "c1f7a3d9-2b64-4f80-9c1a-3d5e8b2a6c47", + "work_item_id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "comment_html": "

Deployed the fix to staging. Please re-test.

", + "comment_stripped": "Deployed the fix to staging. Please re-test.", + "access": "INTERNAL", + "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "edited_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next_cursor": "b3A9MTcxNDpjMWY3YTNkOQ", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/work-item-comments/overview.md b/docs/api-reference/v2/work-item-comments/overview.md new file mode 100644 index 00000000..74b52df7 --- /dev/null +++ b/docs/api-reference/v2/work-item-comments/overview.md @@ -0,0 +1,150 @@ +--- +title: Work item comments overview +description: The Plane API v2 work item comment object. HTML bodies, INTERNAL and EXTERNAL access, the sub-resource path, and why comments never conflict. +keywords: plane api v2, work item comments, issue comments, comment_html, comment access, internal comment, external comment +--- + +# Work item comments overview + +A comment is a note posted on a single work item. Comments are a **sub-resource of a work item**, not a +project-level collection: every route is mounted under the work item, so a comment can only be read or written +through the work item it belongs to. + +```text +/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/ +``` + +That means the collection routes carry three path parameters (`slug`, `project_id`, `work_item_id`) and the +detail routes carry four (plus `pk`). There is no workspace-wide or project-wide comment feed — to gather +comments across work items, list them per work item. + +[Learn more about work item comments](https://docs.plane.so/core-concepts/issues/overview#comment-on-work-items) + +
+
+ +## The comment object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the comment. + +- `work_item_id` _string (uuid)_ + + The work item this comment is attached to. Always matches the `work_item_id` in the request path. + +- `comment_html` _string_ + + The comment body, as HTML. This is the field you write. + +- `comment_stripped` _string_ + + The plain-text version of `comment_html`, derived server-side. It is what `?search=` matches, so markup never + affects a search hit. Read-only — you never send it. + +- `access` _string_ + + Visibility of the comment. One of `INTERNAL` or `EXTERNAL`. `INTERNAL` keeps the comment inside the project + team; `EXTERNAL` marks it as visible outside the team, for example on a published project. + +- `actor_id` _string (uuid)_ or _null_ + + The author of the comment — the principal that posted it. Set server-side from the authenticated caller, so + you cannot post a comment on someone else's behalf. + +- `edited_at` _string (date-time)_ or _null_ + + When the comment was last updated through a `PATCH`. `null` on a comment that has never been edited, which is + how clients render an "edited" marker. + +- `external_id` , `external_source` _string_ or _null_ + + Correlation fields for sync and import. Together they let you map a comment to a record in another system and + find it again with `?external_id=` and `?external_source=`. + +- `created_at` _string (date-time)_ + + When the comment was created. + +- `created_by_id` _string (uuid)_ or _null_ + + The user record that created the comment. For comments posted by a person this is the same user as + `actor_id`; `actor_id` is the domain field that Plane renders as the comment author. + +::: tip Write `comment_html`, read `comment_stripped` +`comment_html` is the only body field you can set. Plane derives `comment_stripped` from it on every write, so +the two never drift apart. +::: + +
+
+ + + +```json +{ + "id": "c1f7a3d9-2b64-4f80-9c1a-3d5e8b2a6c47", + "work_item_id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "comment_html": "

Deployed the fix to staging. Please re-test.

", + "comment_stripped": "Deployed the fix to staging. Please re-test.", + "access": "INTERNAL", + "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "edited_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | -------------------------------------------------------------------------------------------- | ------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/` | List comments | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/` | Create a comment | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/bulk/` | Bulk write comments | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/upsert/` | Upsert a comment | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/{pk}/` | Delete a comment | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/{pk}/` | Get a comment | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/{pk}/` | Update a comment | + +## Comments are not deduplicated + +::: info The API will not collapse duplicates for you +Nothing about a comment's body or `external_id` identifies it uniquely, so posting the same `comment_html` +twice creates two comments. If your integration must post a comment at most once, send an `external_id` and +look for it first with `GET …/comments/?external_id=…&external_source=…` before creating. + +Writes can still fail with `409 conflict`, so branch on the `code` in the problem response instead of assuming +a create always succeeds. +::: + +## Access and visibility + +`access` is set per comment and can be changed later with a `PATCH`. Filter a list down to one visibility with +`?access=INTERNAL` or `?access=EXTERNAL` when you need only the comments a particular audience can see. + +## Reads follow the parent work item + +Comment visibility is inherited from the work item in the path — there is no per-comment read permission. A +`pk` that belongs to a different work item is not visible on this route and returns `404 not_found`, +the same as a comment id that does not exist. Existence is never leaked. + +## Changed from v1 + +- The path segment is `/work-items/{work_item_id}/comments/` and the parent id is reported as **`work_item_id`** + instead of `issue`. +- `actor` is now **`actor_id`**, and `created_by` is now **`created_by_id`**. +- Reads no longer return `updated_at`, `updated_by`, `deleted_at`, `project`, `workspace`, `comment_json`, + `attachments`, or `is_member`. +- `edited_at` is stamped by Plane on every `PATCH`. + +See [Migrating from v1](/api-reference/v2/migrating-from-v1) for the full list. diff --git a/docs/api-reference/v2/work-item-comments/update-comment.md b/docs/api-reference/v2/work-item-comments/update-comment.md new file mode 100644 index 00000000..e14a0c7b --- /dev/null +++ b/docs/api-reference/v2/work-item-comments/update-comment.md @@ -0,0 +1,256 @@ +--- +title: Update a comment +description: Edit a comment on a Plane work item with the v2 REST API. Partial PATCH body, access changes, edited_at, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update work item comment, PATCH comment, edit comment_html, comment access, edited_at +--- + +# Update a comment + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/{pk}/ +
+ +
+
+ +Edit an existing comment. Updates are partial — send only the fields you want to change, and everything you omit +is left untouched. There is no `PUT`. + +Plane stamps `edited_at` on the comment when the update succeeds, which is what clients render as an "edited" +marker. When you send `comment_html`, the plain-text `comment_stripped` is re-derived from it in the same write, +so search stays in sync with the visible body. + +::: tip Editing is not restricted to your own comment id +Whether you can edit a given comment is decided by your role and token scope, not by a body field. A comment you +are not allowed to edit returns `403 forbidden`; a comment on another work item returns `404`. +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + + + +The project the work item belongs to. + + + + + +The work item the comment is attached to. + + + + + +The comment to update. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Replacement body for the comment, as HTML. It overwrites the previous body — there is no append mode, so send +the full new text. + + + + + +Change the comment's visibility. + +- `INTERNAL` — visible to the project team +- `EXTERNAL` — marked as visible outside the team, for example on a published project + + + + + +Your system's identifier for this comment. Maximum 255 characters. Accepts `null` to clear the correlation. + + + + + +The system `external_id` came from, for example `github` or `zendesk`. Maximum 255 characters. Accepts `null`. + + + +
+ +`external_id` is a correlation field, not a key: nothing stops two comments on the same work item from carrying +the same value, so keep your own side of the mapping authoritative. + +
+ +
+ +### Response shaping + +
+ + + +Comma-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: `access`, `actor_id`, `comment_html`, `comment_stripped`, `created_at`, `created_by_id`, `edited_at`, `external_id`, `external_source`, `id`, `work_item_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `actor` (the comment author). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.comments:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | An `access` value outside the enum, or an `external_id` over 255 characters. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't edit this comment. | +| `404` | `not_found` | No such comment, it belongs to another work item, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The update conflicts with the current state of the comment. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "c1f7a3d9-2b64-4f80-9c1a-3d5e8b2a6c47", + "work_item_id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "comment_html": "

Deployed the fix to staging and production.

", + "comment_stripped": "Deployed the fix to staging and production.", + "access": "INTERNAL", + "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "external_id": null, + "external_source": null, + "edited_at": "2026-01-15T11:04:02.115740Z", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" +} +``` + +
+ + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "The request body failed validation.", + "errors": [ + { + "field": "access", + "code": "invalid_choice", + "message": "\"PUBLIC\" is not a valid choice." + } + ] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-comments/upsert-comment.md b/docs/api-reference/v2/work-item-comments/upsert-comment.md new file mode 100644 index 00000000..ed7d9b68 --- /dev/null +++ b/docs/api-reference/v2/work-item-comments/upsert-comment.md @@ -0,0 +1,228 @@ +--- +title: Upsert a comment +description: Upsert a comment with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, upsert a comment, comments, work item comments upsert +--- + +# Upsert a comment + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/upsert/ +
+ +
+
+ +Comments are the discussion thread on a work item. Create a comment, or update the existing one that carries the same `(external_source, external_id)` pair. + +- Both `external_source` and `external_id` are required — without them there is nothing to match on and the request is a `400`. +- A created record answers `201` with `X-Plane-Upsert: created`; an updated one answers `200` with `X-Plane-Upsert: updated`. Branch on the header rather than guessing from the status. +- Upsert is safe for **sequential** importers. Two simultaneous upserts of the same key can each miss and each create, so serialize your writes per key. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The comment html. + + + + + +- `INTERNAL` - INTERNAL +- `EXTERNAL` - EXTERNAL + +One of `INTERNAL`, `EXTERNAL`. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `actor_id`, `comment_html`, `comment_stripped`, `created_at`, `created_by_id`, `edited_at`, `external_id`, `external_source`, `id`, `work_item_id`. + + + + + +Comma-separated relations to embed: `actor`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.comments:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": "INTERNAL", + "actor_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "comment_html": "example", + "comment_stripped": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "edited_at": "2026-01-14T09:22:41.478363Z", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-dependencies/create-work-item-dependencies.md b/docs/api-reference/v2/work-item-dependencies/create-work-item-dependencies.md new file mode 100644 index 00000000..87c0820e --- /dev/null +++ b/docs/api-reference/v2/work-item-dependencies/create-work-item-dependencies.md @@ -0,0 +1,184 @@ +--- +title: Create work item dependencies +description: Create a dependency with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create work item dependencies, dependencies, work item dependencies create +--- + +# Create work item dependencies + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/dependencies/ +
+ +
+
+ +Dependencies record blocking and scheduling order between work items. Create a dependency. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +- `blocked_by` - blocked_by +- `blocking` - blocking +- `start_before` - start_before +- `start_after` - start_after +- `finish_before` - finish_before +- `finish_after` - finish_after + +One of `blocked_by`, `blocking`, `start_before`, `start_after`, `finish_before`, `finish_after`. + + + + + +Ids of the work items to associate. Replaces the current set. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.relations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "blocked_by": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "blocking": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "finish_after": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "finish_before": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "start_after": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "start_before": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-dependencies/delete-work-item-dependency.md b/docs/api-reference/v2/work-item-dependencies/delete-work-item-dependency.md new file mode 100644 index 00000000..88c38ab0 --- /dev/null +++ b/docs/api-reference/v2/work-item-dependencies/delete-work-item-dependency.md @@ -0,0 +1,143 @@ +--- +title: Delete a work item dependency +description: Delete a dependency with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a work item dependency, dependencies, work item dependencies destroy +--- + +# Delete a work item dependency + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/dependencies/{related_work_item_id}/ +
+ +
+
+ +Dependencies record blocking and scheduling order between work items. Delete a dependency. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + + + +The id of the work item on the other side of the relation. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.relations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No dependency matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-dependencies/list-work-item-dependencies.md b/docs/api-reference/v2/work-item-dependencies/list-work-item-dependencies.md new file mode 100644 index 00000000..2ceccd3a --- /dev/null +++ b/docs/api-reference/v2/work-item-dependencies/list-work-item-dependencies.md @@ -0,0 +1,130 @@ +--- +title: List work item dependencies +description: List dependencies with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list work item dependencies, dependencies, work item dependencies list +--- + +# List work item dependencies + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/dependencies/ +
+ +
+
+ +Dependencies record blocking and scheduling order between work items. List the dependencies you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.relations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "blocked_by": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "blocking": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "finish_after": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "finish_before": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "start_after": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "start_before": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-dependencies/overview.md b/docs/api-reference/v2/work-item-dependencies/overview.md new file mode 100644 index 00000000..d9375f4b --- /dev/null +++ b/docs/api-reference/v2/work-item-dependencies/overview.md @@ -0,0 +1,73 @@ +--- +title: Work item dependencies overview +description: The Plane API v2 dependency object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, work item dependencies, dependency object +--- + +# Work item dependencies overview + +Dependencies record blocking and scheduling order between work items. + +
+
+ +## The dependency object + +### Attributes + +- `blocked_by` _array of string (uuid)_ + + The blocked by. + +- `blocking` _array of string (uuid)_ + + The blocking. + +- `finish_after` _array of string (uuid)_ + + The finish after. + +- `finish_before` _array of string (uuid)_ + + The finish before. + +- `start_after` _array of string (uuid)_ + + The start after. + +- `start_before` _array of string (uuid)_ + + The start before. + +
+
+ + + +```json +{ + "blocked_by": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "blocking": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "finish_after": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "finish_before": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "start_after": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"], + "start_before": ["f960d3c2-8524-4a41-b8eb-055ce4be2a7f"] +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/dependencies/` | List work item dependencies | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/dependencies/` | Create work item dependencies | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/dependencies/{related_work_item_id}/` | Delete a work item dependency | + +## Response shaping + +Every dependency read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/work-item-links/create-link.md b/docs/api-reference/v2/work-item-links/create-link.md new file mode 100644 index 00000000..382367ee --- /dev/null +++ b/docs/api-reference/v2/work-item-links/create-link.md @@ -0,0 +1,198 @@ +--- +title: Create a link +description: Create a link with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a link, links, links create +--- + +# Create a link + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/ +
+ +
+
+ +Links attach external URLs to a work item. Create a link. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Target URL. + + + + + +The metadata. + + + + + +Title. + +Maximum 255 characters. Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `metadata`, `title`, `url`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.links:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "metadata": null, + "title": "Example title", + "url": "https://example.com/spec", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-links/delete-link.md b/docs/api-reference/v2/work-item-links/delete-link.md new file mode 100644 index 00000000..dbd031c8 --- /dev/null +++ b/docs/api-reference/v2/work-item-links/delete-link.md @@ -0,0 +1,160 @@ +--- +title: Delete a link +description: Delete a link with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a link, links, links destroy +--- + +# Delete a link + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/{pk}/ +
+ +
+
+ +Links attach external URLs to a work item. Delete a link. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + + + +The link id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `metadata`, `title`, `url`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.links:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No link matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-links/get-link.md b/docs/api-reference/v2/work-item-links/get-link.md new file mode 100644 index 00000000..9bb6aae9 --- /dev/null +++ b/docs/api-reference/v2/work-item-links/get-link.md @@ -0,0 +1,166 @@ +--- +title: Get a link +description: Read a link with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a link, links, links retrieve +--- + +# Get a link + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/{pk}/ +
+ +
+
+ +Links attach external URLs to a work item. Read a single link by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + + + +The link id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `metadata`, `title`, `url`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.links:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "metadata": null, + "title": "Example title", + "url": "https://example.com/spec", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No link matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-links/list-links.md b/docs/api-reference/v2/work-item-links/list-links.md new file mode 100644 index 00000000..136781d8 --- /dev/null +++ b/docs/api-reference/v2/work-item-links/list-links.md @@ -0,0 +1,228 @@ +--- +title: List links +description: List links with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list links, links, links list +--- + +# List links + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/ +
+ +
+
+ +Links attach external URLs to a work item. List the links you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +Filter by `title`. + + + + + +Filter by `url`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `metadata`, `title`, `url`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.links:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "metadata": null, + "title": "Example title", + "url": "https://example.com/spec", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "metadata": null, + "title": "Another example", + "url": "https://example.com/spec", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-links/overview.md b/docs/api-reference/v2/work-item-links/overview.md new file mode 100644 index 00000000..4ef4b179 --- /dev/null +++ b/docs/api-reference/v2/work-item-links/overview.md @@ -0,0 +1,80 @@ +--- +title: Work item links overview +description: The Plane API v2 link object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, work item links, link object +--- + +# Work item links overview + +Links attach external URLs to a work item. + +
+
+ +## The link object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `id` _string (uuid)_ + + Unique identifier. + +- `metadata` _string_ + + The metadata. + +- `title` _string_ + + Title. + +- `url` _string_ + + Target URL. + +- `work_item_id` _string (uuid)_ + + The related work item. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "metadata": null, + "title": "Example title", + "url": "https://example.com/spec", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | --------------------------------------------------------------------------------------- | ------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/` | List links | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/` | Create a link | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/{pk}/` | Delete a link | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/{pk}/` | Get a link | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/{pk}/` | Update a link | + +## Response shaping + +Every link read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/work-item-links/update-link.md b/docs/api-reference/v2/work-item-links/update-link.md new file mode 100644 index 00000000..b5ee72e8 --- /dev/null +++ b/docs/api-reference/v2/work-item-links/update-link.md @@ -0,0 +1,216 @@ +--- +title: Update a link +description: Update a link with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a link, links, links partial update +--- + +# Update a link + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/{pk}/ +
+ +
+
+ +Links attach external URLs to a work item. Update a link. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + + + +The link id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The metadata. + + + + + +Title. + +Maximum 255 characters. Nullable. + + + + + +Target URL. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `metadata`, `title`, `url`, `work_item_id`. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.links:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "metadata": null, + "title": "Example title", + "url": "https://example.com/spec", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No link matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-properties/create-work-item-property.md b/docs/api-reference/v2/work-item-properties/create-work-item-property.md new file mode 100644 index 00000000..d2b85915 --- /dev/null +++ b/docs/api-reference/v2/work-item-properties/create-work-item-property.md @@ -0,0 +1,401 @@ +--- +title: Create a work item property +description: Define a custom work item property in a Plane project with the v2 REST API. property_type and relation_type enums, inline options, default values, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create work item property, custom field, property_type, relation_type, inline options, default_value, POST work item properties +--- + +# Create a work item property + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/ +
+ +
+
+ +Define a new custom field in a project. You choose a label and a `property_type`, and Plane returns the property definition — including the derived `name` slug you can key off in your own storage. + +Two things to know before you send this request: + +- **Creating a property does not put it on any work item.** The definition exists, but nothing renders it until you attach it to a work item type with [Attach a type property](/api-reference/v2/work-item-type-properties/attach-type-property). +- **You write `display_name`, not `name`.** `name` is the read-side slug Plane derives; it is not a body field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project to define the property in. Project-level properties belong to this project alone. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The human-readable label for the field, for example `Severity`. Maximum 255 characters. Plane derives the read-only `name` slug from this value and returns both. + + + + + +What kind of data the field holds. This is the decision that shapes everything else about the property. + +- `TEXT` — Free-form text +- `DATETIME` — A point in time, written as an ISO 8601 timestamp +- `DECIMAL` — A number, carried as a string in the value array +- `BOOLEAN` — A yes/no flag +- `OPTION` — A choice from a fixed set you define +- `RELATION` — A reference to another record; pair it with `relation_type` +- `URL` — A link +- `EMAIL` — An email address +- `FILE` — An uploaded file +- `FORMULA` — A value Plane computes rather than one a person enters + +The full reference, including what each type means for the value you send on a work item, is on the [properties overview](/api-reference/v2/work-item-properties/overview#property-type-reference). + + + + + +What a `RELATION` property points at. Only meaningful when `property_type` is `RELATION` — leave it out otherwise. + +- `ISSUE` — A work item +- `USER` — A member +- `RELEASE` — A release +- `RICH_TEXT` — Rich text content + + + + + +Free-form explanation of what the field is for. Worth filling in — it is the helper text people read when deciding what to type. + + + + + +Write-only. Define the choices for an `OPTION` property inline, in the same request that creates it, instead of a second round-trip per choice. + +Each entry is a property option object — `name` is required, and `description`, `is_default`, `external_id`, and `external_source` are accepted. See [Create a property option](/api-reference/v2/work-item-property-options/create-property-option) for the full field list. + +You never get `options` back in the shape you sent it. The response carries the resolved `options` array, with each choice's generated `id` and `sort_order`. + + + + + +Allow more than one value on a work item — a multi-select list of severities, a set of reviewers, several linked work items. Leave it off for a single-valued field. + + + + + +Force a value to be present. Give a required property a `default_value` so existing flows have something to fall back on. + + + + + +Whether the property is offered. Create it inactive if you want to define the field and its options now but roll it out later. + + + + + +The value applied when none is supplied. **Always an array**, even for a single-valued property — a `DECIMAL` field that defaults to `3` is sent as `["3"]`, not `3`. A property with no default sends `[]` or omits the field. + +For an `OPTION` property, mark the default choice with `is_default` on the option itself rather than repeating it here. + + + + + +Free-form object holding type-specific configuration. What belongs in it depends entirely on `property_type`, so there is no single schema — send the whole object rather than assuming keys. + + + + + +Free-form object holding type-specific validation constraints. Same shape caveat as `settings`. + + + + + +Your system's identifier for this field, for sync and import correlation. Maximum 255 characters. + + + + + +The system `external_id` came from, for example `jira` or `linear`. Maximum 255 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | --------------------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | Missing `display_name` or `property_type`, or an enum value outside the list. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't create properties. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types at the workspace level. Create the property on the workspace endpoint instead. | +| `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. | + +
+ +::: warning A 409 means wrong surface, not missing permission +If the workspace manages work item types at the workspace level, this project endpoint returns `409 work_item_types_managed_at_workspace`. Nothing is broken and no permission is missing — the same property is created through [Create a workspace work item property](/api-reference/v2/workspace-work-item-properties/create-workspace-work-item-property). See [Work item type modes](/api-reference/v2/work-item-type-modes) for how to detect which mode you are in. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "9d2f0b74-6a51-4c8e-b3d7-2f1a8c05e964", + "name": "severity", + "display_name": "Severity", + "description": "How badly this affects customers", + "property_type": "OPTION", + "relation_type": null, + "is_multi": false, + "is_required": true, + "is_active": true, + "default_value": [], + "options": [ + { + "id": "3e7a5c19-42b8-4d06-9f3e-7c1b8a0d2456", + "name": "Critical", + "description": "Production is down", + "is_default": false, + "sort_order": 15000, + "external_id": null, + "external_source": null + }, + { + "id": "b6c04f83-1d29-4e57-8a3b-90e2f5c7d418", + "name": "Major", + "description": "A core workflow is broken", + "is_default": true, + "sort_order": 25000, + "external_id": null, + "external_source": null + }, + { + "id": "7f52d3a8-0e14-4c69-b28d-a1f6e903c5b7", + "name": "Minor", + "description": "Cosmetic or low impact", + "is_default": false, + "sort_order": 35000, + "external_id": null, + "external_source": null + } + ], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_workspace", + "detail": "Work item types are managed at the workspace level for this workspace." +} +``` + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "The request body failed validation.", + "errors": [ + { + "field": "property_type", + "code": "invalid_choice", + "message": "\"SELECT\" is not a valid choice." + } + ] +} +``` + + + +
+
+ +## Creating a relation property + +A `RELATION` property needs `relation_type` to say what it points at. This one lets a work item name several reviewers: + +```json +{ + "display_name": "Reviewer", + "description": "Who signs this off", + "property_type": "RELATION", + "relation_type": "USER", + "is_multi": true +} +``` + +`relation_type` stays `null` on every non-`RELATION` property. + +## After you create + +1. **Attach it to a type.** [Attach a type property](/api-reference/v2/work-item-type-properties/attach-type-property) is what makes the field appear on work items. One property can be attached to several types. +2. **Add or adjust options later.** Inline `options` covers the initial set; [Property options](/api-reference/v2/work-item-property-options/list-property-options) handles changes over the property's life. diff --git a/docs/api-reference/v2/work-item-properties/delete-work-item-property.md b/docs/api-reference/v2/work-item-properties/delete-work-item-property.md new file mode 100644 index 00000000..1be14b95 --- /dev/null +++ b/docs/api-reference/v2/work-item-properties/delete-work-item-property.md @@ -0,0 +1,191 @@ +--- +title: Delete a work item property +description: Delete a custom work item property from a Plane project with the v2 REST API. Path parameters, the deactivate alternative, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete work item property, remove custom field, is_active, 204 no content, DELETE work item property +--- + +# Delete a work item property + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{pk}/ +
+ +
+
+ +Remove a custom property definition from a project. The response is `204` with an empty body — there is nothing to parse, so branch on the status code. + +Deleting a property removes the field itself, along with the choices defined for it and its attachments to work item types. If you only want the field to stop being offered, set `is_active: false` with [Update a work item property](/api-reference/v2/work-item-properties/update-work-item-property) instead — that keeps the definition and everything recorded against it, and it is reversible. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the property belongs to. A property id from a different project returns `404`, even inside the same workspace. + + + + + +The id of the property to delete. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | --------------------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | A malformed identifier in the path. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't delete properties. | +| `404` | `not_found` | No such property, project, or workspace — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types at the workspace level. Delete the property on the workspace endpoint instead. | +| `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. | + +
+ +::: warning A 409 means wrong surface, not missing permission +If the workspace manages work item types at the workspace level, this project endpoint returns `409 work_item_types_managed_at_workspace`. The delete belongs on [Delete a workspace work item property](/api-reference/v2/workspace-work-item-properties/delete-workspace-work-item-property). Reading the same property through this path still works — only writes are mode-specific. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +::: info 204 has no body +A successful delete returns `204 No Content`. Calling `.json()` on the response throws. Check `response.status === 204` (or `response.ok`) instead. +::: + +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_workspace", + "detail": "Work item types are managed at the workspace level for this workspace." +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No IssueProperty matches the given query." +} +``` + + + +
+
+ +## Delete or deactivate? + +| You want to… | Do this | +| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | +| Stop offering the field but keep its history | `PATCH` with `is_active: false` | +| Take a choice out of an `OPTION` field, keep the field | [Delete a property option](/api-reference/v2/work-item-property-options/delete-property-option) | +| Remove the field from one type but keep it elsewhere | [Detach a type property](/api-reference/v2/work-item-type-properties/detach-type-property) | +| Remove the definition from the project entirely | `DELETE` this endpoint | + +Deleting is the only one of these you cannot undo by flipping a flag back. diff --git a/docs/api-reference/v2/work-item-properties/get-work-item-property.md b/docs/api-reference/v2/work-item-properties/get-work-item-property.md new file mode 100644 index 00000000..a7349e72 --- /dev/null +++ b/docs/api-reference/v2/work-item-properties/get-work-item-property.md @@ -0,0 +1,219 @@ +--- +title: Get a work item property +description: Retrieve a single custom work item property from a Plane project with the v2 REST API. Path parameters, the resolved options array, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get work item property, retrieve custom field, property_type, options, GET work item property by id +--- + +# Get a work item property + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{pk}/ +
+ +
+
+ +Retrieve one property definition by id. Reach for this when you already hold a property `id` — from a type's property list, a stored mapping, or a create response — and need its current shape before reading or writing values. + +The response includes the resolved `options` array, so a single request tells you both what the field is and which choices it offers. Check `property_type` before you build a value: it decides what the field can hold, and `relation_type` narrows it further for `RELATION` properties. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the property belongs to. A property id from a different project returns `404`, even inside the same workspace. + + + + + +The id of the property to retrieve. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_properties:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this project's properties. | +| `404` | `not_found` | No such property, project, or workspace — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info Reads ignore the mode +This endpoint works whether the workspace manages work item types at the project or workspace level — only writes are mode-specific, and only writes return a `409`. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +::: info No expansion on properties +Work item properties don't accept `?expand=`. The response is the object shown here, with `options` already resolved inline. +::: + +::: info Existence is never leaked +A property outside your tenant returns `404`, not `403`. If a cached id starts returning `404`, re-read the catalog with [List work item properties](/api-reference/v2/work-item-properties/list-work-item-properties) rather than retrying. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "9d2f0b74-6a51-4c8e-b3d7-2f1a8c05e964", + "name": "severity", + "display_name": "Severity", + "description": "How badly this affects customers", + "property_type": "OPTION", + "relation_type": null, + "is_multi": false, + "is_required": true, + "is_active": true, + "default_value": [], + "options": [ + { + "id": "3e7a5c19-42b8-4d06-9f3e-7c1b8a0d2456", + "name": "Critical", + "description": "Production is down", + "is_default": false, + "sort_order": 15000, + "external_id": null, + "external_source": null + }, + { + "id": "b6c04f83-1d29-4e57-8a3b-90e2f5c7d418", + "name": "Major", + "description": "A core workflow is broken", + "is_default": true, + "sort_order": 25000, + "external_id": null, + "external_source": null + }, + { + "id": "7f52d3a8-0e14-4c69-b28d-a1f6e903c5b7", + "name": "Minor", + "description": "Cosmetic or low impact", + "is_default": false, + "sort_order": 35000, + "external_id": null, + "external_source": null + } + ], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No IssueProperty matches the given query." +} +``` + + + +
+
+ +::: tip `name` is the stable key, `display_name` is the label +People rename fields. If you are storing a mapping between a Plane property and a field in your own system, key it on `id` — and use `name` when you need something human-legible that changes less often than the label. `display_name` is the one that shows up in the UI, and the one you send when you want to change it. +::: diff --git a/docs/api-reference/v2/work-item-properties/list-work-item-properties.md b/docs/api-reference/v2/work-item-properties/list-work-item-properties.md new file mode 100644 index 00000000..26000b5d --- /dev/null +++ b/docs/api-reference/v2/work-item-properties/list-work-item-properties.md @@ -0,0 +1,331 @@ +--- +title: List work item properties +description: List the custom work item properties defined in a Plane project with the v2 REST API. Ordering, pagination, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list work item properties, custom fields, custom properties, project properties, pagination, GET work item properties +--- + +# List work item properties + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/ +
+ +
+
+ +Return every custom property defined in a project as a paginated list. This is how you discover which fields exist before writing values, and how you resolve a field's label to the property `id` you need for options and type attachments. + +The list is the project's full catalog of definitions — including properties with `is_active: false`, and including properties not yet attached to any work item type. To see what a specific type actually offers, use [List type properties](/api-reference/v2/work-item-type-properties/list-type-properties) instead. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project whose properties you want to list. + + + +
+
+ +
+ +### Query Parameters + +There are no filter or search parameters on this endpoint — a project's property catalog is small enough to read whole and filter client-side. Check your spelling on `order_by` and `paginate`: neither is validated, so an unrecognized `order_by` silently falls back to the default ordering and anything other than `paginate=cursor` silently uses offset pagination. A typo shows up as an unexpected sort order or envelope, not as an error. + +
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `sort_order` , `-sort_order` — the configured display order of the fields +- `created_at` , `-created_at` — when each property was defined +- `id` , `-id` + +Order by `sort_order` when you are rendering the fields to a person; it is the order the project itself uses. + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. Most projects define far fewer than 50 properties, so one page is usually the whole catalog. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read the `next` value from the response rather than computing offsets yourself. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead of `next` and `total_count`. Omit it for the default offset envelope. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted from the response. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_properties:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this project's properties. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info Reads work in either mode +Listing properties never returns a mode conflict. A project still surfaces its properties when the workspace manages work item types at the workspace level — only **writes** are mode-specific. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "9d2f0b74-6a51-4c8e-b3d7-2f1a8c05e964", + "name": "severity", + "display_name": "Severity", + "description": "How badly this affects customers", + "property_type": "OPTION", + "relation_type": null, + "is_multi": false, + "is_required": true, + "is_active": true, + "default_value": [], + "options": [ + { + "id": "3e7a5c19-42b8-4d06-9f3e-7c1b8a0d2456", + "name": "Critical", + "description": "Production is down", + "is_default": false, + "sort_order": 15000, + "external_id": null, + "external_source": null + }, + { + "id": "b6c04f83-1d29-4e57-8a3b-90e2f5c7d418", + "name": "Major", + "description": "A core workflow is broken", + "is_default": true, + "sort_order": 25000, + "external_id": null, + "external_source": null + } + ], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" + }, + { + "id": "c81b7e2a-5f34-4d90-8e17-3a6c9b0f2d75", + "name": "story_points", + "display_name": "Story points", + "description": "Relative sizing for planning", + "property_type": "DECIMAL", + "relation_type": null, + "is_multi": false, + "is_required": false, + "is_active": true, + "default_value": ["3"], + "options": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:24:03.117482Z" + }, + { + "id": "5a3e8f01-7c62-4b18-a940-d2e5b7c86139", + "name": "reviewer", + "display_name": "Reviewer", + "description": "Who signs this off", + "property_type": "RELATION", + "relation_type": "USER", + "is_multi": true, + "is_required": false, + "is_active": true, + "default_value": [], + "options": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:26:55.902314Z" + } + ], + "next": null, + "previous": null, + "total_count": 3, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "9d2f0b74-6a51-4c8e-b3d7-2f1a8c05e964", + "name": "severity", + "display_name": "Severity", + "description": "How badly this affects customers", + "property_type": "OPTION", + "relation_type": null, + "is_multi": false, + "is_required": true, + "is_active": true, + "default_value": [], + "options": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + + + +
+
+ +::: tip Inactive properties still appear +`is_active: false` means the field is no longer offered on new edits, not that it is gone. The list returns it. If you are rendering a form, filter to `is_active: true` yourself — and check the type's own property list, since a property in this catalog may not be attached to the type you are editing. +::: diff --git a/docs/api-reference/v2/work-item-properties/overview.md b/docs/api-reference/v2/work-item-properties/overview.md new file mode 100644 index 00000000..8c7e0072 --- /dev/null +++ b/docs/api-reference/v2/work-item-properties/overview.md @@ -0,0 +1,201 @@ +--- +title: Work item properties overview +description: The Plane API v2 project work item property object. Attributes, the property_type and relation_type reference, default values, inline options, and the endpoints that manage custom fields. +keywords: plane api v2, work item properties, custom fields, custom properties, property_type, relation_type, OPTION property, project properties +--- + +# Work item properties overview + +A work item property is a custom field you define on a project. Once a property is attached to a work item type, every work item of that type can carry a value for it — a severity, a customer name, a due-by timestamp, a link to a design doc. + +A property is a **definition**, not a value. It says what the field is called, what kind of data it holds, whether it is required, and what choices are available. The values themselves live on individual work items. + +Properties in this group are **project-level**: they are defined inside one project and belong to that project alone. The workspace equivalent lives at [Work item properties (workspace)](/api-reference/v2/workspace-work-item-properties/list-workspace-work-item-properties). + +::: warning One mode per workspace +A workspace manages work item types — and therefore their properties — in exactly one mode: **project-level** or **workspace-level**. Writing to the endpoints on this page while the workspace is in workspace mode returns `409 work_item_types_managed_at_workspace`, not a `404` or `403`. The capability exists; it just lives on the other surface. Reads are unaffected by mode. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+
+ +## The work item property object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the property. This is the id you pass when attaching the property to a type or managing its options. + +- `name` _string_ + + The machine-readable slug for the property, derived by Plane. Read-only — it is not a create or update body field. Use it when you need a stable key in your own storage. + +- `display_name` _string_ + + The human-readable label shown wherever the property is rendered. This is the field you **write** on create and update. Maximum 255 characters. + +- `description` _string_ + + Free-form explanation of what the field is for. Surfaced as helper text next to the field. + +- `property_type` _string_ + + What kind of data the property holds. One of `TEXT`, `DATETIME`, `DECIMAL`, `BOOLEAN`, `OPTION`, `RELATION`, `URL`, `EMAIL`, `FILE`, or `FORMULA`. See the [property type reference](#property-type-reference) below. + +- `relation_type` _string_ + + What a `RELATION` property points at. One of `ISSUE`, `USER`, `RELEASE`, or `RICH_TEXT`. `null` for every other property type. + +- `is_multi` _boolean_ + + Whether the property accepts more than one value. A multi-select severity list, a set of assignable reviewers, several linked work items. + +- `is_required` _boolean_ + + Whether a value must be present. Turning this on for an existing property affects work items edited from then on — it does not retroactively fill in the blanks. + +- `is_active` _boolean_ + + Whether the property is currently offered. Deactivating is the reversible alternative to deleting: the definition and its recorded values stay, the field stops being offered on new edits. + +- `default_value` _array of string_ + + The value applied when none is supplied. Always an array, even for a single-valued property — a decimal that defaults to `3` is `["3"]`, not `3`. + +- `options` _array of any_ + + For `OPTION` properties, the resolved list of choices. Each entry is a property option object with its own `id`, `name`, `is_default`, and `sort_order`. Manage them through [Property options](/api-reference/v2/work-item-property-options/list-property-options), or define them inline when you [create the property](/api-reference/v2/work-item-properties/create-work-item-property). + +- `settings` _any_ + + Free-form object holding type-specific configuration. What it contains depends entirely on `property_type` — treat it as opaque unless you wrote the value yourself. + +- `validation_rules` _any_ + + Free-form object holding type-specific validation constraints. Same caveat as `settings`: the shape is per type, so read and write it as a whole rather than assuming keys. + +- `logo_props` _any_ + + Icon configuration used when rendering the property. Read-only through this API. + +- `external_id` , `external_source` _string_ + + Correlation fields for sync and import. Together they let you map a property to a field in another system and find it again later. + +- `created_at` _string (date-time)_ + + When the property was created. + +::: tip `display_name` writes, `name` reads +These are two different fields and mixing them up is the most common first error. You send `display_name` on create; Plane derives `name` from it and returns both. There is no `name` field in a request body. +::: + +
+
+ + + +```json +{ + "id": "9d2f0b74-6a51-4c8e-b3d7-2f1a8c05e964", + "name": "severity", + "display_name": "Severity", + "description": "How badly this affects customers", + "property_type": "OPTION", + "relation_type": null, + "is_multi": false, + "is_required": true, + "is_active": true, + "default_value": [], + "options": [ + { + "id": "3e7a5c19-42b8-4d06-9f3e-7c1b8a0d2456", + "name": "Critical", + "description": "Production is down", + "is_default": false, + "sort_order": 15000, + "external_id": null, + "external_source": null + }, + { + "id": "b6c04f83-1d29-4e57-8a3b-90e2f5c7d418", + "name": "Major", + "description": "A core workflow is broken", + "is_default": true, + "sort_order": 25000, + "external_id": null, + "external_source": null + } + ], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + +
+
+ +## Property type reference + +`property_type` is required on create and fixes what the field can hold. The **Value shape** column describes the value the property carries — `default_value` uses exactly that shape, always wrapped in an array. + +| `property_type` | What it is | Value shape | +| --------------- | ------------------------- | -------------------------------------------------------------------------------- | +| `TEXT` | Free-form text | A string, for example `["Needs a repro"]`. | +| `DATETIME` | A point in time | An ISO 8601 timestamp, for example `["2026-03-01T00:00:00Z"]`. | +| `DECIMAL` | A number | The number expressed as a string, for example `["3"]` or `["12.5"]`. | +| `BOOLEAN` | A yes/no flag | `["true"]` or `["false"]`. | +| `OPTION` | A choice from a fixed set | One of the property's defined options. With `is_multi`, several of them. | +| `RELATION` | A reference to a record | The referenced record, with `relation_type` deciding what kind. See below. | +| `URL` | A link | A URL string, for example `["https://example.com/spec"]`. | +| `EMAIL` | An email address | An address string, for example `["ana@example.com"]`. | +| `FILE` | An uploaded file | A reference to the stored file. | +| `FORMULA` | A computed value | Derived by Plane rather than entered by a person. Its configuration is per type. | + +### `relation_type` + +`relation_type` is only meaningful when `property_type` is `RELATION`. It is `null` on every other property. + +| `relation_type` | Points at | +| --------------- | ----------------- | +| `ISSUE` | A work item | +| `USER` | A member | +| `RELEASE` | A release | +| `RICH_TEXT` | Rich text content | + +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------------------------------- | --------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/` | List work item properties | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/` | Create a work item property | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{pk}/` | Get a work item property | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{pk}/` | Update a work item property | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{pk}/` | Delete a work item property | + +## Creating a property is only half the job + +A property that exists is not yet a field anyone can fill in. Creating it defines the field; **attaching it to a work item type** is what makes it appear on work items. + +1. Create the property with [Create a work item property](/api-reference/v2/work-item-properties/create-work-item-property). +2. Attach it to a type with [Attach a type property](/api-reference/v2/work-item-type-properties/attach-type-property). + +The same property can be attached to more than one type, which is why the two steps are separate. + +## Defining options + +`OPTION` properties need choices. You have two paths: + +- **Inline on create** — pass an `options` array in the create or update body and define the choices in the same request. `options` is write-only: you send it, and the response comes back with the resolved `options` array instead. +- **Separately** — create the property first, then add choices one at a time through [Property options](/api-reference/v2/work-item-property-options/create-property-option). Use this when options change over the life of the property. + +## Deactivate before you delete + +`is_active: false` stops a property from being offered without removing it. Deleting is permanent for the definition and takes its options with it. If you are retiring a field but still need to read historical values, deactivate it. diff --git a/docs/api-reference/v2/work-item-properties/update-work-item-property.md b/docs/api-reference/v2/work-item-properties/update-work-item-property.md new file mode 100644 index 00000000..aadfab41 --- /dev/null +++ b/docs/api-reference/v2/work-item-properties/update-work-item-property.md @@ -0,0 +1,310 @@ +--- +title: Update a work item property +description: Update a custom work item property in a Plane project with the v2 REST API. Partial PATCH semantics, default values, inline options, is_active, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update work item property, patch custom field, default_value, is_active, is_required, options, PATCH work item property +--- + +# Update a work item property + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{pk}/ +
+ +
+
+ +Change a property's label, description, default, requirement, or availability. Values already recorded on work items stay where they are — you are editing the field definition, not rewriting anyone's data. + +`PATCH` is partial. Send only the fields you want to change; anything you omit keeps its current value. Omitting a field is not the same as sending `null`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the property belongs to. + + + + + +The id of the property to update. + + + +
+
+ +
+ +### Body Parameters + +Every field is optional — send the subset you are changing. There is no `name` field: `name` is the read-side slug, and `display_name` is what you write. + +
+ + + +New human-readable label for the field. Maximum 255 characters. + + + + + +Free-form explanation of what the field is for, shown as helper text. + + + + + +The kind of data the field holds: `TEXT`, `DATETIME`, `DECIMAL`, `BOOLEAN`, `OPTION`, `RELATION`, `URL`, `EMAIL`, `FILE`, or `FORMULA`. + +Changing the type of a property that already holds values changes what those values mean, so treat this as a migration rather than an edit — in most cases creating a new property and retiring the old one with `is_active: false` is the safer move. The full reference is on the [properties overview](/api-reference/v2/work-item-properties/overview#property-type-reference). + + + + + +What a `RELATION` property points at: `ISSUE`, `USER`, `RELEASE`, or `RICH_TEXT`. Only meaningful when `property_type` is `RELATION`. + + + + + +Write-only. Define `OPTION` choices inline, the same way you can on create. Each entry is a property option object with `name` required. + +For adding, editing, or removing a single choice on a live property, prefer the dedicated endpoints under [Property options](/api-reference/v2/work-item-property-options/list-property-options) — they let you address one option by id instead of restating the set. The response always returns the resolved `options` array, never the payload you sent. + + + + + +Whether the field accepts more than one value. Turning it off on a property that already holds several values per work item is a narrowing change — check your data first. + + + + + +Whether a value must be present. This applies going forward: existing work items with the field empty are not rejected retroactively, but the next edit will ask for a value. Pair it with a `default_value` so automated flows have something to fall back on. + + + + + +Whether the property is offered. Setting `false` is the reversible way to retire a field — the definition, its options, and its recorded values all survive, and the field simply stops being offered. + + + + + +The value applied when none is supplied. **Always an array**, even for a single-valued property — a `DECIMAL` field that defaults to `3` is sent as `["3"]`, not `3`. Send `[]` to clear the default. + + + + + +Free-form object holding type-specific configuration. The shape depends on `property_type`, so send the whole object rather than assuming keys — a partial `settings` object replaces the stored one. + + + + + +Free-form object holding type-specific validation constraints. Same whole-object caveat as `settings`. + + + + + +Your system's identifier for this field, for sync and import correlation. Maximum 255 characters. + + + + + +The system `external_id` came from, for example `jira` or `linear`. Maximum 255 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | --------------------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | An enum value outside the list, or a field over its length limit. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't update properties. | +| `404` | `not_found` | No such property, project, or workspace — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types at the workspace level. Update the property on the workspace endpoint instead. | +| `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. | + +
+ +::: warning A 409 means wrong surface, not missing permission +If the workspace manages work item types at the workspace level, this project endpoint returns `409 work_item_types_managed_at_workspace` — the write belongs on [Update a workspace work item property](/api-reference/v2/workspace-work-item-properties/update-workspace-work-item-property) instead. Reading the same property through this path still works. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +::: info `name` is not writable +`name` is derived by Plane and returned on reads only. Renaming the field means sending a new `display_name`. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "c81b7e2a-5f34-4d90-8e17-3a6c9b0f2d75", + "name": "story_points", + "display_name": "Story points", + "description": "Relative sizing for planning", + "property_type": "DECIMAL", + "relation_type": null, + "is_multi": false, + "is_required": true, + "is_active": true, + "default_value": ["3"], + "options": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:24:03.117482Z" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_workspace", + "detail": "Work item types are managed at the workspace level for this workspace." +} +``` + + + +
+
+ +## Retiring a field without losing data + +Deleting a property is permanent. When you want a field to stop appearing but its history to stay readable, deactivate it: + +```json +{ + "is_active": false +} +``` + +The definition, its options, and every value already recorded stay in place, and setting `is_active` back to `true` brings the field back exactly as it was. diff --git a/docs/api-reference/v2/work-item-property-contexts/create-property-context.md b/docs/api-reference/v2/work-item-property-contexts/create-property-context.md new file mode 100644 index 00000000..baf57ab0 --- /dev/null +++ b/docs/api-reference/v2/work-item-property-contexts/create-property-context.md @@ -0,0 +1,401 @@ +--- +title: Create a property context +description: Create a context that scopes a workspace-level work item property to specific projects and work item types with the Plane v2 REST API. Wildcard flags, id lists, overrides, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create property context, applies_to_all_projects, applies_to_all_work_item_types, project_ids, issue_type_ids, property override, POST contexts +--- + +# Create a property context + +
+ POST + /api/v2/workspaces/{slug}/work-item-properties/{property_id}/contexts/ +
+ +
+
+ +Add a context to a workspace-level property. The context names the projects and work item types it covers, and carries the `is_required`, `is_multi`, `default_value`, `settings`, and option list that apply inside that scope. + +Reach for this when one property definition needs different rules in different places — for example a `Severity` property that is required on `Bug` work items in two projects and optional everywhere else. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace-level property the context belongs to. A project-level property id is not addressable here and returns `404 not_found`. + + + +
+
+ +
+ +### Body Parameters + +No single field is required by the schema, but the **scope is**: each of the two axes must be pinned down, either by setting its wildcard flag to `true` or by supplying a non-empty id list. Both flags default to `false`, so a body that omits `project_ids` and `issue_type_ids` entirely is a `400`, not a context that covers everything. + +
+ + + +Display name, unique among this property's contexts. Maximum 255 characters. Always send one — the context Plane seeded with the property already occupies the name `Default`. + + + + + +Set to `true` to cover every project in the workspace instead of listing them. Defaults to `false`, which means `project_ids` is required. + +Only one context per property may set this to `true`, and the seeded context normally holds that slot, so a second all-projects context returns `400`. + + + + + +The exact projects this context covers. Required when `applies_to_all_projects` is `false`, and rejected with a `400` when it is `true` — the flag and the list are alternatives, never a combination. + +Every id must belong to this workspace, and duplicates are rejected. This field is write-only as an input, but the resulting links are echoed back in the response's read-only `project_ids`. + + + + + +Set to `true` to cover every work item type in the workspace instead of listing them. Defaults to `false`, which means `issue_type_ids` is required. + + + + + +The exact work item types this context covers. Required when `applies_to_all_work_item_types` is `false`, and rejected with a `400` when it is `true`. + +Every id must belong to this workspace, and duplicates are rejected. Write-only as an input; the response echoes the stored links in `issue_type_ids`. + + + + + +Whether the property must be filled in on work items inside this scope. This replaces the property's own `is_required` here — it does not combine with it. + + + + + +Whether the property accepts several values inside this scope. Replaces the property's own `is_multi` here. + + + + + +Values applied when a work item in this scope has nothing set. For `OPTION` properties, any option in this context flagged `is_default` takes precedence over this array. + + + + + +The choices this context offers for an `OPTION` property. Options belong to a context, so this list — not the property's full option list — is what work items in this scope can pick from. + +Each entry either references an existing option of the property or creates a new one: + +- `id` _string (uuid)_ — an existing option of this property. Duplicate ids and ids from another property are rejected. +- `name` _string_ — creates a new option. Maximum 255 characters. Names must be unique within the payload, case-insensitively. +- `description` _string_ — free-form description of the option. +- `is_default` _boolean_ — preselect this option for new work items in this scope. Defaults to `false`. + +Send either `id` or `name` on each entry — an entry with neither is a `400`. Write-only as an input; the response returns the resulting options with their ids. + +An option's own `sort_order` is read-only: Plane assigns it from the order of the entries you send, and it is returned on each option in the response. + + + + + +Type-specific configuration for this scope, as a JSON object. Replaces the property's own `settings` here. + + + + + +Ordering weight among this property's contexts. Lower values sort first. Omit it and Plane places the new context after the ones that already exist. Ordering is presentational — it does not affect which context wins. + + + + + +Your system's identifier for this context, for sync and import correlation. Maximum 255 characters. Must be sent together with `external_source`, and the pair must be unique within the property. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. Must be sent together with `external_id`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `applies_to_all_projects`, `applies_to_all_work_item_types`, `created_at`, `default_value`, `external_id`, `external_source`, `id`, `is_default`, `is_multi`, `is_required`, `issue_type_ids`, `name`, `options`, `project_ids`, `settings`, `sort_order`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | An axis left unpinned, a list sent alongside its wildcard flag, an id outside the workspace, a duplicate name, a half-filled external pair, or an overlap with a same-tier context. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't change workspace property settings. | +| `404` | `not_found` | No such workspace or property, the property is project-level, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types per project, so workspace-level property writes are refused. | +| `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. | + +
+ +::: warning Same-tier overlap is rejected +Two contexts on the same property may overlap only when one is strictly more specific than the other — that is what makes narrow rules beat broad ones. Two contexts at the same level of specificity that share a project return `400`: two listed-project/listed-type contexts sharing a project **and** a type, or two listed-project/all-type contexts sharing a project. +::: + +::: info Workspace mode only +Creating a context requires the workspace to manage work item types at the workspace level. In project mode the request returns `409 work_item_types_managed_at_project` — the capability exists, it just lives on the project surface. Reads are unaffected. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "6e2b90d4-1c73-4f58-a09e-3d8b5c14e7f2", + "name": "Bug severity", + "is_required": true, + "is_multi": false, + "is_default": false, + "default_value": [], + "settings": {}, + "sort_order": 75535, + "applies_to_all_projects": false, + "applies_to_all_work_item_types": false, + "external_id": null, + "external_source": null, + "created_at": "2026-02-03T11:05:17.204918Z", + "project_ids": ["4af68566-94a4-4eb3-94aa-50dc9427067b", "9c3d7e21-6b45-4a80-8f19-2e0c7b5d3a64"], + "issue_type_ids": ["b7e04a95-2f18-4c63-9d57-8a12e6b0f439"], + "options": [ + { + "id": "c58e2f01-4d97-4b3a-8e26-1f0b7a9c5d34", + "name": "S1 — Critical", + "is_default": false, + "sort_order": 10000 + }, + { + "id": "a49b6c72-8e15-4d30-b57c-2f81e0a6d938", + "name": "S2 — Major", + "is_default": true, + "sort_order": 20000 + }, + { + "id": "f13c8a60-7d24-4e59-9b02-6a5f1c3e8d47", + "name": "S3 — Minor", + "is_default": false, + "sort_order": 30000 + } + ] +} +``` + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "The request body failed validation.", + "errors": [ + { + "field": "project_ids", + "code": "invalid", + "message": "Required when applies_to_all_projects is false." + } + ] +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "Work item types are managed at the project level for this workspace." +} +``` + + + +
+
+ +## Pinning each axis + +A context covers the intersection of a project set and a work item type set, and each set is expressed either by its wildcard flag or by its id list. Four shapes are valid, and they map one-to-one onto the precedence tiers Plane uses when it resolves a work item: + +| Projects | Work item types | Covers | +| ------------------------------- | -------------------------------------- | --------------------------------------------------------- | +| `project_ids: [...]` | `issue_type_ids: [...]` | Those types, in those projects. Most specific. | +| `project_ids: [...]` | `applies_to_all_work_item_types: true` | Every type, in those projects | +| `applies_to_all_projects: true` | `issue_type_ids: [...]` | Those types, in every project | +| `applies_to_all_projects: true` | `applies_to_all_work_item_types: true` | Everything. Least specific — normally the seeded context. | + +Two mistakes produce a `400` rather than a surprising context: + +- **Neither the flag nor the list.** Omitting `project_ids` while `applies_to_all_projects` is `false` does not quietly mean "all projects"; it means the context would cover nothing, so it is rejected. +- **Both the flag and the list.** Sending `project_ids` with `applies_to_all_projects: true` is rejected because the list could never take effect. Omit it. + +The same two rules apply on the work item type axis. + +::: tip Verify with a read-back +The response echoes `project_ids` and `issue_type_ids` as stored. A wildcard context always reads back with the matching list empty — `"applies_to_all_projects": true` with `"project_ids": []` means every project, not none. Read the flag first. +::: + +## What the new context changes + +Inside its scope, the context's `is_required`, `is_multi`, `default_value`, `settings`, and options replace the property's own values wholesale. Nothing is merged, and option lists are not unioned: a `Bug` in the Platform project can only choose from `S1`, `S2`, and `S3` above, even if the property's other contexts offer more. + +Outside its scope, nothing changes — work items keep resolving to whichever context was already the most specific match for them. + +See [Property contexts overview](/api-reference/v2/work-item-property-contexts/overview) for the full precedence rules, and [Update a context](/api-reference/v2/work-item-property-contexts/update-property-context) for how scope changes affect values that were already recorded. diff --git a/docs/api-reference/v2/work-item-property-contexts/delete-property-context.md b/docs/api-reference/v2/work-item-property-contexts/delete-property-context.md new file mode 100644 index 00000000..c934cc15 --- /dev/null +++ b/docs/api-reference/v2/work-item-property-contexts/delete-property-context.md @@ -0,0 +1,189 @@ +--- +title: Delete a property context +description: Delete a context from a workspace-level work item property with the Plane v2 REST API. What happens to the context's options and recorded values, mode conflicts, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete property context, remove context, property scope, 204, work item type modes, DELETE contexts +--- + +# Delete a property context + +
+ DELETE + /api/v2/workspaces/{slug}/work-item-properties/{property_id}/contexts/{pk}/ +
+ +
+
+ +Remove a context from a property. A successful delete returns `204` with an empty body. + +Deleting a context does not just remove a rule — it removes coverage. Every work item that resolved to this context falls through to the next most specific context, and if none matches, the property stops appearing on those work items entirely. Read [What a delete takes with it](#what-a-delete-takes-with-it) before calling this on a context that is in use. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace-level property the context belongs to. A project-level property id is not addressable here and returns `404 not_found`. + + + + + +The id of the context to delete. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `applies_to_all_projects`, `applies_to_all_work_item_types`, `created_at`, `default_value`, `external_id`, `external_source`, `id`, `is_default`, `is_multi`, `is_required`, `issue_type_ids`, `name`, `options`, `project_ids`, `settings`, `sort_order`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | --------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | A path segment is not a valid UUID. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't change workspace property settings. | +| `404` | `not_found` | No such context, property, or workspace — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types per project, so workspace-level property writes are refused. | +| `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. | + +
+ +::: info Workspace mode only +Deleting a context requires the workspace to manage work item types at the workspace level. In project mode the request returns `409 work_item_types_managed_at_project` — the capability exists, it just lives on the project surface. Reads are unaffected. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "Work item types are managed at the project level for this workspace." +} +``` + + + +
+
+ +## What a delete takes with it + +The delete is not limited to the context row: + +- **The context's options go with it.** Options belong to a context, not to the property, so deleting the context deletes the choices it offered — and any value recorded against one of those options is cleared. +- **Cells that lose their last covering context lose their values.** For each project and work item type combination that this context covered, Plane re-resolves the property. If a less specific context still matches, work items there switch to its rules. If nothing matches, the property no longer applies in that combination and the values recorded there are removed. + +There is no restore operation. Treat the `204` as final. + +## Deleting the all-projects context + +The context Plane seeds with a property covers all projects and all work item types, which makes it the fallback that every uncovered work item lands on. Deleting it does not delete the property, but it does leave the property invisible everywhere your narrower contexts do not reach. + +That slot also carries a constraint: only one context per property may set `applies_to_all_projects`. So deleting this context is how you free the slot when you want a different context to be the catch-all — create the replacement first if you would rather not have a gap, then move the flag with [Update a context](/api-reference/v2/work-item-property-contexts/update-property-context). + +## A safer teardown + +When a context is in active use, retire it in this order: + +1. [List the property's contexts](/api-reference/v2/work-item-property-contexts/list-property-contexts) and work out which context each covered project and work item type will fall back to. +2. If the fallback is the wrong rule, [update](/api-reference/v2/work-item-property-contexts/update-property-context) it — or create the replacement context — before deleting anything. +3. Delete the context you are retiring. + +Doing it in the other order leaves a window where the property is unreachable in those projects, and the values recorded there are cleaned up rather than parked. diff --git a/docs/api-reference/v2/work-item-property-contexts/get-property-context.md b/docs/api-reference/v2/work-item-property-contexts/get-property-context.md new file mode 100644 index 00000000..6428f2c3 --- /dev/null +++ b/docs/api-reference/v2/work-item-property-contexts/get-property-context.md @@ -0,0 +1,208 @@ +--- +title: Get a property context +description: Retrieve a single context of a workspace-level work item property with the Plane v2 REST API. Scope flags, overrides, options, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get property context, retrieve context, applies_to_all_projects, project_ids, issue_type_ids, GET contexts +--- + +# Get a property context + +
+ GET + /api/v2/workspaces/{slug}/work-item-properties/{property_id}/contexts/{pk}/ +
+ +
+
+ +Retrieve one context by id. Use it to confirm the scope and overrides you just wrote, or to read the current state before sending a `PATCH`. + +The response is the full context: the two wildcard flags, the resolved `project_ids` and `issue_type_ids`, the values this context imposes on the property, and the options it offers. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace-level property the context belongs to. A project-level property id is not addressable here and returns `404 not_found`. + + + + + +The id of the context to retrieve. A context that belongs to a different property returns `404`, even if the id exists. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `applies_to_all_projects`, `applies_to_all_work_item_types`, `created_at`, `default_value`, `external_id`, `external_source`, `id`, `is_default`, `is_multi`, `is_required`, `issue_type_ids`, `name`, `options`, `project_ids`, `settings`, `sort_order`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_properties:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read workspace property settings. | +| `404` | `not_found` | No such context, property, or workspace — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info Reads work in either mode +Retrieving a context does not depend on how the workspace manages work item types. Only `POST`, `PATCH`, and `DELETE` are mode-gated — see [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "6e2b90d4-1c73-4f58-a09e-3d8b5c14e7f2", + "name": "Bug severity", + "is_required": true, + "is_multi": false, + "is_default": false, + "default_value": [], + "settings": {}, + "sort_order": 75535, + "applies_to_all_projects": false, + "applies_to_all_work_item_types": false, + "external_id": null, + "external_source": null, + "created_at": "2026-02-03T11:05:17.204918Z", + "project_ids": ["4af68566-94a4-4eb3-94aa-50dc9427067b", "9c3d7e21-6b45-4a80-8f19-2e0c7b5d3a64"], + "issue_type_ids": ["b7e04a95-2f18-4c63-9d57-8a12e6b0f439"], + "options": [ + { + "id": "c58e2f01-4d97-4b3a-8e26-1f0b7a9c5d34", + "name": "S1 — Critical", + "is_default": false, + "sort_order": 10000 + }, + { + "id": "a49b6c72-8e15-4d30-b57c-2f81e0a6d938", + "name": "S2 — Major", + "is_default": true, + "sort_order": 20000 + }, + { + "id": "f13c8a60-7d24-4e59-9b02-6a5f1c3e8d47", + "name": "S3 — Minor", + "is_default": false, + "sort_order": 30000 + } + ] +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No work item property context matches the given query." +} +``` + + + +
+
+ +## Reading the response + +The scope is two pairs of fields, and each pair has to be read in order: + +- `applies_to_all_projects`, then `project_ids`. The context above lists two projects. Had the flag been `true`, `project_ids` would be `[]` and the context would cover **every** project — an empty list is never "no projects". +- `applies_to_all_work_item_types`, then `issue_type_ids`, on the same principle. + +`is_required`, `is_multi`, `default_value`, `settings`, and `options` are what this context imposes on the property inside that scope. They replace the property's own values rather than combining with them, so reading the property alone will not tell you how it behaves on a given work item — you need the context that resolves for that work item's project and type. + +A single context is also not enough to predict behavior on its own, because a more specific context can outrank it. [List property contexts](/api-reference/v2/work-item-property-contexts/list-property-contexts) returns every context on the property, which is what you need to rank them. diff --git a/docs/api-reference/v2/work-item-property-contexts/list-property-contexts.md b/docs/api-reference/v2/work-item-property-contexts/list-property-contexts.md new file mode 100644 index 00000000..51ac644a --- /dev/null +++ b/docs/api-reference/v2/work-item-property-contexts/list-property-contexts.md @@ -0,0 +1,325 @@ +--- +title: List property contexts +description: List the contexts of a workspace-level work item property with the Plane v2 REST API. Ordering, pagination, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list property contexts, work item property scope, applies_to_all_projects, project_ids, pagination, GET contexts +--- + +# List property contexts + +
+ GET + /api/v2/workspaces/{slug}/work-item-properties/{property_id}/contexts/ +
+ +
+
+ +Return every context defined on a workspace-level property, as a paginated list. This is how you see the full picture of where a property applies and what it does in each place, before deciding whether to add a new context or edit an existing one. + +Read the whole list rather than a single context when you need to reason about precedence: a context's effect depends on which other contexts exist. Contexts are returned in `sort_order` by default. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace-level property whose contexts you want. A project-level property id is not addressable here and returns `404 not_found`. + + + +
+
+ +
+ +### Query Parameters + +Check your spelling on `order_by` and `paginate` — neither is validated, so both fail silently. An unrecognized `order_by` value falls back to the default ordering, and anything other than `paginate=cursor` uses offset pagination. A typo shows up as an unexpected sort order or envelope, not as an error. + +
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `sort_order` , `-sort_order` — the property's own context order. This is the default. +- `created_at` , `-created_at` — when each context was added +- `id` , `-id` + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. A property usually has a handful of contexts, so one page is normally the whole set. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read the `next` value from the response rather than computing offsets yourself. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead of `next` and `total_count`. Pair it with `order_by=created_at` or `order_by=id` — the default `sort_order` ordering is not unique, so it is not cursor-eligible and the request is rejected with `ordering_not_cursor_eligible`. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted from the response. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `applies_to_all_projects`, `applies_to_all_work_item_types`, `created_at`, `default_value`, `external_id`, `external_source`, `id`, `is_default`, `is_multi`, `is_required`, `issue_type_ids`, `name`, `options`, `project_ids`, `settings`, `sort_order`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_properties:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read workspace property settings. | +| `404` | `not_found` | No such workspace or property, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info Reads work in either mode +Listing contexts does not depend on how the workspace manages work item types. Only `POST`, `PATCH`, and `DELETE` are mode-gated — see [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "2f8c40a6-9d13-4b7e-85c0-1e6a3f9b7d52", + "name": "Default", + "is_required": false, + "is_multi": false, + "is_default": true, + "default_value": [], + "settings": {}, + "sort_order": 65535, + "applies_to_all_projects": true, + "applies_to_all_work_item_types": true, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "project_ids": [], + "issue_type_ids": [], + "options": [ + { + "id": "5d0a9e34-7c62-4f18-b93d-0a4e6f2c8b71", + "name": "Low", + "is_default": false, + "sort_order": 10000 + }, + { + "id": "81b4f27c-3a05-4e96-8d21-7f6c0b4a9e53", + "name": "High", + "is_default": false, + "sort_order": 20000 + } + ] + }, + { + "id": "6e2b90d4-1c73-4f58-a09e-3d8b5c14e7f2", + "name": "Bug severity", + "is_required": true, + "is_multi": false, + "is_default": false, + "default_value": [], + "settings": {}, + "sort_order": 75535, + "applies_to_all_projects": false, + "applies_to_all_work_item_types": false, + "external_id": null, + "external_source": null, + "created_at": "2026-02-03T11:05:17.204918Z", + "project_ids": ["4af68566-94a4-4eb3-94aa-50dc9427067b", "9c3d7e21-6b45-4a80-8f19-2e0c7b5d3a64"], + "issue_type_ids": ["b7e04a95-2f18-4c63-9d57-8a12e6b0f439"], + "options": [ + { + "id": "c58e2f01-4d97-4b3a-8e26-1f0b7a9c5d34", + "name": "S1 — Critical", + "is_default": false, + "sort_order": 10000 + }, + { + "id": "a49b6c72-8e15-4d30-b57c-2f81e0a6d938", + "name": "S2 — Major", + "is_default": true, + "sort_order": 20000 + }, + { + "id": "f13c8a60-7d24-4e59-9b02-6a5f1c3e8d47", + "name": "S3 — Minor", + "is_default": false, + "sort_order": 30000 + } + ] + } + ], + "next": null, + "previous": null, + "total_count": 2, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "2f8c40a6-9d13-4b7e-85c0-1e6a3f9b7d52", + "name": "Default", + "is_required": false, + "is_multi": false, + "is_default": true, + "default_value": [], + "settings": {}, + "sort_order": 65535, + "applies_to_all_projects": true, + "applies_to_all_work_item_types": true, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "project_ids": [], + "issue_type_ids": [], + "options": [] + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + + + +
+
+ +## Reading the scope of each row + +Two fields decide coverage on each axis, and the list is only legible if you read them in the right order: + +- `applies_to_all_projects` first, `project_ids` second. The first row above covers **every** project — its empty `project_ids` is a consequence of the wildcard, not an empty scope. +- `applies_to_all_work_item_types` first, `issue_type_ids` second, on the same principle. + +Ranking the rows you get back tells you which one a given work item will actually use. Most specific wins: listed projects and listed types beat listed projects and all types, which beat all projects and listed types, which beat all projects and all types. See [Property contexts overview](/api-reference/v2/work-item-property-contexts/overview) for the full precedence rules. diff --git a/docs/api-reference/v2/work-item-property-contexts/overview.md b/docs/api-reference/v2/work-item-property-contexts/overview.md new file mode 100644 index 00000000..4540ea75 --- /dev/null +++ b/docs/api-reference/v2/work-item-property-contexts/overview.md @@ -0,0 +1,246 @@ +--- +title: Property contexts overview +description: The Plane API v2 work item property context object. How a workspace-level property is scoped to projects and work item types, how the applies_to_all wildcards interact with explicit id lists, and how a context overrides the property it belongs to. +keywords: plane api v2, property contexts, work item property scope, applies_to_all_projects, applies_to_all_work_item_types, project_ids, issue_type_ids, property override +--- + +# Property contexts overview + +A context scopes a **workspace-level** work item property to a set of projects and a set of work item types, and overrides how that property behaves inside that scope. + +A property is defined once for the whole workspace, so it carries exactly one `is_required` flag, one `is_multi` flag, one default value, and one list of options. That is rarely what a real workspace wants. Contexts are how one property definition expresses different rules in different places. + +## The problem a context solves + +Your workspace has a `Severity` property. You want it to be **required** on `Bug` work items in the Platform and Mobile projects, with a full `S1`/`S2`/`S3` scale — and **optional**, with a shorter list, everywhere else. + +That is two sets of rules for one property, so it takes two contexts: + +- The context Plane seeds with the property covers all projects and all work item types. Leave it at `is_required: false`. This is the "everywhere else" rule. +- A second context names the two projects and the `Bug` type, and sets `is_required: true` with its own options. This is the narrow rule. + +The narrow context is the one that wins on a Bug in Platform, because Plane always resolves the **most specific** matching context. On a Task in Platform, or on a Bug in a third project, nothing narrow matches and the seeded context applies instead. + +Creating the narrow rule is a single request: + +```json +{ + "name": "Bug severity", + "applies_to_all_projects": false, + "project_ids": ["4af68566-94a4-4eb3-94aa-50dc9427067b", "9c3d7e21-6b45-4a80-8f19-2e0c7b5d3a64"], + "applies_to_all_work_item_types": false, + "issue_type_ids": ["b7e04a95-2f18-4c63-9d57-8a12e6b0f439"], + "is_required": true, + "is_multi": false, + "options": [{ "name": "S1 — Critical" }, { "name": "S2 — Major", "is_default": true }, { "name": "S3 — Minor" }] +} +``` + +::: info Every property already has one context +When you create a workspace-level property, Plane seeds a context named `Default` that covers all projects and all work item types and mirrors the property's own `is_required`, `is_multi`, `default_value`, and `settings`. It is a normal context — you can read it, update it, and delete it through these endpoints. Its `is_default` flag is a historical marker and does not affect resolution. +::: + +
+
+ +## The property context object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the context. + +- `name` _string_ + + Display name, unique among the contexts of this property. Maximum 255 characters. + +- `is_required` _boolean_ + + Whether the property must be filled in inside this scope. Overrides the property's own `is_required`. + +- `is_multi` _boolean_ + + Whether the property accepts several values inside this scope. Overrides the property's own `is_multi`. + +- `is_default` _boolean_ + + `true` on the context Plane seeded with the property. It is a marker only — precedence is decided by the scope flags, not by this field. + +- `default_value` _array of string_ + + Values applied when a work item in this scope has nothing set. For `OPTION` properties, options in this context flagged `is_default` take precedence over this array. + +- `settings` _any_ + + Type-specific configuration for this scope, as a JSON object. Overrides the property's own `settings`. + +- `sort_order` _number_ + + Ordering weight among this property's contexts. Lower values sort first, and lists are returned in this order by default. + +- `applies_to_all_projects` _boolean_ + + When `true`, the context covers every project in the workspace and `project_ids` is empty. When `false`, `project_ids` is the exact list of projects covered. + +- `applies_to_all_work_item_types` _boolean_ + + When `true`, the context covers every work item type and `issue_type_ids` is empty. When `false`, `issue_type_ids` is the exact list of types covered. + +- `project_ids` _array of any_ + + The ids of the projects this context covers. Empty when `applies_to_all_projects` is `true` — read the flag before reading this list. + +- `issue_type_ids` _array of any_ + + The ids of the work item types this context covers. Empty when `applies_to_all_work_item_types` is `true`. + +- `options` _array of any_ + + The choices this context offers for an `OPTION` property. Each entry has `id`, `name`, `is_default`, and `sort_order`. Options belong to a context, not to the property, so two contexts on the same property can offer different lists. + +- `external_id` , `external_source` _string_ + + Correlation fields for sync and import. Together they let you map a context to a record in another system and find it again later. A pair is unique within a property. + +- `created_at` _string (date-time)_ + + When the context was created. + +
+
+ + + +```json +{ + "id": "6e2b90d4-1c73-4f58-a09e-3d8b5c14e7f2", + "name": "Bug severity", + "is_required": true, + "is_multi": false, + "is_default": false, + "default_value": [], + "settings": {}, + "sort_order": 75535, + "applies_to_all_projects": false, + "applies_to_all_work_item_types": false, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "project_ids": ["4af68566-94a4-4eb3-94aa-50dc9427067b", "9c3d7e21-6b45-4a80-8f19-2e0c7b5d3a64"], + "issue_type_ids": ["b7e04a95-2f18-4c63-9d57-8a12e6b0f439"], + "options": [ + { + "id": "c58e2f01-4d97-4b3a-8e26-1f0b7a9c5d34", + "name": "S1 — Critical", + "is_default": false, + "sort_order": 10000 + }, + { + "id": "a49b6c72-8e15-4d30-b57c-2f81e0a6d938", + "name": "S2 — Major", + "is_default": true, + "sort_order": 20000 + }, + { + "id": "f13c8a60-7d24-4e59-9b02-6a5f1c3e8d47", + "name": "S3 — Minor", + "is_default": false, + "sort_order": 30000 + } + ] +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ----------------------------------------------------------------------------- | ---------------- | +| `GET` | `/api/v2/workspaces/{slug}/work-item-properties/{property_id}/contexts/` | List contexts | +| `POST` | `/api/v2/workspaces/{slug}/work-item-properties/{property_id}/contexts/` | Create a context | +| `GET` | `/api/v2/workspaces/{slug}/work-item-properties/{property_id}/contexts/{pk}/` | Get a context | +| `PATCH` | `/api/v2/workspaces/{slug}/work-item-properties/{property_id}/contexts/{pk}/` | Update a context | +| `DELETE` | `/api/v2/workspaces/{slug}/work-item-properties/{property_id}/contexts/{pk}/` | Delete a context | + +`{property_id}` must be a workspace-level property. A project-level property id is not addressable here and returns `404 not_found`. + +## Scope: wildcards and explicit lists + +A context covers the **intersection** of a project set and a work item type set. Each of those two sets is expressed in one of two ways, and the wildcard flag decides which: + +| Flag | Companion list | Coverage | +| --------------------------------------- | ----------------------- | ------------------------------------- | +| `applies_to_all_projects: true` | `project_ids` empty | Every project in the workspace | +| `applies_to_all_projects: false` | `project_ids` listed | Exactly the projects you listed | +| `applies_to_all_work_item_types: true` | `issue_type_ids` empty | Every work item type in the workspace | +| `applies_to_all_work_item_types: false` | `issue_type_ids` listed | Exactly the types you listed | + +The flag and the list are alternatives, never a combination: + +- **On write**, sending `project_ids` alongside `applies_to_all_projects: true` is a `400` — the list would have no meaning, so Plane rejects it rather than silently dropping it. The same rule applies to `issue_type_ids` and `applies_to_all_work_item_types`. +- **On read**, `project_ids` and `issue_type_ids` are computed from the links Plane actually stores. Turning a wildcard on removes those links, so a wildcard context always reads back with an empty list. + +::: warning An empty list does not mean "nothing" +`"project_ids": []` with `"applies_to_all_projects": true` means **every project**. `"project_ids": []` never appears with the flag `false`, because a context can never be scoped to nothing. Always branch on the flag first, then read the list. +::: + +Each axis must be pinned down one way or the other. On create, omitting both the flag and the list for an axis is a `400` — see [Create a context](/api-reference/v2/work-item-property-contexts/create-property-context). On update, you cannot leave an axis empty either: turning a wildcard off has to arrive in the same request as the ids that replace it. + +::: info Only one context per property can use `applies_to_all_projects` +That slot is occupied by the context Plane seeds with the property. Creating a second all-projects context returns `400`. To move it, update the seeded context or delete it first. +::: + +## How Plane picks the context + +For a given work item, Plane resolves one context from the property's list by walking four tiers, most specific first, using the work item's project and type: + +1. Listed project **and** listed type. +2. Listed project **and** all types. +3. All projects **and** listed type. +4. All projects **and** all types — the seeded context. + +The first tier with a match wins outright; the remaining tiers are not consulted and nothing is merged between them. Contexts at _different_ tiers are meant to overlap — that overlap is exactly how a narrow rule beats a broad one. Overlap _within the same tier_ is a genuine ambiguity and is rejected at write time with a `400`. + +::: warning No matching context means the property disappears +If no tier matches a work item's project and type, the property does not surface on that work item at all. This is the practical consequence of deleting the all-projects, all-types context — see [Delete a context](/api-reference/v2/work-item-property-contexts/delete-property-context). +::: + +## What a context overrides + +The resolved context replaces the property's values wholesale for work items in its scope — `is_required`, `is_multi`, `default_value`, `settings`, and the list of selectable options all come from the context, not from the property. + +Working through the `Severity` example, with the seeded context left at its defaults: + +| Field | On the seeded context | On the `Bug severity` context | Bug in Platform | Task in Platform | +| ------------- | --------------------- | ----------------------------- | --------------- | ---------------- | +| Tier | 4 (all / all) | 1 (listed / listed) | Tier 1 wins | Tier 4 wins | +| `is_required` | `false` | `true` | Required | Optional | +| `is_multi` | `false` | `false` | Single value | Single value | +| Options | `Low`, `High` | `S1`, `S2`, `S3` | `S1`/`S2`/`S3` | `Low`/`High` | +| Default | none | `S2 — Major` (`is_default`) | `S2 — Major` | none | + +The Bug in Platform never sees `Low` or `High`: option lists are not unioned across contexts, and the resolved context's list is the whole set of permitted values. + +## Options belong to a context + +Options created through the [property options endpoints](/api-reference/v2/workspace-work-item-property-options/overview) attach to the property's all-projects, all-types context. To give a narrower context its own choices, send the `options` array when you [create](/api-reference/v2/work-item-property-contexts/create-property-context) or [update](/api-reference/v2/work-item-property-contexts/update-property-context) the context. Each entry either references an existing option of the property by `id` or creates a new one by `name`. + +On update, `options` is a replacement for the whole list, not an addition to it — an option you leave out is removed from the context along with the values recorded through it. + +## Contexts are workspace mode only + +These endpoints exist because the property they hang off is defined at the workspace level. `POST`, `PATCH`, and `DELETE` therefore require the workspace to be managing work item types at the workspace level; if it manages them per project, the write returns `409 work_item_types_managed_at_project` instead. Reads are unaffected by the mode. + +See [Work item type modes](/api-reference/v2/work-item-type-modes) for how to check which mode a workspace is in. + +## Scopes + +| Operation | Scope | +| ---------------------- | --------------------------------------- | +| List, Get | `workspaces.work_item_properties:read` | +| Create, Update, Delete | `workspaces.work_item_properties:write` | diff --git a/docs/api-reference/v2/work-item-property-contexts/update-property-context.md b/docs/api-reference/v2/work-item-property-contexts/update-property-context.md new file mode 100644 index 00000000..d70e9378 --- /dev/null +++ b/docs/api-reference/v2/work-item-property-contexts/update-property-context.md @@ -0,0 +1,363 @@ +--- +title: Update a property context +description: Update the scope or the overrides of a work item property context with the Plane v2 REST API. Partial PATCH semantics, set-replacement for project and type lists, option replacement, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update property context, patch context, applies_to_all_projects, project_ids, issue_type_ids, options replacement, PATCH contexts +--- + +# Update a property context + +
+ PATCH + /api/v2/workspaces/{slug}/work-item-properties/{property_id}/contexts/{pk}/ +
+ +
+
+ +Change what a context covers, or what it imposes on the property inside that coverage. Both halves are editable in one request: the scope fields move the boundary, and `is_required`, `is_multi`, `default_value`, `settings`, and `options` change the rules within it. + +`PATCH` is partial at the field level. Send only the fields you want to change; anything you omit keeps its current value. Fields that hold collections behave differently — see [Lists are replaced, not merged](#lists-are-replaced-not-merged). + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace-level property the context belongs to. A project-level property id is not addressable here and returns `404 not_found`. + + + + + +The id of the context to update. + + + +
+
+ +
+ +### Body Parameters + +Every field is optional — send the subset you are changing. The one constraint that survives a partial update is the scope: neither axis may be left without coverage, so turning a wildcard flag off has to arrive in the same request as the ids that replace it. + +
+ + + +New display name, unique among this property's contexts. Maximum 255 characters. + + + + + +Switch the project axis between "every project" and an explicit list. + +Setting it to `true` discards the context's stored `project_ids`, so the context reads back with an empty list. Setting it to `false` requires `project_ids` in the same request unless the context already has projects stored. + +Only one context per property may have this set to `true`, so turning it on while another context holds the slot returns `400`. + + + + + +Replace the exact set of projects this context covers. What you send becomes the whole list: ids you leave out are unlinked, and ids you add are linked. + +Rejected with a `400` when `applies_to_all_projects` is `true`, and when the list would be empty while the flag is `false`. Every id must belong to this workspace, and duplicates are rejected. Write-only as an input; the response echoes the stored links in `project_ids`. + + + + + +Switch the work item type axis between "every type" and an explicit list. Same rules as `applies_to_all_projects`: turning it on clears `issue_type_ids`, turning it off needs types to fall back on. + + + + + +Replace the exact set of work item types this context covers. Same set-replacement semantics as `project_ids`, and the same validation against the wildcard flag. + + + + + +Whether the property must be filled in inside this scope. Replaces the property's own `is_required` here. + + + + + +Whether the property accepts several values inside this scope. Replaces the property's own `is_multi` here. + + + + + +Values applied when a work item in this scope has nothing set. For `OPTION` properties, options in this context flagged `is_default` take precedence over this array. + + + + + +Replace this context's option list. Entries are the same shape as on create: `id` to keep an existing option, `name` to add a new one, plus optional `description` and `is_default`. An option's `sort_order` is read-only — Plane assigns it from the order of the entries you send. + +Sending this field rewrites the list. Any option currently on the context that you do not reference by `id` is removed, along with the values recorded through it. To keep an option and change nothing about it, include `{ "id": "…" }`. + + + + + +Type-specific configuration for this scope, as a JSON object. Replaces the property's own `settings` here. + + + + + +Ordering weight among this property's contexts. Lower values sort first. Presentational only — it does not affect which context wins for a work item. + + + + + +Your system's identifier for this context. Maximum 255 characters. Must be sent together with `external_source`, and the pair must be unique within the property. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. Must be sent together with `external_id`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `applies_to_all_projects`, `applies_to_all_work_item_types`, `created_at`, `default_value`, `external_id`, `external_source`, `id`, `is_default`, `is_multi`, `is_required`, `issue_type_ids`, `name`, `options`, `project_ids`, `settings`, `sort_order`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | An axis left without coverage, a list sent alongside its wildcard flag, an id outside the workspace, a duplicate name, a half-filled external pair, or an overlap with a same-tier context. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't change workspace property settings. | +| `404` | `not_found` | No such context, property, or workspace — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types per project, so workspace-level property writes are refused. | +| `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. | + +
+ +::: warning A context can never cover nothing +`{"applies_to_all_projects": false}` on its own is a `400` when the context has no stored projects, and so is `{"project_ids": []}`. An empty scope would strand every value recorded under this context, so Plane refuses the write instead. Send the flag and the replacement ids together. +::: + +::: info Workspace mode only +Updating a context requires the workspace to manage work item types at the workspace level. In project mode the request returns `409 work_item_types_managed_at_project`. Reads are unaffected. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "6e2b90d4-1c73-4f58-a09e-3d8b5c14e7f2", + "name": "Bug and incident severity", + "is_required": true, + "is_multi": false, + "is_default": false, + "default_value": [], + "settings": {}, + "sort_order": 75535, + "applies_to_all_projects": false, + "applies_to_all_work_item_types": false, + "external_id": null, + "external_source": null, + "created_at": "2026-02-03T11:05:17.204918Z", + "project_ids": ["4af68566-94a4-4eb3-94aa-50dc9427067b", "9c3d7e21-6b45-4a80-8f19-2e0c7b5d3a64"], + "issue_type_ids": ["b7e04a95-2f18-4c63-9d57-8a12e6b0f439", "3a5c81d7-9e26-4b04-a17f-5c8d2e930b61"], + "options": [ + { + "id": "c58e2f01-4d97-4b3a-8e26-1f0b7a9c5d34", + "name": "S1 — Critical", + "is_default": false, + "sort_order": 10000 + }, + { + "id": "a49b6c72-8e15-4d30-b57c-2f81e0a6d938", + "name": "S2 — Major", + "is_default": true, + "sort_order": 20000 + }, + { + "id": "f13c8a60-7d24-4e59-9b02-6a5f1c3e8d47", + "name": "S3 — Minor", + "is_default": false, + "sort_order": 30000 + } + ] +} +``` + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "The request body failed validation.", + "errors": [ + { + "field": "project_ids", + "code": "invalid", + "message": "Ignored when applies_to_all_projects is true; omit it." + } + ] +} +``` + + + +
+
+ +## Lists are replaced, not merged + +`project_ids`, `issue_type_ids`, and `options` are set-replacements. Sending one is a statement about the whole collection, not an addition to it: + +- **Omit the field** — the collection is left exactly as it was. This is the safe default, and it is why the request above changed the type axis without touching projects or options. +- **Send the field** — what you send becomes the collection. Ids you left out are unlinked; for `options`, an option you left out is deleted along with the values recorded through it. + +To add one project to a context, read the current `project_ids`, append to it, and send the full list back. To keep an option unchanged while editing its siblings, include it as `{ "id": "…" }`. + +## Moving between a list and the wildcard + +The flag and the list are alternatives on each axis, and switching between them has a fixed shape: + +- **List to wildcard.** Send `{"applies_to_all_projects": true}` and nothing else on that axis. Sending `project_ids` in the same request is a `400`. The stored project links are dropped, so the context reads back with `"project_ids": []` — which now means every project. +- **Wildcard to list.** Send `{"applies_to_all_projects": false, "project_ids": ["…"]}` in one request. The flag alone is a `400` when the context has no stored projects to fall back on. + +The work item type axis follows the same two rules with `applies_to_all_work_item_types` and `issue_type_ids`. + +::: warning Changing scope can delete recorded values +Every scope edit re-resolves which context each work item uses, and values that no longer have a home are removed: + +- A work item whose project and type are no longer covered by **any** context on this property loses its value for that property. +- An option-typed value keeps its cell but points at an option owned by a context that no longer resolves there — that value is dropped too, because the resolved context's option list is the only one Plane will offer. + +Narrowing a context, or widening one so it outranks another with a different option list, both trigger this. Read the property's full context list before changing a boundary, and treat scope edits as a migration rather than a cosmetic change. +::: + +## Overrides apply wherever this context resolves + +`is_required`, `is_multi`, `default_value`, `settings`, and `options` take effect as soon as the request succeeds, on every work item that resolves to this context. They replace the property's own values there rather than combining with them, so editing the property itself will not change behavior in a scope that a context already covers. + +Which work items that is depends on the other contexts on the property — a more specific one still outranks this one. See [Property contexts overview](/api-reference/v2/work-item-property-contexts/overview) for the precedence rules, and [List property contexts](/api-reference/v2/work-item-property-contexts/list-property-contexts) to see them all at once. diff --git a/docs/api-reference/v2/work-item-property-options/create-property-option.md b/docs/api-reference/v2/work-item-property-options/create-property-option.md new file mode 100644 index 00000000..94f8bce9 --- /dev/null +++ b/docs/api-reference/v2/work-item-property-options/create-property-option.md @@ -0,0 +1,214 @@ +--- +title: Create a property option +description: Add a selectable option to an OPTION-typed work item property in a Plane project with the v2 REST API. Body parameters, the default option, project-mode 409s, OAuth scopes, and code examples. +keywords: plane api v2, create property option, work item property option, OPTION property, is_default, POST property options +--- + +# Create a property option + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{property_id}/options/ +
+ +
+
+ +Add one more selectable choice to a project-level property whose `property_type` is `OPTION`. The new option becomes immediately pickable on work items that carry the property. + +Use this when the property already exists and you are extending its list. If you are still creating the property, you can pass the full set of choices inline through the property's write-only `options` field instead — see [Work item properties](/api-reference/v2/work-item-properties/overview) — and come back here only for later changes. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project that owns the property. + + + + + +The property to add the option to. It must be an `OPTION` property in this project — a property id from another project returns `404`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The label shown in the picker, for example `Blocker`. Maximum 255 characters. + + + + + +Free-form explanation of what the option means. Worth filling in when the label alone doesn't tell someone when to choose it. + + + + + +Make this the option that is preselected when a work item is created without an explicit value for this property. Defaults to `false`. + + + + + +Your system's identifier for this option, for sync and import correlation. Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. Nullable. Send it alongside `external_id` — an `external_id` is only unique within its source. + + + +
+ +::: info sort_order is not writable +`sort_order` comes back on every read, but it is not a create field. Plane assigns the new option a position when it is created; there is no body parameter that places it. +::: + +
+ +
+ +### Scopes + +`projects.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | `name` missing, or a field over its 255-character limit. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write this project's properties. | +| `404` | `not_found` | No such property, project, or workspace — or the property belongs to a different project. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The workspace manages work item types at the workspace level, so this project-level write is rejected. | +| `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. | + +
+ +::: warning Wrong mode is a 409, not a 404 +A workspace manages work item types in exactly one mode. If yours is in **workspace mode**, this project-level `POST` returns `409 work_item_types_managed_at_workspace` — the option isn't missing and you aren't unauthorized, the write simply belongs on the workspace surface. Create it through [Property options (workspace)](/api-reference/v2/workspace-work-item-property-options/overview) instead, and see [Work item type modes](/api-reference/v2/work-item-type-modes) for how to detect the mode first. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "6f0c8a24-51d7-4e93-bb62-0a7e5c1d9483", + "name": "Blocker", + "description": "Stops all downstream work", + "is_default": false, + "sort_order": 45000, + "external_id": null, + "external_source": null +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_workspace", + "detail": "Work item types are managed at the workspace level for this workspace." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-property-options/delete-property-option.md b/docs/api-reference/v2/work-item-property-options/delete-property-option.md new file mode 100644 index 00000000..0c4016a3 --- /dev/null +++ b/docs/api-reference/v2/work-item-property-options/delete-property-option.md @@ -0,0 +1,158 @@ +--- +title: Delete a property option +description: Delete an option from an OPTION-typed work item property in a Plane project with the v2 REST API. Path parameters, the 204 response, project-mode 409s, OAuth scopes, and code examples. +keywords: plane api v2, delete property option, remove work item property option, OPTION property, 204 no content, DELETE property options +--- + +# Delete a property option + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{property_id}/options/{pk}/ +
+ +
+
+ +Remove a choice from a project-level `OPTION` property. Use this when an option is no longer offered — a retired severity level, a team that no longer exists. + +A successful delete returns `204` with an empty body. There is nothing to parse; branch on the status code. + +If you only want to stop offering a choice without removing it from history, consider renaming it with [Update a property option](/api-reference/v2/work-item-property-options/update-property-option) instead — a rename leaves every stored value intact because values reference the option `id`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project that owns the property. + + + + + +The `OPTION` property the option belongs to. See [Work item properties](/api-reference/v2/work-item-properties/overview). + + + + + +The id of the option to delete. Every ancestor in the path is enforced — an option id that belongs to a different property returns `404` rather than deleting anything. + + + +
+
+ +
+ +### Scopes + +`projects.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request couldn't be processed as sent — for example a malformed identifier in the path. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write this project's properties. | +| `404` | `not_found` | No such option, property, project, or workspace — or the option belongs to a different property. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The workspace manages work item types at the workspace level, so this project-level write is rejected. | +| `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. | + +
+ +::: warning Wrong mode is a 409, not a 404 +If the workspace manages work item types at the **workspace** level, this project-level `DELETE` returns `409 work_item_types_managed_at_workspace`. Nothing was deleted, and the option still exists — the write belongs on the workspace surface. Delete it through [Property options (workspace)](/api-reference/v2/workspace-work-item-property-options/overview), and see [Work item type modes](/api-reference/v2/work-item-type-modes) for how to detect the mode before you write. +::: + +::: info 404 after a successful delete +A repeat `DELETE` of the same option returns `404 not_found`, not another `204`. If you are reconciling state, treat `404` on delete as "already gone" rather than as a failure. +::: + +
+ +
+ + + + + + + + + +```text +(empty body) +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_workspace", + "detail": "Work item types are managed at the workspace level for this workspace." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-property-options/get-property-option.md b/docs/api-reference/v2/work-item-property-options/get-property-option.md new file mode 100644 index 00000000..88b6253c --- /dev/null +++ b/docs/api-reference/v2/work-item-property-options/get-property-option.md @@ -0,0 +1,158 @@ +--- +title: Get a property option +description: Retrieve a single option of an OPTION-typed work item property in a Plane project with the v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get property option, retrieve work item property option, OPTION property, is_default, GET property option by id +--- + +# Get a property option + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{property_id}/options/{pk}/ +
+ +
+
+ +Retrieve one option by id. Reach for this when you already hold an option id — from a work item's property value, a webhook payload, or a stored mapping — and need its current label, description, or default flag. + +Reads work in either work item type mode, so this endpoint keeps returning data even in a workspace where option **writes** live on the workspace surface. See [Work item type modes](/api-reference/v2/work-item-type-modes). + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project that owns the property. + + + + + +The `OPTION` property the option belongs to. See [Work item properties](/api-reference/v2/work-item-properties/overview). + + + + + +The id of the option to retrieve. Every ancestor in the path is enforced — an option id that belongs to a different property returns `404` rather than the option's data. + + + +
+
+ +
+ +### Scopes + +`projects.work_item_properties:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this project's properties. | +| `404` | `not_found` | No such option, property, project, or workspace — or the option belongs to a different property. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info Existence is never leaked +An option outside your tenant returns `404`, not `403`. If a cached id starts returning `404`, re-read the property's choices with [List property options](/api-reference/v2/work-item-property-options/list-property-options) instead of retrying. +::: + +::: info No expansion on options +Property options don't accept `?expand=`. The response is the flat option object shown here. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "9d3b5c71-2e48-4a6f-b1c9-7f0e83d25a64", + "name": "Critical", + "description": "Customer-visible outage", + "is_default": false, + "sort_order": 15000, + "external_id": null, + "external_source": null +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No IssuePropertyOption matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-property-options/list-property-options.md b/docs/api-reference/v2/work-item-property-options/list-property-options.md new file mode 100644 index 00000000..80b72d4c --- /dev/null +++ b/docs/api-reference/v2/work-item-property-options/list-property-options.md @@ -0,0 +1,260 @@ +--- +title: List property options +description: List the selectable options of an OPTION-typed work item property in a Plane project with the v2 REST API. Ordering, pagination, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list property options, work item property options, OPTION property, sort_order, pagination, GET property options +--- + +# List property options + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{property_id}/options/ +
+ +
+
+ +Return the selectable choices of a single project-level property as a paginated list. This is how you build a picker for an `OPTION` property, and how you resolve a human-readable label such as `Critical` to the option `id` you must send when setting a property value on a work item. + +The property in the path must have `property_type: "OPTION"` — see [Work item properties](/api-reference/v2/work-item-properties/overview). Properties of any other type have no options. + +Reads work in either work item type mode — only writes are mode-gated, so listing options never returns `409`. See +[Work item type modes](/api-reference/v2/work-item-type-modes). + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project that owns the property. + + + + + +The property whose options you want to list. A property id from another project returns `404`, even inside the same workspace. + + + +
+
+ +
+ +### Query Parameters + +There are no filters or search on this endpoint — a property's option list is short and is returned whole. Check your spelling on `order_by`: a value outside the list below is not rejected, it silently falls back to the default ordering, so a typo shows up as an unexpected sort order rather than an error. + +
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `sort_order` , `-sort_order` — the order the property itself presents its choices in +- `created_at` , `-created_at` — when each option was added +- `id` , `-id` + +Order by `sort_order` when you are rendering the picker to a user; it matches what Plane's own UI shows. `created_at` is orderable but is not returned in the response body. + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. Most properties have well under 50 options, so one page is usually the entire list. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read the `next` value from the response rather than computing offsets yourself. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead of `next` and `total_count`. Omit it for the default offset envelope. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted from the response. + + + +
+
+ +
+ +### Scopes + +`projects.work_item_properties:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this project's properties. | +| `404` | `not_found` | No such property, project, or workspace — or the property belongs to a different project. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info No expansion on options +Property options don't accept `?expand=`. The response is the flat option object shown here. +::: + +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "9d3b5c71-2e48-4a6f-b1c9-7f0e83d25a64", + "name": "Critical", + "description": "Customer-visible outage", + "is_default": false, + "sort_order": 15000, + "external_id": null, + "external_source": null + }, + { + "id": "5e2a0c93-7d41-4b8e-a36f-91c4d0b7e582", + "name": "Major", + "description": "Degraded but usable", + "is_default": true, + "sort_order": 25000, + "external_id": null, + "external_source": null + }, + { + "id": "a84f1b60-95c2-4d37-8e5a-3b0f6d29c471", + "name": "Minor", + "description": "Cosmetic or low impact", + "is_default": false, + "sort_order": 35000, + "external_id": null, + "external_source": null + } + ], + "next": null, + "previous": null, + "total_count": 3, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "9d3b5c71-2e48-4a6f-b1c9-7f0e83d25a64", + "name": "Critical", + "description": "Customer-visible outage", + "is_default": false, + "sort_order": 15000, + "external_id": null, + "external_source": null + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + + + +
+
+ +::: tip Cache the id, refresh the label +The option `id` is what a work item stores. Cache the id-to-name mapping from this endpoint and refresh it when you render, so a rename in Plane shows up in your UI without breaking the values you already wrote. +::: diff --git a/docs/api-reference/v2/work-item-property-options/overview.md b/docs/api-reference/v2/work-item-property-options/overview.md new file mode 100644 index 00000000..7c800c55 --- /dev/null +++ b/docs/api-reference/v2/work-item-property-options/overview.md @@ -0,0 +1,120 @@ +--- +title: Property options overview +description: The Plane API v2 work item property option object. Attributes, the default option, sort order, how options relate to OPTION-typed properties, and project-mode write rules. +keywords: plane api v2, work item property options, option property, is_default option, sort_order, project properties, custom property choices +--- + +# Property options overview + +A property option is one selectable choice on a project-level work item property. Options exist **only** for properties whose `property_type` is `OPTION` — a property of any other type (`TEXT`, `DATETIME`, `DECIMAL`, `BOOLEAN`, `RELATION`, `URL`, `EMAIL`, `FILE`, or `FORMULA`) has no options to manage. Start at +[Work item properties](/api-reference/v2/work-item-properties/overview) to create the property itself, then use this sub-resource to manage the choices it offers. + +Each option belongs to exactly one property, which belongs to exactly one project. The path carries all three ancestors, and every id in it is checked: an option id that belongs to a different property — or a property that belongs to a different project — returns `404`, never another property's data. + +::: info Two ways to create options +When you create an `OPTION` property you can pass its choices inline through the property's write-only `options` field, which sets up the property and its full list of choices in a single request. This sub-resource is how you manage those choices **afterwards** — adding a new one, renaming it, moving the default, or removing a choice that is no longer offered. +::: + +
+
+ +## The property option object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the option. This is the value stored on a work item when someone picks this choice, so treat it as the stable handle and `name` as the label. + +- `name` _string_ + + The label shown in the picker, for example `Critical`. Maximum 255 characters. + +- `description` _string_ + + Free-form explanation of what the option means. Useful when a choice needs a definition that a one-word label can't carry. + +- `is_default` _boolean_ + + Whether this option is preselected when a work item is created without an explicit value for the property. + +- `sort_order` _number_ + + Ordering weight within the property. Lower values sort first. Plane assigns it when the option is created — it is not something you send. + +- `external_id` , `external_source` _string_ + + Correlation fields for sync and import. Together they let you map an option to a choice in another system and find it again later. Both are nullable. + +::: tip Store the id, not the name +Work item property values reference an option by `id`. If you match on `name` instead, renaming `Critical` to `Sev 1` silently breaks your integration, while the `id` keeps resolving. +::: + +
+
+ + + +```json +{ + "id": "9d3b5c71-2e48-4a6f-b1c9-7f0e83d25a64", + "name": "Critical", + "description": "Customer-visible outage", + "is_default": false, + "sort_order": 15000, + "external_id": null, + "external_source": null +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | -------------------------------------------------------------------------------------------------- | ---------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{property_id}/options/` | List options | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{property_id}/options/` | Create an option | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{property_id}/options/{pk}/` | Get an option | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{property_id}/options/{pk}/` | Update an option | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{property_id}/options/{pk}/` | Delete an option | + +`DELETE` returns `204` with an empty body. + +## What you can write + +The read shape and the write shape are deliberately different. Five fields are writable, and `name` is the only one that is required: + +| Field | Create | Update | +| ----------------- | ----------------- | -------- | +| `name` | required, max 255 | optional | +| `description` | optional | optional | +| `is_default` | optional | optional | +| `external_id` | optional, max 255 | optional | +| `external_source` | optional, max 255 | optional | + +`id` and `sort_order` are read-only. `sort_order` is not accepted on create or update — Plane assigns and maintains it, so there is no request that repositions an option through this endpoint. + +## Project mode only + +Writes here are **project-mode** writes. A workspace manages work item types and their properties in exactly one mode: project-level or workspace-level. + +::: warning Wrong mode returns 409, not 404 +If the workspace is running in **workspace mode**, `POST`, `PATCH`, and `DELETE` on this path return `409` with the code `work_item_types_managed_at_workspace`. Nothing is missing and nothing is forbidden — the capability lives on the workspace surface instead. Manage those options through [Property options (workspace)](/api-reference/v2/workspace-work-item-property-options/overview). + +Reads are unaffected by mode. `GET` on this path keeps working in either mode, so a project still lists the options of the properties it surfaces. + +See [Work item type modes](/api-reference/v2/work-item-type-modes) for how to detect which mode a workspace is in before you write. +::: + +## Scopes + +| Operation | Scope | +| ------------------------- | ------------------------------------- | +| `GET` (list, retrieve) | `projects.work_item_properties:read` | +| `POST`, `PATCH`, `DELETE` | `projects.work_item_properties:write` | + +Options are covered by the property scopes — there is no separate option scope to request. diff --git a/docs/api-reference/v2/work-item-property-options/update-property-option.md b/docs/api-reference/v2/work-item-property-options/update-property-option.md new file mode 100644 index 00000000..2bf8d648 --- /dev/null +++ b/docs/api-reference/v2/work-item-property-options/update-property-option.md @@ -0,0 +1,226 @@ +--- +title: Update a property option +description: Update an option of an OPTION-typed work item property in a Plane project with the v2 REST API. Partial PATCH semantics, body parameters, the default option, project-mode 409s, and code examples. +keywords: plane api v2, update property option, patch property option, work item property option, is_default, PATCH property options +--- + +# Update a property option + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/{property_id}/options/{pk}/ +
+ +
+
+ +Change an option's label, description, default flag, or correlation fields. Work items that already hold this option keep holding it — they store the option's `id`, so a rename is purely cosmetic and no stored value changes. + +`PATCH` is partial. Send only the fields you want to change; anything you omit keeps its current value. Omitting a field is not the same as sending `null`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project that owns the property. + + + + + +The `OPTION` property the option belongs to. See [Work item properties](/api-reference/v2/work-item-properties/overview). + + + + + +The id of the option to update. An option id that belongs to a different property returns `404`. + + + +
+
+ +
+ +### Body Parameters + +Every field is optional — send the subset you are changing. + +
+ + + +New label for the option. Maximum 255 characters. Safe to change at any time: values on work items reference the option `id`, not its name. + + + + + +Free-form explanation of what the option means. + + + + + +Whether this option is preselected when a work item is created without an explicit value for the property. Changing it affects only work items created from now on — existing values are untouched. + + + + + +Your system's identifier for this option, for sync and import correlation. Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. Nullable. + + + +
+ +::: info sort_order is not writable +`sort_order` is returned on every read but is not accepted in the request body. There is no `PATCH` that repositions an option — Plane maintains the ordering weight itself. +::: + +
+ +
+ +### Scopes + +`projects.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | A field over its 255-character limit, or a value of the wrong type. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write this project's properties. | +| `404` | `not_found` | No such option, property, project, or workspace — or the option belongs to a different property. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The workspace manages work item types at the workspace level, so this project-level write is rejected. | +| `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. | + +
+ +::: warning Wrong mode is a 409, not a 404 +If the workspace manages work item types at the **workspace** level, this project-level `PATCH` returns `409 work_item_types_managed_at_workspace`. The `GET` on the same path still works — reads are unaffected by mode, only writes are. Update the option through [Property options (workspace)](/api-reference/v2/workspace-work-item-property-options/overview), and see [Work item type modes](/api-reference/v2/work-item-type-modes) for how to detect which mode you are in. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "9d3b5c71-2e48-4a6f-b1c9-7f0e83d25a64", + "name": "Sev 1", + "description": "Customer-visible outage", + "is_default": true, + "sort_order": 15000, + "external_id": null, + "external_source": null +} +``` + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "The request body failed validation.", + "errors": [ + { + "field": "name", + "code": "max_length", + "message": "Ensure this field has no more than 255 characters." + } + ] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-relation-definitions/create-relation-definition.md b/docs/api-reference/v2/work-item-relation-definitions/create-relation-definition.md new file mode 100644 index 00000000..a713b816 --- /dev/null +++ b/docs/api-reference/v2/work-item-relation-definitions/create-relation-definition.md @@ -0,0 +1,253 @@ +--- +title: Create a relation definition +description: Create a relation definition with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a relation definition, relation definitions, work item relation definitions create +--- + +# Create a relation definition + +
+ POST + /api/v2/workspaces/{slug}/work-item-relation-definitions/ +
+ +
+
+ +Relation definitions are the workspace registry of typed work item relationships. Create a relation definition. + +- Custom relations must be enabled on your plan; otherwise this returns `402 payment_required`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The inward. + +Maximum 255 characters. + + + + + +Display name. + +Maximum 255 characters. + + + + + +The outward. + +Maximum 255 characters. + + + + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. + + + + + +Free-form description. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Whether the record is active. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `inward`, `is_active`, `is_default`, `logo_props`, `name`, `outward`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_relation_definitions:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "inward": "example", + "is_active": true, + "is_default": false, + "logo_props": null, + "name": "Example name", + "outward": "example", + "sort_order": 65535 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-relation-definitions/delete-relation-definition.md b/docs/api-reference/v2/work-item-relation-definitions/delete-relation-definition.md new file mode 100644 index 00000000..5b2a1141 --- /dev/null +++ b/docs/api-reference/v2/work-item-relation-definitions/delete-relation-definition.md @@ -0,0 +1,150 @@ +--- +title: Delete a relation definition +description: Delete a relation definition with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a relation definition, relation definitions, work item relation definitions destroy +--- + +# Delete a relation definition + +
+ DELETE + /api/v2/workspaces/{slug}/work-item-relation-definitions/{pk}/ +
+ +
+
+ +Relation definitions are the workspace registry of typed work item relationships. Delete a relation definition. A successful delete returns `204` with an empty body. + +- Custom relations must be enabled on your plan; otherwise this returns `402 payment_required`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The relation definition id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `inward`, `is_active`, `is_default`, `logo_props`, `name`, `outward`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_relation_definitions:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No relation definition matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-relation-definitions/get-relation-definition.md b/docs/api-reference/v2/work-item-relation-definitions/get-relation-definition.md new file mode 100644 index 00000000..78eca7e8 --- /dev/null +++ b/docs/api-reference/v2/work-item-relation-definitions/get-relation-definition.md @@ -0,0 +1,163 @@ +--- +title: Get a relation definition +description: Read a relation definition with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a relation definition, relation definitions, work item relation definitions retrieve +--- + +# Get a relation definition + +
+ GET + /api/v2/workspaces/{slug}/work-item-relation-definitions/{pk}/ +
+ +
+
+ +Relation definitions are the workspace registry of typed work item relationships. Read a single relation definition by id. + +- Custom relations must be enabled on your plan; otherwise this returns `402 payment_required`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The relation definition id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `inward`, `is_active`, `is_default`, `logo_props`, `name`, `outward`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_relation_definitions:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "inward": "example", + "is_active": true, + "is_default": false, + "logo_props": null, + "name": "Example name", + "outward": "example", + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No relation definition matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-relation-definitions/list-relation-definitions.md b/docs/api-reference/v2/work-item-relation-definitions/list-relation-definitions.md new file mode 100644 index 00000000..350d8ac0 --- /dev/null +++ b/docs/api-reference/v2/work-item-relation-definitions/list-relation-definitions.md @@ -0,0 +1,197 @@ +--- +title: List relation definitions +description: List relation definitions with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list relation definitions, relation definitions, work item relation definitions list +--- + +# List relation definitions + +
+ GET + /api/v2/workspaces/{slug}/work-item-relation-definitions/ +
+ +
+
+ +Relation definitions are the workspace registry of typed work item relationships. List the relation definitions you can see. Results are scoped to the path and to what your token is allowed to read. + +- Custom relations must be enabled on your plan; otherwise this returns `402 payment_required`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Page size (max 200). + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `inward`, `is_active`, `is_default`, `logo_props`, `name`, `outward`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_relation_definitions:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "inward": "example", + "is_active": true, + "is_default": false, + "logo_props": null, + "name": "Example name", + "outward": "example", + "sort_order": 65535 + }, + { + "color": "#3f76ff", + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "inward": "example", + "is_active": true, + "is_default": false, + "logo_props": null, + "name": "Another example", + "outward": "example", + "sort_order": 65535 + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-relation-definitions/overview.md b/docs/api-reference/v2/work-item-relation-definitions/overview.md new file mode 100644 index 00000000..c3801c30 --- /dev/null +++ b/docs/api-reference/v2/work-item-relation-definitions/overview.md @@ -0,0 +1,115 @@ +--- +title: Work item relation definitions overview +description: The Plane API v2 relation definition object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, work item relation definitions, relation definition object +--- + +# Work item relation definitions overview + +Relation definitions are the workspace registry of typed work item relationships. + +
+
+ +## The relation definition object + +### Attributes + +- `color` _string_ + + Hex color used wherever this is rendered, for example `#3f76ff`. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description` _string_ + + Free-form description. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `inward` _string_ + + The inward. + +- `is_active` _boolean_ + + Whether the record is active. + +- `is_default` _boolean_ + + Make this the default for its parent. Setting it clears the flag on the previous default. + +- `logo_props` _string_ + + Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +- `name` _string_ + + Display name. + +- `outward` _string_ + + The outward. + +- `sort_order` _number_ + + Manual ordering weight. Lower sorts first. + +
+
+ + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "inward": "example", + "is_active": true, + "is_default": false, + "logo_props": null, + "name": "Example name", + "outward": "example", + "sort_order": 65535 +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------------------- | ---------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/work-item-relation-definitions/` | List relation definitions | +| `POST` | `/api/v2/workspaces/{slug}/work-item-relation-definitions/` | Create a relation definition | +| `DELETE` | `/api/v2/workspaces/{slug}/work-item-relation-definitions/{pk}/` | Delete a relation definition | +| `GET` | `/api/v2/workspaces/{slug}/work-item-relation-definitions/{pk}/` | Get a relation definition | +| `PATCH` | `/api/v2/workspaces/{slug}/work-item-relation-definitions/{pk}/` | Update a relation definition | + +## Response shaping + +Every relation definition read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/work-item-relation-definitions/update-relation-definition.md b/docs/api-reference/v2/work-item-relation-definitions/update-relation-definition.md new file mode 100644 index 00000000..8d08d41a --- /dev/null +++ b/docs/api-reference/v2/work-item-relation-definitions/update-relation-definition.md @@ -0,0 +1,265 @@ +--- +title: Update a relation definition +description: Update a relation definition with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a relation definition, relation definitions, work item relation definitions partial update +--- + +# Update a relation definition + +
+ PATCH + /api/v2/workspaces/{slug}/work-item-relation-definitions/{pk}/ +
+ +
+
+ +Relation definitions are the workspace registry of typed work item relationships. Update a relation definition. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +- Custom relations must be enabled on your plan; otherwise this returns `402 payment_required`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The relation definition id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. + + + + + +Free-form description. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +The inward. + +Maximum 255 characters. + + + + + +Whether the record is active. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Display name. + +Maximum 255 characters. + + + + + +The outward. + +Maximum 255 characters. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `color`, `created_at`, `created_by_id`, `description`, `external_id`, `external_source`, `id`, `inward`, `is_active`, `is_default`, `logo_props`, `name`, `outward`, `sort_order`. + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_relation_definitions:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "inward": "example", + "is_active": true, + "is_default": false, + "logo_props": null, + "name": "Example name", + "outward": "example", + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No relation definition matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-relations/create-work-item-relations.md b/docs/api-reference/v2/work-item-relations/create-work-item-relations.md new file mode 100644 index 00000000..47735390 --- /dev/null +++ b/docs/api-reference/v2/work-item-relations/create-work-item-relations.md @@ -0,0 +1,179 @@ +--- +title: Create work item relations +description: Create a relation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create work item relations, relations, work item relations create +--- + +# Create work item relations + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/relations/ +
+ +
+
+ +Relations connect work items using the workspace's relation definitions. Create a relation. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The definition's outward or inward label — sets the relation direction. + + + + + +Id of the related relation definition. + + + + + +Ids of the work items to associate. Replaces the current set. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.relations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-relations/delete-work-item-relation.md b/docs/api-reference/v2/work-item-relations/delete-work-item-relation.md new file mode 100644 index 00000000..fc69bc22 --- /dev/null +++ b/docs/api-reference/v2/work-item-relations/delete-work-item-relation.md @@ -0,0 +1,143 @@ +--- +title: Delete a work item relation +description: Delete a relation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a work item relation, relations, work item relations destroy +--- + +# Delete a work item relation + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/relations/{related_work_item_id}/ +
+ +
+
+ +Relations connect work items using the workspace's relation definitions. Delete a relation. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + + + +The id of the work item on the other side of the relation. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.relations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No relation matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-relations/list-work-item-relations.md b/docs/api-reference/v2/work-item-relations/list-work-item-relations.md new file mode 100644 index 00000000..4ab32738 --- /dev/null +++ b/docs/api-reference/v2/work-item-relations/list-work-item-relations.md @@ -0,0 +1,123 @@ +--- +title: List work item relations +description: List relations with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list work item relations, relations, work item relations list +--- + +# List work item relations + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/relations/ +
+ +
+
+ +Relations connect work items using the workspace's relation definitions. List the relations you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Scopes + +`projects.work_items.relations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-relations/overview.md b/docs/api-reference/v2/work-item-relations/overview.md new file mode 100644 index 00000000..ce9e1d90 --- /dev/null +++ b/docs/api-reference/v2/work-item-relations/overview.md @@ -0,0 +1,21 @@ +--- +title: Work item relations overview +description: The Plane API v2 relation object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, work item relations, relation object +--- + +# Work item relations overview + +Relations connect work items using the workspace's relation definitions. + +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------------------------------------------------------------- | --------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/relations/` | List work item relations | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/relations/` | Create work item relations | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/relations/{related_work_item_id}/` | Delete a work item relation | + +## Response shaping + +Every relation read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/work-item-type-modes.md b/docs/api-reference/v2/work-item-type-modes.md new file mode 100644 index 00000000..4ad98963 --- /dev/null +++ b/docs/api-reference/v2/work-item-type-modes.md @@ -0,0 +1,428 @@ +--- +title: Work item type modes +description: A Plane workspace manages work item types at either the project level or the workspace level. How to detect the mode, which endpoints each mode uses, and why the wrong surface returns 409. +keywords: plane api v2, work item types, project mode, workspace mode, work_item_types_managed_at_workspace, work_item_types_managed_at_project, 409 conflict, custom properties +--- + +# Work item type modes + +A workspace manages work item types in **exactly one mode**, and that choice decides which endpoints you write to. + +| Mode | Types are owned by | You write to | +| ------------------ | ------------------ | ------------------------------------------------------------------------------- | +| **Project mode** | Each project | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/` and siblings | +| **Workspace mode** | The workspace | `/api/v2/workspaces/{slug}/work-item-types/` and siblings | + +Both surfaces exist in the API at all times. Only one of them accepts writes for a given workspace. This is the single most confusing thing about v2, and it is worth ten minutes now rather than an afternoon of debugging later. + +## The rule that catches everyone + +Writing to the wrong mode's endpoint returns **`409`** — not `404`, not `403`. + +That status is deliberate. `404` would mean "no such thing" and `403` would mean "you may not do that", and neither is true. The capability exists and you are allowed to use it; it just lives on the other surface. `409` says the request conflicts with the workspace's current configuration, and the stable error `code` tells you which way to go. + +| Situation | Status | `code` | +| ------------------------------------------------------------------- | ------ | -------------------------------------- | +| You wrote to a **project** endpoint, workspace is in workspace mode | `409` | `work_item_types_managed_at_workspace` | +| You wrote to a **workspace** endpoint, workspace is in project mode | `409` | `work_item_types_managed_at_project` | + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_workspace", + "detail": "Work item types are managed at the workspace level for this workspace." +} +``` + + + +Read the code as an instruction, not just a complaint: `work_item_types_managed_at_workspace` means _"retry this against the workspace surface"_, and `work_item_types_managed_at_project` means _"retry this against the project surface"_. + +::: tip Reads are not affected by mode +Only writes are gated. A `GET` works on both surfaces in both modes — and importantly, a project still surfaces its imported types while the workspace is in workspace mode. If you only ever read types, you can ignore this page entirely and list them from the project surface. +::: + +## Discovering the current mode + +Ask the workspace features endpoint. The `is_work_item_types_enabled` field is the mode discriminator. + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/features/" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + + + +```json +{ + "id": "a72f1c40-5b8e-4d19-9f2a-3c6d8e1b7a55", + "is_work_item_types_enabled": true, + "is_workitem_hierarchy_enabled": false, + "is_project_grouping_enabled": false, + "is_teams_enabled": true, + "is_wiki_enabled": true, + "is_initiative_enabled": false, + "is_customer_enabled": false, + "is_release_enabled": false, + "is_state_duration_enabled": false, + "is_pi_enabled": false, + "work_item_type_default_level": 0, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + +- `is_work_item_types_enabled: true` → **workspace mode**. Types are owned by the workspace. +- `is_work_item_types_enabled: false` → **project mode**. Types are owned by each project. + +The toggle only puts the workspace into workspace mode when workspace-level work item types are also available on the workspace's plan. Where they are not, the workspace stays in project mode no matter how the toggle is set — so treat a successful write, not the toggle alone, as proof of which surface is live. + +Switching the mode is a `PATCH` to the same endpoint (`workspaces.features:write` scope required). Treat it as an administrative action, not something an integration flips at runtime — it changes which surface every other client in the workspace must write to. + +```bash +curl -X PATCH "https://api.plane.so/api/v2/workspaces/my-team/features/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"is_work_item_types_enabled": true}' +``` + +See [Workspace features](/api-reference/v2/workspace-features/overview) for every field on this object. + +## Which surface do I use? + +```mermaid +flowchart TD + A["GET /api/v2/workspaces/my-team/features/"] --> B{"is_work_item_types_enabled"} + B -->|"false"| C["Project mode"] + B -->|"true"| D["Workspace mode"] + + C --> C1["Enable types on the project
POST .../projects/PROJECT_ID/work-item-types/enable/"] + C1 --> C2["Create types and properties
under .../projects/PROJECT_ID/..."] + + D --> D1["Create types and properties
under .../workspaces/my-team/..."] + D1 --> D2["Import the types into each project
POST .../projects/PROJECT_ID/work-item-types/import/"] + + C2 --> E["Read the type schema
GET .../projects/PROJECT_ID/work-item-types/TYPE_ID/schema/"] + D2 --> E +``` + +Note where the two paths converge: **reading a type's schema always happens on the project surface**, in both modes, because a schema describes what you can write on a work item _in a project_. + +## Project mode lifecycle + +In project mode each project owns its own types. Two projects can have entirely unrelated type sets. + +### 1. Enable types on the project + +A project starts without work item types. Enabling creates the project's default `Task` type and returns it. + +```bash +curl -X POST "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-types/enable/" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + + + +```json +{ + "id": "2d9b6f31-4a70-4c88-9d15-8b0e3f27c6a4", + "name": "Task", + "description": "Default work item type with the option to add new properties", + "is_active": true, + "is_default": true, + "is_epic": false, + "level": 0, + "logo_props": { "in_use": "icon", "icon": { "name": "Briefcase", "background_color": "#6d7b8a" } }, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + +Send this once per project. Calling it again returns the existing default type rather than creating a second one. + +### 2. Create a type + +```bash +curl -X POST "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-types/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Bug", + "description": "Something is broken in production", + "is_active": true +}' +``` + +### 3. Create a property + +Properties are created against the project, independent of any type. A property needs a `display_name` and a `property_type`. + +```bash +curl -X POST "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-properties/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "display_name": "Severity", + "property_type": "OPTION", + "is_required": true, + "is_multi": false +}' +``` + +For an `OPTION` property, add its choices under the property: + +```bash +curl -X POST "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-properties/9e51a08b-3d47-4f62-bc19-2a7d5e0f8c31/options/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ "name": "Sev 1", "is_default": false }' +``` + +### 4. Attach the property to a type + +Creating a property does not put it on anything. Attach it to the types that should carry it — one property can serve several types. + +```bash +curl -X POST "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-types/2d9b6f31-4a70-4c88-9d15-8b0e3f27c6a4/properties/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ "properties": ["9e51a08b-3d47-4f62-bc19-2a7d5e0f8c31"] }' +``` + +Detaching is a `DELETE` on the same collection with the property id in the path. Detaching removes the property from the type; it does not delete the property. + +### 5. Read the type's schema + +The schema endpoint is the one call that tells you everything writable on a work item of this type — standard fields with their option lists, plus the type's custom properties. + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-types/2d9b6f31-4a70-4c88-9d15-8b0e3f27c6a4/schema/" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + + + +```json +{ + "type_id": "2d9b6f31-4a70-4c88-9d15-8b0e3f27c6a4", + "type_name": "Bug", + "type_description": "Something is broken in production", + "type_logo_props": { "in_use": "icon", "icon": { "name": "Bug", "background_color": "#c2410c" } }, + "fields": { + "name": { "type": "string", "required": true, "max_length": 255 }, + "priority": { + "type": "option", + "required": false, + "default": "none", + "options": [ + { "value": "urgent", "label": "Urgent" }, + { "value": "high", "label": "High" }, + { "value": "medium", "label": "Medium" }, + { "value": "low", "label": "Low" }, + { "value": "none", "label": "None" } + ] + }, + "state_id": { + "type": "uuid", + "required": false, + "options": [ + { "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", "name": "In Progress", "color": "#3f76ff", "group": "started" } + ] + }, + "assignee_ids": { "type": "uuid", "is_multi": true, "required": false }, + "start_date": { "type": "date", "required": false, "format": "YYYY-MM-DD" } + }, + "custom_fields": { + "severity": { + "id": "9e51a08b-3d47-4f62-bc19-2a7d5e0f8c31", + "type": "OPTION", + "name": "severity", + "display_name": "Severity", + "description": "", + "required": true, + "is_multi": false, + "options": [{ "id": "5c3e7d81-91b4-4a2e-8f60-1d4b9c6a2e77", "name": "Sev 1", "logo_props": null }] + } + } +} +``` + + + +Add `?include=members,labels` to inline the assignee and label option lists. They can be large, so they are left out unless you ask. + +## Workspace mode lifecycle + +In workspace mode one set of types is defined once and shared. Projects opt into the types they want by importing them. + +### 1. Create a type on the workspace + +```bash +curl -X POST "https://api.plane.so/api/v2/workspaces/my-team/work-item-types/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Bug", + "description": "Something is broken in production", + "is_active": true +}' +``` + + + +```json +{ + "id": "2d9b6f31-4a70-4c88-9d15-8b0e3f27c6a4", + "name": "Bug", + "description": "Something is broken in production", + "is_active": true, + "is_default": false, + "is_epic": false, + "level": 0, + "logo_props": { "in_use": "icon", "icon": { "name": "Bug", "background_color": "#c2410c" } }, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + +### 2. Create workspace properties and attach them + +Same two steps as project mode, one path segment shorter — there is no `project_id`. + +```bash +# create the property +curl -X POST "https://api.plane.so/api/v2/workspaces/my-team/work-item-properties/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ "display_name": "Severity", "property_type": "OPTION", "is_required": true }' + +# add its options +curl -X POST "https://api.plane.so/api/v2/workspaces/my-team/work-item-properties/9e51a08b-3d47-4f62-bc19-2a7d5e0f8c31/options/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ "name": "Sev 1" }' + +# attach it to a type +curl -X POST "https://api.plane.so/api/v2/workspaces/my-team/work-item-types/2d9b6f31-4a70-4c88-9d15-8b0e3f27c6a4/properties/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ "properties": ["9e51a08b-3d47-4f62-bc19-2a7d5e0f8c31"] }' +``` + +### 3. Narrow a property with contexts (workspace mode only) + +A workspace property applies everywhere by default. A **property context** narrows where it applies and can override `is_required` / `is_multi` / `default_value` for that slice — for example, "Severity is required, but only on the Bug type in these three projects." + +```bash +curl -X POST "https://api.plane.so/api/v2/workspaces/my-team/work-item-properties/9e51a08b-3d47-4f62-bc19-2a7d5e0f8c31/contexts/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "applies_to_all_projects": false, + "applies_to_all_work_item_types": false, + "project_ids": ["4af68566-94a4-4eb3-94aa-50dc9427067b"], + "issue_type_ids": ["2d9b6f31-4a70-4c88-9d15-8b0e3f27c6a4"], + "is_required": true +}' +``` + +Contexts exist only on the workspace surface — there is no project-mode equivalent, because a project-owned property is already scoped to its project. See [Property contexts](/api-reference/v2/work-item-property-contexts/overview). + +### 4. Import workspace types into a project + +A workspace type is not usable in a project until the project imports it. This is what makes a shared type show up on that project's work items. + +::: warning The import endpoint lives on the project path but requires workspace mode +`POST .../projects/{project_id}/work-item-types/import/` sits under a project URL, yet it is a **workspace-mode** operation — importing only makes sense when the workspace owns the types. Calling it in project mode returns `409 work_item_types_managed_at_project`. It is the one endpoint where the path segment and the required mode do not line up, so it surprises people. +::: + +```bash +curl -X POST "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-types/import/" \ + -H "X-Api-Key: $PLANE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ "work_item_types": ["2d9b6f31-4a70-4c88-9d15-8b0e3f27c6a4"] }' +``` + +Importing is idempotent — re-importing a type the project already has changes nothing. + +### 5. Read the schema from the project + +Once imported, the type behaves like any other type in that project. Read its schema from the **project** surface, exactly as in project mode: + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-types/2d9b6f31-4a70-4c88-9d15-8b0e3f27c6a4/schema/" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +This is the practical payoff of "reads are unaffected by mode": the code that reads a type and writes a work item against it is identical in both modes. Only the code that _defines_ types has to care. + +## Endpoints by mode + +| Operation | Project mode | Workspace mode | +| ------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------- | +| Enable types | `POST .../projects/{project_id}/work-item-types/enable/` | Not applicable — `PATCH .../features/` | +| List / create types | `.../projects/{project_id}/work-item-types/` | `.../workspaces/{slug}/work-item-types/` | +| Get / update / delete a type | `.../projects/{project_id}/work-item-types/{pk}/` | `.../workspaces/{slug}/work-item-types/{pk}/` | +| Mark a type as default | `.../projects/{project_id}/work-item-types/{pk}/mark-default/` | `.../workspaces/{slug}/work-item-types/{pk}/mark-default/` | +| List / create properties | `.../projects/{project_id}/work-item-properties/` | `.../workspaces/{slug}/work-item-properties/` | +| Property options | `.../projects/{project_id}/work-item-properties/{property_id}/options/` | `.../workspaces/{slug}/work-item-properties/{property_id}/options/` | +| Attach / detach a property to a type | `.../projects/{project_id}/work-item-types/{type_id}/properties/` | `.../workspaces/{slug}/work-item-types/{type_id}/properties/` | +| Property contexts | Not available | `.../workspaces/{slug}/work-item-properties/{property_id}/contexts/` | +| Import workspace types into a project | Not available | `POST .../projects/{project_id}/work-item-types/import/` | +| Read a type's schema | `GET .../projects/{project_id}/work-item-types/{pk}/schema/` | Same — read from the project surface | + +## Writing mode-agnostic code + +If your integration has to work against workspaces you do not control, resolve the mode once at startup and pick a base path from it: + +```python +import requests + +BASE = "https://api.plane.so/api/v2" +HEADERS = {"X-Api-Key": "your-api-key"} + + +def type_base_path(slug, project_id): + """Return the URL prefix that accepts work-item-type WRITES for this workspace.""" + features = requests.get(f"{BASE}/workspaces/{slug}/features/", headers=HEADERS) + features.raise_for_status() + + if features.json()["is_work_item_types_enabled"]: + return f"{BASE}/workspaces/{slug}" # workspace mode + return f"{BASE}/workspaces/{slug}/projects/{project_id}" # project mode + + +base = type_base_path("my-team", "4af68566-94a4-4eb3-94aa-50dc9427067b") +response = requests.post( + f"{base}/work-item-types/", + headers=HEADERS, + json={"name": "Bug", "description": "Something is broken in production"}, +) +``` + +Also handle the `409` at the call site. The mode can change between your startup check and your write, and the error code tells you exactly which surface to retry on: + +```python +if response.status_code == 409: + code = response.json()["code"] + if code == "work_item_types_managed_at_workspace": + ... # retry against /workspaces/{slug}/... + elif code == "work_item_types_managed_at_project": + ... # retry against /workspaces/{slug}/projects/{project_id}/... +``` + +Branch on `code`, never on the HTTP status alone — a plain `409 conflict` from this family of endpoints means something entirely different, such as a duplicate name or a delete blocked because the type still has work items. + +## Related + +- [Work item types (project)](/api-reference/v2/work-item-types/overview) +- [Work item types (workspace)](/api-reference/v2/workspace-work-item-types/overview) +- [Work item properties (project)](/api-reference/v2/work-item-properties/overview) and [(workspace)](/api-reference/v2/workspace-work-item-properties/overview) +- [Property contexts](/api-reference/v2/work-item-property-contexts/overview) +- [Workspace features](/api-reference/v2/workspace-features/overview) +- [Errors](/api-reference/v2/errors) diff --git a/docs/api-reference/v2/work-item-type-properties/attach-type-property.md b/docs/api-reference/v2/work-item-type-properties/attach-type-property.md new file mode 100644 index 00000000..a53f6295 --- /dev/null +++ b/docs/api-reference/v2/work-item-type-properties/attach-type-property.md @@ -0,0 +1,223 @@ +--- +title: Attach a property to a type +description: Attach existing custom properties to a Plane work item type with the v2 REST API. Body parameters, OAuth scopes, mode conflicts, error codes, and code examples. +keywords: plane api v2, attach property to type, work item type properties, custom properties, POST type properties +--- + +# Attach a property to a type + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{type_id}/properties/ +
+ +
+
+ +Expose one or more existing custom properties on a work item type. After this call, work items of that type collect +those properties. + +::: warning This does not create a property +The body is a list of **ids of properties that already exist** in the project. Sending a definition — `display_name`, +`property_type`, and friends — is a `400`. Create the property first with +[Create a work item property](/api-reference/v2/work-item-properties/create-work-item-property), then attach the id it +returns. +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the work item type belongs to. + + + + + +The work item type to attach the properties to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The ids of the properties to attach. Every id must belong to a property in **this project** — an id from another +project or another workspace fails the whole request with `400 invalid_request`, and nothing is attached. Nothing is +attached partially: either all the ids are valid or none are applied. + +The array cannot be empty. Attaching an id the type already exposes is not an error and does not create a duplicate, so +retrying a request that may have already landed is safe. + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | `properties` missing or empty, or an id that isn't a property in this project. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't edit this project's work item types. | +| `404` | `not_found` | No such workspace, project, or type — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types at the workspace level. See below. | +| `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. | + +
+ +::: warning Wrong mode is a 409, not a 404 +Attaching is a project-mode write. If the workspace manages work item types at the **workspace** level, this endpoint +returns `409 work_item_types_managed_at_workspace` — the capability exists, it just lives on the other surface. Attach +there instead with +[Attach a property to a workspace type](/api-reference/v2/workspace-work-item-type-properties/attach-workspace-type-property). + +Branch on the `code`, not the status: a `409` here means "wrong surface", never "already attached". See +[Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +
+ + + + + + + + + +```json +{ + "properties": ["c1f0a2d4-73b9-4e6c-8a15-9d0e4b3f7c28", "5e8b3417-2a6d-4c91-b0f7-8d2e14a6c395"] +} +``` + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "One or more fields failed validation.", + "errors": [ + { + "field": "properties", + "code": "invalid", + "message": "This list may not be empty." + } + ] +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_workspace", + "detail": "Work item types are managed at the workspace level for this workspace." +} +``` + + + +
+
+ +::: info The response is ids, not objects +`201` returns a `properties` array — the ids that are now attached to the type. It does not echo the property +definitions. Read them back with +[List type properties](/api-reference/v2/work-item-type-properties/list-type-properties) or +[Get a type property](/api-reference/v2/work-item-type-properties/get-type-property) when you need `display_name`, +`property_type`, or the option list. +::: diff --git a/docs/api-reference/v2/work-item-type-properties/detach-type-property.md b/docs/api-reference/v2/work-item-type-properties/detach-type-property.md new file mode 100644 index 00000000..cbfab974 --- /dev/null +++ b/docs/api-reference/v2/work-item-type-properties/detach-type-property.md @@ -0,0 +1,185 @@ +--- +title: Detach a property from a type +description: Detach a custom property from a Plane work item type with the v2 REST API. Path parameters, OAuth scopes, mode conflicts, error codes, and code examples. +keywords: plane api v2, detach property from type, remove property from work item type, work item type properties, DELETE type property +--- + +# Detach a property from a type + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{type_id}/properties/{pk}/ +
+ +
+
+ +Stop a work item type from exposing a custom property. This removes the link between the type and the property and +returns `204` with an empty body. + +::: tip Detaching is not deleting +The property definition survives. It stays in the project, stays attached to every other type that uses it, and can be +attached again later with +[Attach a property to a type](/api-reference/v2/work-item-type-properties/attach-type-property). To remove the +definition itself, use +[Delete a work item property](/api-reference/v2/work-item-properties/delete-work-item-property). +::: + +::: warning Values recorded on this type's work items are removed +The link is not the only thing that goes. Values already stored for this property on work items **of this type** are +removed along with it, and re-attaching does not restore them. Export what you need before detaching a property that +has been in use. Work items of other types keep their values for this property. +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the work item type belongs to. + + + + + +The work item type to detach the property from. + + + + + +The id of the property to detach — the property's own id, not a separate link id. It is the same value you passed in +the `properties` array when you attached it. + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | Malformed request — for example a `pk` that isn't a valid UUID. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't edit this project's work item types. | +| `404` | `not_found` | The property isn't attached to this type, or no such type, project, or workspace. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types at the workspace level. See below. | +| `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. | + +
+ +::: warning Wrong mode is a 409, not a 404 +Detaching is a project-mode write. If the workspace manages work item types at the **workspace** level, this endpoint +returns `409 work_item_types_managed_at_workspace` and nothing is changed. Detach on the workspace surface instead with +[Detach a property from a workspace type](/api-reference/v2/workspace-work-item-type-properties/detach-workspace-type-property). + +Branch on the `code`: a `409` means "wrong surface", a `404` means "that property isn't on this type". See +[Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +::: info Detach is not idempotent the way attach is +Attaching a property twice is harmless, but detaching one that is already gone returns `404` rather than `204`. If you +are replaying a request, treat a `404` as "already detached" instead of retrying. +::: + +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_workspace", + "detail": "Work item types are managed at the workspace level for this workspace." +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "The requested resource was not found." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-type-properties/get-type-property.md b/docs/api-reference/v2/work-item-type-properties/get-type-property.md new file mode 100644 index 00000000..103cf0f4 --- /dev/null +++ b/docs/api-reference/v2/work-item-type-properties/get-type-property.md @@ -0,0 +1,217 @@ +--- +title: Get a type property +description: Retrieve one custom property through a Plane work item type with the v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get type property, work item type properties, custom property, attached property, GET type property by id +--- + +# Get a type property + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{type_id}/properties/{pk}/ +
+ +
+
+ +Retrieve a single property **as exposed by a work item type**. The response is the full property definition — the same +object [List type properties](/api-reference/v2/work-item-type-properties/list-type-properties) returns, for one id. + +The type in the path is part of the lookup, not decoration: a property that exists in the project but is not attached +to this type returns `404`. That makes this endpoint a cheap "is this property on this type?" check before you send a +value or offer the field in a form. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the work item type belongs to. + + + + + +The work item type the property is attached to. + + + + + +The id of the property to retrieve. This is the property's own id — the same value you pass to +[attach](/api-reference/v2/work-item-type-properties/attach-type-property) and +[detach](/api-reference/v2/work-item-type-properties/detach-type-property) it. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | -------------------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this project's work item types. | +| `404` | `not_found` | No such workspace, project, type, or property — or the property isn't attached to this type. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info A 404 here can mean "not attached" +The property may exist and be perfectly healthy — just not linked to this type. Read it without the type in the path +using [Get a work item property](/api-reference/v2/work-item-properties/get-work-item-property) to tell the two cases +apart, or list the type's properties to see what it actually exposes. +::: + +::: info One flat object, and no mode gate +The response is the flat property object shown here, with `options` already inlined for `OPTION` properties — there is +nothing to fetch separately. Reads also work in either work item type mode; only attaching and detaching are +mode-gated. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "c1f0a2d4-73b9-4e6c-8a15-9d0e4b3f7c28", + "name": "severity", + "display_name": "Severity", + "description": "How badly this bug affects users", + "property_type": "OPTION", + "relation_type": null, + "is_required": true, + "is_multi": false, + "is_active": true, + "default_value": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "options": [ + { + "id": "3b6f1c88-0d24-4a97-9f53-71e8c2b4a069", + "name": "Critical", + "description": "Data loss or full outage", + "is_default": false, + "sort_order": 10000, + "external_id": null, + "external_source": null + }, + { + "id": "7e2a9046-5c31-4d8b-a4f6-0b95e13d72c8", + "name": "Major", + "description": "Core workflow blocked", + "is_default": true, + "sort_order": 20000, + "external_id": null, + "external_source": null + } + ] +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "The requested resource was not found." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-type-properties/list-type-properties.md b/docs/api-reference/v2/work-item-type-properties/list-type-properties.md new file mode 100644 index 00000000..a866c43c --- /dev/null +++ b/docs/api-reference/v2/work-item-type-properties/list-type-properties.md @@ -0,0 +1,340 @@ +--- +title: List type properties +description: List the custom properties attached to a Plane work item type with the v2 REST API. Ordering, pagination, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list type properties, work item type properties, custom properties, attached properties, GET type properties +--- + +# List type properties + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{type_id}/properties/ +
+ +
+
+ +Return the custom properties currently attached to one work item type, as a paginated list. This is the call that +answers "what fields does a `Bug` have?" — reach for it before rendering a form, validating an import, or deciding +which property values to send when you create a work item. + +Each entry is the **full property definition**, not a link stub, so labels, input types, and option lists all arrive in +this one response. Properties that exist in the project but are not attached to this type are not returned; for the +project-wide inventory use +[List work item properties](/api-reference/v2/work-item-properties/list-work-item-properties). + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the work item type belongs to. + + + + + +The work item type whose properties you want to list. + + + +
+
+ +
+ +### Query Parameters + +There are no filters on this endpoint — the attachment itself is the filter. What you can control is ordering and page +size. + +Neither `order_by` nor `paginate` is validated — check your spelling, because both fail silently. A value outside the +lists below is not rejected: an unrecognized `order_by` falls back to the default ordering, and anything other than +`paginate=cursor` uses offset pagination. + +
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `sort_order` , `-sort_order` — the order the properties are shown in, and the default +- `created_at` , `-created_at` — when each property was created +- `id` , `-id` + +Order by `sort_order` when you are rendering the type's form to a user; it is the order Plane itself uses. Note that +`sort_order` is not part of the returned property object — sort by `created_at` or `id` if you need the sort key to be +visible in the payload. + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. Most types carry a handful of properties, so one page is usually the whole set. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read the `next` value from the response rather +than computing offsets yourself. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead of +`next` and `total_count`. Pair it with `order_by=created_at` or `order_by=id` — the default `sort_order` is not a +unique key, so it is not cursor-eligible. Omit the parameter for the default offset envelope. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted from the +response. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this project's work item types. | +| `404` | `not_found` | No such workspace, project, or type — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info Reads work in either mode +Listing is not mode-gated. A project surfaces the properties on its types whether the workspace manages types at the +project level or the workspace level — only the writes on this path +([attach](/api-reference/v2/work-item-type-properties/attach-type-property) and +[detach](/api-reference/v2/work-item-type-properties/detach-type-property)) care. See +[Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "c1f0a2d4-73b9-4e6c-8a15-9d0e4b3f7c28", + "name": "severity", + "display_name": "Severity", + "description": "How badly this bug affects users", + "property_type": "OPTION", + "relation_type": null, + "is_required": true, + "is_multi": false, + "is_active": true, + "default_value": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "options": [ + { + "id": "3b6f1c88-0d24-4a97-9f53-71e8c2b4a069", + "name": "Critical", + "description": "Data loss or full outage", + "is_default": false, + "sort_order": 10000, + "external_id": null, + "external_source": null + }, + { + "id": "7e2a9046-5c31-4d8b-a4f6-0b95e13d72c8", + "name": "Major", + "description": "Core workflow blocked", + "is_default": true, + "sort_order": 20000, + "external_id": null, + "external_source": null + } + ] + }, + { + "id": "5e8b3417-2a6d-4c91-b0f7-8d2e14a6c395", + "name": "root-cause", + "display_name": "Root cause", + "description": "What actually broke", + "property_type": "TEXT", + "relation_type": null, + "is_required": false, + "is_multi": false, + "is_active": true, + "default_value": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:24:03.201884Z", + "options": [] + } + ], + "next": null, + "previous": null, + "total_count": 2, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "5e8b3417-2a6d-4c91-b0f7-8d2e14a6c395", + "name": "root-cause", + "display_name": "Root cause", + "description": "What actually broke", + "property_type": "TEXT", + "relation_type": null, + "is_required": false, + "is_multi": false, + "is_active": true, + "default_value": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:24:03.201884Z", + "options": [] + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + + + +
+
+ +::: tip Branch on `property_type`, not on the label +`display_name` is what a human renamed the field to this week. `property_type`, `is_multi`, and `is_required` are what +tell your code whether to render a date picker or a multi-select and whether a value is mandatory. Key your integration +on `id` and `property_type`; treat `display_name` as text for humans. +::: diff --git a/docs/api-reference/v2/work-item-type-properties/overview.md b/docs/api-reference/v2/work-item-type-properties/overview.md new file mode 100644 index 00000000..5cec38b5 --- /dev/null +++ b/docs/api-reference/v2/work-item-type-properties/overview.md @@ -0,0 +1,222 @@ +--- +title: Type properties overview +description: How a Plane work item type exposes custom properties in API v2 — the attach/detach link, the property object reads return, and mode conflicts. +keywords: plane api v2, work item type properties, attach property to type, detach property, custom properties, issue type properties +--- + +# Type properties overview + +A work item type — `Bug`, `Feature`, `Task` — decides which custom properties a work item of that type collects. This +resource is the link between the two. It lists the properties a type currently exposes, attaches more, and detaches +them. + +::: warning This resource attaches properties — it does not create them +There is no create here, and no update at all. A property is a **project-level** object with its own lifecycle: it is +defined once under `/work-item-properties/` and can be exposed by several types. + +- To define a new property, use + [Create a work item property](/api-reference/v2/work-item-properties/create-work-item-property). +- To edit its `display_name`, `is_required`, options, or any other field, use + [Update a work item property](/api-reference/v2/work-item-properties/update-work-item-property). +- To make an existing property appear on a type, use + [Attach a property to a type](/api-reference/v2/work-item-type-properties/attach-type-property) — the `POST` on this + path takes ids of properties that already exist. + +`POST`ing a property definition (`display_name`, `property_type`, …) to this path is a `400`. It only accepts a list of +ids. +::: + +So adding a new field to a type is two calls: create the property in the project, then attach it to the type. The +payoff is that "Severity" can be one definition shared by `Bug` and `Incident` instead of two definitions that drift +apart. + +
+
+ +## The property object + +Reads on this path return the **full property definition**, not a thin link object. One call to +[List type properties](/api-reference/v2/work-item-type-properties/list-type-properties) gives you everything needed to +render the type's form — field labels, input types, option lists — with no second lookup under +`/work-item-properties/`. + +Every field is read-only here. Changing a definition goes through +[Update a work item property](/api-reference/v2/work-item-properties/update-work-item-property). + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the property. This is the id you send to attach it, and the `pk` you delete to detach it. + +- `name` _string_ + + Machine-readable name, slugified by Plane from `display_name`. Use `id` for lookups — `name` follows the label. + +- `display_name` _string_ + + The label shown on the work item form. + +- `description` _string_ + + Free-form explanation of what the property captures. Nullable. + +- `property_type` _string_ + + What kind of value the property holds. One of `TEXT`, `DATETIME`, `DECIMAL`, `BOOLEAN`, `OPTION`, `RELATION`, `URL`, + `EMAIL`, `FILE`, or `FORMULA`. This is the field to branch on when you build an input or parse a value. + +- `relation_type` _string_ + + For a `RELATION` property, what it points at: `ISSUE`, `USER`, `RELEASE`, or `RICH_TEXT`. `null` for every other + `property_type`. + +- `is_required` _boolean_ + + Whether a value must be supplied on work items of a type that exposes this property. + +- `is_multi` _boolean_ + + Whether the property holds several values instead of one. + +- `is_active` _boolean_ + + Whether the property is currently enabled. This lives on the property, not on the link — deactivating affects every + type the property is attached to, while detaching affects only one type. + +- `default_value` _array of string_ + + Values applied when a work item is created without an explicit value for this property. + +- `settings` _any_ + + Type-specific configuration. The shape depends on `property_type`. + +- `validation_rules` _any_ + + Constraints applied to submitted values. The shape depends on `property_type`. + +- `logo_props` _any_ + + Icon and appearance metadata used when the property is rendered. + +- `external_id` , `external_source` _string_ + + Correlation fields for sync and import. Together they map a property to a record in another system. + +- `created_at` _string (date-time)_ + + When the property was created. + +- `options` _array of any_ + + For an `OPTION` property, its selectable options inline — each with `id`, `name`, `description`, `is_default`, + `sort_order`, `external_id`, and `external_source`. An empty array for every other `property_type`. Manage them with + [Property options](/api-reference/v2/work-item-property-options/overview). + +
+
+ + + +```json +{ + "id": "c1f0a2d4-73b9-4e6c-8a15-9d0e4b3f7c28", + "name": "severity", + "display_name": "Severity", + "description": "How badly this bug affects users", + "property_type": "OPTION", + "relation_type": null, + "is_required": true, + "is_multi": false, + "is_active": true, + "default_value": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "options": [ + { + "id": "3b6f1c88-0d24-4a97-9f53-71e8c2b4a069", + "name": "Critical", + "description": "Data loss or full outage", + "is_default": false, + "sort_order": 10000, + "external_id": null, + "external_source": null + }, + { + "id": "7e2a9046-5c31-4d8b-a4f6-0b95e13d72c8", + "name": "Major", + "description": "Core workflow blocked", + "is_default": true, + "sort_order": 20000, + "external_id": null, + "external_source": null + } + ] +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | -------------------------------------------------------------------------------------------- | ------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{type_id}/properties/` | List the properties on a type | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{type_id}/properties/` | Attach properties to a type | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{type_id}/properties/{pk}/` | Get one property through a type | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{type_id}/properties/{pk}/` | Detach a property from a type | + +`{pk}` is the **property id**, not a separate link id. The same id you attach is the id you delete. + +## Attaching + +`POST` takes `{"properties": ["", …]}` and appends those properties to the type. The response is `201` with a +`properties` array. + +- Only properties belonging to the **same project** can be attached. An id from another project is a + `400 invalid_request` that names the offending ids, never a silent skip — one bad id rejects the whole call. +- Re-attaching a property the type already exposes is harmless. The call still returns `201` and the type is not left + with a duplicate, so a replayed request is safe. + +## Detaching + +`DELETE .../properties/{pk}/` removes the link and returns `204`. + +::: warning Detaching clears stored values +The property definition survives — it stays in the project and stays attached to every other type that uses it, ready +to re-attach. What does not survive is the data: the values already recorded for that property on work items **of this +type** are removed with the link, and re-attaching does not bring them back. + +Deleting the definition outright is a different call — +[Delete a work item property](/api-reference/v2/work-item-properties/delete-work-item-property). +::: + +## Mode matters for writes, not reads + +Attaching and detaching are **project-mode** writes. If the workspace manages work item types at the workspace level, +both return `409 work_item_types_managed_at_workspace` — the capability exists, it just lives on the other surface. Use +[Attach a property to a workspace type](/api-reference/v2/workspace-work-item-type-properties/attach-workspace-type-property) +instead. + +Reads are unaffected: listing and retrieving a type's properties works in either mode. + +See [Work item type modes](/api-reference/v2/work-item-type-modes) for how to tell which mode a workspace is in. + +## Changed from v1 + +- In v1 a property was created **inside** a type, at + `POST /api/v1/workspaces/{slug}/projects/{project_id}/work-item-types/{type_id}/work-item-properties/`, so it belonged + to that one type. +- In v2 the definition moved up to the project (`/work-item-properties/`) and this path became a link: + `POST` attaches existing properties, `DELETE` detaches one. There is no create and no update here. +- One property can now be attached to several types. + +See [Migrating from v1](/api-reference/v2/migrating-from-v1) for the full list. diff --git a/docs/api-reference/v2/work-item-types/create-work-item-type.md b/docs/api-reference/v2/work-item-types/create-work-item-type.md new file mode 100644 index 00000000..aa1fdf3d --- /dev/null +++ b/docs/api-reference/v2/work-item-types/create-work-item-type.md @@ -0,0 +1,242 @@ +--- +title: Create a work item type +description: Create a project-level work item type in Plane with the v2 REST API. Body parameters, project mode requirements, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create work item type, issue type, project mode, external_id, POST work item types +--- + +# Create a work item type + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/ +
+ +
+
+ +Add a work item type to a project — `Bug`, `Spike`, `Chore`, whatever your team actually tracks. The new type is immediately selectable on work items, and becomes the target you attach custom properties to. + +Call [Enable work item types](/api-reference/v2/work-item-types/enable-work-item-types) first. Creating a type in a project where the feature has never been turned on leaves you with a type nobody can pick. + +Keep the `id` from the response. Every later step — attaching properties, reading the schema, creating a work item of this type — is keyed on it. + +::: warning This write requires project mode +If the workspace manages types at the workspace level, this returns `409 work_item_types_managed_at_workspace`. Create the type on [the workspace surface](/api-reference/v2/workspace-work-item-types/create-workspace-work-item-type) instead, then [import it into the project](/api-reference/v2/work-item-types/import-work-item-types). See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project to add the type to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name for the type, for example `Bug`. Maximum 255 characters. + + + + + +What this type is for. This is not decoration: it is returned as `type_description` by the [schema endpoint](/api-reference/v2/work-item-types/get-work-item-type-schema), which is how an integration or agent decides between `Bug` and `Task` without a human in the loop. + + + + + +Whether the type can be assigned to new work items. Create it inactive if you are staging a configuration and want to attach its properties before anyone can select it. + + + + + +Your system's identifier for this type, for sync and import correlation. Maximum 255 characters, nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters, nullable. + + + +
+
+ +::: info is_default, is_epic, level and logo_props are not accepted here +`is_default` moves through [mark-default](/api-reference/v2/work-item-types/mark-default-work-item-type). `is_epic` and `level` are read-only. `logo_props` is generated by Plane when the type is created — the response tells you what it picked. +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | Missing `name`, or a field over its 255-character limit. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't create work item types. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A type in this project already uses this `external_id` and `external_source` pair. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240", + "name": "Bug", + "description": "Something is broken and needs a fix", + "is_active": true, + "is_default": false, + "is_epic": false, + "level": 0, + "logo_props": { + "in_use": "icon", + "icon": { + "name": "AlertCircle", + "background_color": "#EF5974" + } + }, + "created_at": "2026-01-14T09:24:03.117482Z" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_workspace", + "detail": "Work item types are managed at the workspace level for this workspace." +} +``` + + + +
+
+ +::: tip Reuse external_id to stay idempotent +`external_id` alone is not unique — the pair `(external_id, external_source)` is, within a project. Send both when you are syncing from another system so a repeated import surfaces as a clean `409 conflict` instead of a duplicate type. +::: diff --git a/docs/api-reference/v2/work-item-types/delete-work-item-type.md b/docs/api-reference/v2/work-item-types/delete-work-item-type.md new file mode 100644 index 00000000..5580c5ce --- /dev/null +++ b/docs/api-reference/v2/work-item-types/delete-work-item-type.md @@ -0,0 +1,181 @@ +--- +title: Delete a work item type +description: Delete a project work item type in Plane with the v2 REST API. Protected deletes, project mode requirements, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete work item type, issue type, 204, conflict, project mode, DELETE work item types +--- + +# Delete a work item type + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{pk}/ +
+ +
+
+ +Remove a work item type from a project. A successful delete returns `204` with an empty body. + +Deletion is guarded. A type that is the project default, or that still has work items on it, is rejected with `409 conflict` rather than cascading — the API will not silently untype existing work. + +If your goal is to stop people picking a type, [set `is_active` to `false`](/api-reference/v2/work-item-types/update-work-item-type) instead. That works regardless of how many work items already use the type, and it is reversible. + +::: warning This write requires project mode +If the workspace manages types at the workspace level, this returns `409 work_item_types_managed_at_workspace`. Delete the type on [the workspace surface](/api-reference/v2/workspace-work-item-types/delete-workspace-work-item-type) instead. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the type belongs to. + + + + + +The id of the work item type to delete. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request could not be processed as sent. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't delete work item types. | +| `404` | `not_found` | No such type, project, or workspace — or the type belongs to another project. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The type is the project default, or work items still use it. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```text +(empty body) +``` + + + + + +```json +{ + "type": "conflict", + "code": "conflict", + "detail": "A work item type with existing work items cannot be deleted." +} +``` + + + +
+
+ +## Before a delete will succeed + +Work through these in order: + +1. **It must not be the default.** Promote another type with [mark-default](/api-reference/v2/work-item-types/mark-default-work-item-type). Marking a new default clears the flag on this one in the same request. +2. **No work items may reference it.** Move them to another type, or delete them. There is no force flag — the check is the point. + +Both failures come back as `409 conflict`, distinguished by the `detail` string. Branch on the status and code; treat `detail` as text for humans. + +::: tip Two 409s, two different meanings +`conflict` means the type is protected or in use — a state you can fix. `work_item_types_managed_at_workspace` means you called the wrong surface — a routing mistake, fixed by calling the workspace endpoint instead. Always read the `code`, never just the status. +::: diff --git a/docs/api-reference/v2/work-item-types/enable-work-item-types.md b/docs/api-reference/v2/work-item-types/enable-work-item-types.md new file mode 100644 index 00000000..02c59e81 --- /dev/null +++ b/docs/api-reference/v2/work-item-types/enable-work-item-types.md @@ -0,0 +1,202 @@ +--- +title: Enable work item types +description: Turn work item types on for a Plane project with the v2 REST API. The first call in the project-mode lifecycle, the provisioned default type, OAuth scopes, error codes, and code examples. +keywords: plane api v2, enable work item types, issue types, project mode, default type, POST enable +--- + +# Enable work item types + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/enable/ +
+ +
+
+ +Turn the work item types feature on for a project. **This is the first call in the project-mode lifecycle** — until it succeeds the project has no types, so there is nothing to create properties against and nothing for a work item's `type_id` to point at. + +Enabling also provisions a default type, normally named `Task`, and returns it. That returned `id` is the type new work items land on when no `type_id` is supplied, and it is a valid target for [attaching properties](/api-reference/v2/work-item-type-properties/attach-type-property) straight away. + +There is **no request body**. The path carries everything the call needs. + +::: warning This write requires project mode +If the workspace manages types at the workspace level, this returns `409 work_item_types_managed_at_workspace`. In that mode there is nothing to enable per project — the workspace already owns the types, and you [import them into the project](/api-reference/v2/work-item-types/import-work-item-types) instead. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project to turn work item types on for. + + + +
+
+ +
+ +### Body Parameters + +None. Send the request without a body. + +
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request could not be processed as sent. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't configure this project's work item types. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The workspace manages types at the workspace level. Import instead of enabling. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "9c2f8e51-4a63-47d8-b1e0-5f7a2c40d693", + "name": "Task", + "description": "Default work item type with the option to add new properties", + "is_active": true, + "is_default": true, + "is_epic": false, + "level": 0, + "logo_props": { + "in_use": "icon", + "icon": { + "name": "Check", + "background_color": "#1FA191" + } + }, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_workspace", + "detail": "Work item types are managed at the workspace level for this workspace." +} +``` + + + +
+
+ +::: tip Calling it twice is safe +A project that already has a default type gets that type back rather than a second one. You can put this call at the head of a provisioning script without guarding it — `200` either way, and the `id` you need in the body. +::: + +## What comes next + +With the feature on and a default type in hand, the rest of the setup runs in order: + +1. [Create your own types](/api-reference/v2/work-item-types/create-work-item-type) — `Bug`, `Spike`, and so on. +2. [Create the properties](/api-reference/v2/work-item-properties/create-work-item-property) they should carry. +3. [Attach each property to a type](/api-reference/v2/work-item-type-properties/attach-type-property), deciding there whether it is required. +4. [Read the schema](/api-reference/v2/work-item-types/get-work-item-type-schema) to confirm what a work item of that type now accepts. + +The full sequence is laid out in the [work item types overview](/api-reference/v2/work-item-types/overview). diff --git a/docs/api-reference/v2/work-item-types/get-work-item-type-schema.md b/docs/api-reference/v2/work-item-types/get-work-item-type-schema.md new file mode 100644 index 00000000..df00bfe9 --- /dev/null +++ b/docs/api-reference/v2/work-item-types/get-work-item-type-schema.md @@ -0,0 +1,375 @@ +--- +title: Get a work item type schema +description: Discover the fields and custom properties a Plane work item type accepts with the v2 REST API. The include parameter, fields and custom_fields shapes, OAuth scopes, error codes, and code examples. +keywords: plane api v2, work item type schema, custom properties, discovery endpoint, custom_fields, include members labels, agent tooling +--- + +# Get a work item type schema + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{pk}/schema/ +
+ +
+
+ +Ask the API what a work item of this type accepts. The response describes the **standard fields** every work item has — with the project's actual states, priorities, and estimate points inlined as valid options — plus the **custom properties** attached to this type, including which ones are required and what values they allow. + +This is the discovery endpoint. A client that is about to create a work item calls this first, then builds its request body from the answer instead of hardcoding a field list. That matters most for two callers: + +- **Integrations**, which must adapt when an admin adds a required property to a type. Reading the schema each time means a new required field surfaces as a field to fill, not as a `400` in production. +- **AI agents and generated tooling**, which need a machine-readable contract. `fields` and `custom_fields` are shaped to be handed straight to a function-calling schema: every entry carries a type, a required flag, and its allowed options. + +Reads are unaffected by [work item type mode](/api-reference/v2/work-item-type-modes) — the schema resolves the type's properties whether they are owned by the project or by the workspace. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the type belongs to. The schema is project-specific: states, labels, members, and estimate points all come from this project. + + + + + +The id of the work item type whose schema you want. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Comma-separated extra option lists to inline. Two values are recognized: + +- `members` — inlines the project's active members as the options for `assignee_ids`, and for any custom property that is a user relation +- `labels` — inlines the project's labels as the options for `label_ids` + +Omit it and those fields still appear, they just describe themselves without an option list. These lists can be large in a big workspace, so ask for them only when you are actually rendering a picker — for example `?include=members,labels`. + + + +
+
+ +
+ +### Response Fields + +
+ + + +The type this schema describes. Send it as `type_id` on the work item you create. Nullable. + + + + + +The type's display name, for example `Bug`. Nullable. + + + + + +The type's description. This is the text a human or an agent reads to decide whether this is the right type for the work at hand, which is why filling it in on [create](/api-reference/v2/work-item-types/create-work-item-type) is worth the effort. Nullable. + + + + + +The icon and background color rendered next to the type. Nullable. + + + + + +The standard work item fields, keyed by the body parameter name you would send — `name`, `description_html`, `priority`, `state_id`, `assignee_ids`, `label_ids`, `start_date`, `target_date`, `parent_id`. Each entry carries a `type`, a `required` flag, and where it applies an `is_multi` flag, a `default`, a `max_length`, a `format`, or an `options` array. + +`state_id` and `priority` always arrive with their options inlined, because those are the two fields a client cannot guess. `estimate_point_id` appears only when the project has an estimate system configured. + + + + + +The custom properties attached to this type, keyed by property name. Each entry carries the property `id`, its `type`, `name`, `display_name`, `description`, a `required` flag, and an `is_multi` flag. + +- `OPTION` properties also carry an `options` array of `{ id, name, logo_props }`, narrowed to the options this type allows. +- `RELATION` properties also carry a `relation_type`. + +Empty when the type has no properties attached, or when custom properties are not available for the workspace. + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this project's work item types. | +| `404` | `not_found` | No such type, project, or workspace — or the type belongs to another project. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "type_id": "d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240", + "type_name": "Bug", + "type_description": "Something is broken and needs a fix", + "type_logo_props": { + "in_use": "icon", + "icon": { + "name": "AlertCircle", + "background_color": "#EF5974" + } + }, + "fields": { + "name": { + "type": "string", + "required": true, + "max_length": 255 + }, + "description_html": { + "type": "string", + "required": false + }, + "priority": { + "type": "option", + "required": false, + "default": "none", + "options": [ + { "value": "urgent", "label": "Urgent" }, + { "value": "high", "label": "High" }, + { "value": "medium", "label": "Medium" }, + { "value": "low", "label": "Low" }, + { "value": "none", "label": "None" } + ] + }, + "state_id": { + "type": "uuid", + "required": false, + "options": [ + { + "id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48", + "name": "Backlog", + "color": "#8b8d98", + "group": "backlog" + }, + { + "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "In Progress", + "color": "#3f76ff", + "group": "started" + }, + { + "id": "7b1e9d40-3c86-4f2a-9a5d-8e2b0c47d613", + "name": "Done", + "color": "#26a05f", + "group": "completed" + } + ] + }, + "assignee_ids": { + "type": "uuid", + "is_multi": true, + "required": false, + "options": [ + { + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "display_name": "rin", + "email": "rin@my-team.dev" + } + ] + }, + "label_ids": { + "type": "uuid", + "is_multi": true, + "required": false, + "options": [ + { + "id": "5e0c7b93-1a24-4d68-8f31-9b7e2c05a4d7", + "name": "regression", + "color": "#f5a623" + } + ] + }, + "start_date": { + "type": "date", + "required": false, + "format": "YYYY-MM-DD" + }, + "target_date": { + "type": "date", + "required": false, + "format": "YYYY-MM-DD" + }, + "parent_id": { + "type": "uuid", + "required": false + } + }, + "custom_fields": { + "severity": { + "id": "a8b31d67-5e42-4c09-9f78-2d6b41e0c395", + "type": "OPTION", + "name": "severity", + "display_name": "Severity", + "description": "How badly this breaks the product", + "required": true, + "is_multi": false, + "options": [ + { + "id": "3f8c2a71-6d90-4e15-b243-0c9e7a5b16f8", + "name": "S1", + "logo_props": {} + }, + { + "id": "b47e0d29-8153-4a6c-9e70-2f1d5c83a904", + "name": "S2", + "logo_props": {} + } + ] + }, + "found_by": { + "id": "c0a95f38-7b1e-4d62-8305-6e4a9b27fd51", + "type": "RELATION", + "name": "found_by", + "display_name": "Found by", + "description": "", + "required": false, + "is_multi": false, + "relation_type": "USER", + "options": [ + { + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "display_name": "rin", + "email": "rin@my-team.dev" + } + ] + } + } +} +``` + + + +
+
+ +## How to use the schema + +The response is a plan for the next request. A client that reads it correctly needs no hardcoded knowledge of the project's configuration. + +1. **Pick the type.** Read `type_name` and `type_description` to confirm this is the right type, and carry `type_id` into the work item body. +2. **Fill the standard fields from `fields`.** Anything with `"required": true` must be present. Where an entry has `options`, send one of those `id` values — a state id from another project is a `400`, never a silent link. +3. **Fill the custom properties from `custom_fields`.** Send them under `custom_fields` on the work item, keyed by the same property name. Entries with `"required": true` must be present on create. +4. **Respect `is_multi`.** A field with `"is_multi": true` takes an array; one without takes a single value. + +::: tip Re-read the schema, don't cache it forever +Admins add and retire properties. A property that became required since your last sync is the most common cause of a surprise `400` on work item create. Reading the schema at the start of a sync run costs one request and removes the failure mode entirely. +::: + +## Property types + +`custom_fields` entries carry the property's `type`. The possible values are `TEXT`, `DATETIME`, `DECIMAL`, `BOOLEAN`, `OPTION`, `RELATION`, `URL`, `EMAIL`, `FILE`, and `FORMULA`. + +A `RELATION` property additionally carries `relation_type`, one of `ISSUE`, `USER`, `RELEASE`, or `RICH_TEXT`, telling you what kind of id its value refers to. + +::: info Options are narrowed per type +The `options` list on an `OPTION` property is what **this type** allows, which can be a subset of every option defined on the property. Read the options from the schema rather than from [the property options endpoint](/api-reference/v2/work-item-property-options/list-property-options) when you want the values valid for this specific type. +::: + +## Related + +- [Attach a property to a type](/api-reference/v2/work-item-type-properties/attach-type-property) — what puts an entry into `custom_fields` +- [Create a work item property](/api-reference/v2/work-item-properties/create-work-item-property) — defining the property in the first place +- [Create a work item](/api-reference/v2/work-items/create-work-item) — where the schema gets spent diff --git a/docs/api-reference/v2/work-item-types/get-work-item-type.md b/docs/api-reference/v2/work-item-types/get-work-item-type.md new file mode 100644 index 00000000..9b88fb2c --- /dev/null +++ b/docs/api-reference/v2/work-item-types/get-work-item-type.md @@ -0,0 +1,183 @@ +--- +title: Get a work item type +description: Retrieve a single project work item type from Plane with the v2 REST API. Path parameters, response attributes, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get work item type, issue type, type_id, is_default, is_epic, GET work item types +--- + +# Get a work item type + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{pk}/ +
+ +
+
+ +Retrieve one work item type by id. Use it to confirm a type still exists and is still active before you write a work item against it, or to refresh a cached name after someone renamed the type in the app. + +This returns the type's own attributes only. It does **not** tell you which fields or custom properties a work item of this type accepts — that is [Get a work item type schema](/api-reference/v2/work-item-types/get-work-item-type-schema). + +Reads work in either [work item type mode](/api-reference/v2/work-item-type-modes), so this call never needs to branch on how the workspace is configured. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the type belongs to. + + + + + +The id of the work item type to retrieve. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this project's work item types. | +| `404` | `not_found` | No such type, project, or workspace — or the type belongs to another project. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240", + "name": "Bug", + "description": "Something is broken and needs a fix", + "is_active": true, + "is_default": false, + "is_epic": false, + "level": 0, + "logo_props": { + "in_use": "icon", + "icon": { + "name": "AlertCircle", + "background_color": "#EF5974" + } + }, + "created_at": "2026-01-14T09:24:03.117482Z" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No work item type matches the given query." +} +``` + + + +
+
+ +::: info A type from another project is a 404 +Type ids are scoped to the project path you call them on. Passing a valid type id under the wrong `project_id` returns `404 not_found` rather than `403` — existence outside your scope is never leaked. +::: + +::: tip Check is_active before offering a type +`is_active: false` means the type has been retired from pickers without being deleted. Existing work items keep it, but new ones should not be created with it. +::: diff --git a/docs/api-reference/v2/work-item-types/import-work-item-types.md b/docs/api-reference/v2/work-item-types/import-work-item-types.md new file mode 100644 index 00000000..00bc4599 --- /dev/null +++ b/docs/api-reference/v2/work-item-types/import-work-item-types.md @@ -0,0 +1,193 @@ +--- +title: Import work item types +description: Import workspace-level work item types into a Plane project with the v2 REST API. Body parameters, mode requirements, OAuth scopes, error codes, and code examples. +keywords: plane api v2, import work item types, workspace types, workspace mode, project types, POST import +--- + +# Import work item types + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/import/ +
+ +
+
+ +Make a set of **workspace-level** work item types available inside one project. Import does not create anything: the types already exist on the workspace, and this call adds them to a project's roster so work items there can use them. + +This is the workspace-mode way to answer "which types does this project use". Reach for it when a workspace defines `Bug`, `Task`, and `Incident` centrally and a new project should offer `Bug` and `Task` but not `Incident`. List the candidates with [List workspace work item types](/api-reference/v2/workspace-work-item-types/list-workspace-work-item-types), then send the ids you want. + +After a successful import, the imported types show up in [List work item types](/api-reference/v2/work-item-types/list-work-item-types) for the project and each one's [schema](/api-reference/v2/work-item-types/get-work-item-type-schema) resolves against this project's states, labels, and members. + +::: warning This is the one write here that requires workspace mode +Every other write in this group requires project mode. Import is the opposite: the types it moves are workspace-owned, so it only makes sense when the workspace manages types at the workspace level. + +Calling it while the workspace runs in **project** mode returns `409 work_item_types_managed_at_project` — in that mode a project authors its own types with [Create a work item type](/api-reference/v2/work-item-types/create-work-item-type) and there is nothing to import. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project to import the types into. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The ids of the workspace work item types to make available in this project. Send them in one request rather than looping — the import is applied as a batch. + +An id that does not resolve to a work item type on this workspace is rejected with `400 invalid_request`. + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | `work_item_types` missing, or an entry that isn't a UUID. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't configure this project's work item types. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The workspace manages types per project. Create the type in the project instead. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```text +(empty body) +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "Work item types are managed at the project level for this workspace." +} +``` + + + +
+
+ +::: info The response tells you nothing — read the list instead +A successful import returns `200` with an empty body, not the types it imported. To confirm the result, follow it with [List work item types](/api-reference/v2/work-item-types/list-work-item-types) for the project. +::: + +::: tip Re-importing is safe +Sending an id the project already has is not an error and does not duplicate anything. That makes the call safe to run on every provisioning pass: send the full desired set each time rather than diffing first. +::: + +## When to import versus create + +| Situation | Use | +| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| The workspace owns types and this project should offer some of them | Import | +| The project owns its own types | [Create a work item type](/api-reference/v2/work-item-types/create-work-item-type) | +| You need a new type that does not exist at the workspace level yet | [Create it on the workspace](/api-reference/v2/workspace-work-item-types/create-workspace-work-item-type), then import it | diff --git a/docs/api-reference/v2/work-item-types/list-work-item-types.md b/docs/api-reference/v2/work-item-types/list-work-item-types.md new file mode 100644 index 00000000..dac25cfe --- /dev/null +++ b/docs/api-reference/v2/work-item-types/list-work-item-types.md @@ -0,0 +1,288 @@ +--- +title: List work item types +description: List a Plane project's work item types with the v2 REST API. Ordering, pagination, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list work item types, issue types, type_id, pagination, GET work item types +--- + +# List work item types + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/ +
+ +
+
+ +Return the work item types available in a project as a paginated list. This is how you resolve a type name such as `Bug` to the `type_id` you send when creating a work item, and how you build a type picker that stays in sync with the project's configuration. + +Reading types is unaffected by [work item type mode](/api-reference/v2/work-item-type-modes). A project in workspace mode still lists the types imported into it here, so a read integration never has to branch on mode. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project whose work item types you want to list. + + + +
+
+ +
+ +### Query Parameters + +Neither `order_by` nor `paginate` is validated — check your spelling, because both fail silently. An unrecognized `order_by` value falls back to the default ordering, and anything other than `paginate=cursor` uses offset pagination. A typo surfaces as an unexpected sort order or envelope, not as an error. + +
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `level` , `-level` — the type hierarchy level +- `name` , `-name` — alphabetical +- `created_at` , `-created_at` — when each type was added +- `id` , `-id` + +Order by `level` when you are rendering types to a user; it is the order the project itself uses. + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. Most projects define a handful of types, so one page is usually the whole set. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read the `next` value from the response rather than computing offsets yourself. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead of `next` and `total_count`. Omit it for the default offset envelope. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted from the response. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this project's work item types. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "9c2f8e51-4a63-47d8-b1e0-5f7a2c40d693", + "name": "Task", + "description": "Default work item type with the option to add new properties", + "is_active": true, + "is_default": true, + "is_epic": false, + "level": 0, + "logo_props": { + "in_use": "icon", + "icon": { + "name": "Check", + "background_color": "#1FA191" + } + }, + "created_at": "2026-01-14T09:22:41.478363Z" + }, + { + "id": "d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240", + "name": "Bug", + "description": "Something is broken and needs a fix", + "is_active": true, + "is_default": false, + "is_epic": false, + "level": 0, + "logo_props": { + "in_use": "icon", + "icon": { + "name": "AlertCircle", + "background_color": "#EF5974" + } + }, + "created_at": "2026-01-14T09:24:03.117482Z" + } + ], + "next": null, + "previous": null, + "total_count": 2, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "9c2f8e51-4a63-47d8-b1e0-5f7a2c40d693", + "name": "Task", + "description": "Default work item type with the option to add new properties", + "is_active": true, + "is_default": true, + "is_epic": false, + "level": 0, + "logo_props": { + "in_use": "icon", + "icon": { + "name": "Check", + "background_color": "#1FA191" + } + }, + "created_at": "2026-01-14T09:22:41.478363Z" + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + + + +
+
+ +::: info An empty list is a real answer +A project that has never called [enable](/api-reference/v2/work-item-types/enable-work-item-types) returns an empty `data` array rather than an error. Treat "no types" as "the feature has not been turned on for this project yet", not as a failure. +::: + +::: tip Listing does not tell you what a type accepts +The list gives you names and ids. To find out which fields and custom properties a work item of a given type takes, call [Get a work item type schema](/api-reference/v2/work-item-types/get-work-item-type-schema) with that type's `id`. +::: diff --git a/docs/api-reference/v2/work-item-types/mark-default-work-item-type.md b/docs/api-reference/v2/work-item-types/mark-default-work-item-type.md new file mode 100644 index 00000000..90fded54 --- /dev/null +++ b/docs/api-reference/v2/work-item-types/mark-default-work-item-type.md @@ -0,0 +1,202 @@ +--- +title: Mark a work item type as default +description: Promote a Plane project work item type to the project default with the v2 REST API. Single-request promotion, project mode requirements, OAuth scopes, error codes, and code examples. +keywords: plane api v2, mark default work item type, is_default, default issue type, project mode, POST mark-default +--- + +# Mark a work item type as default + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{pk}/mark-default/ +
+ +
+
+ +Promote a type to the project's default — the type new work items get when the request supplies no `type_id`. + +**Exactly one type per project is the default.** Marking a new one clears the flag on the type that held it, so promotion is a single request rather than a demote-then-promote pair. There is no way to have zero defaults, and no way to have two. + +There is **no request body**. The type is identified by `{pk}` in the path, and the response is the updated type. + +`is_default` is deliberately not writable through [create](/api-reference/v2/work-item-types/create-work-item-type) or [update](/api-reference/v2/work-item-types/update-work-item-type). Moving the flag has a side effect on another row, so it gets its own endpoint instead of hiding inside a `PATCH`. + +::: warning This write requires project mode +If the workspace manages types at the workspace level, this returns `409 work_item_types_managed_at_workspace`. Promote the type on [the workspace surface](/api-reference/v2/workspace-work-item-types/mark-default-workspace-work-item-type) instead. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the type belongs to. + + + + + +The id of the work item type to make the default. + + + +
+
+ +
+ +### Body Parameters + +None. Send the request without a body. + +
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request could not be processed as sent. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't update work item types. | +| `404` | `not_found` | No such type, project, or workspace — or the type belongs to another project. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The workspace manages types at the workspace level. Use the workspace surface. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240", + "name": "Bug", + "description": "Something is broken and needs a fix", + "is_active": true, + "is_default": true, + "is_epic": false, + "level": 0, + "logo_props": { + "in_use": "icon", + "icon": { + "name": "AlertCircle", + "background_color": "#EF5974" + } + }, + "created_at": "2026-01-14T09:24:03.117482Z" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_workspace", + "detail": "Work item types are managed at the workspace level for this workspace." +} +``` + + + +
+
+ +::: warning The response only shows the type you promoted +The previously default type is demoted in the same operation, but it is not included in the response. If you are holding cached type objects, refresh the whole set with [List work item types](/api-reference/v2/work-item-types/list-work-item-types) after promoting, or your cache will briefly show two defaults. +::: + +::: tip The default type is protected +Once a type is the default it cannot be deleted, and it cannot be deactivated with `"is_active": false`. Both are the same guard from two directions: a project always needs somewhere for untyped work items to land. To retire the current default, promote a replacement first — this call — and then delete or deactivate the old one. +::: diff --git a/docs/api-reference/v2/work-item-types/overview.md b/docs/api-reference/v2/work-item-types/overview.md new file mode 100644 index 00000000..ecadbd1c --- /dev/null +++ b/docs/api-reference/v2/work-item-types/overview.md @@ -0,0 +1,154 @@ +--- +title: Work item types overview +description: The Plane API v2 project work item type object. Attributes, the project-mode lifecycle from enable to schema, endpoints, and the mode conflict rules. +keywords: plane api v2, work item types, issue types, custom properties, project mode, work item type schema, is_epic, mark default +--- + +# Work item types overview + +A work item type gives work items a shape. `Bug`, `Task`, and `Spike` are not just labels — each type carries its own set of custom properties, so a `Bug` can require a severity and a reproduction URL while a `Task` requires neither. Every work item in a type-enabled project points at exactly one type through `type_id`. + +The pages in this group cover the **project-level** work item type endpoints: types owned by a single project and configured under `/projects/{project_id}/work-item-types/`. + +::: warning A workspace runs in exactly one mode +Work item types are managed either **per project** or **per workspace** — never both. The endpoints on this page are the project surface, and their **writes require project mode**. Calling one while the workspace runs in workspace mode returns `409 work_item_types_managed_at_workspace`, not a `404` or `403`: the capability exists, it just lives on the other surface. + +**Reads are unaffected by mode.** A project still lists and reads its types in either mode, so `GET` and the schema endpoint always work. + +See [Work item type modes](/api-reference/v2/work-item-type-modes) for how to detect which mode a workspace is in, and [the workspace type endpoints](/api-reference/v2/workspace-work-item-types/list-workspace-work-item-types) for the other surface. +::: + +
+
+ +## The work item type object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the type. This is the value you send as `type_id` when creating a work item, and the `{type_id}` in the type-properties paths. + +- `name` _string_ + + Display name, for example `Bug`. Maximum 255 characters. + +- `description` _string_ + + Free-form explanation of when this type should be used. Worth filling in — an integration or agent reading the [schema endpoint](/api-reference/v2/work-item-types/get-work-item-type-schema) sees this text as `type_description` and uses it to choose between types. + +- `is_active` _boolean_ + + Whether the type can be assigned to new work items. Deactivating a type hides it from pickers without deleting it or touching the work items that already use it. + +- `is_default` _boolean_ + + Whether new work items land on this type when no `type_id` is supplied. Exactly one type per project holds the flag. It is not writable through create or update — move it with [Mark a work item type as default](/api-reference/v2/work-item-types/mark-default-work-item-type). + +- `is_epic` _boolean_ + + Whether this is the project's epic type. Read-only — epics are provisioned by Plane, not authored through this endpoint. + +- `level` _number_ + + Hierarchy level for the type. Read-only, and the value to sort on when you want the order the project itself uses — see [List work item types](/api-reference/v2/work-item-types/list-work-item-types). + +- `logo_props` _any_ + + The icon and background color rendered next to the type. Generated by Plane when the type is created, so it is read-only here. + +- `created_at` _string (date-time)_ + + When the type was created. + +::: info external_id is write-only +`external_id` and `external_source` are accepted on create and update as correlation fields for sync and import, but they are **not returned** in the read shape. Neither is `updated_at`. To find a type you previously synced, keep your own mapping from `external_id` to the returned `id`. +::: + +
+
+ + + +```json +{ + "id": "d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240", + "name": "Bug", + "description": "Something is broken and needs a fix", + "is_active": true, + "is_default": false, + "is_epic": false, + "level": 0, + "logo_props": { + "in_use": "icon", + "icon": { + "name": "AlertCircle", + "background_color": "#EF5974" + } + }, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + +
+
+ +## The project-mode lifecycle + +Types and their properties are set up in a fixed order. Each step depends on the id returned by the one before it. + +1. **Enable the feature** — `POST .../work-item-types/enable/` + + Turns work item types on for the project. This is the first call: until it succeeds, the project has no types to create properties against. It returns the default type Plane provisions, normally named `Task`. See [Enable work item types](/api-reference/v2/work-item-types/enable-work-item-types). + +2. **Create your types** — `POST .../work-item-types/` + + Add `Bug`, `Spike`, or whatever your team actually tracks. Keep the `id` from the response — every later step needs it. See [Create a work item type](/api-reference/v2/work-item-types/create-work-item-type). + +3. **Create the properties** — `POST .../work-item-properties/` + + Properties are defined at the project level and are independent of any type. A `Severity` option property is created once. See [Create a work item property](/api-reference/v2/work-item-properties/create-work-item-property). + +4. **Attach properties to a type** — `POST .../work-item-types/{type_id}/properties/` + + This is the step that makes a property apply to a type, and where you decide whether it is required for that type. The same property can be attached to several types with different settings. See [Attach a property to a type](/api-reference/v2/work-item-type-properties/attach-type-property). + +5. **Read the schema** — `GET .../work-item-types/{pk}/schema/` + + Ask the API what a work item of this type accepts: the standard fields with their valid options, plus every custom property attached in step 4. This is the call a client makes right before it constructs a work item. See [Get a work item type schema](/api-reference/v2/work-item-types/get-work-item-type-schema). + +After that, create work items with `type_id` set to the type and `custom_fields` filled in from the schema. + +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------------------------------------ | ------------------------------------- | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/enable/` | Enable work item types for a project | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/` | List work item types | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/` | Create a work item type | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{pk}/` | Get a work item type | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{pk}/` | Update a work item type | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{pk}/` | Delete a work item type | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{pk}/schema/` | Get a work item type schema | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{pk}/mark-default/` | Mark a type as the project default | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/import/` | Import workspace types into a project | + +## What you can and cannot write + +The writable surface is deliberately small: `name`, `description`, `is_active`, `external_id`, and `external_source`. + +- `is_default` moves through [mark-default](/api-reference/v2/work-item-types/mark-default-work-item-type), not the request body. +- `is_epic` and `level` are read-only. Epic types are a separate surface and do not appear in these lists. +- `logo_props` is generated when the type is created. + +## The default type + +Exactly one type per project is the default. Marking a new one clears the flag on the previous holder, so promoting a type is a single request rather than a demote-then-promote pair. + +The default type is protected. It cannot be deleted, and it cannot be deactivated with `"is_active": false` — promote a different type first. + +## Importing instead of creating + +`POST .../work-item-types/import/` copies **workspace-level** types into a project rather than creating new ones. It is the workspace-mode counterpart to `create`, so it is the one endpoint in this group whose write requires workspace mode. See [Import work item types](/api-reference/v2/work-item-types/import-work-item-types). diff --git a/docs/api-reference/v2/work-item-types/update-work-item-type.md b/docs/api-reference/v2/work-item-types/update-work-item-type.md new file mode 100644 index 00000000..dbc61991 --- /dev/null +++ b/docs/api-reference/v2/work-item-types/update-work-item-type.md @@ -0,0 +1,250 @@ +--- +title: Update a work item type +description: Update a project work item type in Plane with the v2 REST API. Partial PATCH semantics, body parameters, project mode requirements, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update work item type, patch issue type, is_active, external_id, project mode, PATCH work item types +--- + +# Update a work item type + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/{pk}/ +
+ +
+
+ +Rename a type, rewrite its description, retire it from pickers, or correct its sync correlation fields. Work items already using the type are untouched — you are editing the type, not retyping anything. + +`PATCH` is partial. Send only the fields you want to change; anything you omit keeps its current value. Omitting a field is not the same as sending `null`. + +::: warning This write requires project mode +If the workspace manages types at the workspace level, this returns `409 work_item_types_managed_at_workspace`. Edit the type on [the workspace surface](/api-reference/v2/workspace-work-item-types/update-workspace-work-item-type) instead. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the type belongs to. + + + + + +The id of the work item type to update. + + + +
+
+ +
+ +### Body Parameters + +Every field is optional — send the subset you are changing. + +
+ + + +New display name for the type. Maximum 255 characters. Renaming is safe: work items reference the type by `type_id`, not by name. + + + + + +What this type is for. It is returned as `type_description` by the [schema endpoint](/api-reference/v2/work-item-types/get-work-item-type-schema), so this is the field to edit when integrations are choosing the wrong type. + + + + + +Whether the type can be assigned to new work items. Setting it to `false` retires the type from pickers without deleting it — existing work items keep the type and stay readable. This is the safe alternative to [delete](/api-reference/v2/work-item-types/delete-work-item-type), which a type in use rejects. + + + + + +Your system's identifier for this type, for sync and import correlation. Maximum 255 characters, nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters, nullable. + + + +
+
+ +::: info is_default, is_epic and level are not accepted here +`is_default` moves through [mark-default](/api-reference/v2/work-item-types/mark-default-work-item-type), which is a dedicated request precisely because promoting one type demotes another. `is_epic` and `level` are read-only, as is `logo_props`. +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ---------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | A field over its 255-character limit, or `"is_active": false` on the default type. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't update work item types. | +| `404` | `not_found` | No such type, project, or workspace — or the type belongs to another project. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | Another type in this project already uses this `external_id` and `external_source` pair. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240", + "name": "Defect", + "description": "A verified regression in shipped behavior", + "is_active": true, + "is_default": false, + "is_epic": false, + "level": 0, + "logo_props": { + "in_use": "icon", + "icon": { + "name": "AlertCircle", + "background_color": "#EF5974" + } + }, + "created_at": "2026-01-14T09:24:03.117482Z" +} +``` + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "The default work item type cannot be deactivated.", + "errors": [] +} +``` + + + +
+
+ +::: warning The default type cannot be deactivated +Sending `"is_active": false` for the project's default type is rejected — every project needs a type for untyped work items to land on. Promote another type with [mark-default](/api-reference/v2/work-item-types/mark-default-work-item-type) first, then deactivate this one. +::: + +::: tip Deactivate rather than delete +[Delete](/api-reference/v2/work-item-types/delete-work-item-type) refuses to remove a type that still has work items. `"is_active": false` is the usual intent anyway: the type stops appearing in pickers, historical work items keep rendering correctly, and the change is reversible. +::: diff --git a/docs/api-reference/v2/work-item-worklogs/create-worklog.md b/docs/api-reference/v2/work-item-worklogs/create-worklog.md new file mode 100644 index 00000000..c537be2c --- /dev/null +++ b/docs/api-reference/v2/work-item-worklogs/create-worklog.md @@ -0,0 +1,196 @@ +--- +title: Create a worklog +description: Create a worklog with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a worklog, worklogs, worklogs create +--- + +# Create a worklog + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/worklogs/ +
+ +
+
+ +Worklogs record time spent on a work item. Create a worklog. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Time logged, in minutes. + + + + + +Free-form description. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `duration`, `id`, `logged_by_id`, `updated_at`, `work_item_id`. + + + + + +Comma-separated relations to embed: `logged_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.worklogs:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "duration": 90, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logged_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-worklogs/delete-worklog.md b/docs/api-reference/v2/work-item-worklogs/delete-worklog.md new file mode 100644 index 00000000..8fbaeb75 --- /dev/null +++ b/docs/api-reference/v2/work-item-worklogs/delete-worklog.md @@ -0,0 +1,168 @@ +--- +title: Delete a worklog +description: Delete a worklog with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a worklog, worklogs, worklogs destroy +--- + +# Delete a worklog + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/worklogs/{pk}/ +
+ +
+
+ +Worklogs record time spent on a work item. Delete a worklog. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + + + +The worklog id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `duration`, `id`, `logged_by_id`, `updated_at`, `work_item_id`. + + + + + +Comma-separated relations to embed: `logged_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.worklogs:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No worklog matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-worklogs/get-project-worklog-summary.md b/docs/api-reference/v2/work-item-worklogs/get-project-worklog-summary.md new file mode 100644 index 00000000..66117832 --- /dev/null +++ b/docs/api-reference/v2/work-item-worklogs/get-project-worklog-summary.md @@ -0,0 +1,122 @@ +--- +title: Get the project worklog summary +description: Get the project worklog summary with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get the project worklog summary, project worklog summary, project worklogs summary +--- + +# Get the project worklog summary + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/worklogs/summary/ +
+ +
+
+ +Worklogs record time spent on a work item. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Scopes + +This endpoint is exempt from fine-grained scope checks. + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +[ + { + "duration": 90, + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } +] +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-worklogs/get-worklog.md b/docs/api-reference/v2/work-item-worklogs/get-worklog.md new file mode 100644 index 00000000..e40d3f69 --- /dev/null +++ b/docs/api-reference/v2/work-item-worklogs/get-worklog.md @@ -0,0 +1,175 @@ +--- +title: Get a worklog +description: Read a worklog with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a worklog, worklogs, worklogs retrieve +--- + +# Get a worklog + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/worklogs/{pk}/ +
+ +
+
+ +Worklogs record time spent on a work item. Read a single worklog by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + + + +The worklog id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `duration`, `id`, `logged_by_id`, `updated_at`, `work_item_id`. + + + + + +Comma-separated relations to embed: `logged_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.worklogs:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "duration": 90, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logged_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No worklog matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-worklogs/list-worklogs.md b/docs/api-reference/v2/work-item-worklogs/list-worklogs.md new file mode 100644 index 00000000..328c7a42 --- /dev/null +++ b/docs/api-reference/v2/work-item-worklogs/list-worklogs.md @@ -0,0 +1,244 @@ +--- +title: List worklogs +description: List worklogs with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list worklogs, worklogs, worklogs list +--- + +# List worklogs + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/worklogs/ +
+ +
+
+ +Worklogs record time spent on a work item. List the worklogs you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `duration__gte`. + + + + + +Filter by `duration__lte`. + + + + + +Filter by `logged_by_id`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `duration`, `id`, `logged_by_id`, `updated_at`, `work_item_id`. + + + + + +Comma-separated relations to embed: `logged_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.worklogs:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "duration": 90, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logged_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "duration": 90, + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "logged_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-item-worklogs/overview.md b/docs/api-reference/v2/work-item-worklogs/overview.md new file mode 100644 index 00000000..ac27fbf9 --- /dev/null +++ b/docs/api-reference/v2/work-item-worklogs/overview.md @@ -0,0 +1,86 @@ +--- +title: Work item worklogs overview +description: The Plane API v2 worklog object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, work item worklogs, worklog object +--- + +# Work item worklogs overview + +Worklogs record time spent on a work item. + +
+
+ +## The worklog object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description` _string_ + + Free-form description. + +- `duration` _integer_ + + Time logged, in minutes. + +- `id` _string (uuid)_ + + Unique identifier. + +- `logged_by_id` _string (uuid)_ + + The related logged by. + +- `updated_at` _string (date-time)_ + + When the record last changed. + +- `work_item_id` _string (uuid)_ + + The related work item. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "duration": 90, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logged_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------------------------------------------ | ------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/worklogs/` | List worklogs | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/worklogs/` | Create a worklog | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/worklogs/{pk}/` | Delete a worklog | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/worklogs/{pk}/` | Get a worklog | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/worklogs/{pk}/` | Update a worklog | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/worklogs/summary/` | Get the project worklog summary | + +## Response shaping + +Every worklog read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/work-item-worklogs/update-worklog.md b/docs/api-reference/v2/work-item-worklogs/update-worklog.md new file mode 100644 index 00000000..11573c6d --- /dev/null +++ b/docs/api-reference/v2/work-item-worklogs/update-worklog.md @@ -0,0 +1,214 @@ +--- +title: Update a worklog +description: Update a worklog with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a worklog, worklogs, worklogs partial update +--- + +# Update a worklog + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/worklogs/{pk}/ +
+ +
+
+ +Worklogs record time spent on a work item. Update a worklog. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The work item the resource hangs off. Accepts the work item UUID or its `PROJ-123` identifier. + + + + + +The worklog id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Free-form description. + + + + + +Time logged, in minutes. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `duration`, `id`, `logged_by_id`, `updated_at`, `work_item_id`. + + + + + +Comma-separated relations to embed: `logged_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items.worklogs:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "duration": 90, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "logged_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "work_item_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No worklog matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-items/archive-work-item.md b/docs/api-reference/v2/work-items/archive-work-item.md new file mode 100644 index 00000000..b4d39540 --- /dev/null +++ b/docs/api-reference/v2/work-items/archive-work-item.md @@ -0,0 +1,226 @@ +--- +title: Archive a work item +description: Archive a Plane work item with the v2 REST API. Bodyless POST, the archived_at timestamp, visibility of archived items, OAuth scopes, error codes, and code examples. +keywords: plane api v2, archive work item, archived_at, POST archive, hide work item, reversible archive +--- + +# Archive a work item + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{pk}/archive/ +
+ +
+
+ +Archive a work item: stamp `archived_at` with the current time and take it out of the active work item set. + +This is a **bodyless** `POST` — send no JSON at all. The response is the full work item in its usual read shape, with +`archived_at` now populated, so you can confirm the change without a follow-up read. + +Archiving is reversible with [Unarchive a work item](/api-reference/v2/work-items/unarchive-work-item), and it is +entirely separate from [deleting](/api-reference/v2/work-items/delete-work-item) — an archived work item still exists. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the work item belongs to. + + + + + +The work item's UUID. This lookup is UUID-only — a `PROJ-142` identifier here returns `404`. + + + +
+
+ +
+ +### Body Parameters + +None. The endpoint takes no request body; anything you send is ignored. + +
+ +::: warning Archiving twice returns 404 +Once archived, a work item is outside the default query set, so a second `archive` call on the same work item can't +resolve it and returns `404 not_found` — not a `409`, and not a silent success. If you're re-running a job, +treat a `404` here as "already archived" rather than an error worth retrying. +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `archived_at`, `assignee_ids`, `created_at`, `created_by_id`, `custom_fields`, `cycle_id`, `id`, `identifier`, `is_draft`, `label_ids`, `module_ids`, `name`, `parent_id`, `priority`, `project_id`, `sequence_id`, `start_date`, `state_id`, `target_date`, `type_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `assignees` (the assigned users), `cycle` (the cycle it belongs to), `labels` (the applied labels), `modules` (the modules it belongs to), `parent` (its parent work item), `state` (the work item's state object), `type` (its work item type). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request could not be processed — for example a `pk` that isn't a valid UUID. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't archive this work item. | +| `404` | `not_found` | No such work item, it's outside your project or tenant, or it is already archived. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The write collides with a protected-resource constraint. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect loop", + "identifier": "PROJ-142", + "sequence_id": 142, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": "2d9d1a97-5c6f-4a1e-9d5b-8c2f7e30b6a4", + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"], + "parent_id": null, + "start_date": "2026-01-12", + "target_date": "2026-01-20", + "is_draft": false, + "archived_at": "2026-02-03T11:47:19.204518Z", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "custom_fields": { + "Severity": { + "id": "5e2a7c81-4f39-4b60-a1d8-0c6b3e9f2d74", + "value": ["Sev-2"], + "value_detail": [{ "id": "c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03", "name": "Sev-2" }] + } + } +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "The requested resource was not found." +} +``` + + + +
+
+ +## What archiving changes + +- `archived_at` goes from `null` to a timestamp. It is the only field the call touches — state, assignees, dates, and + labels are untouched. +- The work item drops out of [List work items](/api-reference/v2/work-items/list-work-items) and out of plain detail + reads, including the [identifier route](/api-reference/v2/work-items/get-work-item-by-identifier). +- Nothing is deleted. Comments, links, and relations survive, and + [unarchiving](/api-reference/v2/work-items/unarchive-work-item) restores the work item exactly as it was. + +`archived_at` is read-only on the work item itself, so you cannot archive by `PATCH`ing a timestamp — this endpoint is +the only way in, and unarchive is the only way out. diff --git a/docs/api-reference/v2/work-items/bulk-work-items.md b/docs/api-reference/v2/work-items/bulk-work-items.md new file mode 100644 index 00000000..639feb46 --- /dev/null +++ b/docs/api-reference/v2/work-items/bulk-work-items.md @@ -0,0 +1,269 @@ +--- +title: Bulk write work items +description: Bulk write work items with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, bulk write work items, work items, work items bulk +--- + +# Bulk write work items + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/bulk/ +
+ +
+
+ +Work items are the issues, tasks and epics in a project. Write up to 100 work items in one request, with a per-row result for each. + +- Send `{"items": [...]}`, at most 100 per request. Add `"upsert": true` to match existing rows on `(external_source, external_id)` instead of always creating. +- The response is always `200`. Each row reports its own outcome, so a partial failure is reported rather than silently dropped — read `succeeded`/`failed` and the per-row `status`. +- Rows are written on independent savepoints: a failing row rolls back only itself. +- `?fields=` does not apply here — the bulk response is a per-row result envelope, not a resource body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The items. + + + + + +Reconcile each row on (external_source, external_id) instead of creating it. + + + +
+
+ +
+ +### Scopes + +`projects.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "failed": 1, + "results": [null], + "succeeded": 1 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-items/create-work-item.md b/docs/api-reference/v2/work-items/create-work-item.md new file mode 100644 index 00000000..8af2c110 --- /dev/null +++ b/docs/api-reference/v2/work-items/create-work-item.md @@ -0,0 +1,462 @@ +--- +title: Create a work item +description: Create a work item in a Plane project with the v2 REST API. Body parameters, id and human-readable inputs, priority values, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create work item, POST work items, state_id, assignees by email, labels by name, parent identifier, priority +--- + +# Create a work item + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/ +
+ +
+
+ +Create a work item in a project. Only `name` is required — everything else falls back to the project's defaults, so the +smallest useful create is one field. + +Each relation accepts either its id field or a **write-only human-readable parallel**: `state` instead of `state_id`, +`assignees` (emails) instead of `assignee_ids`, `labels` (names) instead of `label_ids`, and so on. Send one or the +other, never both. This is what lets an importer or an agent write a fully-populated work item without first resolving +six UUIDs. + +The response is the standard sparse read shape, with `custom_fields` populated. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project to create the work item in. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Title of the work item. Maximum 255 characters. The only required field. + + + + + +Rich-text body as HTML, for example `

Steps to reproduce…

`. The HTML is sanitized on the way in; content that +can't be sanitized is rejected with a `400`. + +`description_html` is **not** part of the read shape, so it will not appear in the response. + +
+ + + +One of `urgent`, `high`, `medium`, `low`, `none`. Defaults to `none`. + + + + + +The workflow state to create the work item in. Must be a state of this project. Omit it and the work item lands in the +project's default state. + + + + + +The state's **name** instead of its id, for example `In Progress`. Matched case-insensitively within the project. +Write-only. + +Unknown name or a name that matches more than one state is a `400`. Sending both `state` and `state_id` is a `400`. + + + + + +The work item type. Accepts a type owned by the project and a workspace-level type imported into it. Requires work item +types to be enabled. + + + + + +The type's **name** instead of its id, for example `Bug`. Matched case-insensitively among the project's non-epic +types. Write-only. + + + + + +The parent work item. The parent must be in the same workspace, but it may live in a different project. + + + + + +The parent's **identifier** instead of its id, for example `PROJ-118`. Resolved within the workspace. Write-only. + + + + + +User ids to assign. Each must be an active, assignable member of the project. + +Leave it out and the project's default assignee is applied, if one is configured. Send `[]` to create the work item +with no assignee at all. + + + + + +Member **email addresses** instead of ids, for example `["ana@example.com"]`. Every address must belong to an active, +assignable project member — any that don't are named in the `400`. Write-only. + + + + + +Label ids to apply. Each must be a label of this project, or a workspace-level label available to it. + + + + + +Label **names** instead of ids, for example `["regression", "auth"]`. Matched case-insensitively against the project's +labels and the workspace-level labels available to it. A name that exists at both levels is ambiguous and returns a +`400` telling you to use `label_ids`. Write-only. + + + + + +The estimate point to assign, from the project's active estimate system. + + + + + +The estimate point's **value** instead of its id, for example `5` or `L`. Resolved against the project's active +estimate. Write-only. + + + + + +Planned start, for example `2026-01-12`. Must not be after `target_date`. + + + + + +Planned due date, for example `2026-01-20`. + + + + + +Your system's identifier for this work item, for sync and import correlation. Maximum 255 characters. + +Stored and filterable on [List work items](/api-reference/v2/work-items/list-work-items), but not returned on reads. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. + + + +
+
+ +::: info Setting custom property values +Work item type custom properties are written through a separate `custom_fields` object on the request body, keyed by +property name, and are validated against the type you selected. Its shape depends on the type you chose, so it is not +declared in the OpenAPI schema — read it from +[the type's schema endpoint](/api-reference/v2/work-item-types/get-work-item-type-schema). It requires custom +properties to be enabled for the workspace — sending it otherwise is a `400`. Properties you omit are filled from the +type's defaults. See [work item properties](/api-reference/v2/work-item-properties/list-work-item-properties). +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `archived_at`, `assignee_ids`, `created_at`, `created_by_id`, `custom_fields`, `cycle_id`, `id`, `identifier`, `is_draft`, `label_ids`, `module_ids`, `name`, `parent_id`, `priority`, `project_id`, `sequence_id`, `start_date`, `state_id`, `target_date`, `type_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `assignees` (the assigned users), `cycle` (the cycle it belongs to), `labels` (the applied labels), `modules` (the modules it belongs to), `parent` (its parent work item), `state` (the work item's state object), `type` (its work item type). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | Missing `name`; a bad `priority`; `start_date` after `target_date`; an unresolvable or ambiguous `state`/`type`/`parent`/`assignees`/`labels`/`estimate`; both a name and its `*_id`; or an id from another project or workspace. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | A workflow rule forbids creating a work item in the requested state. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The write collides with a uniqueness or protected-resource constraint. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect loop", + "identifier": "PROJ-142", + "sequence_id": 142, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": null, + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": [], + "parent_id": null, + "start_date": null, + "target_date": "2026-01-20", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "custom_fields": {} +} +``` + + + + + + + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "The request body failed validation.", + "errors": [ + { + "field": "state", + "code": "invalid", + "message": "No state named 'In Reviewing' in this project." + } + ] +} +``` + + + +
+
+ +## Names or ids, not both + +The human-readable inputs are resolved to their id equivalents before anything is written, and the rules are the same +for all six: + +| You send | What happens | +| ------------------------------------ | --------------------------------------------------------- | +| `state_id` only | Used directly. Must belong to this project. | +| `state` only | Resolved by name, case-insensitively, within the project. | +| `state` and `state_id` | `400` — "Provide either state_id or state, not both." | +| A name matching nothing | `400` naming the field and the value you sent. | +| A name matching more than one record | `400` telling you to use the id field instead. | + +Prefer the id fields in code you control and reuse across runs; prefer the names when you're bridging a system whose +vocabulary is already words — a Jira status, a Slack email, a label the user typed. + +## Workflow rules can reject a create + +If the project runs workflow rules, creating a work item directly into a restricted state returns `403` with +`workflow_transition_denied` — distinct from a plain permission `forbidden`. Branch on the `code`: `forbidden` means +ask for access, `workflow_transition_denied` means create it in an allowed state and move it from there. diff --git a/docs/api-reference/v2/work-items/delete-work-item.md b/docs/api-reference/v2/work-items/delete-work-item.md new file mode 100644 index 00000000..09801542 --- /dev/null +++ b/docs/api-reference/v2/work-items/delete-work-item.md @@ -0,0 +1,198 @@ +--- +title: Delete a work item +description: Delete a work item in a Plane project with the v2 REST API. Soft-delete behavior, the 204 response, when to archive instead, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete work item, DELETE work item, soft delete, 204 no content, archive vs delete +--- + +# Delete a work item + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{pk}/ +
+ +
+
+ +Delete a work item. The delete is a soft delete — the row is retained internally — but it is gone from every API +surface, and there is no endpoint to bring it back. + +A successful delete returns `204` with an empty body. Deleting a work item that is already gone returns `404`, so the +call is not idempotent from the client's point of view. + +If you want the work item out of the way but recoverable, use +[Archive a work item](/api-reference/v2/work-items/archive-work-item) instead. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the work item belongs to. + + + + + +The work item's UUID. This lookup is UUID-only — a `PROJ-142` identifier here returns `404`. + + + +
+
+ +::: warning Deleting is not reversible through the API +There is no undelete endpoint. Confirm with the user before wiring this into an automation, and prefer archiving for +anything you might want back. +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `archived_at`, `assignee_ids`, `created_at`, `created_by_id`, `custom_fields`, `cycle_id`, `id`, `identifier`, `is_draft`, `label_ids`, `module_ids`, `name`, `parent_id`, `priority`, `project_id`, `sequence_id`, `start_date`, `state_id`, `target_date`, `type_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `assignees` (the assigned users), `cycle` (the cycle it belongs to), `labels` (the applied labels), `modules` (the modules it belongs to), `parent` (its parent work item), `state` (the work item's state object), `type` (its work item type). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request could not be processed — for example a `pk` that isn't a valid UUID. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't delete this work item. | +| `404` | `not_found` | No such work item, or it's outside your project or tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The delete collides with a protected-resource constraint. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```text +No content. +``` + + + + + +```json +{ + "type": "forbidden", + "code": "forbidden", + "detail": "You do not have permission to delete this work item." +} +``` + + + +
+
+ +## Who can delete + +Deleting is gated per work item, not just per project. Depending on how the project's roles are configured, the right +to delete may be limited to administrators plus the work item's own creator — so a token that can edit a work item may +still get a `403` when it tries to delete one it didn't create. The response is `403 forbidden` and nothing is written. + +## Delete or archive? + +| | Delete | Archive | +| --------------- | ------------------------- | ---------------------------------------------------------------------- | +| Endpoint | This page | [Archive](/api-reference/v2/work-items/archive-work-item) | +| Response | `204`, empty body | `200` with the work item | +| Reversible | No | Yes, via [unarchive](/api-reference/v2/work-items/unarchive-work-item) | +| Effect on reads | Gone everywhere | Excluded from the default list and detail reads | +| Use it for | Mistakes, spam, test data | Finished or abandoned work you want to keep | diff --git a/docs/api-reference/v2/work-items/get-work-item-by-identifier.md b/docs/api-reference/v2/work-items/get-work-item-by-identifier.md new file mode 100644 index 00000000..886c48ee --- /dev/null +++ b/docs/api-reference/v2/work-items/get-work-item-by-identifier.md @@ -0,0 +1,233 @@ +--- +title: Get a work item by identifier +description: Look up a Plane work item by its PROJ-123 human key across a workspace, without knowing the project UUID. Path parameters, expandable relations, OAuth scopes, errors, and code examples. +keywords: plane api v2, work item identifier, PROJ-123, human key, lookup work item by key, workspace scoped work item +--- + +# Get a work item by identifier + +
+ GET + /api/v2/workspaces/{slug}/work-items/{identifier}/ +
+ +
+
+ +Retrieve a work item by its human key — `PROJ-142` — anywhere in the workspace. + +This route exists for callers that **don't know the project UUID**. A `PROJ-142` in a commit message, a chat command, a +support ticket, or an LLM prompt carries no project id, and making the caller first list projects, find the one whose +identifier is `PROJ`, and then call the project-scoped route would be three requests to answer one question. Here it is +one request against the workspace. + +The route is read-only. Creates, updates, deletes, and archiving all go through the project-scoped UUID routes. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The work item's human key: the project identifier, a hyphen, and the work item's number — `PROJ-142`. + +The project part must start with a letter, and it is upper-cased before lookup, so `proj-142` resolves the same work +item as `PROJ-142`. A project identifier is unique within a workspace, which is what makes this key unambiguous. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Comma-separated relations to embed alongside the ids: `assignees` (the assigned users), `cycle` (the cycle it belongs to), `labels` (the applied labels), `modules` (the modules it belongs to), `parent` (its parent work item), `state` (the work item's state object), `type` (its work item type). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +::: info Everything unresolvable is a 404 +A malformed key (`PROJ_142`, `142`, `-142`), a key from a project you can't see, a key in another workspace, and a key +that resolves to nothing all return the same `404`. That is deliberate: the response never reveals whether a work item +exists, only whether _you_ can read it. Archived work items are not served from this route either. +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `archived_at`, `assignee_ids`, `created_at`, `created_by_id`, `custom_fields`, `cycle_id`, `id`, `identifier`, `is_draft`, `label_ids`, `module_ids`, `name`, `parent_id`, `priority`, `project_id`, `sequence_id`, `start_date`, `state_id`, `target_date`, `type_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_items:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | --------------------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your token scope can't read work items. | +| `404` | `not_found` | Malformed identifier, no such work item, it's outside your tenant or visibility, or archived. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect loop", + "identifier": "PROJ-142", + "sequence_id": 142, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": "2d9d1a97-5c6f-4a1e-9d5b-8c2f7e30b6a4", + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"], + "parent_id": null, + "start_date": "2026-01-12", + "target_date": "2026-01-20", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "custom_fields": { + "severity": { + "id": "b52e7c18-4d3f-4a90-8e61-0f7a3c9d2b45", + "value": "Sev-1", + "value_detail": { + "id": "7c0a5f39-2e84-4b17-9a6c-1d8e4f2b60c9", + "name": "Sev-1", + "logo_props": {} + } + } + } +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "The requested resource was not found." +} +``` + + + +
+
+ +## Getting from the identifier to a write + +The response body is the full read shape, so it hands you everything a follow-up write needs — `id` for the detail +route. The project UUID is not in the read shape, so pair the lookup with the project you already know, or resolve it +once and cache it: + +```bash +# 1. resolve the human key +GET /api/v2/workspaces/my-team/work-items/PROJ-142/ + +# 2. write against the project-scoped UUID route +PATCH /api/v2/workspaces/my-team/projects/{project_id}/work-items/8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13/ +``` + +## Identifier vs UUID + +| | `identifier` | `id` | +| ---------- | ----------------------------------------- | ----------------------------------------------------------------------------- | +| Example | `PROJ-142` | `8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13` | +| Scope | Unique within a workspace | Globally unique | +| Route | This page — reads only | [Get a work item](/api-reference/v2/work-items/get-work-item) and every write | +| Comes from | The project identifier plus `sequence_id` | Assigned by Plane at creation | + +Both are stable. Use the identifier where a human is in the loop, and the UUID everywhere else. diff --git a/docs/api-reference/v2/work-items/get-work-item.md b/docs/api-reference/v2/work-items/get-work-item.md new file mode 100644 index 00000000..2be1a213 --- /dev/null +++ b/docs/api-reference/v2/work-items/get-work-item.md @@ -0,0 +1,252 @@ +--- +title: Get a work item +description: Retrieve a single work item by UUID with the Plane v2 REST API. Path parameters, expandable relations, custom fields, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get work item, retrieve work item, work item by id, expand state assignees, custom fields +--- + +# Get a work item + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{pk}/ +
+ +
+
+ +Retrieve one work item by its UUID. This is the endpoint to use after a list call, or any time you already hold the +work item's `id`. + +Unlike the list endpoint, this response populates `custom_fields` — the work item type's custom property values, keyed +by property name. If you only have a `PROJ-142`-style key and not the project UUID, use +[Get a work item by identifier](/api-reference/v2/work-items/get-work-item-by-identifier) instead. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the work item belongs to. + + + + + +The work item's UUID. This lookup is UUID-only — a `PROJ-142` identifier here returns `404`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Comma-separated relations to embed alongside the ids: `assignees` (the assigned users), `cycle` (the cycle it belongs to), `labels` (the applied labels), `modules` (the modules it belongs to), `parent` (its parent work item), `state` (the work item's state object), `type` (its work item type). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +::: warning Archived work items are not returned +An archived work item sits outside the default query set, so a read of one returns `404` — the same response as a work +item that never existed. [Unarchive](/api-reference/v2/work-items/unarchive-work-item) it first if you need to read it +again. See [Archive a work item](/api-reference/v2/work-items/archive-work-item). +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `archived_at`, `assignee_ids`, `created_at`, `created_by_id`, `custom_fields`, `cycle_id`, `id`, `identifier`, `is_draft`, `label_ids`, `module_ids`, `name`, `parent_id`, `priority`, `project_id`, `sequence_id`, `start_date`, `state_id`, `target_date`, `type_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_items:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read work items in this project. | +| `404` | `not_found` | No such work item, it belongs to another project or tenant, or it's archived. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect loop", + "identifier": "PROJ-142", + "sequence_id": 142, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": "2d9d1a97-5c6f-4a1e-9d5b-8c2f7e30b6a4", + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"], + "parent_id": null, + "start_date": "2026-01-12", + "target_date": "2026-01-20", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "custom_fields": { + "severity": { + "id": "b52e7c18-4d3f-4a90-8e61-0f7a3c9d2b45", + "value": "Sev-1", + "value_detail": { + "id": "7c0a5f39-2e84-4b17-9a6c-1d8e4f2b60c9", + "name": "Sev-1", + "logo_props": {} + } + } + } +} +``` + + + + + +```json +{ + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect loop", + "identifier": "PROJ-142", + "sequence_id": 142, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": "2d9d1a97-5c6f-4a1e-9d5b-8c2f7e30b6a4", + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"], + "parent_id": null, + "start_date": "2026-01-12", + "target_date": "2026-01-20", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "custom_fields": {}, + "state": { + "id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "name": "In Progress", + "color": "#3f76ff", + "group": "started" + }, + "assignees": [ + { + "id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "display_name": "ana", + "avatar_url": "https://assets.plane.so/avatars/ana.png", + "email": "ana@example.com" + } + ] +} +``` + + + +
+
+ +## What you get here that a list doesn't give you + +- **`custom_fields`** — the work item type's custom property values. The list endpoint returns `null` for this field on + every row to avoid resolving properties per row; only single-item reads populate it. +- **Cheaper expansion** — `?expand=` works on both, but expanding one work item is a fixed cost while expanding a full + page multiplies it. + +Everything else — the field set, the sparse `*_id` shape — is identical to a row from +[List work items](/api-reference/v2/work-items/list-work-items). diff --git a/docs/api-reference/v2/work-items/list-work-items.md b/docs/api-reference/v2/work-items/list-work-items.md new file mode 100644 index 00000000..e084c57a --- /dev/null +++ b/docs/api-reference/v2/work-items/list-work-items.md @@ -0,0 +1,498 @@ +--- +title: List work items +description: List work items in a Plane project with the v2 REST API. Filters, search, ordering, offset and cursor pagination, expandable relations, OAuth scopes, and code examples. +keywords: plane api v2, list work items, filter work items, work item pagination, order_by, state_group, priority filter, cursor pagination +--- + +# List work items + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/ +
+ +
+
+ +List the work items in a project. This is the endpoint you build reports, syncs, and board views on: it takes a wide +set of filters, orders by stored columns or by meaning, and paginates by offset or by cursor. + +Results are always scoped to the project in the path and to what your token is allowed to see. Filters can only narrow +that set — they never widen it. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project to list work items from. + + + +
+
+ +
+ +### Filters + +Every filter is optional, and filters combine with AND. Each concept below has a base parameter plus the `__in` and +`__isnull` variants noted in its description. + +
+ + + +Match work items in a specific state. Use `state_id__in` with a comma-separated list to match any of several states. + + + + + +Match by the state's workflow group instead of a specific state — stable across projects that name their states +differently. One of `backlog`, `unstarted`, `started`, `completed`, `cancelled`, `triage`. + +Use `state_group__in` for several groups, for example `?state_group__in=started,completed`. + + + + + +One of `urgent`, `high`, `medium`, `low`, `none`. Use `priority__in` for several, for example +`?priority__in=urgent,high`. + + + + + +Match work items assigned to a user. `assignee_id__in` matches any of several users; `assignee_id__isnull=true` +returns only unassigned work items. + + + + + +Match work items carrying a label. `label_id__in` matches any of several labels; `label_id__isnull=true` returns only +unlabeled work items. + + + + + +Match work items of a given work item type. `type_id__in` accepts a comma-separated list. + + + + + +Match the children of a work item. `parent_id__in` accepts several parents; `parent_id__isnull=true` returns only +top-level work items. + + + + + +Match work items in a cycle. `cycle_id__in` accepts several cycles; `cycle_id__isnull=true` returns the backlog of +work items in no cycle at all. + + + + + +Match work items in a module. `module_id__in` accepts several modules; `module_id__isnull=true` returns work items in +no module. + + + + + +Match the single work item with this number within the project — the `142` of `PROJ-142`. + + + + + +Filter drafts in or out. Drafts are work items still being composed in the Plane app. + + + + + +Correlation filters for sync and import. `external_source` is the system a record came from and `external_id` is its +key there, so the pair is how you find the Plane work item that mirrors a row in your own database. + +These values are not returned on reads — the lookup is one-way. + + + + + +Bound the creation timestamp. Pass an ISO 8601 datetime, for example `2026-01-01T00:00:00Z`. Use both for a window. + + + + + +Bound the last-modified timestamp. This is the pair to use for incremental sync — poll with +`?updated_at__gte=&order_by=updated_at`. + +`updated_at` is filterable and orderable but is not part of the read shape, so it does not come back in the response +body. + + + + + +Bound the planned start date, for example `2026-01-01`. + + + + + +Bound the planned due date. `?target_date__lte=2026-01-31&state_group__in=backlog,unstarted,started` is the "what is +about to slip" query. + + + +
+
+ +
+ +### Search and ordering + +
+ + + +Free-text search over the work item name. + + + + + +Field to sort by. Prefix with `-` for descending. Defaults to `-created_at`. + +- `created_at`, `-created_at` +- `updated_at`, `-updated_at` +- `sequence_id`, `-sequence_id` +- `id`, `-id` +- `sort_order`, `-sort_order` — the manual board ordering +- `priority`, `-priority` — semantic: `urgent` → `high` → `medium` → `low` → `none`, not alphabetical +- `state_group`, `-state_group` — semantic: workflow order, not alphabetical + +`priority` and `state_group` sort by meaning, which is what you want for a board but is not cursor-eligible. Pair +either with `?paginate=cursor` and you get `400 ordering_not_cursor_eligible`; use the default offset pages instead. + + + +
+
+ +
+ +### Pagination + +Offset is the default. Cursor is opt-in per request. + +
+ + + +Page size. Defaults to 50, maximum 200. + + + + + +Rows to skip from the start of the result set. Maximum 10000 — past that, switch to cursor pagination. + + + + + +Defaults to `true`. Pass `?count=false` to skip the `COUNT(*)` and omit `total_count` from the envelope. Worth doing on +large projects when you only need the rows. + + + + + +Set to `cursor` to opt into keyset pagination. The envelope changes to `next_cursor` / `has_more` and drops +`total_count`. Use it for deep or long-running traversals where offset pages would drift as rows are inserted. Follow +the returned `next_cursor` as described in [Pagination](/api-reference/v2/pagination). + + + +
+
+ +
+ +### Expansion + +
+ + + +Comma-separated relations to embed alongside the ids: `assignees` (the assigned users), `cycle` (the cycle it belongs to), `labels` (the applied labels), `modules` (the modules it belongs to), `parent` (its parent work item), `state` (the work item's state object), `type` (its work item type). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +::: warning Bad filter values fail loudly, bad `order_by` does not +The enum-backed _filters_ (`priority`, `priority__in`, `state_group`, `state_group__in`) are validated against their +allowed values. A typo returns `400 invalid_request` naming the parameter — it does not silently return an empty page. +Treat an empty `data` array as a genuine "no matches". + +`order_by` and `paginate` are the exception: neither is validated. An unrecognized `order_by` silently falls back to the +default ordering, and anything other than `paginate=cursor` silently uses offset pagination — so check your spelling +there, because a typo shows up as an unexpected sort order or envelope rather than an error. +::: + +::: info Archived work items are excluded +The list returns active work items only — anything with `archived_at` set is filtered out, so archiving a work item +takes it off every page of this list. See [Archive a work item](/api-reference/v2/work-items/archive-work-item). +::: + +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `archived_at`, `assignee_ids`, `created_at`, `created_by_id`, `cycle_id`, `id`, `identifier`, `is_draft`, `label_ids`, `module_ids`, `name`, `parent_id`, `priority`, `project_id`, `sequence_id`, `start_date`, `state_id`, `target_date`, `type_id`. + +Naming `custom_fields` here is a `400` — it is only available on single-object responses. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`projects.work_items:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read work items in this project. | +| `404` | `not_found` | No such workspace or project, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect loop", + "identifier": "PROJ-142", + "sequence_id": 142, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": "2d9d1a97-5c6f-4a1e-9d5b-8c2f7e30b6a4", + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"], + "parent_id": null, + "start_date": "2026-01-12", + "target_date": "2026-01-20", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + }, + { + "id": "3b7d9e40-1c62-4a85-b0f3-9d5c2e6a8471", + "name": "Rate limit the invite endpoint", + "identifier": "PROJ-141", + "sequence_id": 141, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": null, + "assignee_ids": [], + "label_ids": [], + "parent_id": "a7e51d24-3b98-4c6d-9f10-7d2c8e4b5a61", + "start_date": null, + "target_date": null, + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next": 50, + "previous": null, + "total_count": 327, + "pagination": { "style": "offset" } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect loop", + "identifier": "PROJ-142", + "sequence_id": 142, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": "2d9d1a97-5c6f-4a1e-9d5b-8c2f7e30b6a4", + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"], + "parent_id": null, + "start_date": "2026-01-12", + "target_date": "2026-01-20", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430" + } + ], + "next_cursor": "b3A9MTcxJmxpbWl0PTUw", + "has_more": true, + "pagination": { "style": "cursor" } +} +``` + + + +
+
+ +## Why `custom_fields` is absent here + +Custom property values are resolved per work item, and doing that for a full page would mean an extra lookup pass for +every row. So `custom_fields` is **detail-only**: the key is simply not present on list rows. + +Absent, not `null`. A `null` would be ambiguous — it can't distinguish "this work item has no property values" from +"this endpoint didn't look". Omitting the key says the second thing honestly. Asking for it anyway is a `400` rather +than a silent empty answer: + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "One or more fields failed validation.", + "errors": [ + { + "field": "fields", + "code": "invalid", + "message": "custom_fields is not available on collection responses; retrieve the work item." + } + ] +} +``` + +To read custom property values, fetch the work item on its own: +[Get a work item](/api-reference/v2/work-items/get-work-item) or +[Get by identifier](/api-reference/v2/work-items/get-work-item-by-identifier). Both populate `custom_fields`, as do +create, update, upsert and the archive verbs — every single-object response. + +## Paging through everything + +Branch your client on `pagination.style` rather than guessing from the keys present. + +- **Offset** — follow the integer in `next` until it is `null`. Fine for the first few thousand rows; `offset` caps at 10000. +- **Cursor** — send `?paginate=cursor`, then follow `next_cursor` while `has_more` is `true`. There is no + `total_count`, and rows inserted mid-traversal won't shift your position. + +Full details in [Pagination](/api-reference/v2/pagination). diff --git a/docs/api-reference/v2/work-items/list-workspace-work-items.md b/docs/api-reference/v2/work-items/list-workspace-work-items.md new file mode 100644 index 00000000..d19689de --- /dev/null +++ b/docs/api-reference/v2/work-items/list-workspace-work-items.md @@ -0,0 +1,461 @@ +--- +title: List work items across a workspace +description: List workspace work items with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list work items across a workspace, workspace work items, workspace work items list +--- + +# List work items across a workspace + +
+ GET + /api/v2/workspaces/{slug}/work-items/ +
+ +
+
+ +Work items are the issues, tasks and epics in a project. List the workspace work items you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Filter by `assignee_id`. + + + + + +Multiple values may be separated by commas. + + + + + +Filter by `assignee_id__isnull`. + + + + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `created_at__gte`. + + + + + +Filter by `created_at__lte`. + + + + + +Filter by `cycle_id`. + + + + + +Multiple values may be separated by commas. + + + + + +Filter by `cycle_id__isnull`. + + + + + +Filter by `external_id`. + + + + + +Filter by `external_source`. + + + + + +Filter by `is_draft`. + + + + + +Filter by `label_id`. + + + + + +Multiple values may be separated by commas. + + + + + +Filter by `label_id__isnull`. + + + + + +Filter by `module_id`. + + + + + +Multiple values may be separated by commas. + + + + + +Filter by `module_id__isnull`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Filter by `parent_id`. + + + + + +Multiple values may be separated by commas. + + + + + +Filter by `parent_id__isnull`. + + + + + +Page size (max 200). + + + + + +- `urgent` - Urgent +- `high` - High +- `medium` - Medium +- `low` - Low +- `none` - None + +One of `high`, `low`, `medium`, `none`, `urgent`. + + + + + +Multiple values may be separated by commas. + +- `urgent` - Urgent +- `high` - High +- `medium` - Medium +- `low` - Low +- `none` - None + + + + + +Filter by `project_id`. + + + + + +Multiple values may be separated by commas. + + + + + +A search term. + + + + + +Filter by `sequence_id`. + + + + + +Filter by `start_date__gte`. + + + + + +Filter by `start_date__lte`. + + + + + +- `backlog` - Backlog +- `unstarted` - Unstarted +- `started` - Started +- `completed` - Completed +- `cancelled` - Cancelled +- `triage` - Triage + +One of `backlog`, `cancelled`, `completed`, `started`, `triage`, `unstarted`. + + + + + +Multiple values may be separated by commas. + +- `backlog` - Backlog +- `unstarted` - Unstarted +- `started` - Started +- `completed` - Completed +- `cancelled` - Cancelled +- `triage` - Triage + + + + + +Filter by `state_id`. + + + + + +Multiple values may be separated by commas. + + + + + +Filter by `target_date__gte`. + + + + + +Filter by `target_date__lte`. + + + + + +Filter by `type_id`. + + + + + +Multiple values may be separated by commas. + + + + + +Filter by `updated_at__gte`. + + + + + +Filter by `updated_at__lte`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `assignee_ids`, `created_at`, `created_by_id`, `cycle_id`, `id`, `identifier`, `is_draft`, `label_ids`, `module_ids`, `name`, `parent_id`, `priority`, `project_id`, `sequence_id`, `start_date`, `state_id`, `target_date`, `type_id`. + + + + + +Comma-separated relations to embed: `assignees`, `cycle`, `labels`, `modules`, `parent`, `state`, `type`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "archived_at": null, + "assignee_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "custom_fields": {}, + "cycle_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "identifier": "PROJ", + "is_draft": false, + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "module_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "name": "Example name", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "priority": "high", + "project_id": "4af68566-94a4-4eb3-94aa-50dc9427067b", + "sequence_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "start_date": "2026-01-12", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20", + "type_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/work-items/overview.md b/docs/api-reference/v2/work-items/overview.md new file mode 100644 index 00000000..6e16c1ac --- /dev/null +++ b/docs/api-reference/v2/work-items/overview.md @@ -0,0 +1,276 @@ +--- +title: Work items overview +description: The Plane API v2 work item object. Sparse id-based reads, the PROJ-123 identifier, custom fields, human-readable write inputs, expandable relations, and every work item endpoint. +keywords: plane api v2, work items, issues, tasks, sequence id, identifier, custom fields, expand, priority, archive work item +--- + +# Work items overview + +A work item is the unit of work in Plane — the thing that gets assigned, scheduled, moved through a workflow, and +closed. Every work item belongs to exactly one project and sits in exactly one [state](/api-reference/v2/states/overview) +of that project's workflow. + +Work items are the most heavily used resource in the API, so the v2 shape is deliberately narrow: reads return **ids, +not nested objects**, and every relation is a `*_id` or `*_ids` field you can resolve on your own schedule or pull in +with [`?expand=`](/api-reference/v2/expanding-relations). Writes go the other way — they accept ids **and** the +human-readable names you already have, so you rarely need a lookup round trip before creating something. + +[Learn more about work items](https://docs.plane.so/core-concepts/work-items/overview) + +
+
+ +## The work item object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the work item. This is the `{pk}` on every project-scoped detail route. + +- `name` _string_ + + Title of the work item. Maximum 255 characters. + +- `identifier` _string_ + + The human key, for example `PROJ-142`. It is the project's identifier joined to `sequence_id`, and it is what people + paste into chat and commit messages. Use it with + [Get a work item by identifier](/api-reference/v2/work-items/get-work-item-by-identifier) when you don't have the + project UUID. + +- `sequence_id` _integer_ + + The work item's number within its project. Assigned by Plane and never reused. + +- `priority` _string_ + + One of `urgent`, `high`, `medium`, `low`, or `none`. Never null — an unprioritized work item reads `none`. + +- `state_id` _string (uuid)_ + + The workflow state the work item is currently in. + +- `type_id` _string (uuid)_ + + The work item type. `null` when the project has no types enabled or the item is untyped. + +- `assignee_ids` _array of string_ + + User ids assigned to the work item. Empty array when unassigned. + +- `label_ids` _array of string_ + + Label ids applied to the work item. Empty array when unlabeled. + +- `parent_id` _string (uuid)_ + + The parent work item, or `null` for a top-level item. A parent may live in another project of the same workspace. + +- `start_date` _string (date)_ + + Planned start, or `null`. + +- `target_date` _string (date)_ + + Planned due date, or `null`. + +- `is_draft` _boolean_ + + Whether the work item is still a draft. Drafts are created in the Plane app and are excluded from most boards. + +- `archived_at` _string (date-time)_ + + When the work item was archived, or `null` if it is active. See [Archiving](#archiving-and-deleting). + +- `created_at` _string (date-time)_ + + When the work item was created. + +- `created_by_id` _string (uuid)_ + + The user who created the work item. `null` for items created by an automation with no acting user. + +- `custom_fields` _object_ + + Values of the work item type's custom properties, keyed by property name. **Populated only on single-item + responses** — it is always `null` on the list endpoint. See [Custom fields](#custom-fields). + +::: info The read shape is sparse and fixed +`description_html`, `external_id`, `external_source`, and `estimate_point_id` are accepted on writes but are **not** +part of the read shape — they will not come back on any response. There is also no `updated_at`, no `project_id`, and +no `workspace_id`: you already know the project from the path. +::: + +
+
+ + + +```json +{ + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect loop", + "identifier": "PROJ-142", + "sequence_id": 142, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": "2d9d1a97-5c6f-4a1e-9d5b-8c2f7e30b6a4", + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"], + "parent_id": null, + "start_date": "2026-01-12", + "target_date": "2026-01-20", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "custom_fields": { + "severity": { + "id": "b52e7c18-4d3f-4a90-8e61-0f7a3c9d2b45", + "value": "Sev-1", + "value_detail": { + "id": "7c0a5f39-2e84-4b17-9a6c-1d8e4f2b60c9", + "name": "Sev-1", + "logo_props": {} + } + }, + "root_cause": { + "id": "9e3b6c21-7f45-4d80-a1e9-5c8b2d7f4a36", + "value": "Stale session cookie", + "value_detail": null + } + } +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------------------------------- | ---------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/` | List work items | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/` | Create a work item | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/bulk/` | Bulk write work items | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/upsert/` | Upsert a work item | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{pk}/` | Delete a work item | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{pk}/` | Get a work item | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{pk}/` | Update a work item | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{pk}/archive/` | Archive a work item | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{pk}/unarchive/` | Unarchive a work item | +| `GET` | `/api/v2/workspaces/{slug}/work-items/` | List work items across a workspace | +| `GET` | `/api/v2/workspaces/{slug}/work-items/{identifier}/` | Get a work item by identifier | + +Every project-scoped route needs `projects.work_items:read` for `GET` and `projects.work_items:write` for `POST`, +`PATCH`, and `DELETE`. + +## Two ways to identify a work item + +| Key | Looks like | Route | +| -------------------- | -------------------------------------- | ----------------------------------------------------------------- | +| UUID (`id`) | `8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13` | `.../projects/{project_id}/work-items/{pk}/` — reads and writes | +| Human (`identifier`) | `PROJ-142` | `/api/v2/workspaces/{slug}/work-items/{identifier}/` — reads only | + +The two lookups are separate routes on purpose. The UUID route is the canonical surface for everything, including +writes. The identifier route is workspace-scoped and read-only, and it exists precisely so a caller holding only +`PROJ-142` — a chat bot, a commit-hook, an LLM agent — can fetch the work item without first discovering which project +it belongs to. + +## Custom fields + +`custom_fields` is the work item's custom property values from its work item type, keyed by the property's `name`. +Each entry carries the property definition `id`, a human-useful `value`, and a `value_detail` object that keeps the +underlying record for option and relation properties (`null` for plain scalars). Properties the work item has no value +for are omitted rather than emitted as `null`. + +::: warning `custom_fields` is null on the list endpoint +Resolving custom properties costs one lookup pass per work item, so the list endpoint deliberately skips it to avoid an +N+1 across a full page of results. `custom_fields` is populated only on retrieve, retrieve by identifier, create, and +update. It is `null` on list, and `null` on the archive and unarchive responses too — those return the work item, not +its property values. + +If you are exporting custom property values for many work items, list to get the ids, then fetch the ones you need +individually. Do not expect `custom_fields` to arrive from a list call. +::: + +The object is empty (`{}`) when the work item is untyped or custom properties are not enabled for the workspace. See +[work item properties](/api-reference/v2/work-item-properties/list-work-item-properties) for the property definitions +behind these values. + +## Writing: send ids, or send names + +Every relation on a write has two accepted inputs — the canonical id field, and a **write-only** human-readable +parallel. You may send either one, never both. + +| Relation | Id input | Human input | Human input accepts | +| --------- | ------------------- | ----------- | ----------------------------------------------- | +| State | `state_id` | `state` | The state's name, for example `In Progress` | +| Type | `type_id` | `type` | The type's name, for example `Bug` | +| Parent | `parent_id` | `parent` | The parent's identifier, for example `PROJ-118` | +| Assignees | `assignee_ids` | `assignees` | Member email addresses | +| Labels | `label_ids` | `labels` | Label names | +| Estimate | `estimate_point_id` | `estimate` | The estimate point's value, for example `5` | + +This exists because the values a caller already holds are almost never UUIDs. An importer has a Jira status string. A +Slack command has an email address. An LLM agent has the word "Bug". Without the parallel inputs each of those would +need a list call per field before it could write anything — six lookups to create one work item. With them, the create +is a single request. + +The id fields remain the precise option, and you should prefer them when you already have ids or when a name might be +ambiguous: + +- Names resolve **case-insensitively** and must match exactly one record. A name that matches nothing is a `400`, and + so is a name that matches more than one — the error tells you to use the id field instead. +- Sending both a name and its id (for example `state` and `state_id`) is a `400`. Pick one. +- The human inputs are **write-only**. Responses always come back in the sparse id shape, so `state` never appears in a + response body — `state_id` does. + +## Expanding relations + +Work items are one of only two resources that support `?expand=`. The accepted values are `state`, `type`, `parent`, +`assignees`, and `labels`, comma-separated. + +Expansion is **separate-key**: the `*_id` field is always present, and the expanded object is _added_ beside it under +the bare name. `?expand=state` gives you both `state_id` and a `state` object; it never swaps one for the other, so a +client that reads ids keeps working when someone adds an expand to the request. + +```bash +GET .../work-items/?expand=state,assignees +``` + +An unknown expand value is a `400`. Expansion works on the list, both retrieve routes, and the create/update responses. +See [Expanding relations](/api-reference/v2/expanding-relations). + +## Archiving and deleting + +Archiving and deleting are different operations with different consequences. + +- **Archive** ([archive](/api-reference/v2/work-items/archive-work-item) / + [unarchive](/api-reference/v2/work-items/unarchive-work-item)) sets or clears `archived_at`. It is reversible, and it + removes the work item from the default query set — archived items stop appearing in list results and in plain detail + reads. +- **Delete** ([delete](/api-reference/v2/work-items/delete-work-item)) is a soft delete that returns `204`. It is not + reversible through the API. + +Neither one is `PATCH`-able: `archived_at` is read-only, so you cannot archive a work item by patching a timestamp onto +it. + +## Changed from v1 + +- Relations are ids with explicit names: `state` → **`state_id`**, `parent` → **`parent_id`**, `type` → **`type_id`**, + `assignees` → **`assignee_ids`**, `labels` → **`label_ids`**, `created_by` → **`created_by_id`**. +- Reads no longer return `updated_at`, `updated_by`, `project`, `workspace`, `description_html`, + `description_stripped`, `description_binary`, `sort_order`, `completed_at`, `estimate_point`, or `module`. +- **`identifier`** (`PROJ-142`) and **`custom_fields`** are new on the read shape. +- Writes accept human-readable parallels (`state`, `type`, `parent`, `assignees`, `labels`, `estimate`) alongside the + id fields. +- `?expand=` is now **separate-key** — it adds an object beside the id instead of replacing the id. The allowed values + are `state`, `type`, `parent`, `assignees`, and `labels`; v1's `project` and `module` expansions are gone. +- Updates are `PATCH` only. `PUT` returns `405`. +- Lists return a pagination envelope instead of a bare array, and errors are RFC 9457 `application/problem+json`. + +See [Migrating from v1](/api-reference/v2/migrating-from-v1) for the full list. diff --git a/docs/api-reference/v2/work-items/unarchive-work-item.md b/docs/api-reference/v2/work-items/unarchive-work-item.md new file mode 100644 index 00000000..00cae2e8 --- /dev/null +++ b/docs/api-reference/v2/work-items/unarchive-work-item.md @@ -0,0 +1,233 @@ +--- +title: Unarchive a work item +description: Restore an archived Plane work item with the v2 REST API. Bodyless POST, clearing archived_at, idempotent behavior, OAuth scopes, error codes, and code examples. +keywords: plane api v2, unarchive work item, restore work item, archived_at null, POST unarchive +--- + +# Unarchive a work item + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{pk}/unarchive/ +
+ +
+
+ +Restore an archived work item: clear `archived_at` and put it back into the active set, where it shows up in lists and +plain detail reads again. + +This is a **bodyless** `POST` — send no JSON at all. The response is the full work item in its usual read shape with +`archived_at` back to `null`. + +Unlike most detail routes, this one deliberately looks past the archive filter to find the work item — that is the +whole point. It also means the call is safe to repeat: unarchiving a work item that isn't archived succeeds and changes +nothing. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the work item belongs to. + + + + + +The work item's UUID. This lookup is UUID-only — a `PROJ-142` identifier here returns `404`. + + + +
+
+ +
+ +### Body Parameters + +None. The endpoint takes no request body; anything you send is ignored. + +
+ +::: tip Safe to retry +Unarchive resolves archived and active work items alike, so a repeat call is a no-op that returns `200` with +`archived_at: null`. Archive is the asymmetric one — see +[Archive a work item](/api-reference/v2/work-items/archive-work-item). +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `archived_at`, `assignee_ids`, `created_at`, `created_by_id`, `custom_fields`, `cycle_id`, `id`, `identifier`, `is_draft`, `label_ids`, `module_ids`, `name`, `parent_id`, `priority`, `project_id`, `sequence_id`, `start_date`, `state_id`, `target_date`, `type_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `assignees` (the assigned users), `cycle` (the cycle it belongs to), `labels` (the applied labels), `modules` (the modules it belongs to), `parent` (its parent work item), `state` (the work item's state object), `type` (its work item type). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request could not be processed — for example a `pk` that isn't a valid UUID. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't archive or unarchive this work item. | +| `404` | `not_found` | No such work item, or it's outside your project or tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The write collides with a protected-resource constraint. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect loop", + "identifier": "PROJ-142", + "sequence_id": 142, + "priority": "high", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type_id": "2d9d1a97-5c6f-4a1e-9d5b-8c2f7e30b6a4", + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"], + "label_ids": ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"], + "parent_id": null, + "start_date": "2026-01-12", + "target_date": "2026-01-20", + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "custom_fields": { + "Severity": { + "id": "5e2a7c81-4f39-4b60-a1d8-0c6b3e9f2d74", + "value": ["Sev-2"], + "value_detail": [{ "id": "c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03", "name": "Sev-2" }] + } + } +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "The requested resource was not found." +} +``` + + + +
+
+ +## What comes back, and what doesn't + +The response is the work item's standard read shape with `archived_at` cleared. Because this is a single-object +response, `custom_fields` is populated here — the archive verbs return the same shape as +[Get a work item](/api-reference/v2/work-items/get-work-item), custom property values included. It is only +**collection** responses that omit `custom_fields`. + +Nothing else is restored or changed, because nothing else was touched when the work item was archived: state, +assignees, labels, dates, comments, and relations all survived the archive untouched. + +## Finding archived work items to restore + +Archived work items are excluded from [List work items](/api-reference/v2/work-items/list-work-items) and from plain +detail reads, so the API gives you nothing to browse them with. Record the `id` when you archive — the archive response +returns it — or pick the work item out of the Plane app's archive view, then unarchive it by UUID. This route is the +one place an archived work item still resolves. diff --git a/docs/api-reference/v2/work-items/update-work-item.md b/docs/api-reference/v2/work-items/update-work-item.md new file mode 100644 index 00000000..77165bce --- /dev/null +++ b/docs/api-reference/v2/work-items/update-work-item.md @@ -0,0 +1,390 @@ +--- +title: Update a work item +description: Partially update a Plane work item with the v2 REST API. PATCH semantics, id and human-readable inputs, replacing assignees and labels, workflow rules, scopes, errors, and code examples. +keywords: plane api v2, update work item, PATCH work item, change state, reassign work item, clear parent, workflow transition denied +--- + +# Update a work item + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/{pk}/ +
+ +
+
+ +Update a work item. Every field is optional and the update is partial — send only what changes, and everything you +leave out stays exactly as it was. + +Omitting a field is not the same as sending `null`. Omit `target_date` and the existing due date is preserved; send +`"target_date": null` and it is cleared. + +There is no `PUT` in v2. A `PUT` to this path returns `405 method_not_allowed`. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the work item belongs to. + + + + + +The work item's UUID. This lookup is UUID-only — a `PROJ-142` identifier here returns `404`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +New title. Maximum 255 characters. + + + + + +Replacement rich-text body as HTML. Sanitized on the way in; content that can't be sanitized is rejected with a `400`. +Not part of the read shape, so it won't appear in the response. + + + + + +One of `urgent`, `high`, `medium`, `low`, `none`. + + + + + +Move the work item to another state of the same project. This is the field workflow rules police — see +[Workflow rules](#workflow-rules-can-reject-a-transition). + + + + + +The target state's **name** instead of its id, for example `Done`. Matched case-insensitively within the project. +Write-only. Sending both `state` and `state_id` is a `400`. + + + + + +Change the work item type. Send `null` to make the work item untyped. + + + + + +The type's **name** instead of its id, for example `Bug`. Write-only. + + + + + +Re-parent the work item. The parent must be in the same workspace and may be in a different project. Send `null` to +detach it and make it top-level. + + + + + +The parent's **identifier** instead of its id, for example `PROJ-118`. Write-only. + + + + + +**Replaces** the whole assignee set — it is not additive. Send the complete list you want, `[]` to unassign everyone, +or omit the field to leave assignees untouched. + + + + + +Member **email addresses** instead of ids. Same replace-the-set semantics. Every address must belong to an active, +assignable project member. Write-only. + + + + + +**Replaces** the whole label set. Send `[]` to strip all labels, or omit the field to leave them untouched. + + + + + +Label **names** instead of ids. Same replace-the-set semantics. A name that exists at both project and workspace level +is ambiguous and returns a `400` telling you to use `label_ids`. Write-only. + + + + + +Change the estimate point, from the project's active estimate system. Send `null` to clear the estimate. + + + + + +The estimate point's **value** instead of its id, for example `5`. Write-only. + + + + + +Planned start, or `null` to clear it. Must not be after `target_date`. + + + + + +Planned due date, or `null` to clear it. + + + + + +Your system's identifier for this work item. Maximum 255 characters. Filterable on +[List work items](/api-reference/v2/work-items/list-work-items), but not returned on reads. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. + + + +
+
+ +::: info Fields you cannot patch +`id`, `identifier`, `sequence_id`, `is_draft`, `archived_at`, `created_at`, and `created_by_id` are read-only — sending +them has no effect. In particular, archiving is not a `PATCH` on `archived_at`; use +[Archive a work item](/api-reference/v2/work-items/archive-work-item). + +Custom property values go through a separate `custom_fields` object on the request body, keyed by property name — its +shape follows the work item's type, so it is not declared in the OpenAPI schema. See +[the type's schema endpoint](/api-reference/v2/work-item-types/get-work-item-type-schema). On a `PATCH` only the +properties you submit are replaced; untouched properties keep their values and are never required-checked. +::: + +
+ +### Response shaping + +
+ + + +Comma-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: `archived_at`, `assignee_ids`, `created_at`, `created_by_id`, `custom_fields`, `cycle_id`, `id`, `identifier`, `is_draft`, `label_ids`, `module_ids`, `name`, `parent_id`, `priority`, `project_id`, `sequence_id`, `start_date`, `state_id`, `target_date`, `type_id`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + + + +Comma-separated relations to embed alongside the ids: `assignees` (the assigned users), `cycle` (the cycle it belongs to), `labels` (the applied labels), `modules` (the modules it belongs to), `parent` (its parent work item), `state` (the work item's state object), `type` (its work item type). + +Expansion is separate-key: `?expand=state` keeps `state_id` and adds a `state` object next to it, so an id is never replaced by an object. An unknown value is a `400`. + +`?fields=` and `?expand=` are independent namespaces. Relation names are not valid `?fields=` tokens (and vice versa), and an expanded object survives field filtering — `?fields=id,name&expand=state` returns `id`, `name` and `state`. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | A bad `priority`; `start_date` after `target_date`; an unresolvable or ambiguous `state`/`type`/`parent`/`assignees`/`labels`/`estimate`; both a name and its `*_id`; or an id from another project or workspace. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | A workflow rule forbids the requested state transition. | +| `404` | `not_found` | No such work item, or it's outside your project or tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The write collides with a uniqueness or protected-resource constraint. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "name": "Fix login redirect loop", + "identifier": "PROJ-142", + "sequence_id": 142, + "priority": "urgent", + "state_id": "5d2a91b7-64c0-4f38-b9e2-0a3f7c6d8149", + "type_id": "2d9d1a97-5c6f-4a1e-9d5b-8c2f7e30b6a4", + "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430", "9b7f4c53-2d18-4a6e-8c05-1f3e7d9a2b64"], + "label_ids": ["c1b8f3d6-9a44-4e12-8f7a-2b6d5c9e1a03"], + "parent_id": null, + "start_date": "2026-01-12", + "target_date": null, + "is_draft": false, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "custom_fields": { + "severity": { + "id": "b52e7c18-4d3f-4a90-8e61-0f7a3c9d2b45", + "value": "Sev-1", + "value_detail": { + "id": "7c0a5f39-2e84-4b17-9a6c-1d8e4f2b60c9", + "name": "Sev-1", + "logo_props": {} + } + } + } +} +``` + + + + + +```json +{ + "type": "forbidden", + "code": "workflow_transition_denied", + "detail": "State transition is not allowed." +} +``` + + + +
+
+ +## Omitted, empty, and null + +These three are different, and the difference matters most on the list fields: + +| Request body | Result | +| ------------------------- | ----------------------------------- | +| `{}` — field omitted | Assignees unchanged. | +| `{"assignee_ids": []}` | All assignees removed. | +| `{"assignee_ids": ["…"]}` | Assignees become exactly that list. | +| `{"parent_id": null}` | Parent detached. | + +`assignee_ids` and `label_ids` replace the set rather than adding to it, so to add one assignee you send the existing +ids plus the new one. Read the work item first if you don't already hold the current list. + +## Workflow rules can reject a transition + +If the project runs workflow rules, a state change they don't permit returns `403` with `workflow_transition_denied` +and nothing is written. That is a different situation from `forbidden`, which means your role or token scope can't edit +the work item at all — branch on the `code`, not the status. + +Only a state change can trigger it. Updating a name or a due date never runs the transition check. diff --git a/docs/api-reference/v2/work-items/upsert-work-item.md b/docs/api-reference/v2/work-items/upsert-work-item.md new file mode 100644 index 00000000..a91bdbc8 --- /dev/null +++ b/docs/api-reference/v2/work-items/upsert-work-item.md @@ -0,0 +1,374 @@ +--- +title: Upsert a work item +description: Upsert a work item with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, upsert a work item, work items, work items upsert +--- + +# Upsert a work item + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/work-items/upsert/ +
+ +
+
+ +Work items are the issues, tasks and epics in a project. Create a work item, or update the existing one that carries the same `(external_source, external_id)` pair. + +- Both `external_source` and `external_id` are required — without them there is nothing to match on and the request is a `400`. +- A created record answers `201` with `X-Plane-Upsert: created`; an updated one answers `200` with `X-Plane-Upsert: updated`. Branch on the header rather than guessing from the status. +- Upsert is safe for **sequential** importers. Two simultaneous upserts of the same key can each miss and each create, so serialize your writes per key. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Ids of the assignees to associate. Replaces the current set. + + + + + +The assignees. + + + + + +Id of the related cycle. + +Nullable. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + +Nullable. + + + + + +The estimate. + +Nullable. + + + + + +Id of the related estimate point. + +Nullable. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Ids of the labels to associate. Replaces the current set. + + + + + +The labels. + + + + + +Ids of the modules to associate. Replaces the current set. + + + + + +The parent. + +Nullable. + + + + + +Id of the related parent. + +Nullable. + + + + + +- `none` - None +- `low` - Low +- `medium` - Medium +- `high` - High +- `urgent` - Urgent + +One of `none`, `low`, `medium`, `high`, `urgent`. + + + + + +Planned start date, as `YYYY-MM-DD`. + +Nullable. + + + + + +The state. + +Nullable. + + + + + +Id of the related state. + +Nullable. + + + + + +Planned due date, as `YYYY-MM-DD`. + +Nullable. + + + + + +The type. + +Nullable. + + + + + +Id of the related type. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `archived_at`, `assignee_ids`, `created_at`, `created_by_id`, `custom_fields`, `cycle_id`, `id`, `identifier`, `is_draft`, `label_ids`, `module_ids`, `name`, `parent_id`, `priority`, `project_id`, `sequence_id`, `start_date`, `state_id`, `target_date`, `type_id`. + + + + + +Comma-separated relations to embed: `assignees`, `cycle`, `labels`, `modules`, `parent`, `state`, `type`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`projects.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "archived_at": null, + "assignee_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "custom_fields": {}, + "cycle_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "identifier": "PROJ", + "is_draft": false, + "label_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "module_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "name": "Example name", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "priority": "high", + "project_id": "4af68566-94a4-4eb3-94aa-50dc9427067b", + "sequence_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "start_date": "2026-01-12", + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_date": "2026-01-20", + "type_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflow-states/create-workflow-state.md b/docs/api-reference/v2/workflow-states/create-workflow-state.md new file mode 100644 index 00000000..b0470180 --- /dev/null +++ b/docs/api-reference/v2/workflow-states/create-workflow-state.md @@ -0,0 +1,199 @@ +--- +title: Create a workflow state +description: Create a workflow state with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a workflow state, workflow states, workflow states create +--- + +# Create a workflow state + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/states/ +
+ +
+
+ +Workflow states are the states attached to a project workflow. Create a workflow state. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow the resource belongs to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Whether allow issue creation. + + + + + +Make this the default for its parent. Setting it clears the flag on the previous default. + + + + + +The type. + +Maximum 255 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `allow_issue_creation`, `created_at`, `created_by_id`, `id`, `is_default`, `state_id`, `type`, `workflow_id`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "allow_issue_creation": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_default": false, + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type": "example", + "workflow_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflow-states/delete-workflow-state.md b/docs/api-reference/v2/workflow-states/delete-workflow-state.md new file mode 100644 index 00000000..f9023610 --- /dev/null +++ b/docs/api-reference/v2/workflow-states/delete-workflow-state.md @@ -0,0 +1,160 @@ +--- +title: Delete a workflow state +description: Delete a workflow state with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a workflow state, workflow states, workflow states destroy +--- + +# Delete a workflow state + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/states/{pk}/ +
+ +
+
+ +Workflow states are the states attached to a project workflow. Delete a workflow state. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow the resource belongs to. + + + + + +The workflow state id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `allow_issue_creation`, `created_at`, `created_by_id`, `id`, `is_default`, `state_id`, `type`, `workflow_id`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workflow state matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflow-states/get-workflow-state.md b/docs/api-reference/v2/workflow-states/get-workflow-state.md new file mode 100644 index 00000000..1e8a81b1 --- /dev/null +++ b/docs/api-reference/v2/workflow-states/get-workflow-state.md @@ -0,0 +1,167 @@ +--- +title: Get a workflow state +description: Read a workflow state with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a workflow state, workflow states, workflow states retrieve +--- + +# Get a workflow state + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/states/{pk}/ +
+ +
+
+ +Workflow states are the states attached to a project workflow. Read a single workflow state by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow the resource belongs to. + + + + + +The workflow state id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `allow_issue_creation`, `created_at`, `created_by_id`, `id`, `is_default`, `state_id`, `type`, `workflow_id`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "allow_issue_creation": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_default": false, + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type": "example", + "workflow_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workflow state matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflow-states/list-workflow-states.md b/docs/api-reference/v2/workflow-states/list-workflow-states.md new file mode 100644 index 00000000..5e87f1a2 --- /dev/null +++ b/docs/api-reference/v2/workflow-states/list-workflow-states.md @@ -0,0 +1,212 @@ +--- +title: List workflow states +description: List workflow states with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list workflow states, workflow states, workflow states list +--- + +# List workflow states + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/states/ +
+ +
+
+ +Workflow states are the states attached to a project workflow. List the workflow states you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow the resource belongs to. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `allow_issue_creation`, `created_at`, `created_by_id`, `id`, `is_default`, `state_id`, `type`, `workflow_id`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "allow_issue_creation": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_default": false, + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type": "example", + "workflow_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "allow_issue_creation": false, + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_default": false, + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type": "example", + "workflow_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflow-states/overview.md b/docs/api-reference/v2/workflow-states/overview.md new file mode 100644 index 00000000..225481cb --- /dev/null +++ b/docs/api-reference/v2/workflow-states/overview.md @@ -0,0 +1,85 @@ +--- +title: Workflow states overview +description: The Plane API v2 workflow state object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, workflow states, workflow state object +--- + +# Workflow states overview + +Workflow states are the states attached to a project workflow. + +
+
+ +## The workflow state object + +### Attributes + +- `allow_issue_creation` _boolean_ + + Whether allow issue creation. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_default` _boolean_ + + Make this the default for its parent. Setting it clears the flag on the previous default. + +- `state_id` _string (uuid)_ + + The related state. + +- `type` _string_ + + The type. + +- `workflow_id` _string (uuid)_ + + The related workflow. + +
+
+ + + +```json +{ + "allow_issue_creation": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_default": false, + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type": "example", + "workflow_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | -------------------------------------------------------------------------------------- | ----------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/states/` | List workflow states | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/states/` | Create a workflow state | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/states/{pk}/` | Delete a workflow state | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/states/{pk}/` | Get a workflow state | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/states/{pk}/` | Update a workflow state | + +## Response shaping + +Every workflow state read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/workflow-states/update-workflow-state.md b/docs/api-reference/v2/workflow-states/update-workflow-state.md new file mode 100644 index 00000000..2597faa7 --- /dev/null +++ b/docs/api-reference/v2/workflow-states/update-workflow-state.md @@ -0,0 +1,217 @@ +--- +title: Update a workflow state +description: Update a workflow state with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a workflow state, workflow states, workflow states partial update +--- + +# Update a workflow state + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/states/{pk}/ +
+ +
+
+ +Workflow states are the states attached to a project workflow. Update a workflow state. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow the resource belongs to. + + + + + +The workflow state id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Whether allow issue creation. + + + + + +Make this the default for its parent. Setting it clears the flag on the previous default. + + + + + +The type. + +Maximum 255 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `allow_issue_creation`, `created_at`, `created_by_id`, `id`, `is_default`, `state_id`, `type`, `workflow_id`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "allow_issue_creation": false, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_default": false, + "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "type": "example", + "workflow_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workflow state matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflow-transitions/create-workflow-transition.md b/docs/api-reference/v2/workflow-transitions/create-workflow-transition.md new file mode 100644 index 00000000..8f5ddd64 --- /dev/null +++ b/docs/api-reference/v2/workflow-transitions/create-workflow-transition.md @@ -0,0 +1,223 @@ +--- +title: Create a workflow transition +description: Create a workflow transition with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a workflow transition, workflow transitions, workflow transitions create +--- + +# Create a workflow transition + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/state-transitions/ +
+ +
+
+ +Workflow transitions define the legal moves between workflow states, and who may approve them. Create a workflow transition. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow the resource belongs to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Ids of the members to associate. Replaces the current set. + + + + + +Id of the related rejection state. + +Nullable. + + + + + +How many approvals a transition needs before it may run. + +Nullable. + + + + + +Id of the related state. + + + + + +Id of the related transition state. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `member_ids`, `rejection_state_id`, `required_approvals`, `transition_state_id`, `workflow_state_id`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "rejection_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "required_approvals": 1, + "transition_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "workflow_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflow-transitions/delete-workflow-transition.md b/docs/api-reference/v2/workflow-transitions/delete-workflow-transition.md new file mode 100644 index 00000000..daacaec6 --- /dev/null +++ b/docs/api-reference/v2/workflow-transitions/delete-workflow-transition.md @@ -0,0 +1,160 @@ +--- +title: Delete a workflow transition +description: Delete a workflow transition with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a workflow transition, workflow transitions, workflow transitions destroy +--- + +# Delete a workflow transition + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/state-transitions/{pk}/ +
+ +
+
+ +Workflow transitions define the legal moves between workflow states, and who may approve them. Delete a workflow transition. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow the resource belongs to. + + + + + +The workflow transition id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `member_ids`, `rejection_state_id`, `required_approvals`, `transition_state_id`, `workflow_state_id`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workflow transition matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflow-transitions/get-workflow-transition.md b/docs/api-reference/v2/workflow-transitions/get-workflow-transition.md new file mode 100644 index 00000000..74d20390 --- /dev/null +++ b/docs/api-reference/v2/workflow-transitions/get-workflow-transition.md @@ -0,0 +1,167 @@ +--- +title: Get a workflow transition +description: Read a workflow transition with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a workflow transition, workflow transitions, workflow transitions retrieve +--- + +# Get a workflow transition + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/state-transitions/{pk}/ +
+ +
+
+ +Workflow transitions define the legal moves between workflow states, and who may approve them. Read a single workflow transition by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow the resource belongs to. + + + + + +The workflow transition id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `member_ids`, `rejection_state_id`, `required_approvals`, `transition_state_id`, `workflow_state_id`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "rejection_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "required_approvals": 1, + "transition_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "workflow_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workflow transition matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflow-transitions/list-workflow-transitions.md b/docs/api-reference/v2/workflow-transitions/list-workflow-transitions.md new file mode 100644 index 00000000..f20719b5 --- /dev/null +++ b/docs/api-reference/v2/workflow-transitions/list-workflow-transitions.md @@ -0,0 +1,212 @@ +--- +title: List workflow transitions +description: List workflow transitions with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list workflow transitions, workflow transitions, workflow transitions list +--- + +# List workflow transitions + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/state-transitions/ +
+ +
+
+ +Workflow transitions define the legal moves between workflow states, and who may approve them. List the workflow transitions you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow the resource belongs to. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `member_ids`, `rejection_state_id`, `required_approvals`, `transition_state_id`, `workflow_state_id`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "rejection_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "required_approvals": 1, + "transition_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "workflow_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "rejection_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "required_approvals": 1, + "transition_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "workflow_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflow-transitions/overview.md b/docs/api-reference/v2/workflow-transitions/overview.md new file mode 100644 index 00000000..a475896f --- /dev/null +++ b/docs/api-reference/v2/workflow-transitions/overview.md @@ -0,0 +1,85 @@ +--- +title: Workflow transitions overview +description: The Plane API v2 workflow transition object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, workflow transitions, workflow transition object +--- + +# Workflow transitions overview + +Workflow transitions define the legal moves between workflow states, and who may approve them. + +
+
+ +## The workflow transition object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `id` _string (uuid)_ + + Unique identifier. + +- `member_ids` _array of string_ + + Ids of the associated members. + +- `rejection_state_id` _string (uuid)_ + + The related rejection state. + +- `required_approvals` _integer_ + + How many approvals a transition needs before it may run. + +- `transition_state_id` _string (uuid)_ + + The related transition state. + +- `workflow_state_id` _string (uuid)_ + + The related workflow state. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "rejection_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "required_approvals": 1, + "transition_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "workflow_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------------------------------------------------- | ---------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/state-transitions/` | List workflow transitions | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/state-transitions/` | Create a workflow transition | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/state-transitions/{pk}/` | Delete a workflow transition | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/state-transitions/{pk}/` | Get a workflow transition | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/state-transitions/{pk}/` | Update a workflow transition | + +## Response shaping + +Every workflow transition read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/workflow-transitions/update-workflow-transition.md b/docs/api-reference/v2/workflow-transitions/update-workflow-transition.md new file mode 100644 index 00000000..66319c38 --- /dev/null +++ b/docs/api-reference/v2/workflow-transitions/update-workflow-transition.md @@ -0,0 +1,241 @@ +--- +title: Update a workflow transition +description: Update a workflow transition with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a workflow transition, workflow transitions, workflow transitions partial update +--- + +# Update a workflow transition + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{workflow_id}/state-transitions/{pk}/ +
+ +
+
+ +Workflow transitions define the legal moves between workflow states, and who may approve them. Update a workflow transition. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow the resource belongs to. + + + + + +The workflow transition id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Ids of the members to associate. Replaces the current set. + + + + + +Id of the related rejection state. + +Nullable. + + + + + +How many approvals a transition needs before it may run. + +Nullable. + + + + + +Id of the related state. + + + + + +Id of the related transition state. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `id`, `member_ids`, `rejection_state_id`, `required_approvals`, `transition_state_id`, `workflow_state_id`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "member_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "rejection_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "required_approvals": 1, + "transition_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "workflow_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workflow transition matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflows/create-workflow.md b/docs/api-reference/v2/workflows/create-workflow.md new file mode 100644 index 00000000..a00375e0 --- /dev/null +++ b/docs/api-reference/v2/workflows/create-workflow.md @@ -0,0 +1,212 @@ +--- +title: Create a workflow +description: Create a workflow with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a workflow, workflows, workflows create +--- + +# Create a workflow + +
+ POST + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/ +
+ +
+
+ +Workflows constrain how work items move between states in a project. Create a workflow. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Free-form description. + +Nullable. + + + + + +Whether the record is active. + + + + + +Ids of the work item types to associate. Replaces the current set. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `id`, `is_active`, `is_default`, `name`, `work_item_type_ids`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "is_default": false, + "name": "Example name", + "work_item_type_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]] +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflows/delete-workflow.md b/docs/api-reference/v2/workflows/delete-workflow.md new file mode 100644 index 00000000..156e2ed0 --- /dev/null +++ b/docs/api-reference/v2/workflows/delete-workflow.md @@ -0,0 +1,154 @@ +--- +title: Delete a workflow +description: Delete a workflow with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a workflow, workflows, workflows destroy +--- + +# Delete a workflow + +
+ DELETE + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{pk}/ +
+ +
+
+ +Workflows constrain how work items move between states in a project. Delete a workflow. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `id`, `is_active`, `is_default`, `name`, `work_item_type_ids`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workflow matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflows/get-workflow.md b/docs/api-reference/v2/workflows/get-workflow.md new file mode 100644 index 00000000..df2c5ca0 --- /dev/null +++ b/docs/api-reference/v2/workflows/get-workflow.md @@ -0,0 +1,161 @@ +--- +title: Get a workflow +description: Read a workflow with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a workflow, workflows, workflows retrieve +--- + +# Get a workflow + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{pk}/ +
+ +
+
+ +Workflows constrain how work items move between states in a project. Read a single workflow by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `id`, `is_active`, `is_default`, `name`, `work_item_type_ids`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "is_default": false, + "name": "Example name", + "work_item_type_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]] +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workflow matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflows/list-workflows.md b/docs/api-reference/v2/workflows/list-workflows.md new file mode 100644 index 00000000..f58e7e19 --- /dev/null +++ b/docs/api-reference/v2/workflows/list-workflows.md @@ -0,0 +1,212 @@ +--- +title: List workflows +description: List workflows with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list workflows, workflows, workflows list +--- + +# List workflows + +
+ GET + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/ +
+ +
+
+ +Workflows constrain how work items move between states in a project. List the workflows you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `id`, `is_active`, `is_default`, `name`, `work_item_type_ids`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "is_default": false, + "name": "Example name", + "work_item_type_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]] + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_active": true, + "is_default": false, + "name": "Another example", + "work_item_type_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]] + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workflows/overview.md b/docs/api-reference/v2/workflows/overview.md new file mode 100644 index 00000000..86e1a460 --- /dev/null +++ b/docs/api-reference/v2/workflows/overview.md @@ -0,0 +1,85 @@ +--- +title: Workflows overview +description: The Plane API v2 workflow object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, workflows, workflow object +--- + +# Workflows overview + +Workflows constrain how work items move between states in a project. + +
+
+ +## The workflow object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description` _string_ + + Free-form description. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_active` _boolean_ + + Whether the record is active. + +- `is_default` _boolean_ + + Make this the default for its parent. Setting it clears the flag on the previous default. + +- `name` _string_ + + Display name. + +- `work_item_type_ids` _array of string_ + + Ids of the associated work item types. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "is_default": false, + "name": "Example name", + "work_item_type_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]] +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ----------------------------------------------------------------- | ----------------- | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/` | List workflows | +| `POST` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/` | Create a workflow | +| `DELETE` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{pk}/` | Delete a workflow | +| `GET` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{pk}/` | Get a workflow | +| `PATCH` | `/api/v2/workspaces/{slug}/projects/{project_id}/workflows/{pk}/` | Update a workflow | + +## Response shaping + +Every workflow read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/workflows/update-workflow.md b/docs/api-reference/v2/workflows/update-workflow.md new file mode 100644 index 00000000..bba51cb6 --- /dev/null +++ b/docs/api-reference/v2/workflows/update-workflow.md @@ -0,0 +1,219 @@ +--- +title: Update a workflow +description: Update a workflow with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a workflow, workflows, workflows partial update +--- + +# Update a workflow + +
+ PATCH + /api/v2/workspaces/{slug}/projects/{project_id}/workflows/{pk}/ +
+ +
+
+ +Workflows constrain how work items move between states in a project. Update a workflow. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The project the resource belongs to. Accepts the project UUID or its bare identifier, for example `ENG`. + + + + + +The workflow id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Free-form description. + +Nullable. + + + + + +Whether the record is active. + + + + + +Display name. + +Maximum 255 characters. Nullable. + + + + + +Ids of the work item types to associate. Replaces the current set. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description`, `id`, `is_active`, `is_default`, `name`, `work_item_type_ids`. + + + +
+
+ +
+ +### Scopes + +`projects.workflows:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_active": true, + "is_default": false, + "name": "Example name", + "work_item_type_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]] +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workflow matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-assets/confirm-workspace-asset.md b/docs/api-reference/v2/workspace-assets/confirm-workspace-asset.md new file mode 100644 index 00000000..78bd2ee8 --- /dev/null +++ b/docs/api-reference/v2/workspace-assets/confirm-workspace-asset.md @@ -0,0 +1,163 @@ +--- +title: Confirm a workspace asset upload +description: Update a asset with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, confirm a workspace asset upload, workspace assets, assets partial update +--- + +# Confirm a workspace asset upload + +
+ PATCH + /api/v2/workspaces/{slug}/assets/{pk}/ +
+ +
+
+ +Workspace assets are files uploaded against a workspace. Update a asset. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The asset id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `entity_type`, `external_id`, `external_source`, `id`, `is_uploaded`, `name`, `size`. + + + +
+
+ +
+ +### Scopes + +`assets:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "entity_type": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No asset matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-assets/create-workspace-asset.md b/docs/api-reference/v2/workspace-assets/create-workspace-asset.md new file mode 100644 index 00000000..37ea0055 --- /dev/null +++ b/docs/api-reference/v2/workspace-assets/create-workspace-asset.md @@ -0,0 +1,195 @@ +--- +title: Create a workspace asset upload +description: Create a asset with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a workspace asset upload, workspace assets, assets create +--- + +# Create a workspace asset upload + +
+ POST + /api/v2/workspaces/{slug}/assets/ +
+ +
+
+ +Workspace assets are files uploaded against a workspace. Create a asset. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + + + + + +The size. + + + + + +The entity type. + + + + + +The type. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `entity_type`, `external_id`, `external_source`, `id`, `is_uploaded`, `name`, `size`. + + + +
+
+ +
+ +### Scopes + +`assets:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "entity_type": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-assets/delete-asset.md b/docs/api-reference/v2/workspace-assets/delete-asset.md new file mode 100644 index 00000000..668b8640 --- /dev/null +++ b/docs/api-reference/v2/workspace-assets/delete-asset.md @@ -0,0 +1,148 @@ +--- +title: Delete a asset +description: Delete a asset with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a asset, workspace assets, assets destroy +--- + +# Delete a asset + +
+ DELETE + /api/v2/workspaces/{slug}/assets/{pk}/ +
+ +
+
+ +Workspace assets are files uploaded against a workspace. Delete a asset. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The asset id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `entity_type`, `external_id`, `external_source`, `id`, `is_uploaded`, `name`, `size`. + + + +
+
+ +
+ +### Scopes + +`assets:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No asset matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-assets/get-asset.md b/docs/api-reference/v2/workspace-assets/get-asset.md new file mode 100644 index 00000000..db5098c1 --- /dev/null +++ b/docs/api-reference/v2/workspace-assets/get-asset.md @@ -0,0 +1,159 @@ +--- +title: Get a asset +description: Read a asset with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a asset, workspace assets, assets retrieve +--- + +# Get a asset + +
+ GET + /api/v2/workspaces/{slug}/assets/{pk}/ +
+ +
+
+ +Workspace assets are files uploaded against a workspace. Read a single asset by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The asset id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `entity_type`, `external_id`, `external_source`, `id`, `is_uploaded`, `name`, `size`. + + + +
+
+ +
+ +### Scopes + +`assets:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "entity_type": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No asset matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-assets/list-workspace-assets.md b/docs/api-reference/v2/workspace-assets/list-workspace-assets.md new file mode 100644 index 00000000..25681a9c --- /dev/null +++ b/docs/api-reference/v2/workspace-assets/list-workspace-assets.md @@ -0,0 +1,205 @@ +--- +title: List workspace assets +description: List workspace assets with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list workspace assets, workspace assets, assets list +--- + +# List workspace assets + +
+ GET + /api/v2/workspaces/{slug}/assets/ +
+ +
+
+ +Workspace assets are files uploaded against a workspace. List the workspace assets you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `asset_url`, `attributes`, `content_type`, `created_at`, `created_by_id`, `entity_type`, `external_id`, `external_source`, `id`, `is_uploaded`, `name`, `size`. + + + +
+
+ +
+ +### Scopes + +`assets:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "entity_type": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1 + }, + { + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "entity_type": "example", + "external_id": null, + "external_source": null, + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_uploaded": false, + "name": "Another example", + "size": 1 + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-assets/overview.md b/docs/api-reference/v2/workspace-assets/overview.md new file mode 100644 index 00000000..45b2dc10 --- /dev/null +++ b/docs/api-reference/v2/workspace-assets/overview.md @@ -0,0 +1,105 @@ +--- +title: Workspace assets overview +description: The Plane API v2 workspace asset object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, workspace assets, workspace asset object +--- + +# Workspace assets overview + +Workspace assets are files uploaded against a workspace. + +
+
+ +## The workspace asset object + +### Attributes + +- `asset_url` _string_ + + The asset url. + +- `attributes` _string_ + + The attributes. + +- `content_type` _string_ + + The content type. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `entity_type` _string_ + + The entity type. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_uploaded` _boolean_ + + Whether is uploaded. + +- `name` _string_ + + Display name. + +- `size` _number_ + + The size. + +
+
+ + + +```json +{ + "asset_url": "https://example.com", + "attributes": null, + "content_type": "example", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "entity_type": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_uploaded": false, + "name": "Example name", + "size": 1 +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------- | -------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/assets/` | List workspace assets | +| `POST` | `/api/v2/workspaces/{slug}/assets/` | Create a workspace asset upload | +| `DELETE` | `/api/v2/workspaces/{slug}/assets/{pk}/` | Delete a asset | +| `GET` | `/api/v2/workspaces/{slug}/assets/{pk}/` | Get a asset | +| `PATCH` | `/api/v2/workspaces/{slug}/assets/{pk}/` | Confirm a workspace asset upload | + +## Response shaping + +Every workspace asset read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/workspace-automations/create-automation-edge.md b/docs/api-reference/v2/workspace-automations/create-automation-edge.md new file mode 100644 index 00000000..0c9e7e31 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/create-automation-edge.md @@ -0,0 +1,191 @@ +--- +title: Create a automation edge +description: Create a automation edge with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a automation edge, automation edges, workspace automation edges create +--- + +# Create a automation edge + +
+ POST + /api/v2/workspaces/{slug}/automations/{automation_id}/edges/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Create a automation edge. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Id of the related source node. + + + + + +Id of the related target node. + + + + + +Order for evaluation when multiple edges from same node + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `execution_order`, `id`, `source_node_id`, `target_node_id`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "execution_order": 1, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "source_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/create-automation-node.md b/docs/api-reference/v2/workspace-automations/create-automation-node.md new file mode 100644 index 00000000..a158cb75 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/create-automation-node.md @@ -0,0 +1,223 @@ +--- +title: Create a automation node +description: Create a automation node with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a automation node, automation nodes, workspace automation nodes create +--- + +# Create a automation node + +
+ POST + /api/v2/workspaces/{slug}/automations/{automation_id}/nodes/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Create a automation node. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Name of the handler class (e.g., 'record_created', 'send_email') + +Maximum 100 characters. + + + + + +Display name for the node + +Maximum 255 characters. + + + + + +Type of node: trigger, action, or condition + +- `trigger` - Trigger +- `action` - Action +- `condition` - Condition + +One of `trigger`, `action`, `condition`. + + + + + +Node-specific configuration and parameters + + + + + +Whether the rule is switched on. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `config`, `created_at`, `created_by_id`, `handler_name`, `id`, `is_enabled`, `last_triggered_at`, `name`, `next_scheduled_at`, `node_type`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "config": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "handler_name": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "last_triggered_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "next_scheduled_at": "2026-01-14T09:22:41.478363Z", + "node_type": null, + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/create-workspace-automation.md b/docs/api-reference/v2/workspace-automations/create-workspace-automation.md new file mode 100644 index 00000000..3bad792e --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/create-workspace-automation.md @@ -0,0 +1,210 @@ +--- +title: Create a workspace automation +description: Create a workspace automation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a workspace automation, workspace automations, workspace automations create +--- + +# Create a workspace automation + +
+ POST + /api/v2/workspaces/{slug}/automations/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Create a workspace automation. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Human-readable automation name + +Maximum 255 characters. + + + + + +The scope defines on what entity this automation runs on (e.g., WorkItem, Cycle, Module) + +Maximum 50 characters. + + + + + +Automation description + + + + + +Ids of the projects to associate. Replaces the current set. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `bot_user_id`, `created_at`, `created_by_id`, `current_version_id`, `description`, `id`, `is_enabled`, `is_global`, `last_run_at`, `name`, `project_ids`, `run_count`, `scope`, `status`, `updated_at`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "bot_user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "current_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "is_global": false, + "last_run_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "run_count": 3, + "scope": "example", + "status": "draft", + "updated_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/delete-automation-edge.md b/docs/api-reference/v2/workspace-automations/delete-automation-edge.md new file mode 100644 index 00000000..19b0a8b6 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/delete-automation-edge.md @@ -0,0 +1,154 @@ +--- +title: Delete a automation edge +description: Delete a automation edge with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a automation edge, automation edges, workspace automation edges destroy +--- + +# Delete a automation edge + +
+ DELETE + /api/v2/workspaces/{slug}/automations/{automation_id}/edges/{pk}/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Delete a automation edge. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + + + +The automation edge id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `execution_order`, `id`, `source_node_id`, `target_node_id`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation edge matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/delete-automation-node.md b/docs/api-reference/v2/workspace-automations/delete-automation-node.md new file mode 100644 index 00000000..36b72571 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/delete-automation-node.md @@ -0,0 +1,154 @@ +--- +title: Delete a automation node +description: Delete a automation node with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a automation node, automation nodes, workspace automation nodes destroy +--- + +# Delete a automation node + +
+ DELETE + /api/v2/workspaces/{slug}/automations/{automation_id}/nodes/{pk}/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Delete a automation node. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + + + +The automation node id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `config`, `created_at`, `created_by_id`, `handler_name`, `id`, `is_enabled`, `last_triggered_at`, `name`, `next_scheduled_at`, `node_type`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation node matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/delete-workspace-automation.md b/docs/api-reference/v2/workspace-automations/delete-workspace-automation.md new file mode 100644 index 00000000..d4096a43 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/delete-workspace-automation.md @@ -0,0 +1,148 @@ +--- +title: Delete a workspace automation +description: Delete a workspace automation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a workspace automation, workspace automations, workspace automations destroy +--- + +# Delete a workspace automation + +
+ DELETE + /api/v2/workspaces/{slug}/automations/{pk}/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Delete a workspace automation. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace automation id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `bot_user_id`, `created_at`, `created_by_id`, `current_version_id`, `description`, `id`, `is_enabled`, `is_global`, `last_run_at`, `name`, `project_ids`, `run_count`, `scope`, `status`, `updated_at`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace automation matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/get-automation-activity.md b/docs/api-reference/v2/workspace-automations/get-automation-activity.md new file mode 100644 index 00000000..c8729467 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/get-automation-activity.md @@ -0,0 +1,170 @@ +--- +title: Get a automation activity +description: Read a automation activity with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a automation activity, automation activities, workspace automation activities retrieve +--- + +# Get a automation activity + +
+ GET + /api/v2/workspaces/{slug}/automations/{automation_id}/activities/{pk}/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Read a single automation activity by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + + + +The automation activity id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `actor_id`, `automation_edge_id`, `automation_id`, `automation_node_id`, `automation_run_id`, `automation_scope`, `automation_version_id`, `created_at`, `epoch`, `field`, `id`, `new_identifier`, `new_value`, `node_execution_id`, `old_identifier`, `old_value`, `verb`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "actor_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_edge_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_run_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_scope": "example", + "automation_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "epoch": 1, + "field": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "new_identifier": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "new_value": "example", + "node_execution_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_identifier": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_value": "example", + "verb": "example" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation activity matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/get-automation-edge.md b/docs/api-reference/v2/workspace-automations/get-automation-edge.md new file mode 100644 index 00000000..c46beff7 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/get-automation-edge.md @@ -0,0 +1,161 @@ +--- +title: Get a automation edge +description: Read a automation edge with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a automation edge, automation edges, workspace automation edges retrieve +--- + +# Get a automation edge + +
+ GET + /api/v2/workspaces/{slug}/automations/{automation_id}/edges/{pk}/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Read a single automation edge by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + + + +The automation edge id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `execution_order`, `id`, `source_node_id`, `target_node_id`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "execution_order": 1, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "source_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation edge matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/get-automation-node.md b/docs/api-reference/v2/workspace-automations/get-automation-node.md new file mode 100644 index 00000000..ff4bec2e --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/get-automation-node.md @@ -0,0 +1,165 @@ +--- +title: Get a automation node +description: Read a automation node with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a automation node, automation nodes, workspace automation nodes retrieve +--- + +# Get a automation node + +
+ GET + /api/v2/workspaces/{slug}/automations/{automation_id}/nodes/{pk}/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Read a single automation node by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + + + +The automation node id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `config`, `created_at`, `created_by_id`, `handler_name`, `id`, `is_enabled`, `last_triggered_at`, `name`, `next_scheduled_at`, `node_type`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "config": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "handler_name": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "last_triggered_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "next_scheduled_at": "2026-01-14T09:22:41.478363Z", + "node_type": null, + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation node matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/get-workspace-automation.md b/docs/api-reference/v2/workspace-automations/get-workspace-automation.md new file mode 100644 index 00000000..cd7b62d2 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/get-workspace-automation.md @@ -0,0 +1,162 @@ +--- +title: Get a workspace automation +description: Read a workspace automation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a workspace automation, workspace automations, workspace automations retrieve +--- + +# Get a workspace automation + +
+ GET + /api/v2/workspaces/{slug}/automations/{pk}/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Read a single workspace automation by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace automation id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `bot_user_id`, `created_at`, `created_by_id`, `current_version_id`, `description`, `id`, `is_enabled`, `is_global`, `last_run_at`, `name`, `project_ids`, `run_count`, `scope`, `status`, `updated_at`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "bot_user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "current_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "is_global": false, + "last_run_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "run_count": 3, + "scope": "example", + "status": "draft", + "updated_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace automation matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/list-automation-activities.md b/docs/api-reference/v2/workspace-automations/list-automation-activities.md new file mode 100644 index 00000000..e507a7ad --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/list-automation-activities.md @@ -0,0 +1,223 @@ +--- +title: List automation activities +description: List automation activities with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list automation activities, automation activities, workspace automation activities list +--- + +# List automation activities + +
+ GET + /api/v2/workspaces/{slug}/automations/{automation_id}/activities/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. List the automation activities you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `created_at__gt`. + + + + + +Filter by `field`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +Filter by `verb`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `actor_id`, `automation_edge_id`, `automation_id`, `automation_node_id`, `automation_run_id`, `automation_scope`, `automation_version_id`, `created_at`, `epoch`, `field`, `id`, `new_identifier`, `new_value`, `node_execution_id`, `old_identifier`, `old_value`, `verb`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "actor_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_edge_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_run_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_scope": "example", + "automation_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "epoch": 1, + "field": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "new_identifier": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "new_value": "example", + "node_execution_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_identifier": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_value": "example", + "verb": "example" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/list-automation-edges.md b/docs/api-reference/v2/workspace-automations/list-automation-edges.md new file mode 100644 index 00000000..e2f1a1aa --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/list-automation-edges.md @@ -0,0 +1,218 @@ +--- +title: List automation edges +description: List automation edges with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list automation edges, automation edges, workspace automation edges list +--- + +# List automation edges + +
+ GET + /api/v2/workspaces/{slug}/automations/{automation_id}/edges/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. List the automation edges you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +Filter by `source_node_id`. + + + + + +Filter by `target_node_id`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `execution_order`, `id`, `source_node_id`, `target_node_id`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "execution_order": 1, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "source_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "execution_order": 1, + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "source_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/list-automation-nodes.md b/docs/api-reference/v2/workspace-automations/list-automation-nodes.md new file mode 100644 index 00000000..c63a56b9 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/list-automation-nodes.md @@ -0,0 +1,244 @@ +--- +title: List automation nodes +description: List automation nodes with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list automation nodes, automation nodes, workspace automation nodes list +--- + +# List automation nodes + +
+ GET + /api/v2/workspaces/{slug}/automations/{automation_id}/nodes/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. List the automation nodes you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `handler_name`. + + + + + +Filter by `is_enabled`. + + + + + +Filter by `name`. + + + + + +Filter by `node_type`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `config`, `created_at`, `created_by_id`, `handler_name`, `id`, `is_enabled`, `last_triggered_at`, `name`, `next_scheduled_at`, `node_type`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "config": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "handler_name": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "last_triggered_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "next_scheduled_at": "2026-01-14T09:22:41.478363Z", + "node_type": null, + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + }, + { + "config": null, + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "handler_name": "example", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_enabled": false, + "last_triggered_at": "2026-01-14T09:22:41.478363Z", + "name": "Another example", + "next_scheduled_at": "2026-01-14T09:22:41.478363Z", + "node_type": null, + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/list-workspace-automations.md b/docs/api-reference/v2/workspace-automations/list-workspace-automations.md new file mode 100644 index 00000000..7c729d99 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/list-workspace-automations.md @@ -0,0 +1,230 @@ +--- +title: List workspace automations +description: List workspace automations with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list workspace automations, workspace automations, workspace automations list +--- + +# List workspace automations + +
+ GET + /api/v2/workspaces/{slug}/automations/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. List the workspace automations you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `is_enabled`. + + + + + +Filter by `is_global`. + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +Filter by `scope`. + + + + + +A search term. + + + + + +Filter by `status`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `bot_user_id`, `created_at`, `created_by_id`, `current_version_id`, `description`, `id`, `is_enabled`, `is_global`, `last_run_at`, `name`, `project_ids`, `run_count`, `scope`, `status`, `updated_at`. + + + +
+
+ +
+ +### Scopes + +`automations:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "bot_user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "current_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "is_global": false, + "last_run_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "run_count": 3, + "scope": "example", + "status": "draft", + "updated_at": "2026-01-14T09:22:41.478363Z" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/overview.md b/docs/api-reference/v2/workspace-automations/overview.md new file mode 100644 index 00000000..c82ada82 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/overview.md @@ -0,0 +1,144 @@ +--- +title: Workspace automations overview +description: The Plane API v2 workspace automation object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, workspace automations, workspace automation object +--- + +# Workspace automations overview + +Workspace automations are rule graphs that run across a workspace. + +
+
+ +## The workspace automation object + +### Attributes + +- `actor_id` _string (uuid)_ + + The related actor. + +- `automation_edge_id` _string (uuid)_ + + The related automation edge. + +- `automation_id` _string (uuid)_ + + The related automation. + +- `automation_node_id` _string (uuid)_ + + The related automation node. + +- `automation_run_id` _string (uuid)_ + + The related automation run. + +- `automation_scope` _string_ + + The automation scope. + +- `automation_version_id` _string (uuid)_ + + The related automation version. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `epoch` _number_ + + The epoch. + +- `field` _string_ + + The field. + +- `id` _string (uuid)_ + + Unique identifier. + +- `new_identifier` _string (uuid)_ + + The new identifier. + +- `new_value` _string_ + + The new value. + +- `node_execution_id` _string (uuid)_ + + The related node execution. + +- `old_identifier` _string (uuid)_ + + The old identifier. + +- `old_value` _string_ + + The old value. + +- `verb` _string_ + + The verb. + +
+
+ + + +```json +{ + "actor_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_edge_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_run_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "automation_scope": "example", + "automation_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "epoch": 1, + "field": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "new_identifier": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "new_value": "example", + "node_execution_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_identifier": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "old_value": "example", + "verb": "example" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | --------------------------------------------------------------------------------------------- | -------------------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/automations/` | List workspace automations | +| `POST` | `/api/v2/workspaces/{slug}/automations/` | Create a workspace automation | +| `GET` | `/api/v2/workspaces/{slug}/automations/{automation_id}/activities/` | List automation activities | +| `GET` | `/api/v2/workspaces/{slug}/automations/{automation_id}/activities/{pk}/` | Get a automation activity | +| `GET` | `/api/v2/workspaces/{slug}/automations/{automation_id}/edges/` | List automation edges | +| `POST` | `/api/v2/workspaces/{slug}/automations/{automation_id}/edges/` | Create a automation edge | +| `DELETE` | `/api/v2/workspaces/{slug}/automations/{automation_id}/edges/{pk}/` | Delete a automation edge | +| `GET` | `/api/v2/workspaces/{slug}/automations/{automation_id}/edges/{pk}/` | Get a automation edge | +| `PATCH` | `/api/v2/workspaces/{slug}/automations/{automation_id}/edges/{pk}/` | Update a automation edge | +| `GET` | `/api/v2/workspaces/{slug}/automations/{automation_id}/nodes/` | List automation nodes | +| `POST` | `/api/v2/workspaces/{slug}/automations/{automation_id}/nodes/` | Create a automation node | +| `DELETE` | `/api/v2/workspaces/{slug}/automations/{automation_id}/nodes/{pk}/` | Delete a automation node | +| `GET` | `/api/v2/workspaces/{slug}/automations/{automation_id}/nodes/{pk}/` | Get a automation node | +| `PATCH` | `/api/v2/workspaces/{slug}/automations/{automation_id}/nodes/{pk}/` | Update a automation node | +| `POST` | `/api/v2/workspaces/{slug}/automations/{automation_id}/nodes/{pk}/regenerate-webhook-secret/` | Regenerate an automation node webhook secret | +| `DELETE` | `/api/v2/workspaces/{slug}/automations/{pk}/` | Delete a workspace automation | +| `GET` | `/api/v2/workspaces/{slug}/automations/{pk}/` | Get a workspace automation | +| `PATCH` | `/api/v2/workspaces/{slug}/automations/{pk}/` | Update a workspace automation | +| `POST` | `/api/v2/workspaces/{slug}/automations/{pk}/status/` | Enable or disable a workspace automation | + +## Response shaping + +Every workspace automation read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/workspace-automations/regenerate-node-webhook-secret.md b/docs/api-reference/v2/workspace-automations/regenerate-node-webhook-secret.md new file mode 100644 index 00000000..5c7f6bed --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/regenerate-node-webhook-secret.md @@ -0,0 +1,129 @@ +--- +title: Regenerate an automation node webhook secret +description: Regenerate an automation node webhook secret with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, regenerate an automation node webhook secret, automation nodes, workspace automation nodes regenerate webhook secret +--- + +# Regenerate an automation node webhook secret + +
+ POST + /api/v2/workspaces/{slug}/automations/{automation_id}/nodes/{pk}/regenerate-webhook-secret/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + + + +The automation node id. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "secret": "example" +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/set-workspace-automation-status.md b/docs/api-reference/v2/workspace-automations/set-workspace-automation-status.md new file mode 100644 index 00000000..b36e0b30 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/set-workspace-automation-status.md @@ -0,0 +1,145 @@ +--- +title: Enable or disable a workspace automation +description: Enable or disable a workspace automation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, enable or disable a workspace automation, workspace automations, workspace automations status +--- + +# Enable or disable a workspace automation + +
+ POST + /api/v2/workspaces/{slug}/automations/{pk}/status/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace automation id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Whether the rule is switched on. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/update-automation-edge.md b/docs/api-reference/v2/workspace-automations/update-automation-edge.md new file mode 100644 index 00000000..6c6ef054 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/update-automation-edge.md @@ -0,0 +1,209 @@ +--- +title: Update a automation edge +description: Update a automation edge with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a automation edge, automation edges, workspace automation edges partial update +--- + +# Update a automation edge + +
+ PATCH + /api/v2/workspaces/{slug}/automations/{automation_id}/edges/{pk}/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Update a automation edge. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + + + +The automation edge id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Order for evaluation when multiple edges from same node + + + + + +Id of the related source node. + + + + + +Id of the related target node. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `execution_order`, `id`, `source_node_id`, `target_node_id`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "execution_order": 1, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "source_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "target_node_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation edge matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/update-automation-node.md b/docs/api-reference/v2/workspace-automations/update-automation-node.md new file mode 100644 index 00000000..ab69d649 --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/update-automation-node.md @@ -0,0 +1,235 @@ +--- +title: Update a automation node +description: Update a automation node with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a automation node, automation nodes, workspace automation nodes partial update +--- + +# Update a automation node + +
+ PATCH + /api/v2/workspaces/{slug}/automations/{automation_id}/nodes/{pk}/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Update a automation node. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The automation the resource belongs to. + + + + + +The automation node id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Node-specific configuration and parameters + + + + + +Name of the handler class (e.g., 'record_created', 'send_email') + +Maximum 100 characters. + + + + + +Whether the rule is switched on. + + + + + +Display name for the node + +Maximum 255 characters. + + + + + +Type of node: trigger, action, or condition + +- `trigger` - Trigger +- `action` - Action +- `condition` - Condition + +One of `trigger`, `action`, `condition`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `config`, `created_at`, `created_by_id`, `handler_name`, `id`, `is_enabled`, `last_triggered_at`, `name`, `next_scheduled_at`, `node_type`, `updated_at`, `version_id`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "config": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "handler_name": "example", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "last_triggered_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "next_scheduled_at": "2026-01-14T09:22:41.478363Z", + "node_type": null, + "updated_at": "2026-01-14T09:22:41.478363Z", + "version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No automation node matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-automations/update-workspace-automation.md b/docs/api-reference/v2/workspace-automations/update-workspace-automation.md new file mode 100644 index 00000000..13ed14df --- /dev/null +++ b/docs/api-reference/v2/workspace-automations/update-workspace-automation.md @@ -0,0 +1,228 @@ +--- +title: Update a workspace automation +description: Update a workspace automation with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a workspace automation, workspace automations, workspace automations partial update +--- + +# Update a workspace automation + +
+ PATCH + /api/v2/workspaces/{slug}/automations/{pk}/ +
+ +
+
+ +Workspace automations are rule graphs that run across a workspace. Update a workspace automation. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace automation id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Automation description + + + + + +Human-readable automation name + +Maximum 255 characters. + + + + + +Ids of the projects to associate. Replaces the current set. + + + + + +The scope defines on what entity this automation runs on (e.g., WorkItem, Cycle, Module) + +Maximum 50 characters. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `bot_user_id`, `created_at`, `created_by_id`, `current_version_id`, `description`, `id`, `is_enabled`, `is_global`, `last_run_at`, `name`, `project_ids`, `run_count`, `scope`, `status`, `updated_at`. + + + +
+
+ +
+ +### Scopes + +`automations:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "bot_user_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "current_version_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "description": "What this is for.", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_enabled": false, + "is_global": false, + "last_run_at": "2026-01-14T09:22:41.478363Z", + "name": "Example name", + "project_ids": [["16c61a3a-512a-48ac-b0be-b6b46fe6f430"]], + "run_count": 3, + "scope": "example", + "status": "draft", + "updated_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace automation matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-features/get-workspace-features.md b/docs/api-reference/v2/workspace-features/get-workspace-features.md new file mode 100644 index 00000000..4176a383 --- /dev/null +++ b/docs/api-reference/v2/workspace-features/get-workspace-features.md @@ -0,0 +1,163 @@ +--- +title: Get workspace features +description: Read a Plane workspace's feature flags with the v2 REST API, including is_work_item_types_enabled — the work item type mode discriminator. Scopes, errors, and code examples. +keywords: plane api v2, get workspace features, feature flags, is_work_item_types_enabled, work item type mode, GET features +--- + +# Get workspace features + +
+ GET + /api/v2/workspaces/{slug}/features/ +
+ +
+
+ +Read the workspace's feature flags. There is one feature record per workspace, so this returns a single object rather than a list. + +Call it during client start-up. `is_work_item_types_enabled` tells you which surface accepts work item type and property writes, and the remaining flags tell you which parts of your UI or sync are worth showing at all. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +None. This endpoint takes no filters, no pagination, and no `?expand=` — passing one is not a supported way to widen the response. + +
+ +
+ +### Scopes + +`workspaces.features:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this workspace's features. | +| `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. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: tip This is the mode check +`is_work_item_types_enabled: true` means the workspace manages work item types at the **workspace** level; `false` means each **project** does. Writes sent to the other surface return `409`, with `work_item_types_managed_at_workspace` or `work_item_types_managed_at_project`. Reads are unaffected. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +::: info Cache it, but not forever +The flags change rarely, so caching the response for the life of a request batch is reasonable. Re-read it whenever a write returns `409 work_item_types_managed_at_workspace` or `work_item_types_managed_at_project` — that code means your cached mode is stale. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "a72f1c40-5b8e-4d19-9f2a-3c6d8e1b7a55", + "is_work_item_types_enabled": true, + "work_item_type_default_level": 0, + "is_workitem_hierarchy_enabled": false, + "is_project_grouping_enabled": false, + "is_teams_enabled": true, + "is_wiki_enabled": true, + "is_initiative_enabled": false, + "is_customer_enabled": false, + "is_release_enabled": false, + "is_state_duration_enabled": false, + "is_pi_enabled": false, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No Workspace matches the given query." +} +``` + + + +
+
+ +## Related + +- [Update workspace features](/api-reference/v2/workspace-features/update-workspace-features) +- [Workspace features overview](/api-reference/v2/workspace-features/overview) — every flag, one line each +- [Work item type modes](/api-reference/v2/work-item-type-modes) diff --git a/docs/api-reference/v2/workspace-features/overview.md b/docs/api-reference/v2/workspace-features/overview.md new file mode 100644 index 00000000..2e2d29fd --- /dev/null +++ b/docs/api-reference/v2/workspace-features/overview.md @@ -0,0 +1,159 @@ +--- +title: Workspace features overview +description: The Plane API v2 workspace feature object. Every feature flag explained, how is_work_item_types_enabled selects the work item type mode, and how to read and toggle features. +keywords: plane api v2, workspace features, feature flags, is_work_item_types_enabled, work item type mode, is_teams_enabled, is_wiki_enabled, workspace settings +--- + +# Workspace features overview + +Workspace features are the switches that decide which parts of Plane exist for a workspace. One object, one `GET`, one `PATCH` — but it is the object every capable client reads first, because two of its fields change how the rest of the API behaves. + +The important one is `is_work_item_types_enabled`. It is the **mode discriminator for work item types**: it tells you whether types and their properties are owned by the workspace or by each project, and therefore which endpoints will accept your writes. Getting it wrong is a `409`, not a `404` or a `403`. + +
+
+ +## The workspace feature object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the workspace's feature record. + +- `is_work_item_types_enabled` _boolean_ + + Whether work item types are managed at the **workspace** level. `true` means workspace mode — types and properties are defined once for the workspace and imported into projects. `false` means project mode — each project owns its own types. This is the field to read before any type or property write. See [Work item type modes](/api-reference/v2/work-item-type-modes). + +- `work_item_type_default_level` _integer_ + + The default level applied to work item types in this workspace. The schema constrains it to an integer and declares no enum, so treat any value as legal and leave it as returned unless you are deliberately changing type levels. + +- `is_workitem_hierarchy_enabled` _boolean_ + + Whether work items can be nested into a parent and child hierarchy in this workspace. + +- `is_project_grouping_enabled` _boolean_ + + Whether projects can be organized into groups in the workspace. + +- `is_teams_enabled` _boolean_ + + Whether teamspaces are available. Teamspaces have their own endpoints under the `teamspaces:*` scopes. + +- `is_wiki_enabled` _boolean_ + + Whether the workspace-level wiki is available, behind the `wiki.pages:*` scopes. + +- `is_initiative_enabled` _boolean_ + + Whether initiatives — the layer that groups projects and epics toward a larger outcome — are available. + +- `is_customer_enabled` _boolean_ + + Whether customers and customer requests are available, behind the `customers:*` scopes. + +- `is_release_enabled` _boolean_ + + Whether releases are available, behind the `releases:*` scopes. + +- `is_state_duration_enabled` _boolean_ + + Whether Plane records how long work items spend in each state. + +- `is_pi_enabled` _boolean_ + + Whether Pi, Plane's AI assistant, is available in the workspace. + +- `created_at` _string (date-time)_ + + When the feature record was created. + +::: warning Watch the spelling of the hierarchy flag +It is `is_workitem_hierarchy_enabled` — no underscore between `work` and `item` — while its neighbor is `is_work_item_types_enabled`. Unknown keys in a `PATCH` body do not toggle anything, so a misspelling looks like a silent no-op. Read the response back and confirm the flag actually moved. +::: + +
+
+ + + +```json +{ + "id": "a72f1c40-5b8e-4d19-9f2a-3c6d8e1b7a55", + "is_work_item_types_enabled": true, + "work_item_type_default_level": 0, + "is_workitem_hierarchy_enabled": false, + "is_project_grouping_enabled": false, + "is_teams_enabled": true, + "is_wiki_enabled": true, + "is_initiative_enabled": false, + "is_customer_enabled": false, + "is_release_enabled": false, + "is_state_duration_enabled": false, + "is_pi_enabled": false, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| ------- | ------------------------------------- | ----------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/features/` | Read the workspace's features | +| `PATCH` | `/api/v2/workspaces/{slug}/features/` | Toggle workspace features | + +There is one feature record per workspace, so the path has no id and there is nothing to create or delete. + +## Discovering the work item type mode + +A workspace manages work item types in exactly one mode, and this endpoint is how a client finds out which: + +```bash +curl "https://api.plane.so/api/v2/workspaces/my-team/features/" \ + -H "X-Api-Key: $PLANE_API_KEY" +``` + +- `is_work_item_types_enabled: true` → **workspace mode**. Type and property writes go to `/api/v2/workspaces/{slug}/work-item-types/` and friends. The project-level equivalents return `409 work_item_types_managed_at_workspace`. +- `is_work_item_types_enabled: false` → **project mode**. Those writes go to `/api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/`. The workspace-level equivalents return `409 work_item_types_managed_at_project`. + +**Reads are unaffected by mode.** A project lists its types in either mode, so a read-only integration never has to branch. Only writes are gated. + +Full walkthrough, including how each surface differs: [Work item type modes](/api-reference/v2/work-item-type-modes). + +::: tip Read the mode, do not guess it +Branching on the `409` you got last time bakes yesterday's configuration into your client. Read `is_work_item_types_enabled`, cache it briefly, and re-read it when a write comes back `409` — the mode may have been switched by an admin between your calls. +::: + +## Flags change what other endpoints do + +A disabled feature is not a hidden endpoint. Turning a flag off removes the capability from the workspace, so calls that depend on it stop being useful even when they remain routable. Two consequences worth designing for: + +- **Toggling `is_work_item_types_enabled` moves the write surface for every client in the workspace**, not just yours. Treat it as an administrative action, not something an integration flips at runtime. +- **A flag being `true` is necessary, not sufficient.** Features also have to be available on the workspace's plan. Treat a successful call, not the flag alone, as proof a capability is live. + +## Scopes + +| Operation | Scope | +| --------- | --------------------------- | +| `GET` | `workspaces.features:read` | +| `PATCH` | `workspaces.features:write` | + +## Changed from v1 + +- Flags are now prefixed and suffixed consistently: v1's `project_grouping`, `initiatives`, `teams`, `customers`, `wiki`, and `pi` are `is_project_grouping_enabled`, `is_initiative_enabled`, `is_teams_enabled`, `is_customer_enabled`, `is_wiki_enabled`, and `is_pi_enabled`. +- New in v2: `is_work_item_types_enabled`, `work_item_type_default_level`, `is_workitem_hierarchy_enabled`, `is_release_enabled`, and `is_state_duration_enabled`. +- The response now carries `id` and `created_at` alongside the flags. + +See [Migrating from v1](/api-reference/v2/migrating-from-v1) for the full list. + +## Related + +- [Get workspace features](/api-reference/v2/workspace-features/get-workspace-features) +- [Update workspace features](/api-reference/v2/workspace-features/update-workspace-features) +- [Work item type modes](/api-reference/v2/work-item-type-modes) diff --git a/docs/api-reference/v2/workspace-features/update-workspace-features.md b/docs/api-reference/v2/workspace-features/update-workspace-features.md new file mode 100644 index 00000000..07116fa2 --- /dev/null +++ b/docs/api-reference/v2/workspace-features/update-workspace-features.md @@ -0,0 +1,256 @@ +--- +title: Update workspace features +description: Toggle a Plane workspace's feature flags with the v2 REST API, including switching the work item type mode. Body parameters, scopes, error codes, and code examples. +keywords: plane api v2, update workspace features, toggle feature flags, PATCH features, is_work_item_types_enabled, switch work item type mode +--- + +# Update workspace features + +
+ PATCH + /api/v2/workspaces/{slug}/features/ +
+ +
+
+ +Turn workspace features on or off. The update is partial — send only the flags you want to change, and every field you omit keeps its current value. Omitting a field is not the same as sending `null`. + +The response is the full feature object after the change, so you never need a follow-up `GET` to confirm what happened. + +::: warning Toggling `is_work_item_types_enabled` moves the write surface +This flag decides whether work item types are managed at the workspace or the project level. Flipping it changes which endpoints accept type and property writes **for every client in the workspace**, and in-flight integrations start getting `409` from the surface they were using. Treat it as an administrative action, not a runtime decision. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +Every field is optional. `id` and `created_at` are read-only — sending them has no effect. + +
+ + + +Manage work item types at the workspace level (`true`) or per project (`false`). This is the work item type mode switch — read [Work item type modes](/api-reference/v2/work-item-type-modes) before changing it. + + + + + +The default level applied to work item types in this workspace. The schema declares no enum and no bounds, so it accepts any integer. + + + + + +Allow work items to be nested into a parent and child hierarchy. Note the spelling — no underscore between `work` and `item`. + + + + + +Allow projects to be organized into groups in the workspace. + + + + + +Enable teamspaces for the workspace. + + + + + +Enable the workspace-level wiki. + + + + + +Enable initiatives, the layer that groups projects and epics toward a larger outcome. + + + + + +Enable customers and customer requests. + + + + + +Enable releases. + + + + + +Record how long work items spend in each state. + + + + + +Enable Pi, Plane's AI assistant, in the workspace. + + + +
+
+ +
+ +### Scopes + +`workspaces.features:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | A flag was sent with a non-boolean value, or `work_item_type_default_level` was not an integer. Includes an `errors[]` array naming the field. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't change this workspace's features. | +| `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` | The requested toggle conflicts with the workspace's current state and was not applied. Re-read the object before retrying. | +| `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. | + +
+ +::: info Unknown keys are not toggles +A body key that is not one of the fields above is not a flag and does not change anything. Always compare the response object against what you sent rather than assuming a `200` means your flag moved; `is_workitem_hierarchy_enabled` in particular is easy to mistype as `is_work_item_hierarchy_enabled`. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "a72f1c40-5b8e-4d19-9f2a-3c6d8e1b7a55", + "is_work_item_types_enabled": true, + "work_item_type_default_level": 0, + "is_workitem_hierarchy_enabled": false, + "is_project_grouping_enabled": false, + "is_teams_enabled": true, + "is_wiki_enabled": true, + "is_initiative_enabled": false, + "is_customer_enabled": false, + "is_release_enabled": false, + "is_state_duration_enabled": true, + "is_pi_enabled": false, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "Invalid input.", + "errors": [ + { + "field": "is_teams_enabled", + "code": "invalid", + "message": "Must be a valid boolean." + } + ] +} +``` + + + +
+
+ +::: tip Confirm the mode after switching it +After a `PATCH` that changes `is_work_item_types_enabled`, read the value straight from the response and use it to pick your write surface. Anything you cached before this call is now wrong. Then follow [Work item type modes](/api-reference/v2/work-item-type-modes) — workspace mode also changes how types reach a project, since projects import workspace types instead of defining their own. +::: + +## Related + +- [Get workspace features](/api-reference/v2/workspace-features/get-workspace-features) +- [Workspace features overview](/api-reference/v2/workspace-features/overview) +- [Work item type modes](/api-reference/v2/work-item-type-modes) diff --git a/docs/api-reference/v2/workspace-pages/create-workspace-page.md b/docs/api-reference/v2/workspace-pages/create-workspace-page.md new file mode 100644 index 00000000..8f71312a --- /dev/null +++ b/docs/api-reference/v2/workspace-pages/create-workspace-page.md @@ -0,0 +1,276 @@ +--- +title: Create a workspace page +description: Create a workspace page with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a workspace page, workspace pages, workspace pages create +--- + +# Create a workspace page + +
+ POST + /api/v2/workspaces/{slug}/pages/ +
+ +
+
+ +Workspace pages are the wiki documents that live outside any project. Create a workspace page. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + + + + + +Who can see this. + + + + + +The archived at. + +Nullable. + + + + + +Id of the related collection. + +Nullable. + + + + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Prevents further edits to the content. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Id of the related parent. + +Nullable. + + + + + +Manual ordering weight. Lower sorts first. + + + + + +Editor-owned layout descriptor. Pass back what you read. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `collection_id`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `external_id`, `external_source`, `id`, `is_global`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `parent_id`, `sort_order`, `view_props`. + + + + + +Comma-separated relations to embed: `owned_by`, `parent`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`wiki.pages:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "archived_at": null, + "collection_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_global": false, + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535, + "view_props": null +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/workspace-pages/delete-workspace-page.md b/docs/api-reference/v2/workspace-pages/delete-workspace-page.md new file mode 100644 index 00000000..5dbfee53 --- /dev/null +++ b/docs/api-reference/v2/workspace-pages/delete-workspace-page.md @@ -0,0 +1,156 @@ +--- +title: Delete a workspace page +description: Delete a workspace page with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a workspace page, workspace pages, workspace pages destroy +--- + +# Delete a workspace page + +
+ DELETE + /api/v2/workspaces/{slug}/pages/{pk}/ +
+ +
+
+ +Workspace pages are the wiki documents that live outside any project. Delete a workspace page. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace page id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `collection_id`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `external_id`, `external_source`, `id`, `is_global`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `parent_id`, `sort_order`, `view_props`. + + + + + +Comma-separated relations to embed: `owned_by`, `parent`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`wiki.pages:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace page matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-pages/get-workspace-page.md b/docs/api-reference/v2/workspace-pages/get-workspace-page.md new file mode 100644 index 00000000..0c58667e --- /dev/null +++ b/docs/api-reference/v2/workspace-pages/get-workspace-page.md @@ -0,0 +1,174 @@ +--- +title: Get a workspace page +description: Read a workspace page with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a workspace page, workspace pages, workspace pages retrieve +--- + +# Get a workspace page + +
+ GET + /api/v2/workspaces/{slug}/pages/{pk}/ +
+ +
+
+ +Workspace pages are the wiki documents that live outside any project. Read a single workspace page by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace page id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `collection_id`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `external_id`, `external_source`, `id`, `is_global`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `parent_id`, `sort_order`, `view_props`. + + + + + +Comma-separated relations to embed: `owned_by`, `parent`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`wiki.pages:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "archived_at": null, + "collection_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_global": false, + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535, + "view_props": null +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace page matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-pages/list-workspace-pages.md b/docs/api-reference/v2/workspace-pages/list-workspace-pages.md new file mode 100644 index 00000000..99b41dc4 --- /dev/null +++ b/docs/api-reference/v2/workspace-pages/list-workspace-pages.md @@ -0,0 +1,275 @@ +--- +title: List workspace pages +description: List workspace pages with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list workspace pages, workspace pages, workspace pages list +--- + +# List workspace pages + +
+ GET + /api/v2/workspaces/{slug}/pages/ +
+ +
+
+ +Workspace pages are the wiki documents that live outside any project. List the workspace pages you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +- `0` - Public +- `1` - Private + +One of `0`, `1`. + + + + + +Filter by `collection_id`. + + + + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `external_id`. + + + + + +Filter by `external_source`. + + + + + +Filter by `is_global`. + + + + + +Filter by `is_locked`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Filter by `owned_by_id`. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Filter by `parent_id`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +- `all` - All +- `public` - Public +- `private` - Private +- `shared` - Shared +- `archived` - Archived + +One of `all`, `archived`, `private`, `public`, `shared`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `collection_id`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `external_id`, `external_source`, `id`, `is_global`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `parent_id`, `sort_order`, `view_props`. + + + + + +Comma-separated relations to embed: `owned_by`, `parent`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`wiki.pages:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "access": 0, + "archived_at": null, + "collection_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_global": false, + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535, + "view_props": null + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/workspace-pages/overview.md b/docs/api-reference/v2/workspace-pages/overview.md new file mode 100644 index 00000000..a98a2b4d --- /dev/null +++ b/docs/api-reference/v2/workspace-pages/overview.md @@ -0,0 +1,140 @@ +--- +title: Workspace pages overview +description: The Plane API v2 workspace page object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, workspace pages, workspace page object +--- + +# Workspace pages overview + +Workspace pages are the wiki documents that live outside any project. + +
+
+ +## The workspace page object + +### Attributes + +- `access` _integer_ + + Who can see this. + +- `archived_at` _string (date-time)_ + + When the record was archived, or `null` if it is active. + +- `collection_id` _string_ + + The related collection. + +- `color` _string_ + + Hex color used wherever this is rendered, for example `#3f76ff`. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description_html` _string_ + + Rich-text body as HTML. This is the field the Plane editor round-trips. + +- `description_stripped` _string_ + + The description stripped. + +- `external_id` _string_ + + Your system's identifier for this record, for sync and import correlation. + +- `external_source` _string_ + + The system `external_id` came from, for example `github` or `jira`. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_global` _boolean_ + + Whether this lives at the workspace level rather than inside a project. + +- `is_locked` _boolean_ + + Prevents further edits to the content. + +- `logo_props` _string_ + + Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +- `name` _string_ + + Display name. + +- `owned_by_id` _string (uuid)_ + + The related owned by. + +- `parent_id` _string (uuid)_ + + The related parent. + +- `sort_order` _number_ + + Manual ordering weight. Lower sorts first. + +- `view_props` _string_ + + Editor-owned layout descriptor. Pass back what you read. + +
+
+ + + +```json +{ + "access": 0, + "archived_at": null, + "collection_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_global": false, + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535, + "view_props": null +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | --------------------------------------- | ----------------------- | +| `GET` | `/api/v2/workspaces/{slug}/pages/` | List workspace pages | +| `POST` | `/api/v2/workspaces/{slug}/pages/` | Create a workspace page | +| `DELETE` | `/api/v2/workspaces/{slug}/pages/{pk}/` | Delete a workspace page | +| `GET` | `/api/v2/workspaces/{slug}/pages/{pk}/` | Get a workspace page | +| `PATCH` | `/api/v2/workspaces/{slug}/pages/{pk}/` | Update a workspace page | + +## Response shaping + +Every workspace page read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/workspace-pages/update-workspace-page.md b/docs/api-reference/v2/workspace-pages/update-workspace-page.md new file mode 100644 index 00000000..1d51d0e8 --- /dev/null +++ b/docs/api-reference/v2/workspace-pages/update-workspace-page.md @@ -0,0 +1,294 @@ +--- +title: Update a workspace page +description: Update a workspace page with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a workspace page, workspace pages, workspace pages partial update +--- + +# Update a workspace page + +
+ PATCH + /api/v2/workspaces/{slug}/pages/{pk}/ +
+ +
+
+ +Workspace pages are the wiki documents that live outside any project. Update a workspace page. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace page id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Who can see this. + + + + + +The archived at. + +Nullable. + + + + + +Id of the related collection. + +Nullable. + + + + + +Hex color used wherever this is rendered, for example `#3f76ff`. + +Maximum 255 characters. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Your system's identifier for this record, for sync and import correlation. + +Maximum 255 characters. Nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. + +Maximum 255 characters. Nullable. + + + + + +Prevents further edits to the content. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Display name. + + + + + +Id of the related parent. + +Nullable. + + + + + +Manual ordering weight. Lower sorts first. + + + + + +Editor-owned layout descriptor. Pass back what you read. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `collection_id`, `color`, `created_at`, `created_by_id`, `description_html`, `description_stripped`, `external_id`, `external_source`, `id`, `is_global`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `parent_id`, `sort_order`, `view_props`. + + + + + +Comma-separated relations to embed: `owned_by`, `parent`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`wiki.pages:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "archived_at": null, + "collection_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "color": "#3f76ff", + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "description_stripped": "example", + "external_id": null, + "external_source": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_global": false, + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "parent_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "sort_order": 65535, + "view_props": null +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace page matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-views/create-workspace-view.md b/docs/api-reference/v2/workspace-views/create-workspace-view.md new file mode 100644 index 00000000..386ed7b2 --- /dev/null +++ b/docs/api-reference/v2/workspace-views/create-workspace-view.md @@ -0,0 +1,245 @@ +--- +title: Create a workspace view +description: Create a workspace view with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a workspace view, workspace views, workspace views create +--- + +# Create a workspace view + +
+ POST + /api/v2/workspaces/{slug}/views/ +
+ +
+
+ +Workspace views are saved filter-and-layout combinations across a workspace. Create a workspace view. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Who can see this. + + + + + +Free-form description. + + + + + +Saved display options — grouping, ordering and layout. + + + + + +The display properties. + + + + + +Saved filter set, in the same shape the list endpoints accept. + + + + + +Prevents further edits to the content. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +The pql filters. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `created_at`, `created_by_id`, `description`, `display_filters`, `display_properties`, `filters`, `id`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `pql_filters`, `query`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`workspaces.views:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "display_filters": null, + "display_properties": null, + "filters": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "pql_filters": null, + "query": null, + "sort_order": 65535 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-views/delete-workspace-view.md b/docs/api-reference/v2/workspace-views/delete-workspace-view.md new file mode 100644 index 00000000..f776ffa5 --- /dev/null +++ b/docs/api-reference/v2/workspace-views/delete-workspace-view.md @@ -0,0 +1,156 @@ +--- +title: Delete a workspace view +description: Delete a workspace view with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a workspace view, workspace views, workspace views destroy +--- + +# Delete a workspace view + +
+ DELETE + /api/v2/workspaces/{slug}/views/{pk}/ +
+ +
+
+ +Workspace views are saved filter-and-layout combinations across a workspace. Delete a workspace view. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace view id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `created_at`, `created_by_id`, `description`, `display_filters`, `display_properties`, `filters`, `id`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `pql_filters`, `query`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`workspaces.views:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace view matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-views/get-workspace-view.md b/docs/api-reference/v2/workspace-views/get-workspace-view.md new file mode 100644 index 00000000..e124ed1f --- /dev/null +++ b/docs/api-reference/v2/workspace-views/get-workspace-view.md @@ -0,0 +1,171 @@ +--- +title: Get a workspace view +description: Read a workspace view with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a workspace view, workspace views, workspace views retrieve +--- + +# Get a workspace view + +
+ GET + /api/v2/workspaces/{slug}/views/{pk}/ +
+ +
+
+ +Workspace views are saved filter-and-layout combinations across a workspace. Read a single workspace view by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace view id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `created_at`, `created_by_id`, `description`, `display_filters`, `display_properties`, `filters`, `id`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `pql_filters`, `query`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`workspaces.views:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "display_filters": null, + "display_properties": null, + "filters": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "pql_filters": null, + "query": null, + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace view matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-views/list-workspace-views.md b/docs/api-reference/v2/workspace-views/list-workspace-views.md new file mode 100644 index 00000000..f25f44e2 --- /dev/null +++ b/docs/api-reference/v2/workspace-views/list-workspace-views.md @@ -0,0 +1,236 @@ +--- +title: List workspace views +description: List workspace views with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list workspace views, workspace views, workspace views list +--- + +# List workspace views + +
+ GET + /api/v2/workspaces/{slug}/views/ +
+ +
+
+ +Workspace views are saved filter-and-layout combinations across a workspace. List the workspace views you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +- `0` - Private +- `1` - Public + +One of `0`, `1`. + + + + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `is_locked`. + + + + + +Filter by `name`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Filter by `owned_by_id`. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `created_at`, `created_by_id`, `description`, `display_filters`, `display_properties`, `filters`, `id`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `pql_filters`, `query`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`workspaces.views:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "access": 0, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "display_filters": null, + "display_properties": null, + "filters": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "pql_filters": null, + "query": null, + "sort_order": 65535 + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-views/overview.md b/docs/api-reference/v2/workspace-views/overview.md new file mode 100644 index 00000000..c55fb84f --- /dev/null +++ b/docs/api-reference/v2/workspace-views/overview.md @@ -0,0 +1,125 @@ +--- +title: Workspace views overview +description: The Plane API v2 workspace view object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, workspace views, workspace view object +--- + +# Workspace views overview + +Workspace views are saved filter-and-layout combinations across a workspace. + +
+
+ +## The workspace view object + +### Attributes + +- `access` _integer_ + + Who can see this. + +- `archived_at` _string (date-time)_ + + When the record was archived, or `null` if it is active. + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description` _string_ + + Free-form description. + +- `display_filters` _string_ + + Saved display options — grouping, ordering and layout. + +- `display_properties` _string_ + + The display properties. + +- `filters` _string_ + + Saved filter set, in the same shape the list endpoints accept. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_locked` _boolean_ + + Prevents further edits to the content. + +- `logo_props` _string_ + + Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + +- `name` _string_ + + Display name. + +- `owned_by_id` _string (uuid)_ + + The related owned by. + +- `pql_filters` _string_ + + The pql filters. + +- `query` _string_ + + The query. + +- `sort_order` _number_ + + Manual ordering weight. Lower sorts first. + +
+
+ + + +```json +{ + "access": 0, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "display_filters": null, + "display_properties": null, + "filters": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "pql_filters": null, + "query": null, + "sort_order": 65535 +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | --------------------------------------- | ----------------------- | +| `GET` | `/api/v2/workspaces/{slug}/views/` | List workspace views | +| `POST` | `/api/v2/workspaces/{slug}/views/` | Create a workspace view | +| `DELETE` | `/api/v2/workspaces/{slug}/views/{pk}/` | Delete a workspace view | +| `GET` | `/api/v2/workspaces/{slug}/views/{pk}/` | Get a workspace view | +| `PATCH` | `/api/v2/workspaces/{slug}/views/{pk}/` | Update a workspace view | + +## Response shaping + +Every workspace view read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/workspace-views/update-workspace-view.md b/docs/api-reference/v2/workspace-views/update-workspace-view.md new file mode 100644 index 00000000..3e7264f4 --- /dev/null +++ b/docs/api-reference/v2/workspace-views/update-workspace-view.md @@ -0,0 +1,263 @@ +--- +title: Update a workspace view +description: Update a workspace view with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a workspace view, workspace views, workspace views partial update +--- + +# Update a workspace view + +
+ PATCH + /api/v2/workspaces/{slug}/views/{pk}/ +
+ +
+
+ +Workspace views are saved filter-and-layout combinations across a workspace. Update a workspace view. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace view id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Who can see this. + + + + + +Free-form description. + + + + + +Saved display options — grouping, ordering and layout. + + + + + +The display properties. + + + + + +Saved filter set, in the same shape the list endpoints accept. + + + + + +Prevents further edits to the content. + + + + + +Editor-owned logo descriptor. Pass back what you read rather than composing it by hand. + + + + + +Display name. + +Maximum 255 characters. + + + + + +The pql filters. + + + + + +Manual ordering weight. Lower sorts first. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `access`, `archived_at`, `created_at`, `created_by_id`, `description`, `display_filters`, `display_properties`, `filters`, `id`, `is_locked`, `logo_props`, `name`, `owned_by_id`, `pql_filters`, `query`, `sort_order`. + + + + + +Comma-separated relations to embed: `owned_by`. + +Expansion is separate-key — `?expand=state` keeps `state_id` and adds a `state` object next to it. `?fields=` and `?expand=` are independent: naming a relation in `?fields=` is a `400`, and expanded objects survive field filtering. See [Expanding relations](/api-reference/v2/expanding-relations). + + + +
+
+ +
+ +### Scopes + +`workspaces.views:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "access": 0, + "archived_at": null, + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description": "What this is for.", + "display_filters": null, + "display_properties": null, + "filters": null, + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_locked": false, + "logo_props": null, + "name": "Example name", + "owned_by_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "pql_filters": null, + "query": null, + "sort_order": 65535 +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace view matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-work-item-properties/create-workspace-work-item-property.md b/docs/api-reference/v2/workspace-work-item-properties/create-workspace-work-item-property.md new file mode 100644 index 00000000..060c7c37 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-properties/create-workspace-work-item-property.md @@ -0,0 +1,353 @@ +--- +title: Create a workspace work item property +description: Define a workspace-level custom property with the Plane v2 REST API. Property types, relation types, options, contexts, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create work item property, workspace custom property, property_type, relation_type, POST work item properties, work item type modes +--- + +# Create a workspace work item property + +
+ POST + /api/v2/workspaces/{slug}/work-item-properties/ +
+ +
+
+ +Define a custom property once for the whole workspace. `display_name` and `property_type` are the only +required fields; everything else refines how the value is captured. + +::: warning A new property is not live yet +Creating a property adds it to the workspace catalog. It appears on work items only once a **context** binds +it to projects and work item types. Follow this call with +[Create a property context](/api-reference/v2/work-item-property-contexts/create-property-context), or the +property will sit in the catalog unused. +::: + +::: warning Wrong mode is a 409, not a 404 +If the workspace manages work item types at the **project** level, this route returns `409` with the code +`work_item_types_managed_at_project`. Create the property on the project-level resource instead. See +[Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The label shown wherever the property is rendered. Maximum 255 characters. + + + + + +What kind of value the property holds. Choose carefully — it governs what `relation_type`, `options`, +`settings`, and `validation_rules` mean. + +- `TEXT` — free-form text +- `DATETIME` — a date and time +- `DECIMAL` — a number +- `BOOLEAN` — true or false +- `OPTION` — a choice from a defined list +- `RELATION` — a pointer to another record; pair it with `relation_type` +- `URL` — a link +- `EMAIL` — an email address +- `FILE` — a file +- `FORMULA` — a formula-backed value + +A value outside this list is a `400 invalid_request`. + + + + + +For a `RELATION` property, what it points at. One of `ISSUE`, `USER`, `RELEASE`, or `RICH_TEXT`. Leave it out +— or send `null` — for every other property type. + + + + + +Free-form explanation of what the property captures. Nullable. + + + + + +Whether a value must be supplied for this property. + + + + + +Whether the property accepts more than one value. + + + + + +Whether the property is in use. Send `false` to define a property without putting it into circulation yet. + + + + + +The value applied when none is supplied. Send an array even for a single value. + + + + + +Seed the choices for an `OPTION` property in the same call. Write-only: the created options come back on the +read-only `options` array of the response, and are managed afterwards through +[Property options](/api-reference/v2/workspace-work-item-property-options/overview), which is also where the +fields an option accepts are documented. + + + + + +Type-specific configuration. Its shape depends on `property_type`. + + + + + +Type-specific validation configuration. + + + + + +Your system's identifier for this property, for sync and import correlation. Maximum 255 characters, nullable. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters, nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | Missing `display_name`/`property_type`, or an enum value outside the allowed set. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write workspace work item properties. | +| `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` | This workspace manages work item types at the project level. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "a7e3f1d0-5c92-4b68-8f31-2d4a6b9e0c15", + "name": "severity", + "display_name": "Severity", + "description": "How badly the customer is affected", + "property_type": "OPTION", + "relation_type": null, + "is_required": true, + "is_multi": false, + "is_active": true, + "default_value": [], + "options": [ + { + "id": "9d2c7b41-6a80-4f35-8e19-5c3b0a7d2e46", + "name": "Critical", + "description": "Production is down", + "is_default": false, + "sort_order": 10000, + "external_id": null, + "external_source": null + }, + { + "id": "5a83e0b7-2c46-4d19-9f70-6b12c8e5a03d", + "name": "Major", + "description": "A core workflow is broken", + "is_default": true, + "sort_order": 20000, + "external_id": null, + "external_source": null + } + ], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "The request body failed validation.", + "errors": [ + { + "field": "property_type", + "code": "invalid_choice", + "message": "\"SELECT\" is not a valid choice." + } + ] +} +``` + + + +
+
+ +## After creating + +1. Bind it — [Create a property context](/api-reference/v2/work-item-property-contexts/create-property-context) + decides which projects and work item types see the property. +2. For `OPTION` properties, refine the choices with + [Property options](/api-reference/v2/workspace-work-item-property-options/overview). +3. To put the property on a workspace-level work item type directly, use + [Attach properties to a workspace type](/api-reference/v2/workspace-work-item-type-properties/attach-workspace-type-property). diff --git a/docs/api-reference/v2/workspace-work-item-properties/delete-workspace-work-item-property.md b/docs/api-reference/v2/workspace-work-item-properties/delete-workspace-work-item-property.md new file mode 100644 index 00000000..2d353e85 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-properties/delete-workspace-work-item-property.md @@ -0,0 +1,178 @@ +--- +title: Delete a workspace work item property +description: Delete a workspace-level custom property with the Plane v2 REST API. What deletion takes with it, mode conflicts, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete work item property, DELETE workspace custom property, retire property, work item type modes, 409 conflict +--- + +# Delete a workspace work item property + +
+ DELETE + /api/v2/workspaces/{slug}/work-item-properties/{pk}/ +
+ +
+
+ +Remove a property from the workspace catalog. The delete is soft and the response is `204` with an empty body. + +This is the wide blast radius option: the property leaves every project and work item type it reached, unlike +[detaching it from a single type](/api-reference/v2/workspace-work-item-type-properties/detach-workspace-type-property), +which only affects that one type. + +::: tip Retire before you delete +If the property has been collecting values, `PATCH` it with `is_active: false` first. The definition stays +addressable while you migrate, and you can reverse the decision with a single field. Deleting is the move for +a property created by mistake. +::: + +::: warning Wrong mode is a 409, not a 404 +If the workspace manages work item types at the **project** level, this route returns `409` with the code +`work_item_types_managed_at_project`. Delete the property on the project-level resource instead. See +[Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + + + +The property's id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request failed validation — most often a `pk` that isn't a valid UUID. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write workspace work item properties. | +| `404` | `not_found` | No such workspace or property, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types at the project level. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```text +No content +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "This workspace manages work item types at the project level. Use the project-level endpoint instead." +} +``` + + + +
+
+ +::: info Deleting the property takes its scoping with it +A property's [contexts](/api-reference/v2/work-item-property-contexts/overview) and +[options](/api-reference/v2/workspace-work-item-property-options/overview) are mounted underneath it. Once the +property is gone, those sub-resources are no longer addressable — `GET`s under the deleted `property_id` +return `404 not_found`. +::: diff --git a/docs/api-reference/v2/workspace-work-item-properties/get-workspace-work-item-property.md b/docs/api-reference/v2/workspace-work-item-properties/get-workspace-work-item-property.md new file mode 100644 index 00000000..9cf8ea7a --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-properties/get-workspace-work-item-property.md @@ -0,0 +1,200 @@ +--- +title: Get a workspace work item property +description: Retrieve one workspace-level custom property with the Plane v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get work item property, workspace custom property, GET property by id, property_type +--- + +# Get a workspace work item property + +
+ GET + /api/v2/workspaces/{slug}/work-item-properties/{pk}/ +
+ +
+
+ +Retrieve a single workspace property by id. Use it to read a property's full configuration — its +`property_type`, whether it is required or multi-valued, and the `options` behind an `OPTION` property — +before rendering a field or before sending a `PATCH`. + +Reads are unaffected by the workspace's +[work item type mode](/api-reference/v2/work-item-type-modes). A property outside your tenant returns `404`, +never `403` — existence is never leaked. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + + + +The property's id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_properties:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read workspace work item properties. | +| `404` | `not_found` | No such workspace or property, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "a7e3f1d0-5c92-4b68-8f31-2d4a6b9e0c15", + "name": "severity", + "display_name": "Severity", + "description": "How badly the customer is affected", + "property_type": "OPTION", + "relation_type": null, + "is_required": true, + "is_multi": false, + "is_active": true, + "default_value": ["Major"], + "options": [ + { + "id": "9d2c7b41-6a80-4f35-8e19-5c3b0a7d2e46", + "name": "Critical", + "description": "Production is down", + "is_default": false, + "sort_order": 10000, + "external_id": null, + "external_source": null + }, + { + "id": "5a83e0b7-2c46-4d19-9f70-6b12c8e5a03d", + "name": "Major", + "description": "A core workflow is broken", + "is_default": true, + "sort_order": 20000, + "external_id": null, + "external_source": null + } + ], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No work item property with this id exists in this workspace." +} +``` + + + +
+
+ +::: info This says nothing about where the property applies +The property object does not carry its scoping. To find out which projects and work item types see it, list +its contexts with +[List property contexts](/api-reference/v2/work-item-property-contexts/list-property-contexts). A property with +no contexts is defined but not applied anywhere. +::: diff --git a/docs/api-reference/v2/workspace-work-item-properties/list-workspace-work-item-properties.md b/docs/api-reference/v2/workspace-work-item-properties/list-workspace-work-item-properties.md new file mode 100644 index 00000000..d8b60a9a --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-properties/list-workspace-work-item-properties.md @@ -0,0 +1,329 @@ +--- +title: List workspace work item properties +description: List the custom properties defined at the workspace level with the Plane v2 REST API. Ordering, pagination, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list workspace work item properties, custom properties, custom fields, GET work item properties, pagination +--- + +# List workspace work item properties + +
+ GET + /api/v2/workspaces/{slug}/work-item-properties/ +
+ +
+
+ +Return the workspace's custom property catalog as a paginated list. This is how you resolve a property name to +the `id` you need when creating a context or attaching the property to a work item type, and how you build a +picker that stays in sync with what the workspace has defined. + +The list is the catalog, not what is live: it includes properties that have no +[context](/api-reference/v2/work-item-property-contexts/overview) yet and therefore do not appear on any work +item. Reads here are unaffected by the workspace's +[work item type mode](/api-reference/v2/work-item-type-modes). + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + +
+
+ +
+ +### Query Parameters + +This list takes no filters or search term — the whole catalog is returned, page by page. Check your spelling on +`order_by` and `paginate`: neither is validated, so an unrecognized `order_by` silently falls back to the default +ordering and anything other than `paginate=cursor` silently uses offset pagination. + +
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `sort_order` , `-sort_order` — the display order Plane uses for the property list. This is the order you + want when rendering a form; note that `sort_order` itself is not returned on the property object. +- `created_at` , `-created_at` — when each property was created +- `id` , `-id` + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read the `next` value from the +response rather than computing offsets yourself. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead +of `next` and `total_count`. Omit it for the default offset envelope. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted from +the response. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_properties:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read workspace work item properties. | +| `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. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "a7e3f1d0-5c92-4b68-8f31-2d4a6b9e0c15", + "name": "severity", + "display_name": "Severity", + "description": "How badly the customer is affected", + "property_type": "OPTION", + "relation_type": null, + "is_required": true, + "is_multi": false, + "is_active": true, + "default_value": ["Major"], + "options": [ + { + "id": "9d2c7b41-6a80-4f35-8e19-5c3b0a7d2e46", + "name": "Critical", + "description": "Production is down", + "is_default": false, + "sort_order": 10000, + "external_id": null, + "external_source": null + }, + { + "id": "5a83e0b7-2c46-4d19-9f70-6b12c8e5a03d", + "name": "Major", + "description": "A core workflow is broken", + "is_default": true, + "sort_order": 20000, + "external_id": null, + "external_source": null + } + ], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" + }, + { + "id": "c58b2e94-71a3-4d0f-9e62-4a1c7f38b5d2", + "name": "reviewer", + "display_name": "Reviewer", + "description": "Who signs off before release", + "property_type": "RELATION", + "relation_type": "USER", + "is_required": false, + "is_multi": true, + "is_active": true, + "default_value": [], + "options": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:23:12.904551Z" + }, + { + "id": "3fb6c0d8-4e21-49a7-b5c3-90ad72e14f6b", + "name": "rollout_date", + "display_name": "Rollout date", + "description": "When the fix reaches customers", + "property_type": "DATETIME", + "relation_type": null, + "is_required": false, + "is_multi": false, + "is_active": true, + "default_value": [], + "options": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:24:03.112884Z" + } + ], + "next": null, + "previous": null, + "total_count": 3, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "a7e3f1d0-5c92-4b68-8f31-2d4a6b9e0c15", + "name": "severity", + "display_name": "Severity", + "description": "How badly the customer is affected", + "property_type": "OPTION", + "relation_type": null, + "is_required": true, + "is_multi": false, + "is_active": true, + "default_value": ["Major"], + "options": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + + + +
+
+ +::: tip Match on id, not label +`display_name` is a label humans edit. Resolve it to `id` once and store the id — that is what contexts, type +attachments, and property values refer to. +::: diff --git a/docs/api-reference/v2/workspace-work-item-properties/overview.md b/docs/api-reference/v2/workspace-work-item-properties/overview.md new file mode 100644 index 00000000..66922fff --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-properties/overview.md @@ -0,0 +1,212 @@ +--- +title: Workspace work item properties overview +description: The Plane API v2 workspace work item property object. Property types, relation types, options, and how contexts decide where a workspace property applies. +keywords: plane api v2, workspace work item properties, custom properties, custom fields, property_type, relation_type, property contexts +--- + +# Workspace work item properties overview + +A workspace work item property is a custom field defined **once for the whole workspace** — `Severity`, +`Customer impact`, `Rollout date` — instead of being redefined project by project. It is the workspace-level +counterpart to project work item properties. + +```text +/api/v2/workspaces/{slug}/work-item-properties/ +``` + +::: warning Defining a property is not the same as applying it +Creating a property here adds it to the workspace catalog. It does not put it on any work item. A property +reaches work items through a **context**, which binds it to projects and work item types. A workspace property +with no context is defined but not yet applied anywhere. See +[Property contexts](/api-reference/v2/work-item-property-contexts/overview). +::: + +::: warning Writes require workspace mode +A workspace manages work item types in exactly one mode: project-level or workspace-level. Creating, updating, +or deleting on this route while the workspace is in **project** mode returns `409` with the code +`work_item_types_managed_at_project` — the capability lives on the project surface instead. Reads are +unaffected by mode. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+
+ +## The property object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the property. This is the id you use in a context's payload and when attaching the + property to a workspace work item type. + +- `name` _string_ + + Read-only companion to `display_name`. You never set it directly. + +- `display_name` _string_ + + The label shown wherever the property is rendered. This is the field you write. Maximum 255 characters. + +- `description` _string_ + + Free-form explanation of what the property captures. May be `null`. + +- `property_type` _string_ + + What kind of value the property holds. One of `TEXT`, `DATETIME`, `DECIMAL`, `BOOLEAN`, `OPTION`, + `RELATION`, `URL`, `EMAIL`, `FILE`, or `FORMULA`. This is the one decision worth getting right up front — + it determines what the other fields mean. + +- `relation_type` _string_ + + For a `RELATION` property, what the property points at. One of `ISSUE`, `USER`, `RELEASE`, or `RICH_TEXT`. + `null` for every other property type. + +- `is_required` _boolean_ + + Whether a value must be supplied for this property. + +- `is_multi` _boolean_ + + Whether the property accepts more than one value. + +- `is_active` _boolean_ + + Whether the property is in use. Set it to `false` to retire a property without deleting it. + +- `default_value` _array of string_ + + The value applied when none is supplied. Always an array, even when `is_multi` is `false`. + +- `options` _array of any_ + + The choices for an `OPTION` property. Read-only on this object — manage the list through + [Property options](/api-reference/v2/workspace-work-item-property-options/overview), or seed it at create + time with the write-only `options` body field. + +- `settings` _any_ + + Type-specific configuration. Its shape depends on `property_type`. + +- `validation_rules` _any_ + + Type-specific validation configuration. + +- `logo_props` _any_ + + Presentation data for the property's icon. + +- `external_id` , `external_source` _string_ + + Correlation fields for sync and import. Together they let you map a property to a field in another system + and find it again later. Either may be `null`. + +- `created_at` _string (date-time)_ + + When the property was created. + +
+
+ + + +```json +{ + "id": "a7e3f1d0-5c92-4b68-8f31-2d4a6b9e0c15", + "name": "severity", + "display_name": "Severity", + "description": "How badly the customer is affected", + "property_type": "OPTION", + "relation_type": null, + "is_required": true, + "is_multi": false, + "is_active": true, + "default_value": ["Major"], + "options": [ + { + "id": "9d2c7b41-6a80-4f35-8e19-5c3b0a7d2e46", + "name": "Critical", + "description": "Production is down", + "is_default": false, + "sort_order": 10000, + "external_id": null, + "external_source": null + }, + { + "id": "5a83e0b7-2c46-4d19-9f70-6b12c8e5a03d", + "name": "Major", + "description": "A core workflow is broken", + "is_default": true, + "sort_order": 20000, + "external_id": null, + "external_source": null + } + ], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ------------------------------------------------------ | --------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/work-item-properties/` | List workspace properties | +| `POST` | `/api/v2/workspaces/{slug}/work-item-properties/` | Create a workspace property | +| `GET` | `/api/v2/workspaces/{slug}/work-item-properties/{pk}/` | Get a workspace property | +| `PATCH` | `/api/v2/workspaces/{slug}/work-item-properties/{pk}/` | Update a workspace property | +| `DELETE` | `/api/v2/workspaces/{slug}/work-item-properties/{pk}/` | Delete a workspace property | + +## Property types + +`property_type` decides how the value is captured and which of the other fields matter. + +| `property_type` | What it holds | +| --------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `TEXT` | Free-form text | +| `DATETIME` | A date and time | +| `DECIMAL` | A number | +| `BOOLEAN` | True or false | +| `OPTION` | A choice from a defined list — see [Property options](/api-reference/v2/workspace-work-item-property-options/overview) | +| `RELATION` | A pointer to another record — pair it with `relation_type` | +| `URL` | A link | +| `EMAIL` | An email address | +| `FILE` | A file | +| `FORMULA` | A formula-backed value | + +`relation_type` is only meaningful for `RELATION`, and takes `ISSUE`, `USER`, `RELEASE`, or `RICH_TEXT`. On +every other property type it is `null`. + +## Contexts decide where a property applies + +The catalog answers "what properties exist in this workspace". A **context** answers "where does this one show +up". Each context binds the property to a set of projects and work item types — named explicitly through +`project_ids` and `issue_type_ids`, or opened up with `applies_to_all_projects` and +`applies_to_all_work_item_types` — and carries its own `is_required`, `is_multi`, `default_value`, `options`, +and `sort_order`. + +So the usual sequence is: + +1. `POST /work-item-properties/` — define the property once. +2. `POST /work-item-properties/{property_id}/contexts/` — say where it applies. + +Skip step 2 and the property exists but appears nowhere. Full detail lives in +[Property contexts](/api-reference/v2/work-item-property-contexts/overview). + +## Related resources + +- [Property options](/api-reference/v2/workspace-work-item-property-options/overview) — the choices behind an + `OPTION` property. +- [Workspace type properties](/api-reference/v2/workspace-work-item-type-properties/overview) — attach an + existing property to a workspace-level work item type. +- [Work item type modes](/api-reference/v2/work-item-type-modes) — which surface your workspace writes to. diff --git a/docs/api-reference/v2/workspace-work-item-properties/update-workspace-work-item-property.md b/docs/api-reference/v2/workspace-work-item-properties/update-workspace-work-item-property.md new file mode 100644 index 00000000..2f4173ec --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-properties/update-workspace-work-item-property.md @@ -0,0 +1,320 @@ +--- +title: Update a workspace work item property +description: Partially update a workspace-level custom property with the Plane v2 REST API. PATCH semantics, body parameters, mode conflicts, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update work item property, PATCH work item property, workspace custom property, property_type, work item type modes +--- + +# Update a workspace work item property + +
+ PATCH + /api/v2/workspaces/{slug}/work-item-properties/{pk}/ +
+ +
+
+ +Change a workspace property in place. `PATCH` is partial: send only the fields you want to change, and +everything you omit is left untouched. Omitting a field is not the same as sending `null` — `null` clears a +nullable field, omission changes nothing. + +There is no `PUT` on this resource; sending one returns `405 method_not_allowed`. + +Every project and work item type the property reaches sees the change — that is the point of defining it at +the workspace level. + +::: warning Wrong mode is a 409, not a 404 +If the workspace manages work item types at the **project** level, this route returns `409` with the code +`work_item_types_managed_at_project`. Update the property on the project-level resource instead. See +[Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + + + +The property's id. + + + +
+
+ +
+ +### Body Parameters + +Every field is optional. The body itself is optional too, though an empty `PATCH` does nothing useful. + +
+ + + +The label shown wherever the property is rendered. Maximum 255 characters. Renaming is safe for integrations +that key on `id`. + + + + + +One of `TEXT`, `DATETIME`, `DECIMAL`, `BOOLEAN`, `OPTION`, `RELATION`, `URL`, `EMAIL`, `FILE`, or `FORMULA`. +Changing the type of a property that is already collecting values reinterprets what those values mean — +prefer retiring the old property with `is_active: false` and creating a new one. + + + + + +For a `RELATION` property, what it points at. One of `ISSUE`, `USER`, `RELEASE`, or `RICH_TEXT`. Nullable. + + + + + +Free-form explanation of what the property captures. Nullable — send `null` to clear it. + + + + + +Whether a value must be supplied for this property. + + + + + +Whether the property accepts more than one value. + + + + + +Set to `false` to retire the property without deleting it, and back to `true` to bring it into circulation +again. + + + + + +The value applied when none is supplied. Always an array. Send `[]` to clear it. + + + + + +Write-only. For day-to-day option management — adding one choice, renaming another — use +[Property options](/api-reference/v2/workspace-work-item-property-options/overview), which addresses each +option by id. + + + + + +Type-specific configuration. Its shape depends on `property_type`. + + + + + +Type-specific validation configuration. + + + + + +Your system's identifier for this property. Maximum 255 characters, nullable. + + + + + +The system `external_id` came from. Maximum 255 characters, nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | An enum value outside the allowed set, or a field over its length limit. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write workspace work item properties. | +| `404` | `not_found` | No such workspace or property, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types at the project level. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "a7e3f1d0-5c92-4b68-8f31-2d4a6b9e0c15", + "name": "severity", + "display_name": "Customer severity", + "description": "How badly the customer is affected", + "property_type": "OPTION", + "relation_type": null, + "is_required": false, + "is_multi": false, + "is_active": true, + "default_value": ["Major"], + "options": [ + { + "id": "9d2c7b41-6a80-4f35-8e19-5c3b0a7d2e46", + "name": "Critical", + "description": "Production is down", + "is_default": false, + "sort_order": 10000, + "external_id": null, + "external_source": null + }, + { + "id": "5a83e0b7-2c46-4d19-9f70-6b12c8e5a03d", + "name": "Major", + "description": "A core workflow is broken", + "is_default": true, + "sort_order": 20000, + "external_id": null, + "external_source": null + } + ], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "This workspace manages work item types at the project level. Use the project-level endpoint instead." +} +``` + + + +
+
+ +::: tip Scoping is edited elsewhere +`PATCH` here changes the property's definition, not where it applies. To narrow or widen the projects and work +item types that see it, update its +[property contexts](/api-reference/v2/work-item-property-contexts/overview) instead. +::: diff --git a/docs/api-reference/v2/workspace-work-item-property-options/create-workspace-property-option.md b/docs/api-reference/v2/workspace-work-item-property-options/create-workspace-property-option.md new file mode 100644 index 00000000..5c6dc7bb --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-property-options/create-workspace-property-option.md @@ -0,0 +1,213 @@ +--- +title: Create a workspace property option +description: Add a selectable choice to a workspace-level OPTION property with the Plane v2 REST API. Body parameters, the single-default rule, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create property option, workspace work item property, OPTION property, dropdown choice, is_default, POST options +--- + +# Create a workspace property option + +
+ POST + /api/v2/workspaces/{slug}/work-item-properties/{property_id}/options/ +
+ +
+
+ +Add a choice to a workspace-level `OPTION` property. The new option is appended to the end of the property's list and becomes immediately selectable on work items across the workspace. + +The property must have `property_type: "OPTION"`. Posting an option to a property of any other type returns `400 invalid_request` — the option list is meaningless for a `TEXT` or `DECIMAL` property. + +::: warning Workspace mode only +This write requires the workspace to manage work item types at the workspace level. In project mode it returns `409 work_item_types_managed_at_project` — add the option through the project-level endpoint instead. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace-level `OPTION` property to add the choice to. A project-scoped property id is a `404` here — this path never crosses into a project. See [Workspace work item properties](/api-reference/v2/workspace-work-item-properties/overview). + + + +
+
+ +
+ +### Body Parameters + +
+ + + +The choice as it is displayed. Maximum 255 characters. + + + + + +Free-form text explaining when to pick this choice. + + + + + +Make this the property's default choice. At most one option per property can be the default, and there is no automatic hand-off: if another option already has it, this request is rejected with `400 invalid_request`. Clear the current default with a `PATCH` first. + + + + + +Your system's identifier for this option, for sync and import correlation. Maximum 255 characters. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. + + + +
+ +`sort_order` is not accepted. Plane appends the new option after the current last one, so create options in the order you want them displayed. + +
+ +
+ +### Scopes + +`workspaces.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | Missing `name`, a field over 255 characters, `is_default` when the property already has a default, or a property whose `property_type` is not `OPTION`. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write this workspace's properties. | +| `404` | `not_found` | No such workspace or workspace-level property, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types at the project level. Use the project-level options endpoint. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "2f6a94c1-7b38-4d50-91ae-3c0d5e8b7a26", + "name": "Sandbox", + "description": "Reported on an isolated test tenant", + "is_default": false, + "sort_order": 40000, + "external_id": "env-sandbox", + "external_source": "servicedesk" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "Work item types are managed at the project level for this workspace." +} +``` + + + +
+
+ +::: tip Seeding a whole option set +Create the options in display order in one pass, then set the default with a follow-up `PATCH`. That keeps every create request free of `is_default` and avoids tripping the one-default rule when a request is retried. +::: diff --git a/docs/api-reference/v2/workspace-work-item-property-options/delete-workspace-property-option.md b/docs/api-reference/v2/workspace-work-item-property-options/delete-workspace-property-option.md new file mode 100644 index 00000000..a862ec95 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-property-options/delete-workspace-property-option.md @@ -0,0 +1,154 @@ +--- +title: Delete a workspace property option +description: Remove a selectable choice from a workspace-level OPTION property with the Plane v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete property option, workspace work item property, OPTION property, dropdown choice, DELETE option +--- + +# Delete a workspace property option + +
+ DELETE + /api/v2/workspaces/{slug}/work-item-properties/{property_id}/options/{pk}/ +
+ +
+
+ +Remove a choice from a workspace-level `OPTION` property. The delete is soft — the option stops being selectable and disappears from reads, and the request returns `204` with an empty body. + +Deleting an option is not the same as renaming it. If the choice is being reworded, `PATCH` it instead: [Update a workspace property option](/api-reference/v2/workspace-work-item-property-options/update-workspace-property-option) keeps the option's `id`, so work items already holding it keep their value. + +::: warning Workspace mode only +This write requires the workspace to manage work item types at the workspace level. In project mode it returns `409 work_item_types_managed_at_project` — delete the option through the project-level endpoint instead. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace-level property the option belongs to. See [Workspace work item properties](/api-reference/v2/workspace-work-item-properties/overview). + + + + + +The option to delete. It is looked up within the property, so an option id under the wrong `property_id` is a `404`. + + + +
+ +Deleting the property's default option leaves the property with no default. Nothing promotes another option in its place — set a new default explicitly if the property needs one. + +
+ +
+ +### Scopes + +`workspaces.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write this workspace's properties. | +| `404` | `not_found` | No such workspace, workspace-level property, or option — or it's outside your tenant. Already-deleted options are `404` too. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types at the project level. Use the project-level options endpoint. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```text +(empty body) +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "Work item types are managed at the project level for this workspace." +} +``` + + + +
+
+ +::: info Work items that already hold the option +The delete cascades: property values pointing at this option are soft-deleted with it, so work items that carried the choice lose it. That cascade runs in the background, so a read taken immediately after the `204` can still show the old value for a moment. + +If you are rewording a choice rather than retiring it, [update the option](/api-reference/v2/workspace-work-item-property-options/update-workspace-property-option) instead — a rename keeps the `id` and every work item keeps its value. +::: diff --git a/docs/api-reference/v2/workspace-work-item-property-options/get-workspace-property-option.md b/docs/api-reference/v2/workspace-work-item-property-options/get-workspace-property-option.md new file mode 100644 index 00000000..4fcf2f0d --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-property-options/get-workspace-property-option.md @@ -0,0 +1,151 @@ +--- +title: Get a workspace property option +description: Retrieve a single selectable option on a workspace-level OPTION property with the Plane v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get property option, workspace work item property, OPTION property, dropdown choice, GET option +--- + +# Get a workspace property option + +
+ GET + /api/v2/workspaces/{slug}/work-item-properties/{property_id}/options/{pk}/ +
+ +
+
+ +Retrieve one option on a workspace-level `OPTION` property. Use it to resolve a stored option id back to a display name, or to confirm an option still exists before writing it onto a work item. + +Reads work in either work item type mode — only writes are mode-gated. See [Work item type modes](/api-reference/v2/work-item-type-modes). + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace-level property the option belongs to. See [Workspace work item properties](/api-reference/v2/workspace-work-item-properties/overview). + + + + + +The option to retrieve. + + + +
+ +The option is looked up within the property, so a valid option id under the wrong `property_id` is a `404` rather than a match. Deleted options are gone from this endpoint too — a soft-deleted option returns `404`. + +
+ +
+ +### Scopes + +`workspaces.work_item_properties:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this workspace's properties. | +| `404` | `not_found` | No such workspace, workspace-level property, or option — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "c31a7e04-5f6b-4d29-8a13-7e0c2b9f4a65", + "name": "Production", + "description": "Reported on live customer traffic", + "is_default": true, + "sort_order": 10000, + "external_id": null, + "external_source": null +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "The requested resource was not found." +} +``` + + + +
+
+ +::: tip Fetching every option at once +Reading the property returns its `options` inline, so one property read is cheaper than a fan-out of option reads when you need the whole set. Reach for this endpoint when you hold a single option id and want just that row. +::: diff --git a/docs/api-reference/v2/workspace-work-item-property-options/list-workspace-property-options.md b/docs/api-reference/v2/workspace-work-item-property-options/list-workspace-property-options.md new file mode 100644 index 00000000..8749f279 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-property-options/list-workspace-property-options.md @@ -0,0 +1,253 @@ +--- +title: List workspace property options +description: List the selectable options on a workspace-level OPTION property with the Plane v2 REST API. Ordering, pagination, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list property options, workspace work item property, OPTION property, dropdown choices, pagination, GET options +--- + +# List workspace property options + +
+ GET + /api/v2/workspaces/{slug}/work-item-properties/{property_id}/options/ +
+ +
+
+ +Return the selectable choices on a workspace-level `OPTION` property as a paginated list. This is how you resolve an option name to the `id` you store when setting the property on a work item, and how you build a picker that stays in sync with the property definition. + +Results are ordered by `sort_order` by default, which is the order the option list is meant to be displayed in. + +Reads work in either work item type mode. Only writes are mode-gated — see [Work item type modes](/api-reference/v2/work-item-type-modes). + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace-level property whose options you want — see [Workspace work item properties](/api-reference/v2/workspace-work-item-properties/overview). + +This endpoint filters options by property rather than looking the property up, so an id that isn't a workspace-level property in this workspace — including a project-scoped property id — comes back as an empty list, not a `404`. + + + +
+
+ +
+ +### Query Parameters + +There are no filters on this resource. A property's option list is small and self-contained, so fetch it whole and match on `id` in your own code. + +
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `sort_order` , `-sort_order` — the property's own display order. This is the default. +- `created_at` , `-created_at` — when each option was added +- `id` , `-id` + +A value outside this list is ignored and the default `sort_order` ordering is used, so a typo shows up as an unexpected order rather than an error. + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. Most properties have fewer than 20 options, so one page is usually the whole list. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read the `next` value from the response rather than computing offsets yourself. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead of `next` and `total_count`. Omit it for the default offset envelope. + +Cursor pagination needs a unique, monotonic sort key, and `sort_order` is neither. Pair it with `order_by=created_at` or `order_by=id`; a bare `?paginate=cursor` falls back to the default `sort_order` ordering and is rejected with `ordering_not_cursor_eligible`. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted from the response. + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_properties:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this workspace's properties. | +| `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. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "c31a7e04-5f6b-4d29-8a13-7e0c2b9f4a65", + "name": "Production", + "description": "Reported on live customer traffic", + "is_default": true, + "sort_order": 10000, + "external_id": null, + "external_source": null + }, + { + "id": "5e8b7d21-4a09-4c63-b7f2-90d1c3a86e57", + "name": "Staging", + "description": "Reported on the shared pre-release environment", + "is_default": false, + "sort_order": 20000, + "external_id": null, + "external_source": null + }, + { + "id": "a17f3c95-2d64-4b08-8e51-c94b7d60f382", + "name": "Development", + "description": "Reported on a local or branch environment", + "is_default": false, + "sort_order": 30000, + "external_id": null, + "external_source": null + } + ], + "next": null, + "previous": null, + "total_count": 3, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "c31a7e04-5f6b-4d29-8a13-7e0c2b9f4a65", + "name": "Production", + "description": "Reported on live customer traffic", + "is_default": true, + "sort_order": 10000, + "external_id": null, + "external_source": null + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + + + +
+
+ +::: info An empty list is a real answer +Three different situations all return `200` with an empty `data` array: an `OPTION` property that has no options yet, a property of some other `property_type`, and a `property_id` that isn't a workspace-level property at all. When an empty list is surprising, read the property itself and check both that it exists at the workspace level and that its `property_type` is `OPTION`. +::: diff --git a/docs/api-reference/v2/workspace-work-item-property-options/overview.md b/docs/api-reference/v2/workspace-work-item-property-options/overview.md new file mode 100644 index 00000000..d8346b99 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-property-options/overview.md @@ -0,0 +1,125 @@ +--- +title: Workspace property options overview +description: The Plane API v2 workspace-level work item property option object. Attributes, the single default option, server-assigned sort order, and the mode rule that governs writes. +keywords: plane api v2, workspace property options, work item property options, OPTION property, dropdown choices, is_default, sort_order +--- + +# Workspace property options overview + +An option is one selectable choice on a workspace-level custom property whose `property_type` is `OPTION` — the entries a person picks from when filling that property in on a work item. + +Options hang off the property, not off a project. The path carries no project segment: + +```text +/api/v2/workspaces/{slug}/work-item-properties/{property_id}/options/ +``` + +A property of any other type has no options. Adding one to a `TEXT`, `BOOLEAN`, `DECIMAL` or other non-`OPTION` property is rejected with `400 invalid_request`, so create the property with `property_type: "OPTION"` first — see [Workspace work item properties](/api-reference/v2/workspace-work-item-properties/overview). + +
+
+ +## The property option object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the option. This is the value you store when you set the property on a work item, so treat it as the stable handle — `name` is only a label. + +- `name` _string_ + + The choice as it is displayed. Maximum 255 characters. + +- `description` _string_ + + Free-form text explaining when to pick this choice. + +- `is_default` _boolean_ + + Whether this is the property's default choice. At most one option per property can carry it. + +- `sort_order` _number_ + + Position of the option within the property's list. Lower values come first, and this is the order the list endpoint returns by default. Assigned by Plane — it is not a writable field. + +- `external_id` , `external_source` _string_ + + Correlation fields for sync and import. Together they let you map an option to a record in another system and find it again later. + +::: tip Store the id, not the name +Renaming an option keeps its `id`, so integrations that matched on `id` keep working and integrations that matched on `name` break. Resolve names to ids once, then reference ids. +::: + +
+
+ + + +```json +{ + "id": "c31a7e04-5f6b-4d29-8a13-7e0c2b9f4a65", + "name": "Production", + "description": "Reported on live customer traffic", + "is_default": true, + "sort_order": 10000, + "external_id": null, + "external_source": null +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------------------------------- | ------------- | +| `GET` | `/api/v2/workspaces/{slug}/work-item-properties/{property_id}/options/` | List options | +| `POST` | `/api/v2/workspaces/{slug}/work-item-properties/{property_id}/options/` | Create option | +| `GET` | `/api/v2/workspaces/{slug}/work-item-properties/{property_id}/options/{pk}/` | Get option | +| `PATCH` | `/api/v2/workspaces/{slug}/work-item-properties/{property_id}/options/{pk}/` | Update option | +| `DELETE` | `/api/v2/workspaces/{slug}/work-item-properties/{property_id}/options/{pk}/` | Delete option | + +## Scopes + +| Operation | Scope | +| ------------------------- | --------------------------------------- | +| `GET` (list, retrieve) | `workspaces.work_item_properties:read` | +| `POST`, `PATCH`, `DELETE` | `workspaces.work_item_properties:write` | + +Options are covered by the workspace property scopes — there is no separate option scope to request, and the project-level scope root (`projects.work_item_properties:*`) does not grant access here. + +## Writes are workspace-mode only + +::: warning Wrong mode is a 409, not a 404 +A workspace manages work item types and their properties in exactly one mode. These endpoints are the workspace-mode surface, so `POST`, `PATCH`, and `DELETE` here require workspace mode. Call them while the workspace is in project mode and you get `409 work_item_types_managed_at_project` — the capability exists, it just lives on the project-level surface. + +Reads are unaffected by mode: `GET` works in either mode. See [Work item type modes](/api-reference/v2/work-item-type-modes) for how to check which mode a workspace is in, and [Property options (project)](/api-reference/v2/work-item-property-options/overview) for the project-level equivalent. +::: + +## Only one option can be the default + +Setting `is_default: true` while another option on the same property already has it is rejected with `400 invalid_request`. There is no automatic hand-off. To move the default, clear it on the current holder first: + +1. `PATCH` the current default with `{"is_default": false}`. +2. `PATCH` (or `POST`) the new option with `{"is_default": true}`. + +## Sort order is assigned, not sent + +`sort_order` is not part of the request body on either create or update. A new option is appended after the existing ones, so the creation order is the display order. If you need a specific order, create the options in that order. + +## Options are also inlined on the property + +A property read returns its `options` array inline, so fetching the property is enough when you only need the choices for rendering. Reach for these endpoints when you need to page through a long option list, or to add, rename, or remove a choice. Which work items and projects the property applies to is decided by its contexts — see [Property contexts](/api-reference/v2/work-item-property-contexts/overview). + +## Changed from v1 + +- v1 had no workspace-level surface. Options existed only under `/projects/{project_id}/work-item-properties/{property_id}/options/`; a workspace-level property's options now live at the workspace path with no project segment. +- The scope is `workspaces.work_item_properties:read` / `:write`. v1 used `projects.work_item_property_options:*`. +- The read shape is trimmed to `id`, `name`, `description`, `is_default`, `sort_order`, `external_id`, and `external_source`. `workspace`, `project`, `property`, `parent`, `logo_props`, `is_active`, and the `created_*` / `updated_*` audit fields are no longer returned. +- `is_active` and `parent` are no longer accepted on write. + +See [Migrating from v1](/api-reference/v2/migrating-from-v1) for the full list. diff --git a/docs/api-reference/v2/workspace-work-item-property-options/update-workspace-property-option.md b/docs/api-reference/v2/workspace-work-item-property-options/update-workspace-property-option.md new file mode 100644 index 00000000..a7154119 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-property-options/update-workspace-property-option.md @@ -0,0 +1,220 @@ +--- +title: Update a workspace property option +description: Rename a workspace-level property option or move the default with the Plane v2 REST API. PATCH body parameters, the single-default rule, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update property option, workspace work item property, OPTION property, is_default, PATCH option +--- + +# Update a workspace property option + +
+ PATCH + /api/v2/workspaces/{slug}/work-item-properties/{property_id}/options/{pk}/ +
+ +
+
+ +Change one option on a workspace-level `OPTION` property — rename it, reword its description, or move the property's default onto it. + +The update is partial: fields you omit are left untouched, and omitting a field is not the same as sending `null`. Renaming an option keeps its `id`, so every work item already holding the option keeps its value. + +::: warning Workspace mode only +This write requires the workspace to manage work item types at the workspace level. In project mode it returns `409 work_item_types_managed_at_project` — update the option through the project-level endpoint instead. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace-level property the option belongs to. See [Workspace work item properties](/api-reference/v2/workspace-work-item-properties/overview). + + + + + +The option to update. It is looked up within the property, so an option id under the wrong `property_id` is a `404`. + + + +
+
+ +
+ +### Body Parameters + +Every field is optional. Send only what you are changing. + +
+ + + +The choice as it is displayed. Maximum 255 characters. + + + + + +Free-form text explaining when to pick this choice. + + + + + +Make this the property's default choice, or send `false` to clear it. At most one option per property can be the default: setting it while a different option already holds it returns `400 invalid_request`, so clear the current default first and then set the new one. + + + + + +Your system's identifier for this option, for sync and import correlation. Maximum 255 characters. + + + + + +The system `external_id` came from, for example `github` or `jira`. Maximum 255 characters. + + + +
+ +`sort_order` is not accepted here — an option's position is fixed at creation time and cannot be moved through the API. + +
+ +
+ +### Scopes + +`workspaces.work_item_properties:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | -------------------------------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | A field over 255 characters, or `is_default: true` when another option on the property is already the default. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write this workspace's properties. | +| `404` | `not_found` | No such workspace, workspace-level property, or option — or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types at the project level. Use the project-level options endpoint. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "5e8b7d21-4a09-4c63-b7f2-90d1c3a86e57", + "name": "Pre-production", + "description": "Reported on the shared pre-release environment", + "is_default": false, + "sort_order": 20000, + "external_id": null, + "external_source": null +} +``` + + + + + +```json +{ + "type": "invalid_request", + "code": "invalid_request", + "detail": "Only one option can be the default." +} +``` + + + +
+
+ +## Moving the default + +There is no automatic hand-off, so promoting a new default takes two requests: + +1. `PATCH` the option that currently has `is_default: true` with `{"is_default": false}`. +2. `PATCH` the new option with `{"is_default": true}`. + +Run it the other way round and the promotion is the request that fails, leaving the existing default untouched. Nothing is half-applied either way. diff --git a/docs/api-reference/v2/workspace-work-item-templates/create-workspace-work-item-template.md b/docs/api-reference/v2/workspace-work-item-templates/create-workspace-work-item-template.md new file mode 100644 index 00000000..7feb7fb4 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-templates/create-workspace-work-item-template.md @@ -0,0 +1,237 @@ +--- +title: Create a workspace work item template +description: Create a workspace work item template with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create a workspace work item template, workspace work item templates, workspace work item templates create +--- + +# Create a workspace work item template + +
+ POST + /api/v2/workspaces/{slug}/work-item-templates/ +
+ +
+
+ +Workspace work item templates pre-fill new work items across projects. Create a workspace work item template. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name. + +Maximum 255 characters. + + + + + +Seed payload used on create/update and nested under read `template_data`. + + + + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Whether is published. + + + + + +The short description. + +Nullable. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `is_published`, `name`, `short_description`, `short_id`, `slug`, `template_data`, `template_type`. + + + +
+
+ +
+ +### Scopes + +`templates.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_published": false, + "name": "Example name", + "short_description": "example", + "short_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team", + "template_data": {}, + "template_type": "example" +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/workspace-work-item-templates/delete-workspace-work-item-template.md b/docs/api-reference/v2/workspace-work-item-templates/delete-workspace-work-item-template.md new file mode 100644 index 00000000..0a265451 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-templates/delete-workspace-work-item-template.md @@ -0,0 +1,148 @@ +--- +title: Delete a workspace work item template +description: Delete a workspace work item template with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete a workspace work item template, workspace work item templates, workspace work item templates destroy +--- + +# Delete a workspace work item template + +
+ DELETE + /api/v2/workspaces/{slug}/work-item-templates/{pk}/ +
+ +
+
+ +Workspace work item templates pre-fill new work items across projects. Delete a workspace work item template. A successful delete returns `204` with an empty body. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace work item template id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `is_published`, `name`, `short_description`, `short_id`, `slug`, `template_data`, `template_type`. + + + +
+
+ +
+ +### Scopes + +`templates.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +No response body. + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace work item template matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-work-item-templates/get-workspace-work-item-template.md b/docs/api-reference/v2/workspace-work-item-templates/get-workspace-work-item-template.md new file mode 100644 index 00000000..7d77b12e --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-templates/get-workspace-work-item-template.md @@ -0,0 +1,158 @@ +--- +title: Get a workspace work item template +description: Read a workspace work item template with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get a workspace work item template, workspace work item templates, workspace work item templates retrieve +--- + +# Get a workspace work item template + +
+ GET + /api/v2/workspaces/{slug}/work-item-templates/{pk}/ +
+ +
+
+ +Workspace work item templates pre-fill new work items across projects. Read a single workspace work item template by id. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace work item template id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `is_published`, `name`, `short_description`, `short_id`, `slug`, `template_data`, `template_type`. + + + +
+
+ +
+ +### Scopes + +`templates.work_items:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_published": false, + "name": "Example name", + "short_description": "example", + "short_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team", + "template_data": {}, + "template_type": "example" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace work item template matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-work-item-templates/list-workspace-work-item-templates.md b/docs/api-reference/v2/workspace-work-item-templates/list-workspace-work-item-templates.md new file mode 100644 index 00000000..17de4dcd --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-templates/list-workspace-work-item-templates.md @@ -0,0 +1,221 @@ +--- +title: List workspace work item templates +description: List workspace work item templates with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list workspace work item templates, workspace work item templates, workspace work item templates list +--- + +# List workspace work item templates + +
+ GET + /api/v2/workspaces/{slug}/work-item-templates/ +
+ +
+
+ +Workspace work item templates pre-fill new work items across projects. List the workspace work item templates you can see. Results are scoped to the path and to what your token is allowed to read. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +
+ + + +Set to false to skip the total_count COUNT(\*) (omits total_count). + + + + + +Filter by `is_published`. + + + + + +Number of rows to skip from the start of the result set. + + + + + +Field to order the list by. Prefix with '-' for descending (e.g. '-created_at'). Annotation-backed orders sort semantically and ride the default offset page. + + + + + +Set to 'cursor' to opt into the COUNT-free keyset cursor envelope (use for deep traversal); omit for the default offset envelope with total_count. + +One of `cursor`. + + + + + +Page size (max 200). + + + + + +A search term. + + + + + +Filter by `short_id`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted** from each row, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. + +An unknown name is a `400` that names the valid set, so a typo can't silently cost you the saving. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `is_published`, `name`, `short_description`, `short_id`, `slug`, `template_data`, `template_type`. + + + +
+
+ +
+ +### Scopes + +`templates.work_items:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ----------------------------------------------------------------------------------- | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_published": false, + "name": "Example name", + "short_description": "example", + "short_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team", + "template_data": {}, + "template_type": "example" + }, + { + "created_at": "2026-01-13T16:04:02.911204Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", + "is_published": false, + "name": "Another example", + "short_description": "example", + "short_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team", + "template_data": {}, + "template_type": "example" + } + ], + "next": 1, + "pagination": { + "style": "offset" + }, + "previous": 1, + "total_count": 3 +} +``` + +
+ +
+
diff --git a/docs/api-reference/v2/workspace-work-item-templates/overview.md b/docs/api-reference/v2/workspace-work-item-templates/overview.md new file mode 100644 index 00000000..7d01a222 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-templates/overview.md @@ -0,0 +1,100 @@ +--- +title: Workspace work item templates overview +description: The Plane API v2 workspace work item template object. Attributes, endpoints, OAuth scopes and behavior. +keywords: plane api v2, workspace work item templates, workspace work item template object +--- + +# Workspace work item templates overview + +Workspace work item templates pre-fill new work items across projects. + +
+
+ +## The workspace work item template object + +### Attributes + +- `created_at` _string (date-time)_ + + When the record was created. + +- `created_by_id` _string (uuid)_ + + The user who created the record. + +- `description_html` _string_ + + Rich-text body as HTML. This is the field the Plane editor round-trips. + +- `id` _string (uuid)_ + + Unique identifier. + +- `is_published` _boolean_ + + Whether is published. + +- `name` _string_ + + Display name. + +- `short_description` _string_ + + The short description. + +- `short_id` _string_ + + The related short. + +- `slug` _string_ + + The slug. + +- `template_data` _object_ + + The template data. + +- `template_type` _string_ + + The template type. + +
+
+ + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_published": false, + "name": "Example name", + "short_description": "example", + "short_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team", + "template_data": {}, + "template_type": "example" +} +``` + +
+ +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ----------------------------------------------------- | ------------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/work-item-templates/` | List workspace work item templates | +| `POST` | `/api/v2/workspaces/{slug}/work-item-templates/` | Create a workspace work item template | +| `DELETE` | `/api/v2/workspaces/{slug}/work-item-templates/{pk}/` | Delete a workspace work item template | +| `GET` | `/api/v2/workspaces/{slug}/work-item-templates/{pk}/` | Get a workspace work item template | +| `PATCH` | `/api/v2/workspaces/{slug}/work-item-templates/{pk}/` | Update a workspace work item template | + +## Response shaping + +Every workspace work item template read accepts `?fields=` for sparse responses — see [Sparse fields](/api-reference/v2/sparse-fields). Errors follow the shared [problem+json contract](/api-reference/v2/errors). diff --git a/docs/api-reference/v2/workspace-work-item-templates/update-workspace-work-item-template.md b/docs/api-reference/v2/workspace-work-item-templates/update-workspace-work-item-template.md new file mode 100644 index 00000000..2b4d900d --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-templates/update-workspace-work-item-template.md @@ -0,0 +1,222 @@ +--- +title: Update a workspace work item template +description: Update a workspace work item template with the Plane v2 REST API. Parameters, sparse `?fields=` responses, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update a workspace work item template, workspace work item templates, workspace work item templates partial update +--- + +# Update a workspace work item template + +
+ PATCH + /api/v2/workspaces/{slug}/work-item-templates/{pk}/ +
+ +
+
+ +Workspace work item templates pre-fill new work items across projects. Update a workspace work item template. Send only the keys you want to change — omitted keys keep their current value, and an explicit `null` clears a nullable field. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The workspace work item template id. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Rich-text body as HTML. This is the field the Plane editor round-trips. + + + + + +Whether is published. + + + + + +Display name. + +Maximum 255 characters. + + + + + +The short description. + +Nullable. + + + + + +Seed payload used on create/update and nested under read `template_data`. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return. Unrequested keys are **omitted**, not returned as `null`. `id` always comes back. Pass `all` for every requestable field. An unknown name is a `400`. See [Sparse fields](/api-reference/v2/sparse-fields). + +Requestable here: `created_at`, `created_by_id`, `description_html`, `id`, `is_published`, `name`, `short_description`, `short_id`, `slug`, `template_data`, `template_type`. + + + +
+
+ +
+ +### Scopes + +`templates.work_items:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------- | +| `400` | `invalid_request` | The request body or a query parameter failed validation. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan or is switched off. | +| `403` | `forbidden` | Your role or token scope doesn't allow this. | +| `404` | `not_found` | No such resource, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | A business rule blocks the write — see the notes above. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "created_at": "2026-01-14T09:22:41.478363Z", + "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", + "description_html": "

Details go here.

", + "id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53", + "is_published": false, + "name": "Example name", + "short_description": "example", + "short_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f", + "slug": "my-team", + "template_data": {}, + "template_type": "example" +} +``` + +
+ + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No workspace work item template matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-work-item-type-properties/attach-workspace-type-property.md b/docs/api-reference/v2/workspace-work-item-type-properties/attach-workspace-type-property.md new file mode 100644 index 00000000..5d26774d --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-type-properties/attach-workspace-type-property.md @@ -0,0 +1,191 @@ +--- +title: Attach properties to a workspace type +description: Attach existing workspace custom properties to a workspace-level work item type with the Plane v2 REST API. Body parameters, mode conflicts, OAuth scopes, error codes, and code examples. +keywords: plane api v2, attach property to type, workspace work item type properties, POST type properties, work item type modes, 409 conflict +--- + +# Attach properties to a workspace type + +
+ POST + /api/v2/workspaces/{slug}/work-item-types/{type_id}/properties/ +
+ +
+
+ +Attach one or more **existing** workspace properties to a workspace-level work item type. This does not create +properties — create those first with +[Create a workspace property](/api-reference/v2/workspace-work-item-properties/create-workspace-work-item-property), +then send their ids here. + +Because the same property can be attached to many types, this is how you reuse one `Severity` definition +across `Bug`, `Incident`, and `Escalation` instead of maintaining three near-identical fields. + +::: warning Wrong mode is a 409, not a 404 +If the workspace manages work item types at the **project** level, this route returns `409` with the code +`work_item_types_managed_at_project`. Attach through the project-level route instead. See +[Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + + + +The workspace work item type to attach the properties to. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Ids of properties to attach, taken from the workspace property catalog. Send the whole set you want in one +call rather than one request per property. + +Every id must already exist in this workspace. An unknown id — including one that belongs to another workspace +— fails the request with `400 invalid_request`; nothing is attached, and a cross-tenant id is never silently +linked. + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | `properties` missing, not an array, or holding an id that doesn't exist here. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write workspace work item types. | +| `404` | `not_found` | No such workspace or type, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types at the project level. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "properties": ["a7e3f1d0-5c92-4b68-8f31-2d4a6b9e0c15", "3fb6c0d8-4e21-49a7-b5c3-90ad72e14f6b"] +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "This workspace manages work item types at the project level. Use the project-level endpoint instead." +} +``` + + + +
+
+ +::: info The response is ids, not objects +`201` returns only the `properties` array. To render the attached properties, follow with +[List properties on a workspace type](/api-reference/v2/workspace-work-item-type-properties/list-workspace-type-properties) +or fetch them individually. +::: diff --git a/docs/api-reference/v2/workspace-work-item-type-properties/detach-workspace-type-property.md b/docs/api-reference/v2/workspace-work-item-type-properties/detach-workspace-type-property.md new file mode 100644 index 00000000..a205dbed --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-type-properties/detach-workspace-type-property.md @@ -0,0 +1,159 @@ +--- +title: Detach a property from a workspace type +description: Detach a custom property from a workspace-level work item type with the Plane v2 REST API. Why detaching never deletes, mode conflicts, OAuth scopes, error codes, and code examples. +keywords: plane api v2, detach property, remove property from work item type, DELETE type property, work item type modes, 409 conflict +--- + +# Detach a property from a workspace type + +
+ DELETE + /api/v2/workspaces/{slug}/work-item-types/{type_id}/properties/{pk}/ +
+ +
+
+ +Remove a property from a workspace-level work item type. The type stops carrying the property; nothing else +changes. + +::: info Detaching is not deleting +The property stays in the workspace catalog and stays attached to every other type that uses it. To remove it +from the workspace entirely, call +[Delete a workspace property](/api-reference/v2/workspace-work-item-properties/delete-workspace-work-item-property) +instead. +::: + +::: warning Wrong mode is a 409, not a 404 +If the workspace manages work item types at the **project** level, this route returns `409` with the code +`work_item_types_managed_at_project`. Detach through the project-level route instead. See +[Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + + + +The workspace work item type to detach the property from. + + + + + +The property's id. A property that is not attached to this type returns `404`. + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request failed validation — most often a `pk` that isn't a valid UUID. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write workspace work item types. | +| `404` | `not_found` | No such workspace, type, or property — or the property isn't on this type. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | This workspace manages work item types at the project level. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```text +No content +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "This workspace manages work item types at the project level. Use the project-level endpoint instead." +} +``` + + + +
+
+ +::: tip Re-attaching is a POST away +Detaching is reversible: send the same id back through +[Attach properties to a workspace type](/api-reference/v2/workspace-work-item-type-properties/attach-workspace-type-property). +::: diff --git a/docs/api-reference/v2/workspace-work-item-type-properties/get-workspace-type-property.md b/docs/api-reference/v2/workspace-work-item-type-properties/get-workspace-type-property.md new file mode 100644 index 00000000..894fc77e --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-type-properties/get-workspace-type-property.md @@ -0,0 +1,209 @@ +--- +title: Get a property on a workspace type +description: Retrieve one custom property attached to a workspace-level work item type with the Plane v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get type property, workspace work item type, custom property, GET property by id +--- + +# Get a property on a workspace type + +
+ GET + /api/v2/workspaces/{slug}/work-item-types/{type_id}/properties/{pk}/ +
+ +
+
+ +Retrieve one property as it is attached to a workspace-level work item type. Use it to read a single +property's configuration — its `property_type`, whether it is required, its options — without pulling the +whole list. + +This route also answers the membership question: a `200` means the property is attached to this type, and a +`404` means it is not. A property that exists in the workspace but is not on this type returns `404`, the same +as an id that does not exist at all. + +Reads are unaffected by the workspace's +[work item type mode](/api-reference/v2/work-item-type-modes). + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + + + +The workspace work item type the property is attached to. + + + + + +The property's id. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_types:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read workspace work item types. | +| `404` | `not_found` | No such workspace, type, or property — or the property isn't on this type. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "a7e3f1d0-5c92-4b68-8f31-2d4a6b9e0c15", + "name": "severity", + "display_name": "Severity", + "description": "How badly the customer is affected", + "property_type": "OPTION", + "relation_type": null, + "is_required": true, + "is_multi": false, + "is_active": true, + "default_value": ["Major"], + "options": [ + { + "id": "9d2c7b41-6a80-4f35-8e19-5c3b0a7d2e46", + "name": "Critical", + "description": "Production is down", + "is_default": false, + "sort_order": 10000, + "external_id": null, + "external_source": null + }, + { + "id": "5a83e0b7-2c46-4d19-9f70-6b12c8e5a03d", + "name": "Major", + "description": "A core workflow is broken", + "is_default": true, + "sort_order": 20000, + "external_id": null, + "external_source": null + } + ], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No property with this id is attached to this work item type." +} +``` + + + +
+
+ +::: info Editing happens on the catalog +This route is read-only. To change a property's label, requiredness, or options, `PATCH` it on the workspace +catalog with +[Update a workspace property](/api-reference/v2/workspace-work-item-properties/update-workspace-work-item-property). +Every type the property is attached to sees the change. +::: diff --git a/docs/api-reference/v2/workspace-work-item-type-properties/list-workspace-type-properties.md b/docs/api-reference/v2/workspace-work-item-type-properties/list-workspace-type-properties.md new file mode 100644 index 00000000..aa5837bf --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-type-properties/list-workspace-type-properties.md @@ -0,0 +1,320 @@ +--- +title: List properties on a workspace type +description: List the custom properties attached to a workspace-level work item type with the Plane v2 REST API. Ordering, pagination, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list type properties, workspace work item type, custom properties, GET type properties, pagination +--- + +# List properties on a workspace type + +
+ GET + /api/v2/workspaces/{slug}/work-item-types/{type_id}/properties/ +
+ +
+
+ +Return the custom properties attached to one workspace-level work item type, as a paginated list. Reach for it +when you are building a form for that type, or checking what a type already carries before attaching more. + +Reads are unaffected by the workspace's +[work item type mode](/api-reference/v2/work-item-type-modes) — this route answers even when the workspace +manages types at the project level. + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is +`my-team`. + + + + + +The workspace work item type whose properties you want. Get it from +[List workspace work item types](/api-reference/v2/workspace-work-item-types/list-workspace-work-item-types). + + + +
+
+ +
+ +### Query Parameters + +This list takes no filters or search term — a type's property set is small and returned whole. Check your spelling on +`order_by` and `paginate`: neither is validated, so an unrecognized `order_by` silently falls back to the default +ordering and anything other than `paginate=cursor` silently uses offset pagination. + +
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `sort_order` , `-sort_order` — the display order Plane uses for the property list. This is the order you + want when rendering a form; note that `sort_order` itself is not returned on the property object. +- `created_at` , `-created_at` — when each property was created +- `id` , `-id` + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. Most types carry fewer than 20 properties, so one page is usually the +whole set. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read the `next` value from the +response rather than computing offsets yourself. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead +of `next` and `total_count`. Omit it for the default offset envelope. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted from +the response. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `created_at`, `default_value`, `description`, `display_name`, `external_id`, `external_source`, `id`, `is_active`, `is_multi`, `is_required`, `logo_props`, `name`, `options`, `property_type`, `relation_type`, `settings`, `validation_rules`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_types:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read workspace work item types. | +| `404` | `not_found` | No such workspace or type, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "a7e3f1d0-5c92-4b68-8f31-2d4a6b9e0c15", + "name": "severity", + "display_name": "Severity", + "description": "How badly the customer is affected", + "property_type": "OPTION", + "relation_type": null, + "is_required": true, + "is_multi": false, + "is_active": true, + "default_value": ["Major"], + "options": [ + { + "id": "9d2c7b41-6a80-4f35-8e19-5c3b0a7d2e46", + "name": "Critical", + "description": "Production is down", + "is_default": false, + "sort_order": 10000, + "external_id": null, + "external_source": null + }, + { + "id": "5a83e0b7-2c46-4d19-9f70-6b12c8e5a03d", + "name": "Major", + "description": "A core workflow is broken", + "is_default": true, + "sort_order": 20000, + "external_id": null, + "external_source": null + } + ], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" + }, + { + "id": "3fb6c0d8-4e21-49a7-b5c3-90ad72e14f6b", + "name": "rollout_date", + "display_name": "Rollout date", + "description": "When the fix reaches customers", + "property_type": "DATETIME", + "relation_type": null, + "is_required": false, + "is_multi": false, + "is_active": true, + "default_value": [], + "options": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:24:03.112884Z" + } + ], + "next": null, + "previous": null, + "total_count": 2, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "a7e3f1d0-5c92-4b68-8f31-2d4a6b9e0c15", + "name": "severity", + "display_name": "Severity", + "description": "How badly the customer is affected", + "property_type": "OPTION", + "relation_type": null, + "is_required": true, + "is_multi": false, + "is_active": true, + "default_value": ["Major"], + "options": [], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + + + +
+
+ +::: tip Listing is not the property catalog +This returns only the properties attached to one type. To see everything defined in the workspace, including +properties not yet on any type, use +[List workspace properties](/api-reference/v2/workspace-work-item-properties/list-workspace-work-item-properties). +::: diff --git a/docs/api-reference/v2/workspace-work-item-type-properties/overview.md b/docs/api-reference/v2/workspace-work-item-type-properties/overview.md new file mode 100644 index 00000000..3a0901e4 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-type-properties/overview.md @@ -0,0 +1,198 @@ +--- +title: Workspace type properties overview +description: The Plane API v2 workspace type-property resource. Which properties a workspace-level work item type shows, the attach/detach model, and the work-item-type mode rule. +keywords: plane api v2, workspace work item type properties, attach property to type, detach property, workspace work item types, custom properties +--- + +# Workspace type properties overview + +A **workspace work item type** decides which custom properties appear on the work items that use it. This +resource is the link between the two: it lists the properties currently on a type, and it attaches and +detaches them. + +```text +/api/v2/workspaces/{slug}/work-item-types/{type_id}/properties/ +``` + +The properties themselves are not created here. They live in the workspace property catalog at +[`/work-item-properties/`](/api-reference/v2/workspace-work-item-properties/overview), and one property can be +attached to as many types as you like. That is the point of the split: define `Severity` once for the +workspace, then put it on `Bug`, `Incident`, and `Escalation`. + +Because the catalog is shared, **detaching never deletes**. `DELETE` on this route removes the property from +the type and leaves the property itself — and every other type that uses it — untouched. To remove a property +from the workspace entirely, use +[Delete a workspace property](/api-reference/v2/workspace-work-item-properties/delete-workspace-work-item-property). + +::: warning Writes here require workspace mode +A workspace manages work item types in exactly one mode: project-level or workspace-level. Attaching or +detaching on this route while the workspace is in **project** mode returns `409` with the code +`work_item_types_managed_at_project` — the capability exists, it just lives on the project surface. Reads are +unaffected by mode. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+
+ +## The property object + +Reads on this resource return the full property object, the same shape the workspace property catalog returns. +Every field is read-only here — this route only changes which properties a type carries, never what a property +is. + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the property. This is the id you send in `properties` to attach it, and the `pk` you + use to detach it. + +- `name` _string_ + + Read-only companion to `display_name`. You never set it directly. + +- `display_name` _string_ + + The label shown wherever the property is rendered. + +- `description` _string_ + + Free-form explanation of what the property captures. May be `null`. + +- `property_type` _string_ + + What kind of value the property holds. One of `TEXT`, `DATETIME`, `DECIMAL`, `BOOLEAN`, `OPTION`, + `RELATION`, `URL`, `EMAIL`, `FILE`, or `FORMULA`. + +- `relation_type` _string_ + + For a `RELATION` property, what it points at — one of `ISSUE`, `USER`, `RELEASE`, or `RICH_TEXT`. `null` for + every other property type. + +- `is_required` _boolean_ + + Whether a value must be supplied for this property. + +- `is_multi` _boolean_ + + Whether the property accepts more than one value. + +- `is_active` _boolean_ + + Whether the property is in use. A property can be retired by setting this to `false` on the catalog + resource, without deleting it. + +- `default_value` _array of string_ + + The value applied when none is supplied. Always an array, even when `is_multi` is `false`. + +- `options` _array of any_ + + The choices for an `OPTION` property. Manage them through + [Property options](/api-reference/v2/workspace-work-item-property-options/overview). + +- `settings` _any_ + + Type-specific configuration. Its shape depends on `property_type`. + +- `validation_rules` _any_ + + Type-specific validation configuration. + +- `logo_props` _any_ + + Presentation data for the property's icon. + +- `external_id` , `external_source` _string_ + + Correlation fields for sync and import. Together they let you map a property to a record in another system + and find it again later. Either may be `null`. + +- `created_at` _string (date-time)_ + + When the property was created. + +::: tip There is no separate link object +The response is the property, not a join record. If you need to know whether a given property is on a type, +`GET …/properties/{pk}/` — a `404` means it is not attached, and a `200` means it is. +::: + +
+
+ + + +```json +{ + "id": "a7e3f1d0-5c92-4b68-8f31-2d4a6b9e0c15", + "name": "severity", + "display_name": "Severity", + "description": "How badly the customer is affected", + "property_type": "OPTION", + "relation_type": null, + "is_required": true, + "is_multi": false, + "is_active": true, + "default_value": ["Major"], + "options": [ + { + "id": "9d2c7b41-6a80-4f35-8e19-5c3b0a7d2e46", + "name": "Critical", + "description": "Production is down", + "is_default": false, + "sort_order": 10000, + "external_id": null, + "external_source": null + }, + { + "id": "5a83e0b7-2c46-4d19-9f70-6b12c8e5a03d", + "name": "Major", + "description": "A core workflow is broken", + "is_default": true, + "sort_order": 20000, + "external_id": null, + "external_source": null + } + ], + "settings": {}, + "validation_rules": {}, + "logo_props": {}, + "external_id": null, + "external_source": null, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | ---------------------------------------------------------------------- | ------------------------------- | +| `GET` | `/api/v2/workspaces/{slug}/work-item-types/{type_id}/properties/` | List the properties on a type | +| `POST` | `/api/v2/workspaces/{slug}/work-item-types/{type_id}/properties/` | Attach existing properties | +| `GET` | `/api/v2/workspaces/{slug}/work-item-types/{type_id}/properties/{pk}/` | Get one property on a type | +| `DELETE` | `/api/v2/workspaces/{slug}/work-item-types/{type_id}/properties/{pk}/` | Detach a property from the type | + +There is no `POST` that creates a property here and no `PATCH` that edits one. Attaching takes ids of +properties that already exist, and editing a property is done on the catalog resource. + +## Attaching properties + +`POST` takes a single field, `properties`, holding the ids of properties to attach: + +```json +{ "properties": ["a7e3f1d0-5c92-4b68-8f31-2d4a6b9e0c15", "3fb6c0d8-4e21-49a7-b5c3-90ad72e14f6b"] } +``` + +The response echoes the ids that are now on the type. An id that does not exist in this workspace is rejected +with `400 invalid_request` — a cross-tenant id is never silently ignored. + +## Where a property applies + +Attaching a property to a type is a statement about the type, which lives at the workspace level. When you +need narrower scoping — this property on these projects only, or across a chosen set of types — model it with +[Property contexts](/api-reference/v2/work-item-property-contexts/overview) instead. diff --git a/docs/api-reference/v2/workspace-work-item-types/create-workspace-work-item-type.md b/docs/api-reference/v2/workspace-work-item-types/create-workspace-work-item-type.md new file mode 100644 index 00000000..3ec0dfd1 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-types/create-workspace-work-item-type.md @@ -0,0 +1,228 @@ +--- +title: Create a workspace work item type +description: Create a workspace-level work item type in Plane with the v2 REST API. Body parameters, workspace mode requirement, OAuth scopes, error codes, and code examples. +keywords: plane api v2, create workspace work item type, issue type, workspace mode, POST work item types +--- + +# Create a workspace work item type + +
+ POST + /api/v2/workspaces/{slug}/work-item-types/ +
+ +
+
+ +Define a work item type for the whole workspace. The type becomes available to projects working from the workspace list, so one call gives every team the same `Bug` or `Incident` rather than each project inventing its own. + +::: warning Workspace mode required +This write only succeeds while the workspace manages work item types at the workspace level. In project mode it returns `409` with code `work_item_types_managed_at_project` — create the type on the project endpoint instead. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Body Parameters + +
+ + + +Display name for the type, for example `Incident`. Maximum 255 characters. Reusing a name that already exists in the workspace returns `409`. + + + + + +What this type is for. It is shown next to the type wherever someone picks it, so write it for the person choosing. + + + + + +Whether the type can be selected. Send `false` to create a type that exists but stays out of pickers until you are ready to roll it out. + + + + + +Your system's identifier for this type, for sync and import correlation. Maximum 255 characters, and accepts `null`. Write-only — it is not returned on read, so keep your own mapping to the Plane `id`. + + + + + +The system `external_id` came from, for example `jira`. Maximum 255 characters, and accepts `null`. Write-only, like `external_id` — an `external_id` is only unique within its source, so record both. + + + +
+ +`is_default`, `is_epic`, `level`, and `logo_props` are read-only. A type created here is a standard work item type; promote it to the workspace default with [Mark a type as default](/api-reference/v2/workspace-work-item-types/mark-default-workspace-work-item-type). + +
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ----------------------------------------------------------------------------------------- | +| `400` | `invalid_request` | `name` is missing, empty, or longer than 255 characters. See `errors[]`. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write workspace types. | +| `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` | The type can't be created as requested — most often a name already in use. Read `detail`. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "a3f52d07-8e19-4c6b-92d4-0b7e15c8f326", + "name": "Incident", + "description": "Customer-facing outage that needs a response now", + "is_active": true, + "is_default": false, + "is_epic": false, + "level": 0, + "logo_props": {}, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "This workspace manages work item types at the project level. Create the type on the project's work item types endpoint." +} +``` + + + +
+
+ +::: tip Next steps +A new type starts with no custom properties. [Attach the properties it should collect](/api-reference/v2/workspace-work-item-type-properties/attach-workspace-type-property), then bring it into the projects that need it with [Import work item types](/api-reference/v2/work-item-types/import-work-item-types), passing the `id` returned above. +::: diff --git a/docs/api-reference/v2/workspace-work-item-types/delete-workspace-work-item-type.md b/docs/api-reference/v2/workspace-work-item-types/delete-workspace-work-item-type.md new file mode 100644 index 00000000..a2f70052 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-types/delete-workspace-work-item-type.md @@ -0,0 +1,168 @@ +--- +title: Delete a workspace work item type +description: Delete a workspace-level work item type in Plane with the v2 REST API. Workspace mode requirement, 204 response, OAuth scopes, error codes, and code examples. +keywords: plane api v2, delete workspace work item type, remove issue type, workspace mode, DELETE work item type +--- + +# Delete a workspace work item type + +
+ DELETE + /api/v2/workspaces/{slug}/work-item-types/{pk}/ +
+ +
+
+ +Remove a work item type from the workspace. Because the type is shared, the removal is felt by every project working from the workspace list — check what still uses it before you call this. + +A successful delete returns `204` with an empty body. There is nothing to parse; branch on the status code. + +::: warning Workspace mode required +This write only succeeds while the workspace manages work item types at the workspace level. In project mode it returns `409` with code `work_item_types_managed_at_project`. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The id of the work item type to delete. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The request can't be processed as sent. See `errors[]`. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write workspace types. | +| `404` | `not_found` | No such type or workspace, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The type can't be deleted in its current state — the response `detail` says why. | +| `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. | + +
+ +::: info A repeat delete returns 404 +Delete is not idempotent in its response: the second call for the same id returns `404 not_found`, because the type is already gone. Treat `404` on retry as success rather than as an error worth alerting on. +::: + +
+ +
+ + + + + + + + + +```text +No content +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "This workspace manages work item types at the project level. Delete the type on the project's work item types endpoint." +} +``` + + + +
+
+ +::: tip Consider deactivating instead +If the type is only being phased out, `is_active: false` via [Update](/api-reference/v2/workspace-work-item-types/update-workspace-work-item-type) takes it out of pickers while keeping it resolvable for the work items that already carry it — and you can undo it. +::: diff --git a/docs/api-reference/v2/workspace-work-item-types/get-workspace-work-item-type.md b/docs/api-reference/v2/workspace-work-item-types/get-workspace-work-item-type.md new file mode 100644 index 00000000..d4ec297c --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-types/get-workspace-work-item-type.md @@ -0,0 +1,171 @@ +--- +title: Get a workspace work item type +description: Retrieve a single workspace-level work item type from Plane with the v2 REST API. Path parameters, OAuth scopes, error codes, and code examples. +keywords: plane api v2, get workspace work item type, issue type by id, workspace mode, GET work item type +--- + +# Get a workspace work item type + +
+ GET + /api/v2/workspaces/{slug}/work-item-types/{pk}/ +
+ +
+
+ +Retrieve one workspace-level type by id. Reach for this when you already hold a type id — from a work item's `type_id`, a webhook payload, or a stored mapping — and need its current name, description, or active flag. + +::: info Reads work in either mode +Retrieval is unaffected by [work item type modes](/api-reference/v2/work-item-type-modes). A workspace type stays readable here even when the workspace manages types at the project level; only writes are mode-gated. +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The id of the work item type to retrieve. A project-level type id returns `404` here — this endpoint only resolves types defined on the workspace. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_types:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this workspace's types. | +| `404` | `not_found` | No such type or workspace, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +::: info No expansion, and no external ids +Work item types don't accept `?expand=` — the response is the flat object shown here. `external_id` and `external_source` are write-only and never come back, so resolve a type by the Plane `id` you stored at create time. +::: + +::: info Existence is never leaked +A type outside your tenant returns `404`, not `403`. If a cached id starts returning `404`, re-read the workspace's set with [List workspace work item types](/api-reference/v2/workspace-work-item-types/list-workspace-work-item-types) rather than retrying. +::: + +
+ +
+ + + + + + + + + +```json +{ + "id": "c1a7d3f4-6b28-4e90-8d15-2f7a0b9c4e63", + "name": "Bug", + "description": "Something is broken and needs a fix", + "is_active": true, + "is_default": false, + "is_epic": false, + "level": 0, + "logo_props": {}, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "not_found", + "code": "not_found", + "detail": "No Issue Type matches the given query." +} +``` + + + +
+
diff --git a/docs/api-reference/v2/workspace-work-item-types/list-workspace-work-item-types.md b/docs/api-reference/v2/workspace-work-item-types/list-workspace-work-item-types.md new file mode 100644 index 00000000..55064577 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-types/list-workspace-work-item-types.md @@ -0,0 +1,272 @@ +--- +title: List workspace work item types +description: List the workspace-level work item types in a Plane workspace with the v2 REST API. Ordering, pagination, OAuth scopes, error codes, and code examples. +keywords: plane api v2, list workspace work item types, issue types, workspace mode, pagination, GET work item types +--- + +# List workspace work item types + +
+ GET + /api/v2/workspaces/{slug}/work-item-types/ +
+ +
+
+ +Return the workspace's work item types as a paginated list. This is where you resolve a type name such as `Bug` to the `id` you need — to set `type_id` on a work item, to attach properties to the type, or to hand to a project [import](/api-reference/v2/work-item-types/import-work-item-types). + +There is no project segment in the path: these are the types defined once for the whole workspace. + +::: info Reads work in either mode +Listing is unaffected by [work item type modes](/api-reference/v2/work-item-type-modes). You can read the workspace list even while the workspace manages types at the project level — only writes are mode-gated. +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + +
+
+ +
+ +### Query Parameters + +This endpoint takes ordering and pagination only — there are no filter or search parameters. A workspace's type list is short by design, so read a page and match on `name` or `id` in your own code. + +
+ +
+ +### Ordering + +
+ + + +Field to sort by. Prefix with `-` for descending. + +- `name` , `-name` — alphabetical, which is what you usually want for a picker +- `level` , `-level` — the type's level in the work item hierarchy +- `created_at` , `-created_at` — when each type was added +- `id` , `-id` + +A value outside this list is not rejected — it quietly falls back to the default ordering. Check your spelling: a typo surfaces as a differently-sorted page, not as an error. + + + +
+
+ +
+ +### Pagination + +
+ + + +Page size. Defaults to 50, maximum 200. Most workspaces define fewer than 20 types, so one page is usually the whole set. + + + + + +Number of rows to skip from the start of the result set. Maximum 10000. Read the `next` value from the response rather than computing offsets yourself. + + + + + +Set to `cursor` to opt into the COUNT-free keyset envelope, which returns `next_cursor` and `has_more` instead of `next` and `total_count`. Omit it for the default offset envelope. + + + + + +Defaults to `true`. Set to `false` to skip the `COUNT(*)` behind `total_count`; the field is then omitted from the response. + + + +
+
+ +
+ +### Response shaping + +
+ + + +Comma-separated list of fields to return on each row. 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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_types:read` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------ | ------------------------------------------------------------------------------------ | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't read this workspace's types. | +| `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. | +| `429` | `rate_limited` | Throttled. Honor the `Retry-After` header before retrying. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "data": [ + { + "id": "c1a7d3f4-6b28-4e90-8d15-2f7a0b9c4e63", + "name": "Bug", + "description": "Something is broken and needs a fix", + "is_active": true, + "is_default": false, + "is_epic": false, + "level": 0, + "logo_props": {}, + "created_at": "2026-01-14T09:22:41.478363Z" + }, + { + "id": "5e8b0c19-7a4d-42f6-9b83-1c0d6e2f7a45", + "name": "Epic", + "description": "A large body of work spanning several work items", + "is_active": true, + "is_default": false, + "is_epic": true, + "level": 1, + "logo_props": {}, + "created_at": "2026-01-14T09:22:41.512907Z" + }, + { + "id": "9d2f1b73-4c85-4a06-b3e1-7f8c25a0d914", + "name": "Task", + "description": "Standard unit of work", + "is_active": true, + "is_default": true, + "is_epic": false, + "level": 0, + "logo_props": {}, + "created_at": "2026-01-14T09:22:41.548221Z" + } + ], + "next": null, + "previous": null, + "total_count": 3, + "pagination": { + "style": "offset" + } +} +``` + + + + + +```json +{ + "data": [ + { + "id": "c1a7d3f4-6b28-4e90-8d15-2f7a0b9c4e63", + "name": "Bug", + "description": "Something is broken and needs a fix", + "is_active": true, + "is_default": false, + "is_epic": false, + "level": 0, + "logo_props": {}, + "created_at": "2026-01-14T09:22:41.478363Z" + } + ], + "next_cursor": "b3A9MTcx", + "has_more": true, + "pagination": { + "style": "cursor" + } +} +``` + + + +
+
+ +::: tip Inactive types still list +`is_active: false` retires a type from pickers, but it keeps appearing in this list. Filter on `is_active` in your own code before you show the list to a user, and keep inactive types resolvable so historic work items still render with a name. +::: diff --git a/docs/api-reference/v2/workspace-work-item-types/mark-default-workspace-work-item-type.md b/docs/api-reference/v2/workspace-work-item-types/mark-default-workspace-work-item-type.md new file mode 100644 index 00000000..f2d7d97e --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-types/mark-default-workspace-work-item-type.md @@ -0,0 +1,182 @@ +--- +title: Mark a workspace work item type as default +description: Promote a workspace-level work item type to the workspace default in Plane with the v2 REST API. Workspace mode requirement, OAuth scopes, error codes, and code examples. +keywords: plane api v2, mark default work item type, workspace default issue type, workspace mode, POST mark-default +--- + +# Mark a workspace work item type as default + +
+ POST + /api/v2/workspaces/{slug}/work-item-types/{pk}/mark-default/ +
+ +
+
+ +Promote a type to the workspace default — the type a work item gets when none is supplied. Exactly one type holds the flag, so marking this one clears it on the type that held it before. + +`is_default` is read-only on create and update, which is why this is its own call. **Send no body.** + +::: warning Workspace mode required +This write only succeeds while the workspace manages work item types at the workspace level. In project mode it returns `409` with code `work_item_types_managed_at_project` — set the default on the project endpoint instead. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The id of the work item type to make the default. + + + +
+
+ +
+ +### Body Parameters + +None. The type is identified entirely by `pk`; send an empty request. + +
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | The type can't be made default as requested. See `errors[]`. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write workspace types. | +| `404` | `not_found` | No such type or workspace, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The default can't be moved in the current state — the response `detail` says why. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "9d2f1b73-4c85-4a06-b3e1-7f8c25a0d914", + "name": "Task", + "description": "Standard unit of work", + "is_active": true, + "is_default": true, + "is_epic": false, + "level": 0, + "logo_props": {}, + "created_at": "2026-01-14T09:22:41.548221Z" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "This workspace manages work item types at the project level. Mark the default on the project's work item types endpoint." +} +``` + + + +
+
+ +::: info Confirm the swap in one read +The response is the promoted type, not the pair. If you need to show the change, re-read the set with [List workspace work item types](/api-reference/v2/workspace-work-item-types/list-workspace-work-item-types) — the previous default now reports `is_default: false`. +::: diff --git a/docs/api-reference/v2/workspace-work-item-types/overview.md b/docs/api-reference/v2/workspace-work-item-types/overview.md new file mode 100644 index 00000000..6222110e --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-types/overview.md @@ -0,0 +1,146 @@ +--- +title: Workspace work item types overview +description: The Plane API v2 workspace work item type object. Attributes, workspace mode versus project mode, defaults, and how projects pick up workspace-level types. +keywords: plane api v2, workspace work item types, issue types, workspace mode, work item type modes, shared taxonomy +--- + +# Workspace work item types overview + +A work item type classifies work — `Bug`, `Task`, `Feature`, `Incident` — and carries the set of custom properties that work items of that type collect. + +When a workspace manages types at the **workspace level**, a type is defined once for the whole workspace and made available to projects, instead of being recreated project by project. One `Bug` definition, one set of properties, every project reading from the same list. + +These endpoints hang off the workspace and have no project segment: + +```text +/api/v2/workspaces/{slug}/work-item-types/ +``` + +::: warning Writes require workspace mode +A workspace manages work item types in exactly one mode: project-level or workspace-level. Creating, updating, deleting, or marking a default here works only while the workspace is in **workspace mode**. In project mode the same calls return `409` with code `work_item_types_managed_at_project` — the capability exists, it just lives on the project endpoints. **Reads are unaffected by mode.** See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+
+ +## The work item type object + +### Attributes + +- `id` _string (uuid)_ + + Unique identifier for the type. This is the id you send as `type_id` on a work item, and the id you pass when importing types into a project. + +- `name` _string_ + + Display name, for example `Bug`. Maximum 255 characters. + +- `description` _string_ + + What this type is for. Shown next to the type wherever it is picked in Plane, so it is worth writing for the people choosing, not for your integration. + +- `is_active` _boolean_ + + Whether the type can currently be selected. Deactivating a type retires it from pickers without deleting it or touching the work items already using it. + +- `is_default` _boolean_ + + Whether this is the workspace's default type. Read-only on create and update — change it with [Mark a type as default](/api-reference/v2/workspace-work-item-types/mark-default-workspace-work-item-type). + +- `is_epic` _boolean_ + + Whether the type is an epic type. Read-only, and there is no body parameter for it: every type you create through this API is a standard work item type. + +- `level` _number_ + + The type's level in the work item hierarchy. Read-only, and one of the values you can sort by with `order_by`. + +- `logo_props` _any_ + + Free-form icon metadata Plane uses to render the type's badge. Managed in the app — read-only over the API. + +- `created_at` _string (date-time)_ + + When the type was created. + +::: info `external_id` is write-only +You can send `external_id` and `external_source` when creating or updating a type so an import can correlate it with a record in your own system, but neither field comes back on read. Keep your own mapping from your id to the Plane `id`. +::: + +
+
+ + + +```json +{ + "id": "c1a7d3f4-6b28-4e90-8d15-2f7a0b9c4e63", + "name": "Bug", + "description": "Something is broken and needs a fix", + "is_active": true, + "is_default": false, + "is_epic": false, + "level": 0, + "logo_props": {}, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + +
+
+ +## Endpoints + +| Method | Path | Description | +| -------- | -------------------------------------------------------------- | ---------------------- | +| `GET` | `/api/v2/workspaces/{slug}/work-item-types/` | List types | +| `POST` | `/api/v2/workspaces/{slug}/work-item-types/` | Create a type | +| `GET` | `/api/v2/workspaces/{slug}/work-item-types/{pk}/` | Get a type | +| `PATCH` | `/api/v2/workspaces/{slug}/work-item-types/{pk}/` | Update a type | +| `DELETE` | `/api/v2/workspaces/{slug}/work-item-types/{pk}/` | Delete a type | +| `POST` | `/api/v2/workspaces/{slug}/work-item-types/{pk}/mark-default/` | Mark a type as default | + +There is no `enable` and no `import` action at the workspace level. Both are project-level operations — see [Work item types (project)](/api-reference/v2/work-item-types/overview). + +## Workspace mode versus project mode + +| | Project mode | Workspace mode | +| -------------------------- | -------------------------------------------------------------- | ------------------------------------------ | +| Where a type is defined | Once per project, on `/projects/{project_id}/work-item-types/` | Once per workspace, on `/work-item-types/` | +| Same type in ten projects | Ten separate records to create and keep in step | One record the projects share | +| Renaming or editing a type | Repeat the edit in every project | Edit once | +| Cross-project reporting | Group by name and hope the names match | Group by the type `id` | +| Per-project variation | A project can shape its own types freely | Projects work from the workspace list | + +Choose **workspace mode** when the same taxonomy should hold across many projects — a support org where `Incident` must mean the same thing everywhere, or a company that reports on bug counts across every team. Choose **project mode** when teams are genuinely different and each one should be free to invent its own categories. + +The trade-off is consistency against autonomy, and it is a workspace-wide decision: both surfaces exist, but only one of them accepts writes at a time. + +## How a project gets workspace types + +Defining a type here makes it available to the workspace; a project surfaces it once the project is working from the workspace list. To bring a specific set of workspace types into a project, send their ids to the project-level import endpoint: + +```text +POST /api/v2/workspaces/{slug}/projects/{project_id}/work-item-types/import/ +``` + +Collect the ids from [List workspace work item types](/api-reference/v2/workspace-work-item-types/list-workspace-work-item-types), then see [Import work item types](/api-reference/v2/work-item-types/import-work-item-types) for the request shape. + +## Defaults + +Exactly one type is the workspace default — the one a work item gets when no type is supplied. `is_default` is read-only on create and update, so promoting a different type is its own call: + +```text +POST /api/v2/workspaces/{slug}/work-item-types/{pk}/mark-default/ +``` + +Marking a type as default clears the flag on the type that held it. + +## Retiring a type + +Deactivating (`is_active: false`) and deleting are different moves: + +- **Deactivate** when the type should stop appearing in pickers but existing work items keep their classification. Reversible — send `is_active: true` to bring it back. +- **Delete** when the type should be gone. `DELETE` returns `204` with an empty body. A type that can't be removed in its current state returns `409`; read the `detail` for the reason. diff --git a/docs/api-reference/v2/workspace-work-item-types/update-workspace-work-item-type.md b/docs/api-reference/v2/workspace-work-item-types/update-workspace-work-item-type.md new file mode 100644 index 00000000..9a41bbd3 --- /dev/null +++ b/docs/api-reference/v2/workspace-work-item-types/update-workspace-work-item-type.md @@ -0,0 +1,232 @@ +--- +title: Update a workspace work item type +description: Update a workspace-level work item type in Plane with the v2 REST API. Partial PATCH body, workspace mode requirement, OAuth scopes, error codes, and code examples. +keywords: plane api v2, update workspace work item type, rename issue type, deactivate type, workspace mode, PATCH work item type +--- + +# Update a workspace work item type + +
+ PATCH + /api/v2/workspaces/{slug}/work-item-types/{pk}/ +
+ +
+
+ +Rename a type, rewrite its description, or retire it with `is_active: false`. The edit lands once and every project working from the workspace list sees it — that is the point of managing types at the workspace level. + +The update is partial: omitted fields are left untouched, and omitting a field is not the same as sending `null`. There is no `PUT`. + +::: warning Workspace mode required +This write only succeeds while the workspace manages work item types at the workspace level. In project mode it returns `409` with code `work_item_types_managed_at_project`. See [Work item type modes](/api-reference/v2/work-item-type-modes). +::: + +
+ +### Path Parameters + +
+ + + +The workspace slug. It appears in your Plane URLs — in `https://app.plane.so/my-team/projects/`, the slug is `my-team`. + + + + + +The id of the work item type to update. + + + +
+
+ +
+ +### Body Parameters + +Every field is optional — send only what changes. + +
+ + + +New display name. Maximum 255 characters. Renaming is safe for integrations that key on `id`; anything matching on the old name breaks, which is a reason to key on `id`. + + + + + +New description of what the type is for. + + + + + +Send `false` to retire the type from pickers without deleting it — work items already classified with it keep their type. Send `true` to bring it back. + + + + + +Your system's identifier for this type. Maximum 255 characters. Send `null` to clear it. Write-only — it is not returned on read. + + + + + +The system `external_id` came from, for example `jira`. Maximum 255 characters. Send `null` to clear it. Write-only, like `external_id`. + + + +
+ +`is_default`, `is_epic`, `level`, and `logo_props` are read-only — sending them has no effect. To change which type is the default, call [Mark a type as default](/api-reference/v2/workspace-work-item-types/mark-default-workspace-work-item-type). + +
+ +
+ +### Response shaping + +
+ + + +Comma-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: `created_at`, `description`, `id`, `is_active`, `is_default`, `is_epic`, `level`, `logo_props`, `name`. + +See [Sparse fields](/api-reference/v2/sparse-fields). + + + +
+
+ +
+ +### Scopes + +`workspaces.work_item_types:write` + +
+ +
+ +### Errors + +| Status | Code | Cause | +| ------ | ------------------------ | ------------------------------------------------------------------------------------ | +| `400` | `invalid_request` | An invalid value — for example a `name` over 255 characters. See `errors[]`. | +| `401` | `unauthorized` | Missing or invalid credentials. | +| `402` | `payment_required` | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. | +| `403` | `forbidden` | Your role or token scope can't write workspace types. | +| `404` | `not_found` | No such type or workspace, or it's outside your tenant. | +| `406` | `not_acceptable` | The `Accept` header asks for a representation the API can't produce. | +| `409` | `conflict` | The change collides with another type — most often a name already in use. | +| `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. | + +
+ +
+ +
+ + + + + + + + + +```json +{ + "id": "c1a7d3f4-6b28-4e90-8d15-2f7a0b9c4e63", + "name": "Defect", + "description": "A confirmed break in shipped behavior", + "is_active": true, + "is_default": false, + "is_epic": false, + "level": 0, + "logo_props": {}, + "created_at": "2026-01-14T09:22:41.478363Z" +} +``` + + + + + +```json +{ + "type": "conflict", + "code": "work_item_types_managed_at_project", + "detail": "This workspace manages work item types at the project level. Update the type on the project's work item types endpoint." +} +``` + + + +
+
+ +::: tip Deactivate before you delete +`is_active: false` is the reversible move: the type stops appearing in pickers, existing work items keep their classification, and you can undo it with one call. Reach for [Delete](/api-reference/v2/workspace-work-item-types/delete-workspace-work-item-type) only when the type should be gone for good. +::: diff --git a/docs/dev-tools/build-plane-app/choose-token-flow.md b/docs/dev-tools/build-plane-app/choose-token-flow.md index ecd5b029..d7b9ce68 100644 --- a/docs/dev-tools/build-plane-app/choose-token-flow.md +++ b/docs/dev-tools/build-plane-app/choose-token-flow.md @@ -217,4 +217,4 @@ GET https://api.plane.so/api/v1/workspaces/{workspace_slug}/projects/ Authorization: Bearer YOUR_TOKEN ``` -See the [API Reference](/api-reference/introduction) for available endpoints. +See the [API Reference](/api-reference/v1/introduction) for available endpoints. diff --git a/docs/dev-tools/build-plane-app/examples.md b/docs/dev-tools/build-plane-app/examples.md index f20fc3da..776c6855 100644 --- a/docs/dev-tools/build-plane-app/examples.md +++ b/docs/dev-tools/build-plane-app/examples.md @@ -236,7 +236,7 @@ if __name__ == "__main__": ## Next Steps - [Build an Agent](/dev-tools/agents/overview) - Create AI agents that respond to @mentions -- [API Reference](/api-reference/introduction) - Explore the full Plane API +- [API Reference](/api-reference/v1/introduction) - Explore the full Plane API - [Webhook Events](/dev-tools/intro-webhooks) - All webhook event types - [Example: PRD Agent](https://github.com/makeplane/prd-agent) - Complete agent implementation diff --git a/docs/dev-tools/build-plane-app/overview.md b/docs/dev-tools/build-plane-app/overview.md index a1e8600d..92ed6597 100644 --- a/docs/dev-tools/build-plane-app/overview.md +++ b/docs/dev-tools/build-plane-app/overview.md @@ -125,6 +125,6 @@ See full working implementations: ## Quick links -- [API Reference](/api-reference/introduction) - Explore all available endpoints +- [API Reference](/api-reference/v1/introduction) - Explore all available endpoints - [Build an Agent](/dev-tools/agents/overview) - Create AI agents for Plane - [Webhook Events](/dev-tools/intro-webhooks) - All webhook event types diff --git a/docs/index.md b/docs/index.md index c10dbb1a..ca7d2497 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,7 +18,7 @@ aside: false
@@ -31,7 +31,7 @@ Deploy Plane on your infrastructure with Docker, Kubernetes, or other methods. C - + 180+ endpoints to manage projects, work items, cycles, modules, and more. diff --git a/package.json b/package.json index fb7e71c9..bad07c9c 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vitepress dev docs", - "build": "(git fetch --unshallow || true) && vitepress build docs", + "build": "(git fetch --unshallow || true) && NODE_OPTIONS=--max-old-space-size=8192 vitepress build docs", "preview": "vitepress preview docs", "fix:format": "prettier --write .", "check:format": "prettier --check .", diff --git a/vercel.json b/vercel.json index f1bdea82..24ff4e09 100644 --- a/vercel.json +++ b/vercel.json @@ -6,7 +6,7 @@ "headers": [ { "key": "Link", - "value": "; rel=\"describedby\"; type=\"text/plain\", ; rel=\"service-doc\"; type=\"text/html\", ; rel=\"sitemap\"; type=\"application/xml\"" + "value": "; rel=\"describedby\"; type=\"text/plain\", ; rel=\"service-doc\"; type=\"text/html\", ; rel=\"sitemap\"; type=\"application/xml\"" }, { "key": "X-Robots-Tag", @@ -18,7 +18,11 @@ "redirects": [ { "source": "/api-reference", - "destination": "/api-reference/introduction" + "destination": "/api-reference/v2/introduction" + }, + { + "source": "/api-reference/introduction", + "destination": "/api-reference/v2/introduction" }, { "source": "/api-reference/byoa/build-plane-app", @@ -30,35 +34,39 @@ }, { "source": "/api-reference/cycle-issue/overview", - "destination": "/api-reference/cycle/overview" + "destination": "/api-reference/v1/cycle/overview" }, { "source": "/api-reference/cycle-issue/add-cycle-issue", - "destination": "/api-reference/cycle/add-cycle-work-items" + "destination": "/api-reference/v1/cycle/add-cycle-work-items" }, { "source": "/api-reference/cycle-issue/list-cycle-issues", - "destination": "/api-reference/cycle/list-cycle-work-items" + "destination": "/api-reference/v1/cycle/list-cycle-work-items" }, { "source": "/api-reference/cycle-issue/delete-cycle-issue", - "destination": "/api-reference/cycle/remove-cycle-work-item" + "destination": "/api-reference/v1/cycle/remove-cycle-work-item" }, { "source": "/api-reference/module-issue/overview", - "destination": "/api-reference/module/overview" + "destination": "/api-reference/v1/module/overview" }, { "source": "/api-reference/module-issue/add-module-issue", - "destination": "/api-reference/module/add-module-work-items" + "destination": "/api-reference/v1/module/add-module-work-items" }, { "source": "/api-reference/module-issue/list-module-issues", - "destination": "/api-reference/module/list-module-work-items" + "destination": "/api-reference/v1/module/list-module-work-items" }, { "source": "/api-reference/module-issue/delete-module-issue", - "destination": "/api-reference/module/remove-module-work-item" + "destination": "/api-reference/v1/module/remove-module-work-item" + }, + { + "source": "/api-reference/:path((?!v1/|v2/).*)", + "destination": "/api-reference/v1/:path" }, { "source": "/plane-one/governance/authentication/custom-sso",