Deze referentie is alleen in het Engels. Naar het Nederlandse REST API-overzicht

Diese Referenz gibt es nur auf Englisch. Zur deutschen REST-API-Übersicht

Cette référence existe uniquement en anglais. Aller à la présentation de l'API REST en français

Esta referencia solo está en inglés. Ir a la introducción de la API REST en español

Actions and state changes

Actions and state changes

How the action endpoints work, which ones you may call on a record, how to make them safe to retry, and what each outcome means.

View as Markdown

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

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:

Terminal
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:

JSON
{
  "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.actions is 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 transition can still be refused for an unreachable status, and a sprint lists both start and complete whether or not it is running.
  • meta=actions works on the single read of every resource that has actions. Anywhere else it is 400 unsupported-parameter.

Make an action safe to retry

  • Send If-Match with the tag you last read. When the record changed since, the answer is 412 and nothing happens. See Concurrency and ETags.
  • Idempotency-Key is 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 same If-Match it can answer 412, because that attempt changed the record, and starting or completing a sprint answers 409. With a key, the retry returns the first 200. See Idempotency.

Outcomes

You getWhen
200The action ran. The body is the record in the detail view, and the ETag header is its new tag.
200, nothing changedA 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 warningYou restored a record that was not deleted. Only restore actions send this warning.
409 state-conflictThe record's state does not allow the action now. conflict.reason says why.
412 precondition-failedThe record changed since the tag you sent in If-Match.
422The 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:

Actionconflict.reason
Work item transition and moveillegal_transition, with from_workflow_status_id and to_workflow_status_id
Sprint startalready_started, already_completed
Sprint completealready_completed, not_started

A refused transition looks like this:

JSON
{
  "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:

Terminal
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:

FieldRequiredWhat it does
listyesboard: 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_idnoThe new status, checked the same way as transition. Leave it out to keep the status.
sprint_idnoA 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_idnoThe 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.
Terminal
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_id must be in the target list. Otherwise the move is 422 at /after_work_item_id.
  • On a project that works in sprints, the board shows the running sprint only. So list: board needs the item to end up in that sprint; otherwise the move is 422 at /list.
  • A move that changes something raises the item's version by one. Other items in the list keep their version, 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:

ValueThe unresolved work items
move_to_next_sprintMove to the sprint in next_sprint_id: another sprint on the same project that is not completed.
move_to_backlogGo back to the project backlog.
leave_on_sprintStay on the completed sprint.
Terminal
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.