Docs · HTTP API

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.

HTTP API

The CLI and MCP server are thin clients over this API. Programs can call it directly.

Routes

RouteBodyStatePage
GET /healthnoneLivebelow
POST /v2/status{task?, workspace_id?, max_bytes?, format?}Live in stagingstatus
POST /v2/query{fetch? | about? | fields? | receipt?, depth?, sort?, limit?, max_bytes?, cursor?, workspace_id?, format?}Live in stagingquery
POST /v2/write{request_id, operations, expect?, mode?, reason?, workspace_id?, format?}Live in stagingwrite
POST /v2/review{request_id, decisions, workspace_id?, format?}Live in stagingreview
POST /v2/filesexactly one of upload, confirm, downloadNot live yetFiles
POST /v2/show{preset} | {title, blocks} | {revoke}, plus timezone?, workspace_id?, format?Not live yetwirk show
POST /v2/admin{show} or {request_id, operations}Not live yetadministrators only

"Live in staging" means it answers for invited tokens while WIRK is in private staging.

Example

curl -s https://api.wirk.life/v2/query \
  -H "Authorization: Bearer $WIRK_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"fields": {"kind": "work"}}'

Keep the token in a file readable only by you and out of command history, URLs, logs and WIRK itself.

The envelope

Every /v2 answer, refusals included, is one JSON object:

{"ok": true,
 "text": "cards 1–3 of 26 · newest change first\n…",
 "errors": [],
 "notices": [],
 "page": {"complete": true, "next_cursor": null},
 "timing_ms": {"total": 4.2}}
KeyMeaning
oktrue when the request did what it asked
textThe compact text answer, when format is "text" (the default)
dataThe structured answer, when format is "json"
errorsProblems: {code, message, field?, hint?, choices?, requested_id?, input_index?}
notices{code, message} that did not stop the request, such as replayed
pagecomplete, and next_cursor when more remain
timing_msTime spent on this request

An answer without this envelope came from the network edge in front of WIRK, or from storage for a signed link, not from WIRK. HTTP statuses by code are listed in Errors.

Text or JSON

"format": "text" (default) returns text: compact, readable, with 8-character short IDs. "format": "json" returns data with full IDs. format never changes what a request does, and an identical retry may switch format.

IDs

Every position that names a record takes a full ID or a short one of at least 6 hex characters, with or without its prefix (item_, link_, proposal_, upload_; wirkspaces are wsp_). An ambiguous prefix is refused with choices. workspace_id is optional when your token belongs to one wirkspace; with several, choose_wirkspace lists them.

Request IDs and receipts

write, review, admin and a file confirm require a request_id (1–200 characters) on the wire. The CLI and MCP server make one when you leave it out. The service stores a receipt per request ID:

A 503 database_unavailable envelope means the request was rolled back: a refusal, not an unknown outcome.

The Wirk-Context header

POST /v2/status reads an optional header describing where the agent runs:

Wirk-Context: harness=claude-code version=2.1.281 session=7f3a1b2c9d0e4f56 repo=github.com/acme/api branch=main
KeyRule
harness[a-z0-9.-], 1–40 characters
version[A-Za-z0-9.+-], 1–40 characters
session16 lowercase hex characters
repohost/owner/name in printable ASCII without @ or ://, or local: and 8 hex
branchprintable ASCII, at most 200 characters, none of ~^:?*[\

Each key at most once, the whole header under 2,048 bytes. If any part is invalid, the whole header is ignored with the notice context_ignored. The values are shown as reported and never decide what you may do.

Files

Not live yet. This is how POST /v2/files works at launch.

File bytes never pass through the WIRK server. One route, POST /v2/files, has three uses; send exactly one of upload, confirm and download, and always ask for "format": "json", because text answers never print a signed link.

1. Upload. Ask for a place to put the bytes:

{"upload": {"bytes": 2202010,
            "sha256": "<64 hex>"},
 "format": "json"}

The answer's data is {"present": true} when storage already holds those bytes, or {"present": false, "put": {"url", "headers", "expires_at"}}: a signed PUT valid for 15 minutes. PUT the file to put.url with exactly put.headers and Content-Length, and never with your WIRK token. The signature covers the length and the SHA-256, so storage refuses any other bytes.

2. Confirm. Record the upload once the bytes are there:

{"request_id": "u-4b7e19c2a0",
 "confirm": {"filename": "report.pdf",
             "bytes": 2202010,
             "sha256": "<64 hex>",
             "description": "Load test results"},
 "format": "json"}

WIRK checks the stored object's size, SHA-256 and encryption, then answers data.upload = {id, filename, media_type, bytes, sha256, description, object}. The request_id makes the confirm safe to repeat. upload_incomplete means the bytes are not there yet: put them (with a fresh upload link if the first expired), then confirm again. Attach the upload in a write with data.uploads or patch.attach_uploads.

3. Download.

{"download": {"item": "5c1e7a90",
              "file": "file_…",
              "revision": 3},
 "format": "json"}

The answer's data.download is {url, expires_at, filename, media_type, bytes, sha256}: a signed GET valid for 5 minutes. Fetch it without your WIRK token and check the bytes' SHA-256 against sha256. revision is optional.

Never print, log or store a signed link. Each account's files are kept apart and encrypted with the account's own key. The upload limit is max_upload_bytes, 5 GiB by default. The CLI does all of this in wirk upload and wirk download.

Health

curl https://api.wirk.life/health

Needs no token. Returns {ok, instance_id, build, database: {ready}, files: {ready, reason}, limits: {…}, message}, and HTTP 503 when the database is not ready. limits holds the live values of the Limits the service enforces, such as max_upload_bytes and max_write_operations.

This page as Markdown · every page in one file