# GET /api/v2/work-items

List work items

- MCP id: work-items.index
- Scope: api:read:project_management
- Permission: project_management::view_backlog
- Rate cost: 1 unit, read bucket. `view=detail` makes it 3. Adds: `with_total=1` +2, each include +1 (to-many +3), `q` +2.
- Cache-Control: private, no-store

Returns the work items the caller may read, in each sprint's display order with the id as the tiebreaker.

The list covers the whole organisation and interleaves projects, because `order` is unique within a sprint only; narrow it with `filter[project_id]`, and add `filter[sprint_id][null]=true` for a project's backlog or `filter[resolved]=false` for its open work. Pagination is cursor-only: a backlog drag rewrites `order` on the rows behind you, so an offset page would skip and repeat items. `meta.page.total` is `null` unless you pass `with_total=1`, which costs two rate units. Listing additionally requires `project_management::manage_work_items`. Defaults to the summary view, which omits `description`; pass `view=detail` for the full payload.

## Parameters
- view (string, optional): Which field set to render.
- per_page (integer 1 to 100, optional): Rows per page. At most 25 when `include` names a to-many relation; a larger value is 400 invalid-query-parameter.
- cursor (string, optional): Opaque keyset cursor from a previous page's links.next. Never construct one by hand.
- with_total (boolean, optional): Whether meta.page.total is counted for this request.
- filter[assignee_id] (integer, optional): User the work is assigned to. Accepts `me`. Pass `[null]` for unassigned items. Operators: = in null.
- filter[created_at] (timestamp (YYYY-MM-DDThh:mm:ssZ), optional): Creation timestamp. Pair with `sort=id` for a stable full export. Operators: = gte lt.
- filter[drive_items.id] (integer, optional): Work items linked to a given drive document. The document must be one the caller may open; a document they cannot open matches nothing.
- filter[due_date] (date (YYYY-MM-DD), optional): Due date, a zone-free calendar date. Combine `[gte]` and `[lte]` for a period; pass `[null]` for items with no due date. Operators: = gte lte null.
- filter[labels.id] (integer, optional): Work items carrying a given label.
- filter[parent_id] (integer, optional): Parent work item. Pass a key for one item's children (sub-issues), or `[null]` for top-level items only. Operators: = in null.
- filter[priority] (string, optional): Relative priority. One of highest, high, normal, low, lowest; there is no medium. Operators: = ne in.
- filter[project_id] (integer, optional): Project the work item belongs to. This is how a client reproduces one project's backlog; combine it with the other filters to hit `idx_work_items_project_sprint_order` and its siblings. Operators: = in.
- filter[reporter_id] (integer, optional): User who filed the work item. Accepts `me`. Operators: = in.
- filter[resolved] (boolean, optional): Whether the item sits in a workflow status marked as a resolution. Pass `false` for open work.
- filter[sprint_id] (integer, optional): Sprint the item sits in. Pass `[null]` for the backlog, the items in no sprint. Operators: = in null.
- filter[task_id] (integer, optional): Kanban task the item is booked against. Pass `[null]` for items booked against no task. Operators: = in null.
- filter[updated_at] (timestamp (YYYY-MM-DDThh:mm:ssZ), optional): Last-write timestamp. Use for incremental sync. Operators: = gte lt.
- filter[work_type_id] (integer, optional): Work type of the item. Operators: = in.
- filter[workflow_status_id] (integer, optional): Workflow status the item currently sits in. Operators: = in.
- q (string, optional): Free-text search over `title`, `issue_key`, in that order of relevance. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.
- sort (string, optional): Up to three keys, comma-separated, each optionally prefixed with `-` for descending.
- include (string, optional): Comma-separated names to inline, at most five.
- 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.
- If-None-Match (header, optional): The ETag a read is conditional on. A match is answered 304 with no body.

## Response 200
- 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.
- 304 : Not modified.
- 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 -G \
  'https://<your-url>/api/v2/work-items' \
  --data-urlencode 'filter[project_id]=1' \
  --data-urlencode 'filter[sprint_id][null]=true' \
  --data-urlencode 'filter[resolved]=false' \
  --data-urlencode 'sort=order' \
  --data-urlencode 'with_total=1' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json'
```
