# POST /api/v2/work-items

Create a work item

- MCP id: work-items.store
- Scope: api:write:project_management
- Permission: project_management::manage_work_items
- Rate cost: 1 unit, write bucket. A write costs the same whatever `view` you ask for.
- Cache-Control: private, no-store

Creates a work item and returns it in the detail view with a `Location` header.

The issue key is generated from the project's customer code and a per-customer sequence; `order` places the item last in its sprint or backlog. `reporter_id` is always the authenticated user: there is no way to file on somebody else's behalf. Omit `workflow_status_id` and `work_type_id` and the server picks both; in an organisation with no work types, or a project whose workflow has no statuses, that is a `409` `state-conflict` with `conflict.reason` unconfigured rather than a validation error about a key you never sent; the fix is an admin action. `label_ids` replaces the label set outright, and `drive_item_ids` the set of linked drive documents; each id must name a document the caller may open, and attaching one additionally requires the `project_drive` prefix's access permission and a token scope covering it. `task_id` must name a task in the project, and an item placed in the running sprint of a sprint project needs one; a simple-mode project does not. A work item cannot be moved between projects afterwards, so pick the project carefully.

## Parameters
- view (string, optional): Which field set to render.
- X-Request-Id (header, optional): A correlation id of your own. It is recorded in our logs next to the server's request id but never returned: the response header always carries the server's id. A value that does not match the pattern is discarded silently, never rejected.
- Idempotency-Key (header, optional): A caller-chosen key that makes this POST safe to retry. Replaying it returns the original response with Idempotency-Replayed: true.

## Request body
- project_id (integer → Project.id, required): Project this work item belongs to. It cannot change: the issue key was allocated from the project's customer code.
- title (string ≤ 255, required): Work item title. The other field `?q=` searches.
- description (string or null, optional): Work item description. HTML from the rich editor, sanitised on read and on write. Mention spans are preserved verbatim, so pass them back unchanged. Detail view only.
- priority (string or null, optional): Relative priority. Defaults to normal on create; `null` only on a legacy item that was never prioritised.
- due_date (date (YYYY-MM-DD) or null, optional): Due date, a zone-free calendar date. Null when no date was set.
- estimation (integer or null ≥ 0, optional): Original estimate, in minutes.
- remaining_estimate (integer or null ≥ 0, optional): Remaining estimate, in minutes. Null where none was entered, in which case plnnrly derives one from the estimate and the logged time.
- location_name (string or null ≤ 255, optional): Name of the place the work happens, as picked from the address search in plnnrly. Null when no location was set. Send it together with `location_latitude` and `location_longitude`; `null` all three to clear the location. Detail view only. Required when location_latitude, location_longitude is present.
- location_address (string or null ≤ 255, optional): Formatted address of the location, when the place has one. Detail view only.
- location_latitude (number or null -90 to 90, optional): Latitude of the location in decimal degrees, -90 to 90. Detail view only. Required when location_name, location_longitude is present.
- location_longitude (number or null -180 to 180, optional): Longitude of the location in decimal degrees, -180 to 180. Detail view only. Required when location_name, location_latitude is present.
- work_type_id (integer or null → WorkType.id, optional): Work type of this item.
- workflow_status_id (integer or null → WorkflowStatus.id, optional): Current workflow status. Not writable through PATCH: moving between statuses is `POST work-items/{work_item}/actions/transition`, because the move is validated against the project workflow.
- task_id (integer or null → Task.id, optional): Kanban task this work item is booked against, when it is booked against one. It must belong to the item's project. A sprint project books the running sprint's hours through it, so an item in that project's running sprint needs one; a simple-mode project does not. Setting it also makes the task's owner the assignee. Follow it to `/api/v2/tasks/{task}`.
- assignee_id (integer or null → User.id, optional): User the work is assigned to. Null on an unassigned item. On a sprint project, setting it also moves the item to that user's only open task in the same budget, or in the project when the item has no task yet; with several or no candidates the task is left as it is.
- sprint_id (integer or null → Sprint.id, optional): Sprint the item sits in. Null means it is in the backlog.
- parent_id (integer or null → WorkItem.id, optional): Parent work item, for a sub-issue. Null on a top-level item.
- label_ids (array of integer → Label.id ≤ 50, optional): Labels on this work item. `label_ids` takes at most 50 ids and replaces the whole set: a label you leave out is removed. Build it from `links.labels`, which lists every label while the Sprints feature is enabled, not from `include=labels`, which stops at 25.
- drive_item_ids (array of integer → DriveItem.id ≤ 25, optional): Drive documents linked to this work item that you may open. `drive_item_ids` takes at most 25 ids and replaces the whole set: a document you leave out is unlinked, and so is every linked document you cannot open. Build it from `links.drive_items`, not from `include=drive_items`, which stops at 25. Expanding it additionally requires the `project_drive` prefix's access grant and a token scope covering it.

