v2.0.0next
HTTP API

Budget, events, diff and documents

The board's routes for the daily spend total, raising a budget, the dry run of a create, an item's events, its diff and review, and its 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.

RouteWhat it does
GET /api/budget/todayAnswers the instance's spend since local midnight against the daily cap.
POST /api/work-items/{id}/budget/raiseRaises the dollar cap that stopped an item and retries it.
POST /api/work-items?dry_run=1Runs every check of a create and files nothing.
GET /api/work-items/{id}/eventsReads an item's events, forward or backward.
GET /api/work-items/{id}/diffAnswers the changes the agents made.
GET /api/work-items/{id}/compareDiffs any two review targets.
PUT /api/work-items/{id}/viewedMarks a file viewed.
DELETE /api/work-items/{id}/viewedClears the mark.
GET /api/work-items/{id}/fix-targetAnswers where a request-changes review would restart.
POST /api/work-items/{id}/reviewSubmits 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}/documentsLists the documents linked to an item.
GET /api/work-items/{id}/artifactReads 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.

FieldTypeMeaning
budget_usdnumber or nullThe 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.

StatusWhen
403A worker session or an escalation turn raises a cap. A spending cap is a person's call.
404There is no such item.
409The item has ended.
409The 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.
409Spend a harness never reported stopped it and the body sets a number. Only null passes that stop.
409The 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.
422The 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}}
  }
}
FieldMeaning
nodesThe chain that would be filed, in chain_definition's own node shape.
skippedEvery node the attachments or skip_nodes dropped. covered_by names the attachment kind that made it redundant, and skip is your own choice.
gatesThe ids of the gate nodes left in the chain.
caps.budget_usd, caps.budget_sourceThe 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_usdpolicy.budget.daily_usd, or null.
caps.nodesEach 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.

QueryTypeDefaultMeaning
after_seqinteger0Return events with a greater seq.
before_seqintegernonePage backward instead: return the last limit events with seq below it.
limitintegernone, or 100 with before_seqAt 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.

StatusWhen
404There is no such item.
422limit 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).

QueryTypeDefaultMeaning
ignore_whitespacebooleanfalseAs 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.

StatusWhen
404The item has no worktree on disk. See The worktree_exists field.
409A sandboxed worker can still write the worktree, so host git does not read it.
500Git could not read the worktree.

GET /api/work-items/{id}/compare

Diffs any two review targets of the pending gate.

QueryTypeDefaultMeaning
fromtargetbaseThe older side. It cannot be latest.
totargetlatestThe newer side.
nodesstringnoneComma-separated node ids. Only files those nodes touched are kept, and groups lists each node's files.
ignore_whitespacebooleanfalseAs 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.

StatusWhen
400A target is not one of the four, or from is latest.
404No review has been submitted for the gate (last_review), the gate has no such attempt, or the item has no worktree on disk.
409The item has no base commit, or a sandboxed worker can still write the worktree.
500Git 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.

StatusWhen
400file is empty, absolute, or leaves the repository. Or to is not a compare target.
404The item does not have the target to, or has no worktree on disk.
409A 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.

StatusWhen
400node is not before the pending gate. With no gate pending, it is not at or before the item's current node.
409The item has ended, or gate is not pending.
409With 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.

StatusWhen
404The item has no such attachment, or its copy is gone.
422kind 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}.

StatusWhen
404The 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.

Copyright © 2026