Docs · write

Preview. These docs describe the interface WIRK launches with. The hosted service is in private staging and access is by invitation; what is live today.

write

write changes items and links in one atomic batch: every operation applies, or none does.

Request

POST /v2/write
Authorization: Bearer <token>

{"request_id": "w-3f9a2c41d0",
 "expect": {"5c1e7a90": 3},
 "operations": [Operation, …]}
KeyNotes
operationsRequired. 1–32, applied in order, all or nothing
request_id1–200 characters. Required on the wire; the CLI and MCP server make one when you leave it out, and print it
expectThe revision you read of each record the batch depends on; at most 64 entries
mode"apply" (default) or "propose"
reasonRequired to propose, to archive, to override a duplicate, and to complete work (the evidence). At most 2,048 characters
workspace_idOptional; default: your only wirkspace
format"text" (default) or "json"

Operations

OperationShape
item.create{"op": "item.create", "ref": "new", "data": ItemData, "allow_duplicate_of": ["ID"]} (ref and allow_duplicate_of optional)
item.edit{"op": "item.edit", "id": "ID", "patch": ItemPatch}
item.archive{"op": "item.archive", "id": "ID"}, with the write's reason
item.restore{"op": "item.restore", "id": "ID"}
link.create{"op": "link.create", "data": {"type": "…", "from": "ID", "to": "ID", …}}
link.remove{"op": "link.remove", "id": "LINK_ID"}

ItemData (create): title, body, work or context (at most one), fields, uploads (upload IDs), folder_id, is_folder.

ItemPatch (edit): title, body (replaces the body), work, context, fields, attach_uploads, detach_file_ids, folder_id.

link.create data by type:

TypeData
related_to{"type": "related_to", "from": A, "to": B}
contributes_to{"type": "contributes_to", "from": CHILD, "to": PARENT, "contribution": "…", "criteria": [{"text": "…"}]}. contribution and criteria are optional; an initiative takes contribution only
requires{"type": "requires", "from": WORK, "to": PREREQUISITE, "criterion_id": "…"} (criterion_id optional; work to work only)
cites{"type": "cites", "from": A, "to": EVIDENCE, "target_revision": 2, "selector": Selector, "relation": "supports" | "contradicts" | "background", "quotation": "…"} (quotation optional)
relies_on{"type": "relies_on", "from": A, "to": SOURCE, "target_revision": 3, "assumption": "one line"}

A selector says which part of the cited item is meant:

SelectorShape
The whole body{"type": "whole", "part": {"type": "body"}}
A whole file{"type": "whole", "part": {"type": "file", "file_id": "…"}}
A stretch of text{"type": "text", "part": {"type": "body"}, "start": 120, "end": 310}
Lines{"type": "lines", "part": {"type": "body"}, "first": 4, "last": 9}
Pages of a file{"type": "pages", "file_id": "…", "first": 2, "last": 3}
A stretch of audio or video{"type": "time", "file_id": "…", "start_ms": 61000, "end_ms": 95000}
A region of an image{"type": "region", "file_id": "…", "x": 0.1, "y": 0.2, "width": 0.5, "height": 0.3}

expect

expect maps each record to the revision you read, the rN on its card or fetch: {"5c1e7a90": 3}. Full or short IDs work. Include:

Items created in the same batch ($new) need no entry, and neither does a link you remove. Every entry is checked, even for a record no operation touches. If any has moved on, nothing is applied and the answer names the current revision. In the CLI, ID@N fills expect for you.

Completing work

Set the status to a completed value and give the evidence: text naming the tests that pass, a commit, a link or a file path, or a file attached or cited in the same write. Anyone in the wirkspace may complete work. In the CLI the evidence is --evidence; in MCP and HTTP it is the write's reason. The service stores it with who completed the work and shows it at full depth (Reason (r4): …); it never judges it.

Apply or propose

Examples

IDs are illustrative.

A progress doc linked to two items

wirk write new 'Webhook retries: progress, 1 October' --body-file progress.md --link related_to:5c1e7a90 --link related_to:8d24f6b1
{"request_id": "w-3f9a2c41d0",
 "operations": [
  {"op": "item.create", "ref": "new", "data": {"title": "Webhook retries: progress, 1 October", "body": "…"}},
  {"op": "link.create", "data": {"type": "related_to", "from": "$new", "to": "5c1e7a90"}},
  {"op": "link.create", "data": {"type": "related_to", "from": "$new", "to": "8d24f6b1"}}]}
Applied w-3f9a2c41d0 · 3 operations
  created 9e4c21f7 ($new) · doc — Webhook retries: progress, 1 October
  linked 9e4c21f7 related_to ↔ 5c1e7a90 · link 4d6a0e19
  linked 9e4c21f7 related_to ↔ 8d24f6b1 · link a5c97b32
revisions: 9e4c21f7 r1
next: query 9e4c21f7

A linked doc is the cheap way to record progress; prefer it to rewriting an item's body.

Completing work, with evidence

