List labels
Return the labels defined in a project. Use it to build a label picker, to resolve label names to ids before writing label_ids on a work item, or to look up a label you imported by its external_id.
Results are paginated. In most workspaces, the list holds only this project's labels. In a workspace with governance turned on, it returns the workspace's catalog labels, plus any labels the project still holds from before governance was turned on.
Path parameters
slug:requiredstringThe workspace slug. It appears in your Plane URLs. In https://app.plane.so/my-team/projects/, the slug is my-team.
project_id:requiredstring (uuid)The project whose labels you want.
Query parameters
Filters
name:optionalstringReturn labels whose name matches this value, compared case-insensitively. Use it to resolve a label name to the id you send in label_ids.
parent_id:optionalstring (uuid)Return only the labels nested under this label.
For the opposite view, send parent_id__isnull=true to get every top-level label in the project. Use parent_id__isnull=false for every label that has a parent.
external_id:optionalstringReturn labels whose external_id matches. Combine with external_source to resolve a record from another system to its Plane label without keeping a local id map.
external_source:optionalstringReturn labels that came from this system, for example github or jira.
Search
search:optionalstringA search term matched against the label name. Use it for type-ahead in a picker instead of downloading every page and filtering client-side.
Ordering
order_by:optionalstringField to sort by. Prefix with - for descending. Defaults to sort_order.
sort_order,-sort_order: the project's own label orderingcreated_at,-created_at: oldest or newest firstid,-id
A value outside this list isn't rejected. It falls back to the default sort_order without an error, so a typo in order_by shows up only as a differently sorted page.
Pagination
per_page:optionalintegerPage size. Defaults to 50, maximum 200.
offset:optionalintegerNumber of rows to skip from the start of the result set. Maximum 10000. Read the next value from the response and send it back as offset to walk forward.
paginate:optionalstringSet to cursor to switch from the default offset envelope to the keyset cursor envelope, which skips the COUNT(*) and returns next_cursor and has_more instead of next and total_count. Send the returned next_cursor back as cursor to fetch the following page.
Only created_at and id are cursor-eligible, because a keyset needs a strictly ordered column and sort_order is neither unique nor monotonic. Pair paginate=cursor with order_by=created_at or order_by=id. With the default sort_order ordering, the request returns 400 ordering_not_cursor_eligible.
Most projects have few enough labels that the default offset envelope is all you need.
count:optionalbooleanDefaults to true. Set to false to omit total_count and skip the count query.
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 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: color, created_at, created_by_id, description, external_id, external_source, id, name, parent_id, sort_order, updated_at, updated_by_id.
See Sparse fields.
Scopes
projects.labels:read
Errors
| Status | Code | Cause |
|---|---|---|
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. |
403 | forbidden | Your role or token scope can't read labels. |
404 | not_found | No such workspace or project, or it's outside your tenant. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
429 | rate_limited | Throttled. Honor the Retry-After header before retrying. |
curl -X GET \
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/labels/?per_page=50&order_by=sort_order" \
-H "X-Api-Key: $PLANE_API_KEY"import requests
response = requests.get(
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/labels/",
headers={"X-Api-Key": "your-api-key"},
params={"per_page": 50, "order_by": "sort_order"},
)
print(response.json())const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/labels/?per_page=50&order_by=sort_order",
{
method: "GET",
headers: {
"X-Api-Key": "your-api-key",
},
},
);
const data = await response.json();{
"data": [
{
"id": "2b7d5e94-3c1a-4f60-9a8d-7e1c4b0f2d35",
"name": "Area",
"description": "Which part of the product",
"color": "#6b7280",
"sort_order": 32767,
"parent_id": null,
"external_id": null,
"external_source": null,
"created_at": "2026-01-09T11:04:18.220914Z",
"updated_at": "2026-01-09T11:04:18.220914Z",
"created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
"updated_by_id": null
},
{
"id": "9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88",
"name": "Regression",
"description": "Worked before the last release",
"color": "#e5484d",
"sort_order": 65535,
"parent_id": "2b7d5e94-3c1a-4f60-9a8d-7e1c4b0f2d35",
"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
}
],
"next": null,
"previous": null,
"total_count": 2,
"pagination": {
"style": "offset"
}
}{
"type": "not_found",
"code": "not_found",
"detail": "No project matches the given query."
}
