# WorkItem

A work item: the unit of work a project's backlog and board are made of, addressed by numeric id or by its human-readable issue key (ACM-123) on the same path. It carries a title, a rich-text description, an estimate, a priority and a position in its project's workflow, and it is the parent of comments, checklist subtasks, attachments, links to other work items and its own child work items. Moving it to another workflow status is not a PATCH: the move is validated against the project workflow, so it lives at POST `work-items/{work_item}/actions/transition`.

## Used by
- GET /api/v2/work-items: List work items (WorkItemList)
- POST /api/v2/work-items: Create a work item (WorkItem)
- GET /api/v2/work-items/{work_item}: Read a work item (WorkItem)
- PATCH /api/v2/work-items/{work_item}: Update a work item (WorkItem)
- POST /api/v2/work-items/{work_item}/actions/move: Move a work item to a position (WorkItem)
- POST /api/v2/work-items/{work_item}/actions/transition: Transition a work item (WorkItem)

## WorkItem fields
- 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.

Only on `?view=detail`: description, location_name, location_address, location_latitude, location_longitude

## Variants

### WorkItemList

What a collection returns, and what `?view=summary` returns on a single record.

39 fields.
Omits: description, location_name, location_address, location_latitude, location_longitude

### WorkItemSummary

The embedded form: what you get when another resource carries this one and you did not `include` it.

3 fields.
Omits: description, priority, due_date, estimation, remaining_estimate, location_name, location_address, location_latitude, location_longitude, order, column_order, version, created_at, updated_at, project_id, task_id, workflow_status_id, work_type_id, sprint_id, parent_id, assignee_id, reporter_id, web_url, workflow_status, parent, assignee, reporter, work_type, sprint, labels, drive_items, comments, subtasks, comments_count, subtasks_count, attachments_count, child_work_items_count, labels_count, drive_items_count, links_count, links

### WorkItemLinks

The `links` object on this resource.

Keys: self, comments, subtasks, links, attachments, child_work_items, labels, drive_items

## Example
```json
{
  "id": 1,
  "issue_key": "ACM-123",
  "title": "Portal login rejects SSO users",
  "description": "<p>…</p>",
  "priority": "high",
  "due_date": "2026-03-04",
  "estimation": 240,
  "remaining_estimate": 1,
  "location_name": "…",
  "location_address": "…",
  "location_latitude": "45.00",
  "location_longitude": "90.00",
  "order": 1,
  "column_order": 1,
  "version": 1,
  "created_at": "2026-03-04T09:15:00Z",
  "updated_at": "2026-03-04T09:15:00Z",
  "project_id": 1,
  "task_id": 1,
  "workflow_status_id": 1,
  "work_type_id": 1,
  "sprint_id": 1,
  "parent_id": 1,
  "assignee_id": 1,
  "reporter_id": 1,
  "web_url": "…",
  "workflow_status": {
    "id": 1
  },
  "parent": null,
  "assignee": null,
  "reporter": {
    "id": 1
  },
  "work_type": {
    "id": 1
  },
  "sprint": null,
  "labels": {
    "data": [
      {
        "…": "3 fields of LabelSummary"
      }
    ],
    "meta": {
      "truncated": false,
      "…": "1 optional field left out"
    }
  },
  "drive_items": {
    "data": [
      {
        "…": "5 fields of DriveItemSummary"
      }
    ],
    "meta": {
      "truncated": false,
      "…": "1 optional field left out"
    }
  },
  "comments": {
    "data": [
      {
        "…": "5 fields of CommentSummary"
      }
    ],
    "meta": {
      "truncated": false,
      "…": "1 optional field left out"
    }
  },
  "subtasks": {
    "data": [
      {
        "…": "3 fields of SubtaskSummary"
      }
    ],
    "meta": {
      "truncated": false,
      "…": "1 optional field left out"
    }
  },
  "comments_count": 1,
  "subtasks_count": 1,
  "attachments_count": 1,
  "child_work_items_count": 1,
  "…": "4 more fields"
}
```
