Some changes are more than a field update: starting a sprint, moving a work item through its workflow, restoring a deleted record. The API calls these actions. Each one is a POST to /{resource}/{id}/actions/{verb}, and each returns the record as it is afterwards.
In the examples, <your-url> is the address you sign in to plnnrly on, such as acme.plnnrly.com, or your organisation's own domain.
The actions
- POSTRestore a deleted customer subscription
/customer-subscriptions/{customer_subscription} /actions /restore - POSTRestore a deleted customer
/customers/{customer} /actions /restore - POSTRestore an archived drive item
/drive-items/{drive_item} /actions /restore - POSTRestore a deleted event
/events/{event} /actions /restore - POSTRestore a deleted project
/projects/{project} /actions /restore - POSTRestore a deleted sales invoice
/sales-invoices/{sales_invoice} /actions /restore - POSTComplete a sprint
/sprints/{sprint} /actions /complete - POSTStart a sprint
/sprints/{sprint} /actions /start - POSTRestore a deleted subscription product
/subscription-products/{subscription_product} /actions /restore - POSTRestore a deleted subscription type
/subscription-types/{subscription_type} /actions /restore - POSTMove a work item to a position
/work-items/{work_item} /actions /move - POSTTransition a work item
/work-items/{work_item} /actions /transition
Some actions take a JSON body and some take none. Each operation page gives the body in full.
Which actions you may call
Add meta=actions to the single read of a record:
curl -G "https://<your-url>/api/v2/work-items/ACM-12" \
-H "Authorization: Bearer <token>" \
-H "Accept: application/json" \
--data-urlencode "meta=actions"The meta member of the response lists the verbs you may call on that record, in alphabetical order:
{
"actions": ["move", "transition"]
}
- The list reflects your token's scope, your role's permissions and your access to the record. When you may call none,
meta.actionsis left out. - It answers "may I?", not "will it succeed?". It does not know the body you will send or every rule about the record's state. A listed
transitioncan still be refused for an unreachable status, and a sprint lists bothstartandcompletewhether or not it is running. meta=actionsworks on the single read of every resource that has actions. Anywhere else it is400unsupported-parameter.
Make an action safe to retry
- Send
If-Matchwith the tag you last read. When the record changed since, the answer is412and nothing happens. See Concurrency and ETags. Idempotency-Keyis optional, but it is what makes a retry after a lost response safe. Without one, the retry can fail although the first attempt worked: with the sameIf-Matchit can answer412, because that attempt changed the record, and starting or completing a sprint answers409. With a key, the retry returns the first200. See Idempotency.
Outcomes
| You get | When |
|---|---|
200 | The action ran. The body is the record in the detail view, and the ETag header is its new tag. |
200, nothing changed | A move to where the item already is. Nothing is written and its version stays the same, so a retry is safe. |
200 with an already_in_target_state warning | You restored a record that was not deleted. Only restore actions send this warning. |
409 state-conflict | The record's state does not allow the action now. conflict.reason says why. |
412 precondition-failed | The record changed since the tag you sent in If-Match. |
422 | The body is wrong, for example a sprint from another project. errors[].pointer names the field. |
A transition to the status the item already has also answers 200 and leaves the status as it is. It still raises version by one, so any tag you hold for the item goes stale.
These are the reasons an action gives in conflict.reason:
| Action | conflict.reason |
|---|---|
Work item transition and move | illegal_transition, with from_workflow_status_id and to_workflow_status_id |
Sprint start | already_started, already_completed |
Sprint complete | already_completed, not_started |
A refused transition looks like this:
{
"type": "https://developers.plnnrly.com/api/v2/problems/state-conflict",
"title": "State conflict",
"status": 409,
"detail": "The target status is not reachable from this work item's current status.",
"instance": "/api/v2/work-items/ACM-12/actions/transition",
"request_id": "01M4ZDC3F2P8V6YQ1RJ4M7XA0S",
"conflict": {
"reason": "illegal_transition",
"from_workflow_status_id": 3,
"to_workflow_status_id": 9
}
}
Work items: transition or move
transition changes the status and nothing else:
curl -X POST "https://<your-url>/api/v2/work-items/ACM-12/actions/transition" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H 'If-Match: "work-items-812-v11-8c2e"' \
-d '{
"workflow_status_id": 9
}'The target must be reachable from the current status: the project's workflow needs a transition from the one to the other. To see which statuses are reachable, list GET/workflow-transitions with filter[from_status_id] set to the item's current workflow_status_id.
move does what a drag on the board or in the backlog does. It places the item in a list, and can change its status and its sprint in the same call:
| Field | Required | What it does |
|---|---|---|
list | yes | board: the column of the item's status, written to column_order. backlog: the item's sprint, or the backlog when it is in no sprint, written to order. |
workflow_status_id | no | The new status, checked the same way as transition. Leave it out to keep the status. |
sprint_id | no | A sprint on the same project that is not completed, or null for the backlog. Leave it out to keep the sprint. On a project that works in sprints, an item needs a task_id before it can join the running sprint. |
after_work_item_id | no | The item to land directly below, by id or issue key. null puts the item at the top. Left out, the item keeps its place, or goes to the end of a list it is new to. |
curl -X POST "https://<your-url>/api/v2/work-items/ACM-12/actions/move" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"list": "board",
"workflow_status_id": 9,
"after_work_item_id": "ACM-7"
}'- The item named in
after_work_item_idmust be in the target list. Otherwise the move is422at/after_work_item_id. - On a project that works in sprints, the board shows the running sprint only. So
list: boardneeds the item to end up in that sprint; otherwise the move is422at/list. - A move that changes something raises the item's
versionby one. Other items in the list keep theirversion, even when they shift.
Use transition for a status change on its own. Use move for anything that changes where the item sits.
Sprints: start and complete
start takes no body. It sets is_active to true and stamps started_at. A project runs one sprint at a time: starting a second one is 409 with already_started, and conflict.sprint_id names the sprint that is running. Complete that one first.
complete needs unresolved_work_items, which decides where the work items that are not resolved go:
| Value | The unresolved work items |
|---|---|
move_to_next_sprint | Move to the sprint in next_sprint_id: another sprint on the same project that is not completed. |
move_to_backlog | Go back to the project backlog. |
leave_on_sprint | Stay on the completed sprint. |
curl -X POST "https://<your-url>/api/v2/sprints/7/actions/complete" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6b0e2f1d-93a4-4c57-8e1b-2d7f4a9c0e35" \
-d '{
"unresolved_work_items": "move_to_next_sprint",
"next_sprint_id": 8
}'Resolved work items always stay on the completed sprint, so it keeps its history. Every work item that moves gets a new version: re-read any you hold a tag for.
Restore
Most records that a DELETE archives come back with POST …/actions/restore. See Deleting and restoring for which ones, and for what a restore does not bring back.
Over MCP
AI clients call an action with the api_write tool, as a POST to the action's path, such as work-items/ACM-12/actions/transition. The tool takes no headers, so neither If-Match nor Idempotency-Key applies there. The other outcomes on this page are the same.
Related
- Move work items through the workflow: a worked example.
- Deleting and restoring: what restore brings back.
- Concurrency and ETags:
If-Matchin full. - Errors and retries:
state-conflictand the other problem types.