wirk write edit 5c1e7a90@3 status=completed --evidence 'Fixed in 4f2a9c1; tests/test_webhooks.py::test_retry_backoff passes'
{"request_id": "w-8b1d07e2c4", "expect": {"5c1e7a90": 3},
 "reason": "Fixed in 4f2a9c1; tests/test_webhooks.py::test_retry_backoff passes",
 "operations": [{"op": "item.edit", "id": "5c1e7a90", "patch": {"fields": {"status": "completed"}}}]}
Applied w-8b1d07e2c4 · 1 operation
  edited 5c1e7a90 r3 → r4 · status completed · evidence recorded
next: query 5c1e7a90

With a file as the evidence: upload it, then wirk write edit 5c1e7a90@3 status=completed --upload upload_7e8f9a0b1c2d4e5f8a9b0c1d2e3f4a5b --evidence 'Load test log attached'.

A task under an initiative, taken by you

wirk write new 'Rate-limit the public API' owner=me --criterion 'Limits are per partner' --criterion 'A 429 names the retry time' --link contributes_to:2f9b3c4e
Applied w-1c5e9a7b30 · 2 operations
  created 8d24f6b1 ($new) · work — Rate-limit the public API
  linked 8d24f6b1 contributes_to → 2f9b3c4e · link 9a0b1c2d
revisions: 8d24f6b1 r1
next: query 8d24f6b1

owner= and --criterion make the new item work. Under parent work rather than an initiative, add the parent's revision: --link contributes_to:2f9b3c4e@7.

Archiving, with a reason

{"reason": "Superseded by 9e4c21f7",
 "expect": {"3b8e1d42": 2},
 "operations": [{"op": "item.archive", "id": "3b8e1d42"}]}

The same completion, proposed by a background job

A job that watches pull requests sees the fix merge. Nobody asked it to act, so it proposes; the reason's first line becomes the proposal's title.

{"request_id": "nightly-20261002-7", "mode": "propose",
 "reason": "Mark webhook retries completed\n\nThe retry fix merged in 4f2a9c1 and the retry tests pass.",
 "expect": {"5c1e7a90": 3},
 "operations": [{"op": "item.edit", "id": "5c1e7a90", "patch": {"fields": {"status": "completed"}}}]}
Proposed nightly-20261002-7 · 1 operation for review
  proposal c4a1e902 r1 — Mark webhook retries completed
next: query c4a1e902

Receipts

Text receipts list one line per result, then the final revision of every changed item and the one fetch that shows the effect. JSON carries full IDs, and revisions is exactly the expect map for your next write:

{"ok": true,
 "data": {"request_id": "w-8b1d07e2c4", "write_state": "applied",
          "results": [{"resource": "item", "id": "item_5c1e7a90…", "revision": 4, "operation_index": 0}],
          "defaults": [], "revisions": {"item_5c1e7a90…": 4}},
 "errors": [], "notices": [], "page": {"complete": true, "next_cursor": null}, "timing_ms": {"total": 1.4}}

Receipts may suggest links for you to confirm, each as a runnable line, for example confirm one: write link 8d24f6b1@1 contributes_to 2f9b3c4e@7. Only links you write are stored. Writing a context item adds the notice context_parsed (the parts found) and, over the authoring target, context_long.

Retries

After an uncertain result (a timeout, a dropped connection), resend the identical body with the same request_id. It applies once, or returns the stored receipt with the notice replayed and the line stored receipt: this retry changed nothing. The CLI resends once on its own and prints the exact retry otherwise. You can also look the receipt up: wirk query receipt=w-8b1d07e2c4.

Refusals

Nothing is applied when a write is refused. The text names the operation and the fix:

Not applied w-8b1d07e2c4 · basis_changed: 5c1e7a90 is at r5; you read r3
  Fetch it again (query 5c1e7a90), check that your change still applies, and resend under a new request_id.
Not applied w-8b1d07e2c4 · reason_required: Completing work needs its evidence: a note naming the tests that pass, a file path or a link, or a file attached or cited in this write
Not applied w-6e2d4b8a10 · likely_duplicate at operations[0] (item.create): 71f0c8ae looks like the same work
  Use 71f0c8ae, or rerun with --allow-duplicate-of 71f0c8ae --reason REASON (MCP: "allow_duplicate_of": ["71f0c8ae"] on the item.create, and a "reason")

Others: not_available (no readable record has that ID), ambiguous_ref, unknown_owner (with the members as choices), unknown_enum_field and unknown_enum_option (with the allowed values), invalid_context, invalid_link, requires_review (context someone else maintains: propose instead), request_conflict. All codes: Errors.

Files

Upload first, then attach:

wirk upload report.pdf --description 'Load test results'
Uploaded report.pdf · application/pdf · 2.1 MB · sha256 4a5b6c7d
attach with: write new report.pdf --upload upload_7e8f9a0b1c2d4e5f8a9b0c1d2e3f4a5b

To attach to an existing item: wirk write edit ID@N --upload upload_…, or "patch": {"attach_uploads": ["upload_…"]}. Anyone who can edit the item can attach any upload in the wirkspace. How uploads work over HTTP: HTTP API.

This page as Markdown · every page in one file