Skip to content

Get a work item by identifier ​

GET/api/v2/workspaces/{slug}/work-items/{identifier}/

Retrieve a work item anywhere in the workspace by its human key, such as PROJ-142.

Use this route when you 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. Without this route, the caller would need three requests: list projects, find the one whose identifier is PROJ, and then call the project-scoped route. This route answers in one request against the workspace.

The route is read-only. Creates, updates, deletes, and archiving all go through the project-scoped routes, which take the UUID or the identifier as {pk}. The one exception under this prefix is the subscriber list, which you can update by identifier.

Path parameters ​

slug:requiredstring

The workspace slug. It appears in your Plane URLs. In https://app.plane.so/my-team/projects/, the slug is my-team.

identifier:requiredstring

The work item's human key: the project identifier, a hyphen, and the work item's number, for example 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, so the key always points to one work item.

Query parameters ​

expand:optionalstring

Comma-separated relations to embed alongside the ids: assignees (the assigned users), cycle (the cycle it belongs to), estimate_point (its estimate point), 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).

Expanded objects get their own 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 returns 400.

?fields= and ?expand= take separate sets of names. Relation names aren't valid in ?fields=, field names aren't valid in ?expand=, and field filtering never removes an expanded object: ?fields=id,name&expand=state returns id, name and state. See Expanding relations.

Any key that can't be resolved returns 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. This is deliberate. The response never reveals whether a work item exists, only whether you can read it. This route doesn't return archived work items either.

Response shaping ​

fields:optionalstring

Comma-separated list of fields to return. Fields you don't request are left out of the response instead of coming back as null, so a missing key means you didn't ask for it and null means the value is null. id is always included, whether or not you name it.

Pass all to get every requestable field. An unknown field name returns 400 with the list of valid names and the closest match, so a typo is caught instead of being silently ignored.

Requestable here: archived_at, assignee_ids, completed_at, created_at, created_by_id, custom_fields, cycle_id, description_html, estimate_point_id, external_id, external_source, id, identifier, is_draft, label_ids, module_ids, name, parent_id, priority, project_id, sequence_id, sort_order, start_date, state_id, target_date, type_id, updated_at, updated_by_id.

See Sparse fields.

Scopes ​

projects.work_items:read

Errors ​

StatusCodeCause
401unauthorizedMissing or invalid credentials.
402payment_requiredThe feature this endpoint belongs to isn't enabled on your plan, or is switched off.
403forbiddenYour token scope can't read work items.
404not_foundMalformed identifier, no such work item, it's outside your tenant or visibility, or archived.
406not_acceptableThe Accept header asks for a representation the API can't produce.
429rate_limitedThrottled. Honor the Retry-After header before retrying.
Get a work item by identifier
bash
curl -X GET \
  "https://api.plane.so/api/v2/workspaces/my-team/work-items/PROJ-142/" \
  -H "X-Api-Key: $PLANE_API_KEY"
Response200
json
{
  "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13",
  "name": "Fix login redirect loop",
  "description_html": "<p>Signing in after a session expires sends the user back to the login page in a loop.</p>",
  "identifier": "PROJ-142",
  "sequence_id": 142,
  "priority": "high",
  "project_id": "4af68566-94a4-4eb3-94aa-50dc9427067b",
  "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"],
  "cycle_id": null,
  "module_ids": [],
  "parent_id": null,
  "estimate_point_id": null,
  "start_date": "2026-01-12",
  "target_date": "2026-01-20",
  "is_draft": false,
  "sort_order": 85535.0,
  "completed_at": null,
  "archived_at": null,
  "external_id": null,
  "external_source": null,
  "created_at": "2026-01-14T09:22:41.478363Z",
  "updated_at": "2026-01-14T09:22:41.478363Z",
  "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
  "updated_by_id": null,
  "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": {}
      }
    }
  }
}
Response404
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 has everything a follow-up write needs: project_id and id for the project-scoped detail route.

bash
# 1. resolve the human key
GET /api/v2/workspaces/my-team/work-items/PROJ-142/

# 2. write against the project-scoped route, using project_id and id from step 1
PATCH /api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13/

Identifier vs UUID ​

identifierid
ExamplePROJ-1428f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13
ScopeUnique within a workspaceGlobally unique
RouteThis page (reads only), subscribers, and {pk} on the project-scoped routesGet a work item and every write
Comes fromThe project identifier plus sequence_idAssigned by Plane at creation

Both are stable. Use the identifier where a human is in the loop, and the UUID everywhere else.