Unknown keys are refused with `422` validation-failed, naming the key.

## Response 201
- id (integer, required): Unique identifier.
- issue_key (string or null ≤ 255, required, server-assigned): Human-readable key, e.g. `ACM-123`, allocated by the server from the project's customer code and a per-customer sequence. It also works in the path: GET `work-items/ACM-123` works. This is one of the two fields `?q=` searches.
- title (string ≤ 255, required): Work item title. The other field `?q=` searches.
- description (html or null, required, detail only): Work item description. HTML from the rich editor, sanitised on read and on write. Mention spans are preserved verbatim, so pass them back unchanged. Detail view only.
- priority (string or null, required): Relative priority. Defaults to normal on create; `null` only on a legacy item that was never prioritised.
- due_date (date (YYYY-MM-DD) or null, required): Due date, a zone-free calendar date. Null when no date was set.
- estimation (integer or null, required): Original estimate, in minutes.
- remaining_estimate (integer or null, required): Remaining estimate, in minutes. Null where none was entered, in which case plnnrly derives one from the estimate and the logged time.
- location_name (string or null ≤ 255, required, detail only): Name of the place the work happens, as picked from the address search in plnnrly. Null when no location was set. Send it together with `location_latitude` and `location_longitude`; `null` all three to clear the location. Detail view only.
- location_address (string or null ≤ 255, required, detail only): Formatted address of the location, when the place has one. Detail view only.
- location_latitude (decimal (2 dp) or null -90 to 90, required, detail only): Latitude of the location in decimal degrees, -90 to 90. Detail view only.
- location_longitude (decimal (2 dp) or null -180 to 180, required, detail only): Longitude of the location in decimal degrees, -180 to 180. Detail view only.
- order (integer, required, server-assigned): Position within the sprint or backlog, and the default sort. Not writable through PATCH: move an item with POST `work-items/{work_item}/actions/move` and `list: backlog`.
- column_order (integer or null, required, server-assigned): Position within the board column. Not writable through PATCH: move a card with `POST work-items/{work_item}/actions/move` and `list: board`.
- version (integer, required): Revision counter, bumped once per write. It is the `ETag`'s revision: send it back as `If-Match` to detect a concurrent edit, which answers `412`.
- created_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): Creation timestamp.
- updated_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): Last-write timestamp; use for incremental sync.
- project_id (integer, required): Project this work item belongs to. It cannot change: the issue key was allocated from the project's customer code.
- task_id (integer or null, required): Kanban task this work item is booked against, when it is booked against one. It must belong to the item's project. A sprint project books the running sprint's hours through it, so an item in that project's running sprint needs one; a simple-mode project does not. Setting it also makes the task's owner the assignee. Follow it to `/api/v2/tasks/{task}`.
- workflow_status_id (integer, required): Current workflow status. Not writable through PATCH: moving between statuses is `POST work-items/{work_item}/actions/transition`, because the move is validated against the project workflow.
- work_type_id (integer, required): Work type of this item.
- sprint_id (integer or null, required): Sprint the item sits in. Null means it is in the backlog.
- parent_id (integer or null, required): Parent work item, for a sub-issue. Null on a top-level item.
- assignee_id (integer or null, required): User the work is assigned to. Null on an unassigned item. On a sprint project, setting it also moves the item to that user's only open task in the same budget, or in the project when the item has no task yet; with several or no candidates the task is left as it is.
- reporter_id (integer, required, server-assigned): User who filed the work item. Always the authenticated user at create time; there is no way to file on somebody else's behalf.
- web_url (string, required): Absolute URL that opens this work item in plnnrly, on the organisation's own hostname. It is a stable link, safe to store: it redirects to the first of the project's backlog, all-work list or board the signed-in user may open, and opens the item there; it lands on the project page when none of them applies. Read-only.
- workflow_status (Ref or WorkflowStatusSummary, required): The workflow status this item currently sits in. Always present.
- parent (Ref or WorkItemSummary or null, required): The parent work item, on a sub-issue.
- assignee (Ref or UserSummary or null, required): The user the work is assigned to. Expanding it additionally requires the users prefix's read grant and a token scope covering it.
- reporter (Ref or UserSummary, required): The user who filed the work item. Expanding it additionally requires the users prefix's read grant and a token scope covering it.
- work_type (Ref or WorkTypeSummary, required): The work type of this item. Expanding it additionally requires the `work_types` prefix's read grant and a token scope covering it.
- sprint (Ref or SprintSummary or null, required): The sprint this item sits in, when it is not in the backlog.
- comments_count (integer ≥ 0, required): Number of comments on this work item.
- subtasks_count (integer ≥ 0, required): Number of checklist subtasks on this work item.
- attachments_count (integer ≥ 0, required): Number of files attached to this work item.
- child_work_items_count (integer ≥ 0, required): Number of child work items (sub-issues).
- labels_count (integer ≥ 0, required): Number of labels on this work item.
- drive_items_count (integer ≥ 0, required): Number of linked drive documents the caller may open. Archived documents are not counted.
- links_count (integer, required): Number of work items linked to this one, in either direction.
- links (WorkItemLinks, required)
  - self (url, required): The canonical URI of this record.
  - comments (url, required): Comments on this work item.
  - subtasks (url, required): Checklist subtasks on this work item.
  - links (url, required): Links between this work item and others, in either direction.
  - attachments (url, required): Files attached to this work item.
  - child_work_items (url, required): Child work items (sub-issues) of this work item.
  - labels (url, optional): Every label on this work item, paginated. Present only while the Sprints feature is enabled, as `GET /labels` is.
  - drive_items (url, required): Every drive document linked to this work item that you may open, paginated.
