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. Use it to render a work item detail view or to mirror a thread into another system.
The list is flat. Replies come back as rows of their own, with parent_id set to the top-level comment they answer. Group rows by parent_id to rebuild the threads. Hidden comments are listed too, with hidden_at set.
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.
external_source:optionalstring
Return comments that came from this system, for example github or zendesk.
Search
search:optionalstring
Match comments against their plain-text body (comment_stripped), so HTML markup in comment_html never affects whether a term hits.
Ordering
order_by:optionalstring
Field to sort by. Prefix with - for descending. Send it explicitly whenever order matters. The server's default order is not documented, so a thread you render should not depend on it.
created_at: oldest first
-created_at: newest first
id
-id
Pagination
per_page:optionalinteger
Page size. Defaults to 50, maximum 200.
offset:optionalinteger
Number of rows to skip from the start of the result set. Maximum 10000. For deeper traversal, switch to cursor pagination.
paginate:optionalstring
Set to cursor to switch to the keyset envelope (no COUNT query) 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 for the full envelope.
count:optionalboolean
Defaults to true. Set to false to skip the COUNT(*) and omit total_count from the offset envelope.
access is validated, order_by is not
access is checked against its allowed values. An unrecognized value returns 400 invalid_request instead of a silently empty list. order_by is not checked: an unrecognized value falls back to the default ordering, so a typo shows up as an unexpected sort order instead of an error. Check your spelling there.
Comma-separated list of fields to return on each row. 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.
Comma-separated relations to embed alongside the ids: actor (the comment author).
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.
List 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. Use it to render a work item detail view or to mirror a thread into another system.
The list is flat. Replies come back as rows of their own, with
parent_idset to the top-level comment they answer. Group rows byparent_idto rebuild the threads. Hidden comments are listed too, withhidden_atset.Path parameters
slug:requiredstringThe workspace slug. It appears in your Plane URLs. In
https://app.plane.so/my-team/projects/, the slug ismy-team.project_id:requiredstring (uuid)The project the work item belongs to.
work_item_id:requiredstring (uuid)The work item whose comments you want. Comments never span work items, so this narrows the result set by itself.
Query parameters
Filters
access:optionalstringReturn only comments with this visibility.
INTERNAL: visible to the project teamEXTERNAL: marked as visible outside the teamexternal_id:optionalstringReturn comments carrying this identifier from your system.
external_idis not a unique key, so this can match more than one comment. Pair it withexternal_sourceand handle a multi-row result.external_source:optionalstringReturn comments that came from this system, for example
githuborzendesk.Search
search:optionalstringMatch comments against their plain-text body (
comment_stripped), so HTML markup incomment_htmlnever affects whether a term hits.Ordering
order_by:optionalstringField to sort by. Prefix with
-for descending. Send it explicitly whenever order matters. The server's default order is not documented, so a thread you render should not depend on it.created_at: oldest first-created_at: newest firstid-idPagination
per_page:optionalintegerPage size. Defaults to 50, maximum 200.
offset:optionalintegerNumber of rows to skip from the start of the result set. Maximum 10000. For deeper traversal, switch to cursor pagination.
paginate:optionalstringSet to
cursorto switch to the keyset envelope (noCOUNTquery) instead of the default offset envelope. The response then carriesnext_cursorandhas_more; send the value ofnext_cursorback as?cursor=to fetch the next page. See Pagination for the full envelope.count:optionalbooleanDefaults to
true. Set tofalseto skip theCOUNT(*)and omittotal_countfrom the offset envelope.accessis validated,order_byis notaccessis checked against its allowed values. An unrecognized value returns400 invalid_requestinstead of a silently empty list.order_byis not checked: an unrecognized value falls back to the default ordering, so a typo shows up as an unexpected sort order instead of an error. Check your spelling there.Response shaping
fields:optionalstringComma-separated list of fields to return on each row. 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 andnullmeans the value is null.idis always included, whether or not you name it.Pass
allto get every requestable field. An unknown field name returns400with the list of valid names and the closest match, so a typo is caught instead of being silently ignored.Requestable here:
access,actor_id,comment_html,comment_stripped,created_at,created_by_id,edited_at,external_id,external_source,hidden_at,hidden_by_id,hidden_reason,id,parent_id,updated_at,updated_by_id,work_item_id.See Sparse fields.
expand:optionalstringComma-separated relations to embed alongside the ids:
actor(the comment author).Expanded objects get their own key:
?expand=statekeepsstate_idand adds astateobject next to it, so an id is never replaced by an object. An unknown value returns400.?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=statereturnsid,nameandstate. See Expanding relations.Scopes
projects.work_items.comments:readErrors
401unauthorized402payment_required403forbidden404not_found406not_acceptableAcceptheader asks for a representation the API can't produce.429rate_limitedRetry-Afterheader before retrying.