Cast a vote on a work item
Set your vote on a work item to an upvote or a downvote. The vote belongs to the caller. You can't cast a vote for another user.
If you have already voted on the work item, the request changes that vote in place. The response has the same id and the new vote. Sending the same value again changes nothing. The response is 200 with your vote in every case, including your first vote on the work item.
You need the workitem:react permission on the work item, or the response is 403. A work item you can't see, or an archived one, returns 404.
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:requiredstringThe project the work item belongs to. Accepts the project UUID or its identifier, for example ENG.
pk:requiredstringThe work item. Accepts its UUID or its human identifier, for example ENG-142.
Body Parameters
vote:requiredinteger1 for an upvote or -1 for a downvote. Any other value returns 400.
Response shaping
fields:optionalstringComma-separated list of fields to return. Unrequested keys are omitted from the response, not returned as null, so absent means "not requested" and null means "actually null". id always comes back whether or not you name it.
Pass all for every requestable field. An unknown name is a 400 that lists the valid set and suggests the closest match, so a typo can't silently cost you the saving.
Requestable here: actor_id, created_at, id, project_id, updated_at, vote, work_item_id.
See Sparse fields.
expand:optionalstringComma-separated relations to embed alongside the ids: actor (you, as the voter, with id, display_name, avatar_url, and email). An unknown value is a 400. See Expanding relations.
Scopes
projects.work_items:write
Errors
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | vote is missing, or isn't 1 or -1. |
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 | You can't react to this work item, or your token scope doesn't allow this. |
404 | not_found | No such work item, project, or workspace, you can't see the work item, or it's archived. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
409 | conflict | Not raised by this endpoint's own checks. A repeated cast changes the existing vote. |
413 | payload_too_large | The request body is over the size limit. |
415 | unsupported_media_type | The Content-Type isn't one this endpoint accepts. |
429 | rate_limited | Throttled. Honor the Retry-After header before retrying. |
curl -X POST \
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/8c3d1f72-4a9e-4b65-9d20-6e1f3b7a5c48/votes/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"vote": 1
}'import requests
response = requests.post(
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/8c3d1f72-4a9e-4b65-9d20-6e1f3b7a5c48/votes/",
headers={"X-Api-Key": "your-api-key"},
json={"vote": 1},
)
print(response.json())const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/8c3d1f72-4a9e-4b65-9d20-6e1f3b7a5c48/votes/",
{
method: "POST",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({ vote: 1 }),
},
);
const data = await response.json();{
"id": "d6a2f8c1-3e7b-4a95-8c04-9f1e5b2d7a63",
"work_item_id": "8c3d1f72-4a9e-4b65-9d20-6e1f3b7a5c48",
"project_id": "4af68566-94a4-4eb3-94aa-50dc9427067b",
"actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
"vote": 1,
"created_at": "2026-01-14T09:22:41.478363Z",
"updated_at": "2026-01-14T09:22:41.478363Z"
}{
"type": "invalid_request",
"code": "invalid_request",
"detail": "One or more fields failed validation.",
"errors": [
{
"field": "vote",
"code": "invalid_choice",
"message": "\"5\" is not a valid choice."
}
]
}
