Budget, events, diff and documents
This page lists the board's routes for a work item's budget, the dry run of a create, its events, its diff and review, and its documents. Like the other board routes they can change in any release: see Filing, reading and changing work items for the rule, and the stable routes for the two that keep their shape. Prefer the kraft command with --json. The fields these routes read from a work item's detail are under The work item object.
| Route | What it does |
|---|---|
GET /api/budget/today | Answers the instance's spend since local midnight against the daily cap. |
POST /api/work-items/{id}/budget/raise | Raises the dollar cap that stopped an item and retries it. |
POST /api/work-items?dry_run=1 | Runs every check of a create and files nothing. |
GET /api/work-items/{id}/events | Reads an item's events, forward or backward. |
GET /api/work-items/{id}/diff | Answers the changes the agents made. |
GET /api/work-items/{id}/compare | Diffs any two review targets. |
PUT /api/work-items/{id}/viewed | Marks a file viewed. |
DELETE /api/work-items/{id}/viewed | Clears the mark. |
GET /api/work-items/{id}/fix-target | Answers where a request-changes review would restart. |
POST /api/work-items/{id}/review | Submits a review; this page covers its digest. |
GET /api/work-items/{id}/attachments/{kind} | Reads the spec or plan attached at intake. |
GET /api/work-items/{id}/documents | Lists the documents linked to an item. |
GET /api/work-items/{id}/artifact | Reads the pending gate's document. |
GET /api/work-items/{id}/artifacts/{kind} | Reads the document a task wrote. |
Budget
GET /api/budget/today
Answers the instance's spend since local midnight against policy.budget.daily_usd:
{"spent_usd": 3.4, "cap_usd": 20.0}
cap_usd is null when policy.budget.daily_usd is unset. The same two numbers are daily in a work item's budget_cap.
POST /api/work-items/{id}/budget/raise
Raises the dollar cap that stopped the item and retries the stopped node, as POST /api/work-items/{id}/retry does. kraft item raise-budget calls it. It raises the item's own cap, or the item-wide budget_usd of its policy: whichever stopped the item. Raise a cap that stopped an item says what the command accepts and refuses.
| Field | Type | Meaning |
|---|---|---|
budget_usd | number or null | The new cap. null means no cap. The field is required: leaving it out answers 422. |
The answer is /retry's with no path: {id, node_id, path, loop, steer, attempt}, or the stored row when the retry stops at once.
| Status | When |
|---|---|
403 | A worker session or an escalation turn raises a cap. A spending cap is a person's call. |
404 | There is no such item. |
409 | The item has ended. |
409 | The item is not stopped, or a different cap stopped it: a node, step or task's budget_usd, a token_budget or the daily cap. |
409 | Spend a harness never reported stopped it and the body sets a number. Only null passes that stop. |
409 | The retry could not start, with every slot busy or a walk still running. The cap is not changed. These are two of /retry's 409s. |
422 | The new cap is at or below what the item has already spent, so it would stop again at once. Or the policy's maxima refuse an item-wide cap. |
If the retry is refused after the cap was raised, the answer has the retry's own status, 409 for example, and its message says the cap was raised and the retry was not.
Dry run
POST /api/work-items?dry_run=1
Runs every check POST /api/work-items runs, up to and including validating node_overrides, then answers 200 without filing anything: no row, no bead, no attachment copy, no spawn. The body is a create's. A body that would answer 403, 422 or 503 on an ordinary create answers the same on a dry run. The 403 for an agent's autostart is checked before the 422s, so it runs here too.
{
"dry_run": true,
"nodes": [...],
"skipped": [{"node": "spec", "why": "covered_by", "kind": "spec"}, {"node": "...", "why": "skip"}],
"gates": ["spec_approval", "..."],
"caps": {
"budget_usd": 5.0,
"budget_source": "item",
"daily_usd": 20.0,
"nodes": {"implement": {"attempts": 3, "wall_clock_s": 7200}}
}
}
| Field | Meaning |
|---|---|
nodes | The chain that would be filed, in chain_definition's own node shape. |
skipped | Every node the attachments or skip_nodes dropped. covered_by names the attachment kind that made it redundant, and skip is your own choice. |
gates | The ids of the gate nodes left in the chain. |
caps.budget_usd, caps.budget_source | The create route's own cap resolution: "item" for the body's own budget_usd, else "policy". budget_usd is null when no cap applies. |
caps.daily_usd | policy.budget.daily_usd, or null. |
caps.nodes | Each fix-loop node's attempts and wall_clock_s, resolved as the walk resolves them. See below. |
A dry run skips everything after those checks: it has no slots key, no repo_warning, duplicate_warning or bead_warning, and it files no bead and copies no attachment.
caps.nodes resolves the way the walk resolves caps at run time: the node's own policy, the node_overrides entry and any item-wide override are layered on in the same order. A node's own override and an item-wide value each show where they apply.
Events
GET /api/work-items/{id}/events
Reads an item's events, oldest first. Each row is {seq, work_item_id, type, payload, node_id, created_at}: see Events.
| Query | Type | Default | Meaning |
|---|---|---|---|
after_seq | integer | 0 | Return events with a greater seq. |
before_seq | integer | none | Page backward instead: return the last limit events with seq below it. |
limit | integer | none, or 100 with before_seq | At most this many events, from 1 to 500. With after_seq alone it bounds the forward window to the first limit events after it. |
Without before_seq, the route returns every event after after_seq. With it, limit is 100 when omitted.
| Status | When |
|---|---|
404 | There is no such item. |
422 | limit is outside 1 to 500, or after_seq and before_seq are both given. |
Diff and review
GET /api/work-items/{id}/diff
Answers the changes the agents made, for a reviewer with no filesystem access. It keeps two ranges apart: the top level is HEAD to the working tree (the uncommitted part, usually empty at a gate), and landed is base_ref to HEAD (what the chain's nodes committed).
| Query | Type | Default | Meaning |
|---|---|---|---|
ignore_whitespace | boolean | false | As git diff -w. A file whose only change is whitespace drops out of files, the per-file counts and diff together, so the three never disagree. untracked does not follow it. |
The answer carries work_item_id, base_ref, files, diff, untracked, truncated, ignore_whitespace (the value it used), landed ({commits, files, diff, truncated}), diff_max_bytes and worktree_path. An item with no base_ref answers the same keys empty, with base_ref: null.
| Status | When |
|---|---|
404 | The item has no worktree on disk. See The worktree_exists field. |
409 | A sandboxed worker can still write the worktree, so host git does not read it. |
500 | Git could not read the worktree. |
GET /api/work-items/{id}/compare
Diffs any two review targets of the pending gate.
| Query | Type | Default | Meaning |
|---|---|---|---|
from | target | base | The older side. It cannot be latest. |
to | target | latest | The newer side. |
nodes | string | none | Comma-separated node ids. Only files those nodes touched are kept, and groups lists each node's files. |
ignore_whitespace | boolean | false | As on /diff. touched_by does not follow it. |
A target is base, latest (the working tree), last_review or attempt:N. The answer carries from and to (each {target, sha}), rebased, files (each with touched_by and viewed), groups, diff, untracked, truncated, ignore_whitespace and diff_max_bytes.
| Status | When |
|---|---|
400 | A target is not one of the four, or from is latest. |
404 | No review has been submitted for the gate (last_review), the gate has no such attempt, or the item has no worktree on disk. |
409 | The item has no base commit, or a sandboxed worker can still write the worktree. |
500 | Git could not diff the two targets. |
PUT /api/work-items/{id}/viewed
Marks a file viewed. ?file=<path>&to=<target> names it: file is a repository-relative path, and to is a compare target, latest by default. The answer is {file, to, viewed}, with viewed true.
A mark belongs to the file's content at to, not to the from side. A file that has not changed since you ticked it stays ticked in any other comparison, and a new attempt that edits it clears the tick.
| Status | When |
|---|---|
400 | file is empty, absolute, or leaves the repository. Or to is not a compare target. |
404 | The item does not have the target to, or has no worktree on disk. |
409 | A sandboxed worker can still write the worktree. |
DELETE /api/work-items/{id}/viewed
Clears the mark, with the same query, answer (viewed false) and refusals as PUT. It clears the mark on that content wherever it was made.
GET /api/work-items/{id}/fix-target
Answers where the work restarts if the reviewer requests changes now. It takes ?node=&gate= for a target the reviewer picked, and answers {gate, node, then, round, reason}: the shape of fix_target with a reason.
With no gate pending it gives what a gateless request_changes would target, with gate and round null. It does git work per thread, so call it on demand.
reason is one of requested (you named the node), threads on <path or node>, current node, or gate.
| Status | When |
|---|---|
400 | node is not before the pending gate. With no gate pending, it is not at or before the item's current node. |
409 | The item has ended, or gate is not pending. |
409 | With no gate pending, the item has no current node, or its changes target cannot be worked out. The answer says why. |
POST /api/work-items/{id}/review
Submits a review on whichever gate is pending: outcome is approve, request_changes or comment. Review a change covers it. POST /api/work-items/{id}/gates/{gate}/review takes the same body for a named gate, and answers 409 when that gate is not pending.
A review with outcome: approve takes an optional digest, passed to the gate approval as POST /api/work-items/{id}/gates/{gate}/approve takes it. A chain revision gate needs it: the one GET /api/work-items/{id}/artifact returned. Without it, or with a stale one, the route answers 409. Any other gate ignores it.
Documents
GET /api/work-items/{id}/attachments/{kind}
Reads the spec or plan attached at intake. kind is spec or plan. It reads the copy Kraft keeps under run/attachments/<id>/, the one the worktree gets when the item starts. Before the item starts nothing is indexed, so this is how the board and the phone open one, as do kraft view docs --attachment and the MCP tool get_attachment.
It answers {work_item_id, kind, path, title, content, truncated}, path being where the worktree puts it.
| Status | When |
|---|---|
404 | The item has no such attachment, or its copy is gone. |
422 | kind is neither spec nor plan. |
GET /api/work-items/{id}/documents
Lists the documents linked to the item, newest first, as {work_item_id, documents}. An attachment that is not indexed yet is listed as attachment:<id>:<kind>, which GET /api/documents/{id} reads. It comes from the copy under run/attachments/<id>/ while the item has no worktree directory, before it starts and after the worktree is removed.
GET /api/work-items/{id}/artifact
Reads the pending gate's document from the item's worktree, as it is on disk now. It answers {work_item_id, path, absolute_path, title, content, truncated, artifact_max_bytes}. A chain revision is rendered as its gate shows it, and the answer adds digest, which an approval sends back. kraft view artifact calls it.
It answers 404 when the gate has no artifact or the file cannot be read.
GET /api/work-items/{id}/artifacts/{kind}
Reads the document of kind a task wrote (its produces), whether or not a gate is pending on it: what the task's Output opens. kind must be one the item's chain names, as a task's produces or a gate's artifact, else 404.
It reads the item's worktree as GET /api/work-items/{id}/artifact does, with a chain revision rendered as its gate shows it, and answers {kind, work_item_id, path, absolute_path, title, content, truncated}.
| Status | When |
|---|---|
404 | The file there is refused (a symlink, an unreadable file): the answer gives the reason, and the item's timeline records artifact_refused. Or the worktree has no file and Kraft has no copy. |
With no file in the worktree it reads the copy the gate's approval indexed. That answer has the same fields with origin in place of absolute_path.
Related
- Filing, reading and changing work items and Gates, run control, ending and retry: the routes that file, change, decide and end a work item.
- The work item object: the fields these routes read from an item's detail.
- Events: every event type, and the WebSocket that streams them.
- Reviewing a change: the review these routes carry, from the board.
The work item object
The three shapes a work item answers in, its stored columns, and the fields that need an explanation, from its status and stop to its budget cap and plan progress.
Repos, chains, harnesses and the server
The board's routes for connecting a repo, reading chain templates and harnesses, applying saved changes, checking for updates and opening editors.