Filing, reading and changing work items
This page lists the board's routes for filing, reading and changing a work item. The routes that decide a gate, pause, resume, skip, escalate, end and retry an item are on Gates, run control, ending and retry. The shapes the routes answer with are on The work item object.
Every route under /api/ except the stable routes, and the /api/ws/events WebSocket, exists for the board and can change in any release. For those, prefer the kraft command with --json, which is stable.
| Route | What it does |
|---|---|
POST /api/work-items | Files a work item, paused unless the body starts it. |
POST /api/work-items/{id}/duplicate | Files a fresh paused item from an existing one. |
GET /api/work-items | Lists the items on the board. |
GET /api/work-items/{id} | Answers one item in full. |
PATCH /api/work-items/{id} | Changes an item's title, description, chain, overrides, budget, policy or attachments. |
POST /api/work-items/bulk | Pauses, cancels, archives or restores several items. |
Routes that read an item's budget, events, diff and documents are on Budget, events, diff and documents.
Filing a work item
POST /api/work-items
Files a work item. The item is paused unless the body sets autostart: true, like kraft item create without --autostart. POST /api/work-items?dry_run=1 runs the same checks and files nothing: see Dry run.
| Field | Type | Default | Meaning |
|---|---|---|---|
title | string | required | One line of plain text, up to 500 characters, with no tab, escape or other control character. |
description | string | "" | The brief: prose the spec node writes a design from. |
repo | string | required | The absolute path of a connected repo. |
chain | string | the repo's default_chain | The chain to run. The pre-2.0 name chain_template is still read, by PATCH too. |
workspace, members, root_pointer_policy | string, list, string | null, [], null | A workspace item: the workspace repo is the root of, the members it selects, and ignore or bump. An unset policy is the workspace's root_pointer_default. |
base_branch | string | null | The branch the work starts from and the merge request targets: the repo's default branch when null. Refused unless origin has it. |
attachments | list | [] | Existing documents, {kind, path} with kind spec or plan and path inside the repo. At most one per kind. They trim the gates they satisfy. |
cwd | string | null | The caller's working directory, sent only with attachment paths. A path is also read from another working tree of the same repo there. |
autostart | boolean | false | Start the item now instead of filing it paused. |
auto_gate | boolean | true | Arms agent review for the item's auto_escalate gates. |
implements_beads | list | [] | Bead ids the item implements, closed when it completes. |
skip_nodes | list | [] | Node ids to drop from the chain. An id that is no node of the chain is refused. |
budget_usd | number or null | the policy default | The item's own dollar cap. Omitted uses the policy default, null means no cap. |
node_overrides | object | {} | {node_id: {field: value}}, as PATCH takes it. |
policy | object | null | The item's own policy override. |
A POST or PATCH body naming two different chains under chain and chain_template is refused with 422, its detail one sentence naming both. The same chain twice is fine.
The answer is 201 with {"id": "...", "status": "paused"} for an item filed paused, and also when autostart: true found every slot busy. These keys appear only when they apply:
| Key | When |
|---|---|
slots | autostart: true was sent but every slot (max_concurrent) was busy, so the item was filed paused: {"busy": 3, "limit": 3}. |
repo_warning | The repo declares no setup command, or no test command nor test scopes. It says where the item will stop. |
duplicate_warning | Another open item has the same title or implements a bead the body names. |
bead_warning | Why no bead was filed for the item. |
An item that starts at once answers 201 with bead_id, chain_definition, materialized_chain and current_node_id beside id and status.
| Status | When |
|---|---|
503 | policy.yaml does not load. It is checked first, before the 403. Kraft refuses new work until the file loads. |
403 | autostart: true came on a request that carries X-Kraft-Session-Id (a worker session) or X-Kraft-Client: mcp (the MCP server). An agent files work paused, and you start it. |
503 | The library or a chain does not load. |
422 | A field is refused: a relative or unconnected repo, an unknown chain, a title or description that fails its check, a missing attachment, an unknown skip_nodes id, an unknown base_branch, or an override the chain or policy refuses. |
502 | Filing the item failed after the checks passed. |
The server cannot tell a coding agent that runs kraft item create --autostart in its own shell from a person, so that call starts the item.
Every repo path you send must be absolute: the server would read a relative one against its own directory, not yours. POST /api/work-items, POST /api/repos and POST /api/triggers refuse a relative path with 422, and PATCH or DELETE /api/repos?path= answer 404.
POST /api/work-items/{id}/duplicate
Files a fresh, paused item from the item {id}: its title, description, repo, chain, workspace selection and attachments, snapshotted again under the new item's own id. Any source status is accepted, archived and cancelled included. No run state, override, policy override, budget or bead link carries over. This is a fresh intake, not a clone of the row. Agent review of the item's auto_escalate gates is armed, as for an ordinary create, whatever the source had. The body is empty.
{"id": "...", "status": "paused", "duplicate_warning": "..."}
duplicate_warning is present only when another open item looks like a duplicate, the same check POST /api/work-items makes.
| Status | When |
|---|---|
404 | There is no such item. |
409 | The source's stored attachment copy has gone missing, as it does when the source is abandoned. The answer names it, before anything is filed. |
422 | The chain or workspace the source used is gone, or its title is empty. |
502 | Filing the item failed after the checks passed. |
503 | policy.yaml, the library or a chain does not load. |
Reading work items
GET /api/work-items
Answers the board: {"items": [...], "cursor": N}. cursor is the highest event seq at the time of the read. Each row is the list row: a short subset of the item's fields, with display_status, stop (without facts, task and attempt), step, mr_ref and progress (without tasks) in place of the detail's per-row lookups. It carries no agent_overrides, node_overrides or policy_override.
| Query | Meaning |
|---|---|
archived=true | Only archived items. Without it, archived items are left out. An archived item that was cancelled or abandoned also needs include_abandoned=true. |
include_abandoned=true | Include cancelled and abandoned items. Without it they are left out, archived or not. |
GET /api/work-items/{id}
Answers one item as the detail object, and 404 for an id that names no item.
Changing a work item
PATCH /api/work-items/{id}
Changes the fields the body names and leaves the rest. A body naming none is refused with 422. The write is one transaction, so a body that changes several fields never lands half.
| Field | Type | Meaning |
|---|---|---|
title | string | The new title. It cannot be empty and is one line. |
description | string | The new brief. "" clears it. |
chain | string | Switches a not-yet-started item onto that chain. |
agent_overrides | object | Replaces the item's whole model and effort override. See below. |
node_overrides | object | Merges per node. See below. |
budget_usd | number or null | The item's own dollar cap. null sets no cap, and leaving the field out leaves the cap alone. |
policy | object | Replaces the item's policy override. {} clears it. A change applies from the next node the item enters and the next time it observes a wait. |
attachments | object | {"spec": PATH} snapshots that kind again, {"spec": null} drops it and puts back the gate it trimmed. A kind not named keeps its copy. |
cwd | string | The caller's working directory, as at create. |
agent_overrides replaces the override as a whole. A field you leave out, or send as null, is dropped, and {} clears the override.
node_overrides merges per node. {"plan": {"effort": "high"}} sets that field and keeps the node's others, {"plan": {"effort": null}} drops one field, and {"plan": {}} drops the node's overrides. {} resets every node.
The answer is {"id": "...", ...} with each field you sent. A chain you sent is echoed as chain_template. agent_overrides and node_overrides are echoed as stored: {} once an override is cleared, and for node_overrides only the nodes you named.
| Status | When |
|---|---|
403 | A worker session sent attachments, an override, a policy or a budget_usd for its own item. An escalation turn is refused policy and budget_usd too. |
404 | There is no such item, or chain names a chain that does not exist. |
409 | The item has ended. |
409 | chain or attachments was sent for an item that has started: its chain and documents are fixed for its life. Another write changed them meanwhile. |
409 | node_overrides names a node that has started, or is {} for an item that has. |
422 | The body names no field to change. |
422 | A field is refused, and detail says which: an unknown node id or field, a model or effort the harness refuses, a limit above the policy's maxima, or a path that is not a file in the repo. |
422 | auto_escalate: true on a gate that declares no auto_review. |
A started node accepts one exception. On an item stopped at that node because its stored model or escalate_model is not a model id, clearing that node ({"<node>": {}}) and changing model, escalate_model or effort alone are accepted, so the stop can be repaired.
POST /api/work-items/bulk
Applies one action to several items, each through the single-item route, in ids order, each in its own write.
{"action": "pause", "ids": ["...", "..."], "reason": "..."}
| Field | Type | Meaning |
|---|---|---|
action | string | pause, cancel, archive or restore. |
ids | list | From 1 to 200 item ids. |
reason | string | Required when action is cancel, and checked before anything is touched (422 otherwise). The other actions ignore it. |
The answer is one result per id, in order:
{"results": [{"id": "...", "ok": true, "status": "paused"}, {"id": "...", "ok": false, "error": "..."}]}
An id whose action raises, an item in the wrong status for example, becomes {id, ok: false, error, status}, and the rest of the batch still runs. An id that names no item answers {id, ok: false, error: "not found"} with no status. status is the item's stored status after the action ran.
| Status | When |
|---|---|
422 | action is none of the four, ids holds fewer than 1 or more than 200 ids, or action is cancel and reason is blank. Nothing is touched. |
Related
- Gates, run control, ending and retry: the routes that decide, pause, resume, skip, escalate, end and retry an item.
- The work item object: the stored row, the list row and the detail object these routes answer with.
- Item verbs: the
kraft itemcommand behind each action here, with its flags and output. - MCP tools: which of these an agent may call on its own item.
- Agents and gates: why a worker session is refused some of these, and what that check does not stop.
Overview
Which of Kraft's HTTP routes are stable, which the pages here document, where the full schema is, and who may call them.
Gates, run control, ending and retry
The board's routes for deciding a gate, pausing, resuming, skipping, escalating, ending, archiving and retrying a work item, and the routes a worker calls.