- labels (array of LabelSummary ≤ 25, optional, only with include): Labels on this work item. `label_ids` takes at most 50 ids and replaces the whole set: a label you leave out is removed. Build it from `links.labels`, which lists every label while the Sprints feature is enabled, not from `include=labels`, which stops at 25. Wrapped in `{ data, meta }`; `meta.truncated` says whether the cap was hit and `meta.count` is the full count.
  - id (integer, required): Unique identifier.
  - name (string ≤ 255, required): Label name as shown on the chip.
  - color (string ≤ 255, required): Six-digit hex colour, e.g. `#ef4444`.
- drive_items (array of DriveItemSummary ≤ 25, optional, only with include): Drive documents linked to this work item that you may open. `drive_item_ids` takes at most 25 ids and replaces the whole set: a document you leave out is unlinked, and so is every linked document you cannot open. Build it from `links.drive_items`, not from `include=drive_items`, which stops at 25. Expanding it additionally requires the `project_drive` prefix's access grant and a token scope covering it. Wrapped in `{ data, meta }`; `meta.truncated` says whether the cap was hit and `meta.count` is the full count.
  - id (integer, required): Unique identifier.
  - title (string ≤ 255, required): Item title.
  - owner_type (string, required): The kind of record `owner` points at; it says which branch of `owner` applies.
  - owner_id (integer, required): The id of the record `owner` points at.
  - owner (Ref, required): The project, customer, organisation or user whose drive this item lives in.
- comments (array of CommentSummary ≤ 25, optional, only with include): Comments on this work item, newest first, capped at 25. Follow `links.comments` for the rest. Wrapped in `{ data, meta }`; `meta.truncated` says whether the cap was hit and `meta.count` is the full count.
  - id (integer, required): Unique identifier.
  - author_type (string, required): The kind of record `author` points at; it says which branch of `author` applies.
  - author_id (integer, required): The id of the record `author` points at.
  - author (Ref or UserSummary, required): Who wrote the comment.
  - created_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): When the comment was posted.
- subtasks (array of SubtaskSummary ≤ 25, optional, only with include): Checklist subtasks on this work item, capped at 25. Follow `links.subtasks` for the rest. Wrapped in `{ data, meta }`; `meta.truncated` says whether the cap was hit and `meta.count` is the full count.
  - id (integer, required): Unique identifier.
  - title (string ≤ 255, required): Subtask title.
  - is_resolved (boolean, required): Whether the subtask is ticked off.

## Errors
- 403 forbidden-by-policy | insufficient-permission | insufficient-token-scope: Forbidden.
- 409 idempotency-key-in-flight | idempotency-key-reused | state-conflict: Conflict.
- 415 unsupported-media-type: Unsupported media type.
- 422 unprocessable-relationship | validation-failed: Unprocessable content.
- 400 idempotency-key-required | invalid-cursor | invalid-query-parameter | malformed-request | unsupported-parameter: Bad request.
- 401 token-expired | unauthenticated: Unauthenticated.
- 404 endpoint-not-found | resource-not-found: Not found.
- 406 not-acceptable: Not acceptable.
- 429 rate-limit-exceeded: Too many requests.
- 500 internal-error: Server error.
- 503 tenant-unavailable: Service unavailable.

## Example
```bash
curl -X POST \
  'https://<your-url>/api/v2/work-items' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <key>' \
  -d '{
    "project_id": 1,
    "title": "Portal login rejects SSO users",
    "priority": "high",
    "estimation": 240,
    "label_ids": [
      1
    ]
  }'
```
