Skip to content
quarry Artifact [d8dedfda9b]
Login

Artifact [d8dedfda9b]

Artifact d8dedfda9b58e42ad4bcd257598dd7edb6b226265a473a4c789bdaf7ebdfcaee:


# quarry API

Generated from the op registry by `quarry docs` — edit the ops, not this file; regenerate with `make docs`.
Every op also appears in the OpenAPI document (`quarry spec`). Query inputs ride the query string; the same names work as JSON.

## account.add

`POST /api/v1/accounts` — command, admin

Create a forge account. The password is hashed with argon2id and never stored or returned in plaintext.

Input:

| field | type | rules |
|---|---|---|
| `name` | string | `required,max=64` |
| `password` | string | `required,max=1024` |
| `admin` | boolean | — |

Output fields:

- `name` (string)
- `admin` (boolean)

## account.claim

`POST /api/v1/accounts/claim` — command, anonymous

Redeem an invite: set the account's password from its one-time link. The link is spent; sign in as usual after this.

Input:

| field | type | rules |
|---|---|---|
| `token` | string | `required,min=32,max=128` |
| `password` | string | `required,min=8,max=200` |

Output fields:

- `name` (string)

## account.delete

`POST /api/v1/accounts/delete` — command, admin

Delete an account and all of its grants.

Input:

| field | type | rules |
|---|---|---|
| `name` | string | `required,max=64` |

Output fields:

- `name` (string)

## account.disable

`POST /api/v1/accounts/disable` — command, admin

Shut an account (on=true) or reopen it. Shutting ends every session it holds at once; the account, its grants and its history stay.

Input:

| field | type | rules |
|---|---|---|
| `name` | string | `required,max=64` |
| `on` | boolean | — |


## account.explain

`GET /api/v1/accounts/explain` — query, admin

Read one account's capability string on a repository as a role, the abilities that differ from it, and a sentence of what the person can do there (RFC 0008).

Input:

| field | type | rules |
|---|---|---|
| `name` | string | `required,max=64` |
| `repo` | string | `required,max=64` |

Output fields:

- `name` (string)
- `repo` (string)
- `caps` (string)
- `effective` (string)
- `role` (string)
- `exact` (boolean)
- `plus` (array of string)
- `minus` (array of string)
- `can` (string)
- `grants` (array of string)

## account.grant

`POST /api/v1/accounts/grant` — command, admin

Give an account a role on one repository: reader, developer, maintainer or admin (RFC 0008; read and write are accepted as older spellings of the first two). The role's capability string replaces any string the account held there.

Input:

| field | type | rules |
|---|---|---|
| `name` | string | `required,max=64` |
| `repo` | string | `required,max=64` |
| `role` | string | `required,oneof=read\|write\|admin\|reader\|developer\|maintainer` |

Output fields:

- `name` (string)
- `repo` (string)
- `role` (string)
- `warning` (string)

## account.invite

`POST /api/v1/accounts/invite` — command, admin

Invite a person: create their account with no password and mint a one-time link that lets them set one. Sent to the email through notify when it is set up; returned either way so it can be passed on by hand.

Input:

| field | type | rules |
|---|---|---|
| `name` | string | `required,max=64` |
| `email` | string | `max=200` |
| `admin` | boolean | — |
| `base` | string | `max=200` |

Output fields:

- `name` (string)
- `link` (string)
- `emailed` (boolean)
- `mailError` (string)

## account.list

`GET /api/v1/accounts` — query, admin

List forge accounts and their per-repository grants.

Output fields:

- `accounts` (array of object {Account: object {name: string, admin: boolean, created: string, email: string, disabled: string, pending: boolean, lastLogin: string}, grants: array of object {repo: string, role: string}})
- `count` (integer)

## account.passwd

`POST /api/v1/accounts/passwd` — command, admin

Set an account's password.

Input:

| field | type | rules |
|---|---|---|
| `name` | string | `required,max=64` |
| `password` | string | `required,max=1024` |

Output fields:

- `name` (string)

## account.peek

`GET /api/v1/accounts/invite/{token}` — query, anonymous

Name the account behind a live invite token, so the invite page can greet its owner. Says nothing about spent or unknown tokens beyond not found.

Input:

| field | type | rules |
|---|---|---|
| `token` | string | `required,min=32,max=128` |

Output fields:

- `name` (string)

## account.revoke

`POST /api/v1/accounts/revoke` — command, admin

Remove an account's grant on one repository.

Input:

| field | type | rules |
|---|---|---|
| `name` | string | `required,max=64` |
| `repo` | string | `required,max=64` |

Output fields:

- `name` (string)
- `repo` (string)
- `role` (string)
- `warning` (string)

## archive.get

`GET /api/v1/r/{repo}/archive/{version}` — query, anonymous

Download a check-in's files as a reproducible archive (tar.gz or zip).

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `version` | string | `max=100` |
| `format` | string | `oneof=tar\|zip` |

Output fields:

- `name` (string)
- `size` (integer)
- `version` (string)
- `content` (string)

## artifact.resolve

`GET /api/v1/r/{repo}/info/{prefix}` — query, anonymous

Resolve an artifact hash prefix to its kind and canonical reference — fossil's /info dispatch.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `prefix` | string | `required,min=4,max=64` |

Output fields:

- `kind` (string)
- `ref` (string)

## attachment.begin

`POST /api/v1/r/{repo}/attachment/begin` — command, token

Begin an upload (RFC 0016 Phase 3): name the target, the file's name, size, type and SHA-256, and receive a presigned PUT — valid fifteen minutes, its content length and type signed — and a ticket for `attachment.commit`. Refused when the target is unknown or read-only, no bucket is bound, the size is over the binding's limit, or the type is not on the policy (text, the common images, PDF, JSON, the common audio and video; archives only when the repository allows them; executables never). An empty `target` with a `family` (ticket, forum or wiki) DEFERS the ticket for an object that does not exist yet or is about to change: the same gates apply against the family's letter, and the ticket is named its target by the `attach` list of `forum.post`, `forum.reply`, `ticket.new`, `ticket.set` and `wiki.save`, or by `attachment.commit`.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `target` | string | `max=200` |
| `family` | string | `max=16` |
| `name` | string | `required,max=255` |
| `size` | integer | `min=1` |
| `type` | string | `required,max=128` |
| `sha256` | string | `required,min=64,max=64` |

Output fields:

- `ticket` (string)
- `url` (string)
- `method` (string)
- `headers` (map)
- `expires` (string)

## attachment.commit

`POST /api/v1/r/{repo}/attachment/commit` — command, token

Finish an upload: quarry HEADs the object and refuses if it is absent or its length differs from what `attachment.begin` was told, then writes the stub and the A card through zit and answers both hashes. A deferred ticket (begun with no target) needs `target` here, checked against the family it was begun for. `held` is true when the caller lacks the family's moderation letter (q on tickets, l on the wiki and notes, 5 on the forum) and is not an administrator: the file is in the DAG and shown as a link until a moderator vouches for it.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `ticket` | string | `required,max=64` |
| `target` | string | `max=200` |

Output fields:

- `artifact` (string)
- `blob` (string)
- `name` (string)
- `size` (integer)
- `type` (string)
- `sha256` (string)
- `held` (boolean)

## attachment.get

`GET /api/v1/r/{repo}/attachment/{hash}` — query, anonymous

Fetch one attachment's content by its content-artifact hash.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `hash` | string | `required,min=4,max=64` |

Output fields:

- `name` (string)
- `size` (integer)
- `content` (string)

## attachment.head

`GET /api/v1/r/{repo}/attachment/{hash}/head` — query, anonymous

Describe one attachment without downloading it: its size, which of the four shapes it takes (image, text, media, other), and for text the first few lines.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `hash` | string | `required,min=4,max=64` |
| `lines` | integer | `min=0,max=1000` |

Output fields:

- `name` (string)
- `size` (integer)
- `kind` (string)
- `mimetype` (string)
- `lines` (integer)
- `text` (string)
- `truncated` (boolean)
- `external` (boolean)
- `sha256` (string)
- `held` (boolean)
- `reason` (string)

## attachment.held

`GET /api/v1/r/{repo}/attachments/held` — query, token

The review queue: uploads on this repository nobody trusted has vouched for, oldest first.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `holds` (array of object {repo: string, artifact: string, target: string, name: string, who: string, created: string})

## attachment.link

`GET /api/v1/r/{repo}/attachment/{hash}/link` — query, anonymous

Where an uploaded attachment's bytes are: a link on the bucket's public base when the repository is public and one is set, otherwise a presigned GET good for an hour that names the file and its type. `inline` is answered only for a vouched file whose type may render in a page; SVG never. A real (non-upload) attachment answers `external: false` and is served by quarry's own routes.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `hash` | string | `required,min=4,max=64` |

Output fields:

- `external` (boolean)
- `name` (string)
- `type` (string)
- `size` (integer)
- `held` (boolean)
- `download` (string)
- `inline` (string)
- `expires` (string)

## attachment.list

`GET /api/v1/r/{repo}/attachments/{target}` — query, anonymous

List the attachments live on one target — a ticket id, a wiki page name, a technote event id, or a forum post hash.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `target` | string | `required,max=200` |

Output fields:

- `attachments` (array of object {hash: string, name: string, target: string, src: string, date: string, user: string, mimetype: string, external: boolean, sha256: string, size: integer, held: boolean})

## attachment.reject

`POST /api/v1/r/{repo}/attachments/reject` — command, token

Reject a held upload: the hold is cleared and the attachment removed as `attachment.remove` would, object deletion included when the repository says so.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `artifact` | string | `required,min=4,max=64` |

Output fields:

- `artifact` (string)
- `target` (string)
- `name` (string)
- `removed` (boolean)
- `deleted` (boolean)

## attachment.remove

`POST /api/v1/r/{repo}/attachments/remove` — command, token

Remove an attachment: an A card with no source, fossil's own deletion form. Nothing leaves the DAG. When the repository's setting `deleteObjects` is on and the attachment was an upload, the object is deleted from the bucket too, unless another live attachment names the same bytes.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `target` | string | `required,max=200` |
| `name` | string | `required,max=255` |

Output fields:

- `artifact` (string)
- `target` (string)
- `name` (string)
- `removed` (boolean)
- `deleted` (boolean)

## attachment.vouch

`POST /api/v1/r/{repo}/attachments/vouch` — command, token

Vouch for a held upload: it renders from now on. Needs the family's moderation letter (q, l or 5) or the administrator's.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `artifact` | string | `required,min=4,max=64` |

Output fields:

- `artifact` (string)
- `held` (boolean)

## attention.read

`POST /api/v1/me/attention/read` — command, token

Move the signed-in account's attention read pointer to seq (default: everything so far). Forward-only — a stale client replaying an old cursor cannot resurrect mail already dismissed.

Input:

| field | type | rules |
|---|---|---|
| `seq` | integer | — |

Output fields:

- `ok` (boolean)
- `seq` (integer)

## branch.list

`GET /api/v1/r/{repo}/branch` — query, anonymous

List branches with their tip check-in, most recently active first.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `limit` | integer | `min=0,max=5000` |
| `open` | boolean | — |

Output fields:

- `branches` (array of object {name: string, tip: string, date: string, closed: boolean})
- `count` (integer)

## branch.unproposed

`GET /api/v1/r/{repo}/branch/unproposed` — query, anonymous

Branch tips whose work nobody has proposed: an open leaf, not the default branch, with no open landing naming it. The blind spot of the landing queue — work that is finished and not going anywhere, which no other surface reports.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `rows` (array of object {branch: string, tip: string, date: string, target: string})
- `count` (integer)

## bucket.get

`GET /api/v1/r/{repo}/bucket` — query, admin

The bucket this repository's uploads go to (RFC 0016): endpoint, bucket, region, access key, public base, limit and the last probe. `corsOk` says the bucket answered a browser preflight for the forge's origin at that probe; false means the page's scripted upload is blocked until the bucket's CORS rule allows the forge, while the multipart form still works. A companion store answers its parent's binding with `inherited` naming the parent. The secret is never returned.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `repo` (string)
- `bound` (boolean)
- `endpoint` (string)
- `bucket` (string)
- `region` (string)
- `accessKey` (string)
- `publicBase` (string)
- `limit` (integer)
- `probedAt` (string)
- `probeErr` (string)
- `corsOk` (boolean)
- `inherited` (string)
- `archives` (boolean)
- `deleteObjects` (boolean)

## bucket.limit

`GET /api/v1/r/{repo}/bucket/limit` — query, token

Whether this repository takes uploads and how large one may be — the uploader's question, answered to holders of the upload letters without the binding's detail.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `repo` (string)
- `bound` (boolean)
- `limit` (integer)

## bucket.policy

`POST /api/v1/r/{repo}/bucket/policy` — command, admin

The binding's two switches: `archives` opens the upload type policy to zip, gzip and tar; `deleteObjects` makes a removal delete the object from the bucket as well. Both are off by default.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `archives` | boolean | — |
| `deleteObjects` | boolean | — |

Output fields:

- `repo` (string)
- `bound` (boolean)
- `endpoint` (string)
- `bucket` (string)
- `region` (string)
- `accessKey` (string)
- `publicBase` (string)
- `limit` (integer)
- `probedAt` (string)
- `probeErr` (string)
- `corsOk` (boolean)
- `inherited` (string)
- `archives` (boolean)
- `deleteObjects` (boolean)

## bucket.probe

`POST /api/v1/r/{repo}/bucket/probe` — command, admin

Probe the bound bucket again — a presigned PUT and DELETE, then a browser preflight from the forge's origin — and record the outcome. The answer carries `probedAt` and, when it failed, `probeErr`; `corsOk` is the preflight's verdict. A failed probe is an answer, not an error.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `repo` (string)
- `bound` (boolean)
- `endpoint` (string)
- `bucket` (string)
- `region` (string)
- `accessKey` (string)
- `publicBase` (string)
- `limit` (integer)
- `probedAt` (string)
- `probeErr` (string)
- `corsOk` (boolean)
- `inherited` (string)
- `archives` (boolean)
- `deleteObjects` (boolean)

## bucket.set

`POST /api/v1/r/{repo}/bucket` — command, admin

Bind (or re-bind) the bucket this repository's uploads go to. The endpoint must be https; region defaults to `auto` (R2); limit is bytes and defaults to 100 MiB. The binding is probed first — a presigned PUT and DELETE of one small object under `attachments/` — and refused with the probe's answer if either fails. An empty secret on a bound repository keeps the stored one. Refused on a mirror, which accepts no writes, and on a companion store, which uses its parent's bucket.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `endpoint` | string | `required,max=512` |
| `bucket` | string | `required,max=128` |
| `region` | string | `max=64` |
| `accessKey` | string | `required,max=256` |
| `secret` | string | `max=512` |
| `publicBase` | string | `max=512` |
| `limit` | integer | `min=0` |

Output fields:

- `repo` (string)
- `bound` (boolean)
- `endpoint` (string)
- `bucket` (string)
- `region` (string)
- `accessKey` (string)
- `publicBase` (string)
- `limit` (integer)
- `probedAt` (string)
- `probeErr` (string)
- `corsOk` (boolean)
- `inherited` (string)
- `archives` (boolean)
- `deleteObjects` (boolean)

## bucket.unset

`POST /api/v1/r/{repo}/bucket/unset` — command, admin

Forget the repository's bucket binding. The objects stay in the steward's bucket and the stubs stay in the DAG; binding a bucket with the same objects makes them live again.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `repo` (string)
- `unbound` (boolean)

## changelog.announce

`POST /api/v1/changelog/announce` — command, admin

Tell every account about the newest changelog entry: an inbox item and a push through the notify plane, naming the entry and linking the page. Idempotent per entry heading.

Input:

| field | type | rules |
|---|---|---|
| `base` | string | `max=200` |

Output fields:

- `heading` (string)
- `recipients` (integer)

## changelog.get

`GET /api/v1/changelog` — query, anonymous

The forge's changelog, rendered, with its entry headings newest first and the build serving it.

Output fields:

- `version` (string)
- `html` (string)
- `entries` (array of string)

## chat.delete

`POST /api/v1/r/{repo}/chat/{seq}/delete` — command, token

Delete a chat message: own with C, any message with a.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `seq` | integer | `required` |

Output fields:

- `ok` (boolean)
- `seq` (integer)

## chat.edit

`POST /api/v1/r/{repo}/chat/{seq}/edit` — command, token

Edit a chat message posted within the last 5 minutes, own only.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `seq` | integer | `required` |
| `text` | string | `required,max=4096` |

Output fields:

- `ok` (boolean)
- `seq` (integer)

## chat.get

`GET /api/v1/r/{repo}/chat/{seq}` — query, anonymous

One folded chat message with its direct replies; the permalink.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `seq` | integer | `required` |
| `before` | integer | `min=0,max=50` |
| `after` | integer | `min=0,max=50` |

Output fields:

- `message` (object {seq: integer, kind: string, ref: integer, repo: string, user: string, text: string, origin: string, at: string, edited: boolean, deleted: boolean, html: string, refs: array of string, replies: integer, refUser: string, refLine: string})
- `replies` (array of object {seq: integer, kind: string, ref: integer, repo: string, user: string, text: string, origin: string, at: string, edited: boolean, deleted: boolean, html: string, refs: array of string, replies: integer, refUser: string, refLine: string})
- `earlier` (array of object {seq: integer, kind: string, ref: integer, repo: string, user: string, text: string, origin: string, at: string, edited: boolean, deleted: boolean, html: string, refs: array of string, replies: integer, refUser: string, refLine: string})
- `later` (array of object {seq: integer, kind: string, ref: integer, repo: string, user: string, text: string, origin: string, at: string, edited: boolean, deleted: boolean, html: string, refs: array of string, replies: integer, refUser: string, refLine: string})
- `more_earlier` (integer)
- `more_later` (integer)
- `expired` (boolean)

## chat.history

`GET /api/v1/r/{repo}/chat` — query, anonymous

Recent messages in a repository's chat channel, oldest first.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `before` | integer | — |
| `after` | integer | — |
| `limit` | integer | `min=0,max=500,default=100` |

Output fields:

- `messages` (array of object {seq: integer, kind: string, ref: integer, repo: string, user: string, text: string, origin: string, at: string, edited: boolean, deleted: boolean, html: string, refs: array of string, replies: integer, refUser: string, refLine: string})
- `count` (integer)
- `next` (integer)
- `read` (integer)
- `later` (integer)

## chat.post

`POST /api/v1/r/{repo}/chat` — command, token

Post a message to a repository's chat channel.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `text` | string | `required,max=4096` |
| `ref` | integer | — |

Output fields:

- `ok` (boolean)
- `seq` (integer)

## chat.preview

`POST /api/v1/r/{repo}/chat/preview` — command, token

Render a draft message exactly as posting it would — same markdown, same mentions, same reference resolution. A preview from any other renderer is a guess.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `text` | string | `required,max=4096` |

Output fields:

- `html` (string)

## chat.purge

`POST /api/v1/r/{repo}/chat/{seq}/purge` — command, token

Remove an already-deleted message from the timeline: erase the message and its tombstone from the stream. Own with C, any with a. Irreversible.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `seq` | integer | `required` |

Output fields:

- `ok` (boolean)
- `purged` (integer)

## chat.read

`POST /api/v1/r/{repo}/chat/read` — command, token

Mark a repository's chat channel read up to seq (default: latest).

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `seq` | integer | — |

Output fields:

- `ok` (boolean)
- `seq` (integer)

## chat.robot

`POST /api/v1/r/{repo}/chat/robot` — command, token

Set which events the activity robot announces in a repository's chat channel, in the webhook pattern syntax (`*`, a noun like `checkin`, or an exact kind like `landing.accepted`), space-separated. Empty turns the robot off.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `events` | string | `max=512` |

Output fields:

- `repo` (string)
- `events` (string)
- `on` (boolean)

## chat.robot.get

`GET /api/v1/r/{repo}/chat/robot` — query, token

The repository's activity-robot event patterns; empty when the robot is off. Its own op rather than a field on repo.get, because it is chat configuration and only an administrator may see it.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `repo` (string)
- `events` (string)
- `on` (boolean)

## chat.stream

`GET /api/v1/r/{repo}/chat/stream` — stream, anonymous

Live chat messages as Server-Sent Events. The body is text/event-stream, not JSON: each event's `id:` line is the message's stream sequence, and a client that reconnects with `Last-Event-ID: <seq>` resumes after it. A resume older than the window replays from the window's first message, preceded by an `event: gap` frame carrying {"from":<asked>,"first":<available>} so a renderer re-renders rather than appends.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `from` | integer | — |

Output fields:

- `seq` (integer)
- `kind` (string)
- `ref` (integer)
- `repo` (string)
- `user` (string)
- `text` (string)
- `origin` (string)
- `at` (string)
- `edited` (boolean)
- `deleted` (boolean)
- `html` (string)
- `refs` (array of string)
- `replies` (integer)
- `refUser` (string)
- `refLine` (string)

## chat.unread

`GET /api/v1/me/chat/unread` — query, token

Per-repository unread chat counts for the signed-in account.

Output fields:

- `repos` (map)

## check.report

`POST /api/v1/r/{repo}/checks` — command, anonymous

Report an external check's state onto a check-in — the commit-status contract, quarry-shaped (RFC 0009 §external-ci). Authenticated by a reporter token minted in the repository's CI settings; the token reaches this op and nothing else.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `checkin` | string | `required,min=4,max=200` |
| `context` | string | `required,max=100` |
| `state` | string | `required,oneof=pending\|success\|failure\|error` |
| `url` | string | `max=300` |
| `summary` | string | `max=2048` |
| `token` | string | `required,max=128` |

Output fields:

- `ok` (boolean)
- `context` (string)
- `state` (string)

## checkin.close

`POST /api/v1/r/{repo}/checkin/{hash}/close` — command, token

Close a leaf, or reopen one: records fossil's `closed` tag on the check-in. Closing says a line of work has ended; it hides the leaf from the open set and nothing else.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `hash` | string | `required,min=4,max=64` |
| `reopen` | boolean | — |

Output fields:

- `hash` (string)
- `closed` (boolean)

## checkin.diff

`GET /api/v1/r/{repo}/checkin/{hash}/diff` — query, anonymous

Diff one check-in against its primary parent.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `hash` | string | `required,min=4,max=64` |
| `file` | string | `max=4096` |
| `offset` | integer | `min=0` |
| `maxLines` | integer | `min=0` |
| `branch` | boolean | — |
| `from` | string | `max=64` |
| `unified` | boolean | — |

Output fields:

- `from` (string)
- `to` (string)
- `files` (array of object {status: string, name: string})
- `structured` (array of object {path: string, prevPath: string, change: string, hunks: array of object {oldStart: integer, newStart: integer, header: string, lines: array of object {kind: string, oldNum: integer, newNum: integer, tokens: array of object {class: string, text: string, mark: boolean}}}, added: integer, deleted: integer, before: integer, omitted: integer})
- `unified` (string)
- `note` (string)
- `omittedLines` (integer)
- `nextOffset` (integer)
- `totalLines` (integer)
- `lastOffset` (integer)
- `offset` (integer)

## checkin.get

`GET /api/v1/r/{repo}/checkin/{hash}` — query, anonymous

Describe one check-in: metadata, parents, and its file set.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `hash` | string | `required,min=4,max=64` |
| `files` | string | `max=4` |

Output fields:

- `hash` (string)
- `ci` (string)
- `ciRun` (string)
- `branch` (string)
- `date` (string)
- `user` (string)
- `comment` (string)
- `parents` (array of string)
- `files` (array of object {blob: string, name: string, size: integer, perm: string, binary: boolean})
- `discussedIn` (array of object {root: string, title: string, posts: integer, created: string, updated: string, user: string, excerpt: string, hasPoll: boolean, participants: array of string, dates: array of string})
- `chatRefs` (object {count: integer, seq: integer, at: string})
- `tags` (array of string)
- `commentOrig` (string)
- `hidden` (boolean)
- `closed` (boolean)
- `fork` (string)
- `context` (array of object {kind: string, hash: string, ref: string, date: string, user: string, comment: string, branch: string, isLeaf: boolean, isMerge: boolean, parents: array of string, ci: string, commentOrig: string, hidden: boolean, closed: boolean, landing: string, landingTitle: string, landingApprover: string, excerpt: string})

## checkin.manifest

`GET /api/v1/r/{repo}/checkin/{hash}/manifest` — query, anonymous

One check-in's manifest artifact, as cards. The file list is counted, not returned — it is 99% of the bytes and the tree browser already renders it.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `hash` | string | `required,min=4,max=64` |

Output fields:

- `hash` (string)
- `size` (integer)
- `cards` (array of object {letter: string, text: string, note: string})
- `files` (integer)

## checkin.tags

`GET /api/v1/r/{repo}/checkin/{hash}/tags` — query, anonymous

The full tag history of a check-in — every tag artifact that touched it, oldest first, with which rows still stand.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `hash` | string | `required,min=4,max=64` |

Output fields:

- `hash` (string)
- `date` (string)
- `user` (string)
- `comment` (string)
- `commentOrig` (string)
- `branch` (string)
- `hidden` (boolean)
- `closed` (boolean)
- `events` (array of object {date: string, src: string, name: string, sign: string, value: string, standing: boolean})

## ci.gate.ack

`POST /api/v1/r/{repo}/ci/gate/ack` — command, token

Acknowledge gate-bypass findings — check-ins that moved a protected branch without a forge accept. Acknowledging hides a finding from the CI page's strip; the record stays in the history. One check-in, or all=true for every open finding.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `checkin` | string | `max=64` |
| `all` | boolean | — |

Output fields:

- `acked` (integer)

## ci.gate.list

`GET /api/v1/r/{repo}/ci/gate` — query, anonymous

Every gate-bypass finding for a repository, open or acknowledged, newest first, paged.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `offset` | integer | `min=0` |
| `limit` | integer | `min=0,max=200,default=50` |

Output fields:

- `findings` (array of object {repo: string, branch: string, checkin: string, seen: string, who: string, acked: string})
- `count` (integer)
- `total` (integer)

## ci.list

`GET /api/v1/r/{repo}/ci` — query, anonymous

List a repository's CI runs, newest first.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `limit` | integer | `min=0,max=200,default=50` |
| `offset` | integer | `min=0` |

Output fields:

- `runs` (array of object {url: string, runId: string, checkin: string, pair: string, workflow: string, pipeline: string, status: string, started: string, completed: string, branch: string, comment: string, user: string})
- `count` (integer)
- `bypasses` (array of object {repo: string, branch: string, checkin: string, seen: string, who: string, acked: string})

## ci.pipeline.save

`POST /api/v1/r/{repo}/ci/pipeline` — command, token

Validate a pipeline, or commit it to a fresh branch and open a landing. Never writes to the target branch: a pipeline edit lands like any other change.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `path` | string | `required,max=120` |
| `content` | string | `required,max=262144` |
| `message` | string | `max=500` |
| `validate` | boolean | — |

Output fields:

- `diagnostics` (array of string)
- `hash` (string)
- `branch` (string)
- `landing` (string)

## ci.token.list

`GET /api/v1/r/{repo}/ci/tokens` — query, admin

List a repository's reporter tokens: labels, creation times and hashes — never the tokens.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `tokens` (array of object {hash: string, label: string, created: string, used: string})
- `count` (integer)

## ci.token.new

`POST /api/v1/r/{repo}/ci/tokens` — command, admin

Mint a reporter token for external CI on one repository. The token is returned once and never stored; only its hash is kept.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `label` | string | `max=100` |

Output fields:

- `token` (string)

## ci.token.revoke

`POST /api/v1/r/{repo}/ci/tokens/revoke` — command, admin

Revoke one reporter token by its stored hash, returning the remaining list.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `hash` | string | `required,max=64` |

Output fields:

- `tokens` (array of object {hash: string, label: string, created: string, used: string})
- `count` (integer)

## ci.trigger

`POST /api/v1/r/{repo}/ci/trigger` — command, token

Run a repository's pipelines now, at a check-in or the tip (RFC 0009 §triggers). The re-run button; deploys and nightlies by hand.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `checkin` | string | `max=64` |

Output fields:

- `ok` (boolean)

## config.import

`POST /api/v1/r/{repo}/config/import` — command, admin

Apply a fossil configuration-area export file to a store — replace (import) or fold (merge), as the file's cards decide (RFC 0008 rung 8). supported=false until zit#153.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `file` | string | `required,max=8388608` |
| `merge` | boolean | — |

Output fields:

- `supported` (boolean)
- `note` (string)
- `settings` (array of object {name: string, value: string, versionable: boolean})

## doc.get

`GET /api/v1/r/{repo}/doc/{hash}/{path...}` — query, anonymous

Render one file from the tree as embedded documentation, chosen by extension.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `hash` | string | `required,min=1,max=64` |
| `path` | string | `required,max=512` |

Output fields:

- `hash` (string)
- `path` (string)
- `size` (integer)
- `mimetype` (string)
- `html` (string)
- `outline` (array of object {level: integer, id: string, text: string})
- `contentType` (string)
- `content` (string)

## doc.list

`GET /api/v1/r/{repo}/doc` — query, anonymous

List the documents in a check-in's conventional documentation directories.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `version` | string | `max=64` |

Output fields:

- `version` (string)
- `docs` (array of object {path: string, size: integer, date: string, user: string})

## file.get

`GET /api/v1/r/{repo}/file/{hash}/{path...}` — query, anonymous

Fetch one file's content at a check-in.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `hash` | string | `required,min=4,max=64` |
| `path` | string | `required,max=512` |
| `from` | integer | `min=0` |
| `lines` | integer | `min=0,max=5000` |

Output fields:

- `hash` (string)
- `path` (string)
- `size` (integer)
- `binary` (boolean)
- `content` (string)
- `html` (string)
- `renderable` (boolean)
- `plain` (string)
- `from` (integer)
- `nextFrom` (integer)
- `totalLines` (integer)

## file.history

`GET /api/v1/r/{repo}/history/{version}/{path...}` — query, anonymous

List the check-ins that changed one file, newest first.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `version` | string | `max=100` |
| `path` | string | `required,max=1024` |
| `limit` | integer | `min=0,max=200,default=50` |

Output fields:

- `path` (string)
- `version` (string)
- `changes` (array of object {hash: string, date: string, user: string, comment: string})
- `count` (integer)

## fleet.exception.clear

`POST /api/v1/fleet/exceptions/clear` — command, admin

Discard one error signature and its count. The next occurrence is a first sighting.

Input:

| field | type | rules |
|---|---|---|
| `sig` | string | — |

Output fields:

- `cleared` (boolean)

## fleet.status

`GET /api/v1/fleet` — query, admin

Report every served store's ingest health.

Output fields:

- `repos` (array of object {name: string, parent: string, code: string, checkins: integer, indexing: boolean, health: object {lastOk: string, roundSeconds: number, lastCount: integer, total: integer, fallbacks: integer, gaps: integer, lastErr: string, lastErrAt: string, failures: integer, stalled: boolean, retryAt: string}, bucket: string, bucketProbedAt: string, bucketErr: string})
- `count` (integer)
- `unhealthy` (integer)
- `errors` (array of object {message: string, surface: string, path: string, count: integer, first: object {}, last: object {}, repo: string, trace: string, why: string, fix: string, fields: array of object {key: string, value: string}, build: string, req: string, rate: array of integer, group: string})
- `errorTotal` (integer)
- `errorDistinct` (integer)

## fleet.sync

`POST /api/v1/fleet/sync` — command, admin

Run an ingest round now — one store, or every store when `repo` is empty.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `max=64` |

Output fields:

- `results` (array of object {repo: string, artifacts: integer, err: string})
- `failed` (integer)

## forum.get

`GET /api/v1/r/{repo}/forum/{root}` — query, anonymous

Fetch one forum thread: every post, rendered, in reading order.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `root` | string | `required,min=4,max=64` |

Output fields:

- `root` (string)
- `title` (string)
- `excerpt` (string)
- `posts` (array of object {hash: string, parent: string, date: string, user: string, html: string, edits: integer, edited: string, score: integer, yourVote: integer, hidden: boolean, hiddenReason: string, poll: object {present: boolean, question: string, options: array of string, counts: array of integer, percents: array of integer, total: integer, yourChoice: integer, closed: boolean, closes: string}, attachments: array of object {hash: string, name: string, target: string, src: string, date: string, user: string, mimetype: string, external: boolean, sha256: string, size: integer, held: boolean}})
- `yourSub` (string)
- `chatRefs` (object {count: integer, seq: integer, at: string})

## forum.history

`GET /api/v1/r/{repo}/forum/{root}/history/{post}` — query, anonymous

Every version of one forum post, oldest first — the edits a thread page folds into its newest version.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `root` | string | `required,min=4,max=64` |
| `post` | string | `required,min=4,max=64` |

Output fields:

- `root` (string)
- `title` (string)
- `versions` (array of object {hash: string, parent: string, date: string, user: string, html: string, edits: integer, edited: string, score: integer, yourVote: integer, hidden: boolean, hiddenReason: string, poll: object {present: boolean, question: string, options: array of string, counts: array of integer, percents: array of integer, total: integer, yourChoice: integer, closed: boolean, closes: string}, attachments: array of object {hash: string, name: string, target: string, src: string, date: string, user: string, mimetype: string, external: boolean, sha256: string, size: integer, held: boolean}})

## forum.list

`GET /api/v1/r/{repo}/forum` — query, anonymous

List a repository's forum threads, most-recently-active first.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `sort` | string | `max=8` |
| `limit` | integer | `min=0,max=200,default=50` |
| `before` | string | `max=96` |
| `offset` | integer | `min=0,max=100000` |

Output fields:

- `threads` (array of object {ForumThread: object {root: string, title: string, posts: integer, created: string, updated: string, user: string, excerpt: string, hasPoll: boolean, participants: array of string, dates: array of string}, score: integer, category: string, spark: array of integer, read: boolean})
- `announcements` (array of object {ForumThread: object {root: string, title: string, posts: integer, created: string, updated: string, user: string, excerpt: string, hasPoll: boolean, participants: array of string, dates: array of string}, score: integer, category: string, spark: array of integer, read: boolean})
- `count` (integer)
- `total` (integer)
- `next` (string)

## forum.post

`POST /api/v1/r/{repo}/forum` — command, token

Open a new forum thread. `attach` lists deferred upload tickets to commit against the new post; refusals come back in `attachErrors`.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `title` | string | `required,max=200` |
| `body` | string | `required,max=1048576` |
| `mimetype` | string | `max=64` |
| `attach` | array of string | `max=20` |

Output fields:

- `hash` (string)
- `staged` (boolean)
- `attachErrors` (array of string)

## forum.reply

`POST /api/v1/r/{repo}/forum/{root}` — command, token

Reply to a forum post within its thread. `attach` lists deferred upload tickets to commit against the reply; refusals come back in `attachErrors`.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `root` | string | `required,min=4,max=64` |
| `to` | string | `max=64` |
| `body` | string | `required,max=1048576` |
| `mimetype` | string | `max=64` |
| `attach` | array of string | `max=20` |

Output fields:

- `hash` (string)
- `staged` (boolean)
- `attachErrors` (array of string)

## hook.deliveries

`GET /api/v1/r/{repo}/hook/{sub}/deliveries` — query, admin

What one webhook endpoint actually received: recent attempts with their status, latency and error. The events page says what quarry RAISED; this says what arrived, and the two differ exactly when something is broken.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `sub` | integer | `required,min=1` |
| `limit` | integer | `min=0,max=100,default=10` |

Output fields:

- `deliveries` (array of object {id: integer, sub: integer, kind: string, at: string, status: integer, ms: integer, err: string, attempt: integer})
- `count` (integer)

## hook.ping

`POST /api/v1/r/{repo}/hook/{id}/ping` — command, admin

Send a signed test event to one endpoint. ID names the SUBSCRIPTION. Proves an endpoint is reachable and its signature check works, without waiting for something real to happen.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | integer | `required,min=1` |

Output fields:

- `sent` (boolean)

## hook.redeliver

`POST /api/v1/r/{repo}/hook/delivery/{id}/redeliver` — command, admin

Send a recorded delivery again, byte for byte. ID names the DELIVERY. The stored body is replayed rather than the event rebuilt, so the signature is the one the endpoint saw before.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | integer | `required,min=1` |

Output fields:

- `sent` (boolean)

## inbox.count

`GET /api/v1/me/inbox/count` — query, token

How much is waiting for the signed-in account: the unread notify inbox PLUS the local attention events after the account's read pointer that notify holds no copy of. When notify is configured every person-addressed event is written to BOTH halves, so the two are paired on the idempotency key quarry set on each and a paired event is counted once — under notify's read state, which is the one `inbox.list` renders. The pairing is read off a bounded page of each half; a local backlog deeper than that page is counted whole rather than silently dropped. A forge with no notify companion counts only the attention stream.

Output fields:

- `unread` (integer)

## inbox.list

`GET /api/v1/me/inbox` — query, token

One page of everything addressed to the signed-in account, newest first, merging the notify inbox with quarry's own attention stream. Each row carries a `reason` (mention, reply, landing, activity) derived from the workflow that sent it, and an `id` that `inbox.mark` takes back — a notify message id, or `local:<seq>` for an attention event. An event quarry raised while notify was configured exists in both halves; the two are paired on the idempotency key each carries and render as ONE row, notify's, whose id names both halves so a single mark reaches both. `before` is the opaque `next` cursor from a previous page; it addresses BOTH sources at once, so a row the page limit cut off is served by the next page rather than skipped.

Input:

| field | type | rules |
|---|---|---|
| `before` | string | `max=1024` |
| `limit` | integer | `min=0,max=200` |

Output fields:

- `rows` (array of object {id: string, source: string, reason: string, repo: string, from: string, summary: string, link: string, at: string, read: boolean, seq: integer})
- `next` (string)
- `read` (integer)

## inbox.mark

`POST /api/v1/me/inbox/mark` — command, token

Mark inbox rows read, or done (archived — off the list). Takes the ids `inbox.list` gave, or `all` for the whole inbox. Ids reach notify; `local:<seq>` ids advance the account's attention read pointer, the same forward-only pointer `attention.read` moves, so clearing the inbox clears the badge whichever half a row came from.

Input:

| field | type | rules |
|---|---|---|
| `ids` | array of string | `max=500` |
| `all` | boolean | — |
| `as` | string | `oneof=read\|done,max=8` |

Output fields:

- `ok` (boolean)
- `seq` (integer)

## inbox.recent

`GET /api/v1/me/inbox/recent` — query, token

What was recently addressed to the signed-in account, newest first, with their read pointer. This is the local attention window — the same events me.attention streams — so an inbox is a page a forge with no notify companion still renders correctly.

Input:

| field | type | rules |
|---|---|---|
| `limit` | integer | `min=0,max=200` |

Output fields:

- `events` (array of object {seq: integer, kind: string, repo: string, from: string, summary: string, link: string, at: string})
- `read` (integer)

## landing.accept

`POST /api/v1/r/{repo}/landing/{id}/accept` — command, token

Land the branch on its target and settle the record. Writes a merge check-in, so it needs the check-in capability rather than the forum one.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | string | `required,min=4,max=64` |
| `message` | string | `max=65536` |
| `to` | string | `max=64` |
| `override` | string | `max=2048` |

Output fields:

- `hash` (string)
- `state` (string)

## landing.auto

`POST /api/v1/r/{repo}/landing/{id}/auto` — command, token

Arm or disarm auto-land: an armed open landing accepts itself once every required check on its target is green. The deferred accept is the ordinary one, written as the arming account.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | string | `required,min=4,max=64` |
| `on` | boolean | — |

Output fields:

- `autoBy` (string)

## landing.comment

`POST /api/v1/r/{repo}/landing/{id}/comment` — command, token

Add a remark to a landing's discussion. An ordinary forum reply.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | string | `required,min=4,max=64` |
| `message` | string | `max=65536` |
| `to` | string | `max=64` |
| `override` | string | `max=2048` |

Output fields:

- `hash` (string)
- `state` (string)

## landing.get

`GET /api/v1/r/{repo}/landing/{id}` — query, anonymous

One landing: the record, its discussion, and whether the caller may accept it.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | string | `required,min=4,max=64` |

Output fields:

- `landing` (object {id: string, branch: string, target: string, title: string, author: string, opened: string, updated: string, state: string, settled_by: string, replies: integer, superseded_by: string, claimed_by: string, ci: string, stack: string, position: integer, depth: integer, broken: string})
- `checks` (array of object {url: string, runId: string, checkin: string, pair: string, workflow: string, pipeline: string, status: string, started: string, completed: string, branch: string, comment: string, user: string})
- `required` (array of string)
- `premerge` (boolean)
- `behind` (integer)
- `unmet` (array of string)
- `mayOverride` (boolean)
- `autoBy` (string)
- `posts` (array of object {transition: string, updated: string, retarget: string, titleIsBody: boolean, root: boolean})
- `chatRefs` (object {count: integer, seq: integer, at: string})
- `canAccept` (boolean)
- `mergeable` (string)
- `conflicts` (array of string)
- `merge` (object {checkin: string, date: string, user: string, comment: string})
- `mergeCi` (string)
- `mergeCiRun` (string)
- `stack` (object {base: string, top: string, ci: string, segments: array of object {id: string, title: string, branch: string, target: string, state: string, position: integer, landable: boolean, ci: string}, broken: string})
- `mayLand` (boolean)
- `protected` (boolean)
- `owners` (array of string)
- `commits` (array of object {hash: string, date: string, user: string, comment: string})

## landing.land

`POST /api/v1/r/{repo}/landing/{id}/land` — command, token

Land a strata bottom-up, up to and including the named rung. Each rung is an ordinary accept; zit retargets the next; the first rung that will not merge stops the walk and is reported, with everything below it landed.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | string | `required,min=4,max=64` |
| `message` | string | `max=65536` |

Output fields:

- `landed` (array of string)
- `stopped` (string)
- `reason` (string)
- `remaining` (integer)

## landing.list

`GET /api/v1/r/{repo}/landing` — query, anonymous

Landings in this repository: branches proposed to land on a target, newest activity first. A landing is a forum thread, so it reads on the forum capability.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `state` | string | `max=16` |
| `limit` | integer | `min=0,max=200,default=50` |

Output fields:

- `landings` (array of object {id: string, branch: string, target: string, title: string, author: string, opened: string, updated: string, state: string, settled_by: string, replies: integer, superseded_by: string, claimed_by: string, ci: string, stack: string, position: integer, depth: integer, broken: string})
- `open` (integer)
- `count` (integer)

## landing.open

`POST /api/v1/r/{repo}/landing` — command, token

Propose a branch to land on a target. Mints a forum thread — no new artifact kind — so a stock fossil clone reads it as an ordinary discussion.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `branch` | string | `required,max=120` |
| `target` | string | `max=120` |
| `message` | string | `required,max=65536` |

Output fields:

- `id` (string)

## landing.reject

`POST /api/v1/r/{repo}/landing/{id}/reject` — command, token

Settle a landing without merging. A decision, so it takes the check-in capability like accept; a remark needs only the forum letter.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | string | `required,min=4,max=64` |
| `message` | string | `max=65536` |
| `to` | string | `max=64` |
| `override` | string | `max=2048` |

Output fields:

- `hash` (string)
- `state` (string)

## landing.update

`POST /api/v1/r/{repo}/landing/{id}/update` — command, token

Bring the target into the proposed branch, on the store, as a merge check-in (zit#152). Refused when the merge would not be clean; nothing is written. Writes to the PROPOSED branch, so it needs the check-in capability but not ownership of a protected target: membership of the target does not change.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | string | `required,min=4,max=64` |
| `message` | string | `max=65536` |
| `to` | string | `max=64` |
| `override` | string | `max=2048` |

Output fields:

- `hash` (string)
- `state` (string)

## markup.preview

`POST /api/v1/r/{repo}/markup-preview` — command, token

Render markdown exactly as saving it would — the composer's preview IS the production renderer, so what you see is what readers get.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `body` | string | `max=1048576` |

Output fields:

- `html` (string)

## me.attention

`GET /api/v1/me/attention` — stream, token

Everything addressed to the signed-in account, as Server-Sent Events. The body is text/event-stream, not JSON: each event's `id:` line is its stream sequence, and a client that reconnects with `Last-Event-ID: <seq>` resumes after it. A resume older than the window replays from the window's first event, preceded by an `event: gap` frame carrying {"from":<asked>,"first":<available>}. The first frame is `event: live`, sent once the tail is attached; nothing addressed to the account after it is missed.

Input:

| field | type | rules |
|---|---|---|
| `from` | integer | — |

Output fields:

- `seq` (integer)
- `kind` (string)
- `repo` (string)
- `from` (string)
- `summary` (string)
- `link` (string)
- `at` (string)

## me.feed

`GET /api/v1/me/feed` — query, token

The signed-in dashboard: the caller's granted repositories ordered by activity, the merged activity feed across them, and the caller's own recorded work.

Input:

| field | type | rules |
|---|---|---|
| `kind` | string | `max=2` |
| `offset` | integer | `min=0,max=1000` |
| `find` | string | `max=64` |
| `repo` | string | `max=64` |

Output fields:

- `user` (string)
- `repos` (array of object {name: string, description: string, lastActivity: string, open: integer})
- `feed` (array of object {repo: string, kind: string, ref: string, hash: string, date: string, user: string, comment: string})
- `kinds` (array of object {kind: string, count: integer})
- `total` (integer)
- `depth` (integer)
- `mine` (array of object {repo: string, kind: string, ref: string, hash: string, date: string, user: string, comment: string})

## me.prefs

`GET /api/v1/me/prefs` — query, token

The signed-in account's notification preferences: RFC 0011 §6's family x channel matrix ({mention, reply, landing, activity} x {inapp, push, email}) as notify holds it, plus the forge-local switches quarry owns — in-page toasts, desktop notifications, the repositories this account muted, and the browsers push was enabled in. A channel with no stored preference is ON, which is notify's own rule.

Output fields:

- `families` (map)
- `channels` (array of string)
- `digests` (map)
- `toasts` (boolean)
- `notify_desktop` (boolean)
- `muted` (array of string)
- `unknown` (array of string)
- `devices` (array of object {endpoint_hash: string, ua: string, at: string, pending_remote_delete: boolean})
- `push` (boolean)
- `applied` (object {local: boolean, notify: boolean})

## me.prefs.set

`POST /api/v1/me/prefs` — command, token

Update the signed-in account's preferences. Each family carries the channels that stay ON (repeat the key), so the whole matrix is posted and an absent channel is off. The two forge-local switches are EXPLICIT — `toasts` and `notify_desktop` take "on" or "off", and leaving one out changes it not at all. `mute` and `unmute` are list operations for the same reason: naming neither changes no mute. A muted repository still reaches the account's own inbox, it just stops being sent. The answer's `applied` says which halves landed: the forge-local one always does, the notify one does not on a companion that did not answer its preferences route.

Input:

| field | type | rules |
|---|---|---|
| `mention` | array of string | `max=8` |
| `reply` | array of string | `max=8` |
| `landing` | array of string | `max=8` |
| `activity` | array of string | `max=8` |
| `toasts` | string | `oneof=on\|off,max=8` |
| `notify_desktop` | string | `oneof=on\|off,max=8` |
| `mute` | array of string | `max=200` |
| `unmute` | array of string | `max=200` |
| `digest` | array of string | `max=8` |

Output fields:

- `families` (map)
- `channels` (array of string)
- `digests` (map)
- `toasts` (boolean)
- `notify_desktop` (boolean)
- `muted` (array of string)
- `unknown` (array of string)
- `devices` (array of object {endpoint_hash: string, ua: string, at: string, pending_remote_delete: boolean})
- `push` (boolean)
- `applied` (object {local: boolean, notify: boolean})

## me.push.key

`GET /api/v1/me/push/key` — query, token

notify's VAPID application-server public key, base64url — what a browser passes to pushManager.subscribe. Empty when this forge has no notify companion, which a page reads as "push is not available here". quarry never holds the private half.

Output fields:

- `publicKey` (string)

## me.push.subscribe

`POST /api/v1/me/push` — command, token

Register a browser for push. The body is the browser's own PushSubscription JSON ({endpoint, keys:{p256dh, auth}}), forwarded to notify as subscriber credentials for the webpush provider. quarry stores no endpoint and no key — only a hash, a user-agent label and a timestamp, so the preferences page can list the devices somebody may want to turn off. Also sweeps this account's unconfirmed unsubscribes: a device notify would not drop earlier is retried here, which is the one moment quarry knows the companion is reachable.

Input:

| field | type | rules |
|---|---|---|
| `endpoint` | string | `required,max=2048` |
| `keys` | object {p256dh: string, auth: string} | — |
| `ua` | string | `max=256` |

Output fields:

- `ok` (boolean)
- `devices` (array of object {endpoint_hash: string, ua: string, at: string, pending_remote_delete: boolean})
- `pending_remote_delete` (boolean)

## me.push.test

`POST /api/v1/me/push/test` — command, token

Push one test notification to this account's own devices. The one question a person cannot answer from the forge alone is whether push reaches their screen: everything up to the push service answers success, and an operating system that is holding notifications back says nothing to anybody. This sends a real push through the ordinary path so the answer is the notification itself, or its absence.

Output fields:

- `sent` (boolean)
- `devices` (integer)

## me.push.unsubscribe

`POST /api/v1/me/push/delete` — command, token

Stop pushing to one browser: drops the endpoint's credential in notify and forgets the device row. A credential notify does not hold (404) and one the push service already discarded (410 Gone) are both done, since that is the end state asked for. A notify that FAILED the delete has not dropped anything, so quarry keeps the device row marked `pending_remote_delete`, holding the endpoint only until the delete can be confirmed, and retries on the next `me.push.subscribe`. Forgetting the row on an unconfirmed delete is how a phone keeps buzzing with nothing left in the interface to stop it.

Input:

| field | type | rules |
|---|---|---|
| `endpoint` | string | `required,max=2048` |

Output fields:

- `ok` (boolean)
- `devices` (array of object {endpoint_hash: string, ua: string, at: string, pending_remote_delete: boolean})
- `pending_remote_delete` (boolean)

## meta.docs

`GET /api/v1/docs` — query, anonymous

The API reference, generated from this registry and rendered.

Output fields:

- `markdown` (string)
- `html` (string)

## meta.version

`GET /api/v1/version` — query, anonymous

Report the quarry build version.

Output fields:

- `version` (string)
- `zit` (string)

## mirror.chatbridge

`POST /api/v1/r/{repo}/mirror/chatbridge` — command, admin

Bridge a repository's chat channel to a Fossil server's chat, or detach it. The password is stored and never returned.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `url` | string | `max=512` |
| `user` | string | `max=64` |
| `password` | string | `max=256` |
| `detach` | boolean | — |

Output fields:

- `bridge` (object {repo: string, url: string, user: string, lastMsgId: integer, at: string, lastErr: string})
- `detached` (boolean)

## mirror.detach

`POST /api/v1/mirrors/detach` — command, admin

Detach a store from its upstream: quarry becomes the source of truth and accepts writes; the upstream is kept as provenance (`detachedFrom`).

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `repo` (string)

## mirror.list

`GET /api/v1/mirrors` — query, admin

List configured upstream mirrors and their recent history.

Output fields:

- `mirrors` (array of object {repo: string, url: string, every: integer, enabled: boolean, lastAt: string, lastOk: string, lastErr: string, failures: integer, acceptWrites: boolean, detachedFrom: string, state: string, bridged: boolean, chatBridge: object {repo: string, url: string, user: string, lastMsgId: integer, at: string, lastErr: string}})
- `count` (integer)

## mirror.run

`POST /api/v1/mirrors/run` — command, admin

Pull from upstream now — one store, or every configured mirror when `repo` is empty.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `max=64` |

Output fields:

- `results` (array of object {repo: string, changed: boolean, err: string})
- `failed` (integer)
- `changed` (integer)

## mirror.set

`POST /api/v1/mirrors` — command, admin

Configure a store's upstream and pull interval.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `url` | string | `required,max=2048` |
| `every` | integer | `min=0,max=604800,default=3600` |

Output fields:

- `mirror` (object {repo: string, url: string, every: integer, enabled: boolean, lastAt: string, lastOk: string, lastErr: string, failures: integer, acceptWrites: boolean, detachedFrom: string, state: string, bridged: boolean, chatBridge: object {repo: string, url: string, user: string, lastMsgId: integer, at: string, lastErr: string}})

## mirror.writes

`POST /api/v1/mirrors/writes` — command, admin

Allow or refuse local writes (push, tickets, wiki, forum) on a mirror. Refused by default: a write landing on a tracking store forks it by accident.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `accept` | boolean | — |

Output fields:

- `mirror` (object {repo: string, url: string, every: integer, enabled: boolean, lastAt: string, lastOk: string, lastErr: string, failures: integer, acceptWrites: boolean, detachedFrom: string, state: string, bridged: boolean, chatBridge: object {repo: string, url: string, user: string, lastMsgId: integer, at: string, lastErr: string}})

## ops.history

`GET /api/v1/ops/history` — query, admin

Renders read back from the durable analytics store, newest or slowest first.

Input:

| field | type | rules |
|---|---|---|
| `find` | string | `max=200` |
| `repo` | string | `max=64` |
| `sort` | string | `oneof=recent\|slowest` |
| `days` | integer | `min=0,max=90,default=14` |
| `limit` | integer | `min=0,max=500,default=100` |

Output fields:

- `rows` (array of object {at: string, path: string, url: string, repo: string, elapsedMs: integer, opsMs: integer, zitMs: integer, status: integer})
- `find` (string)
- `repo` (string)
- `sort` (string)
- `days` (integer)
- `store` (object {configured: boolean, path: string, err: string, kept: string})

## ops.route

`GET /api/v1/ops/route` — query, admin

One route's recent renders, split by repository, with the store calls each one made.

Input:

| field | type | rules |
|---|---|---|
| `path` | string | `required,max=200` |
| `limit` | integer | `min=0,max=100,default=20` |
| `days` | integer | `min=0,max=90,default=14` |

Output fields:

- `path` (string)
- `renders` (array of object {at: string, url: string, repo: string, elapsedMs: integer, opsMs: integer, zitMs: integer, status: integer, calls: array of object {verb: string, repo: string, durationMs: integer}, callMs: integer})
- `byRepo` (array of object {repo: string, count: integer, p50Ms: integer, maxMs: integer})
- `trend` (array of object {day: string, n: integer, p50Ms: integer, p95Ms: integer, maxMs: integer})
- `trendDays` (integer)

## ops.status

`GET /api/v1/ops` — query, admin

Operational analytics: slowest routes, recent renders, store subprocess load.

Input:

| field | type | rules |
|---|---|---|
| `limit` | integer | `min=0,max=200,default=25` |
| `find` | string | `max=200` |
| `sort` | string | `oneof=p95\|p50\|max\|n\|path` |

Output fields:

- `phase` (object {name: string, repos: integer, ready: integer, working: array of string, stalled: array of string, artifacts: integer, slowestRoundSeconds: number})
- `paths` (array of object {path: string, count: integer, p50Ms: integer, p95Ms: integer, maxMs: integer})
- `renders` (array of object {at: string, path: string, url: string, repo: string, elapsedMs: integer, opsMs: integer, zitMs: integer, status: integer})
- `verbs` (array of object {verb: string, count: integer, totalMs: integer, maxMs: integer})
- `liveSubprocess` (integer)
- `peakSubprocess` (integer)
- `lanes` (array of object {name: string, bound: integer, running: integer, admitted: integer, refused: integer, waitedMs: integer})
- `errorTotal` (integer)
- `budgetMs` (integer)
- `store` (object {configured: boolean, path: string, err: string, kept: string})

## people.list

`GET /api/v1/people` — query, anonymous

List contributors across every repository the caller may read.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `max=64` |
| `q` | string | `max=100` |
| `offset` | integer | `min=0,max=1000000` |
| `limit` | integer | `min=0,max=500,default=100` |

Output fields:

- `people` (array of object {user: string, events: integer, first: string, last: string, repos: array of string, appearances: array of object {repo: string, events: integer, first: string, last: string}})
- `count` (integer)
- `total` (integer)

## people.mention

`GET /api/v1/r/{repo}/mention` — query, token

Handles with any capability on this repository, for @ completion.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `q` | string | `max=64` |

Output fields:

- `handles` (array of object {handle: string, caps: string})

## ref.resolve

`GET /api/v1/r/{repo}/ref` — query, anonymous

Resolve one reference token to the facts a preview card shows.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `ref` | string | `required,max=512` |

Output fields:

- `kind` (string)
- `id` (string)
- `repo` (string)
- `title` (string)
- `summary` (string)
- `state` (string)
- `href` (string)
- `expired` (boolean)
- `author` (string)
- `at` (string)

## repo.about

`GET /api/v1/r/{repo}/about` — query, anonymous

The repository's About aggregate: description, homepage and topics (.quarry/repo.json), the landing page's document tabs, the latest release tag, and the contributor count. One op so the panel costs one invoke and the JSON API sees the same panel.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `doc` | string | `max=512` |

Output fields:

- `docPath` (string)
- `docLabel` (string)
- `docHtml` (string)
- `description` (string)
- `homepage` (string)
- `topics` (array of string)
- `docs` (array of object {path: string, label: string})
- `latestTag` (string)
- `latestTagTip` (string)
- `latestTagDate` (string)
- `tagCount` (integer)
- `contributors` (integer)
- `licenseName` (string)
- `announceRoot` (string)
- `announceTitle` (string)
- `announceDate` (string)
- `noteEvent` (string)
- `noteTitle` (string)
- `noteDate` (string)
- `noteUser` (string)
- `noteExcerpt` (string)
- `noteCount` (integer)
- `problem` (string)

## repo.activity

`GET /api/v1/r/{repo}/activity` — query, anonymous

Sample a repository's recent check-ins — who, when, and which files — with merge-carried touches marked inherited.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `branch` | string | `max=64` |
| `user` | string | `max=64` |
| `since` | string | `max=10` |
| `until` | string | `max=10` |

Output fields:

- `since` (string)
- `until` (string)
- `authors` (array of object {name: string, checkins: integer, files: integer, hue: integer, chroma: integer, shade: integer})
- `paths` (array of string)
- `rows` (array of object {day: integer, hour: integer, author: integer, merge: integer, files: integer, hash: string, branch: string})
- `touches` (array of object {day: integer, path: integer, author: integer, inherited: integer})
- `window` (string)

## repo.activity.days

`GET /api/v1/r/{repo}/activity/days` — query, anonymous

Fold a repository's whole history to one entry per day — who led it, how many merges, which directory, which releases.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `branch` | string | `max=64` |

Output fields:

- `first` (string)
- `last` (string)
- `authors` (array of object {name: string, checkins: integer, files: integer, hue: integer, chroma: integer, shade: integer})
- `days` (map)
- `paths` (array of string)
- `releases` (map)

## repo.add

`POST /api/v1/repos` — command, admin

Serve a repository: an existing store at `path`, a new empty one (`create`), or a clone of `clone`. Cloning also records a mirror unless `noMirror`.

Input:

| field | type | rules |
|---|---|---|
| `name` | string | `required,max=64` |
| `path` | string | `max=4096` |
| `description` | string | `max=300` |
| `private` | boolean | — |
| `create` | boolean | — |
| `clone` | string | `max=2048` |
| `readme` | boolean | — |
| `license` | string | `max=16` |
| `ignore` | boolean | — |
| `noMirror` | boolean | — |
| `mirrorEvery` | integer | `min=0,max=604800` |

Output fields:

- `name` (string)
- `path` (string)
- `mirrored` (boolean)

## repo.counts

`GET /api/v1/r/{repo}/counts` — query, anonymous

Count a repository's content by section — what the navigation shows.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `indexBehind` (boolean)
- `checkins` (integer)
- `wikiPages` (integer)
- `tickets` (integer)
- `forumThreads` (integer)
- `openLandings` (integer)
- `technotes` (integer)
- `contributors` (integer)
- `tags` (integer)

## repo.delete

`POST /api/v1/repos/delete` — command, admin

Destroy a repository: its store, its index and everything keyed to it. `confirm` must equal `name`. Only repositories quarry created can be deleted.

Input:

| field | type | rules |
|---|---|---|
| `name` | string | `required,max=64` |
| `confirm` | string | `required,max=64` |

Output fields:

- `name` (string)

## repo.get

`GET /api/v1/r/{repo}/info` — query, anonymous

Describe one repository: its settings and headline counts.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `name` (string)
- `visibility` (string)
- `contributors` (integer)
- `mirroredAt` (string)
- `mirror` (object {tracking: boolean, url: string, every: integer, lastOk: string, lastAt: string, lastErr: string, failures: integer, acceptWrites: boolean, state: string})
- `detachedFrom` (string)
- `deletable` (boolean)
- `forum` (string)
- `homepage` (string)
- `topics` (string)
- `chat` (boolean)
- `chatSetting` (boolean)
- `fossilClone` (boolean)
- `lastActive` (string)
- `description` (string)
- `private` (boolean)
- `checkins` (integer)
- `tipDate` (string)
- `tipComment` (string)
- `indexing` (boolean)
- `starting` (boolean)

## repo.imports

`GET /api/v1/repos/imports` — query, admin

Repositories being copied from an upstream, and ones whose copy failed.

Output fields:

- `imports` (array of object {name: string, url: string, state: string, error: string, started: string, bytes: integer})
- `count` (integer)

## repo.list

`GET /api/v1/repos` — query, anonymous

List the served repositories with their tip check-in.

Input:

| field | type | rules |
|---|---|---|
| `q` | string | `max=64` |
| `sort` | string | `oneof=active\|name\|size` |
| `limit` | integer | `min=0,max=500` |
| `kind` | string | `oneof=\|mirror\|native` |

Output fields:

- `repos` (array of object {name: string, visibility: string, contributors: integer, mirroredAt: string, mirror: object {tracking: boolean, url: string, every: integer, lastOk: string, lastAt: string, lastErr: string, failures: integer, acceptWrites: boolean, state: string}, detachedFrom: string, deletable: boolean, forum: string, homepage: string, topics: string, chat: boolean, chatSetting: boolean, fossilClone: boolean, lastActive: string, description: string, private: boolean, checkins: integer, tipDate: string, tipComment: string, indexing: boolean, starting: boolean})
- `count` (integer)
- `total` (integer)
- `served` (integer)

## repo.reindex

`POST /api/v1/r/{repo}/reindex` — command, admin

Wipe this repository's derived index and replay it from the store, in process, while the forge serves. The sanctioned repair for any index that disagrees with its store.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `artifacts` (integer)

## repo.remove

`POST /api/v1/repos/remove` — command, admin

Stop serving a repository. The store on disk is left untouched.

Input:

| field | type | rules |
|---|---|---|
| `name` | string | `required,max=64` |

Output fields:

- `name` (string)

## repo.update

`POST /api/v1/repos/update` — command, admin

Set a repository's settings: description, visibility, about fields and chat. Fields are applied whole. private=true blanks the nobody and anonymous category strings (RFC 0004 §Private); false returns them to the fleet default.

Input:

| field | type | rules |
|---|---|---|
| `name` | string | `required,max=64` |
| `description` | string | `max=300` |
| `private` | boolean | — |
| `homepage` | string | `max=300` |
| `topics` | string | `max=600` |
| `chat` | boolean | — |

Output fields:

- `name` (string)
- `description` (string)
- `private` (boolean)
- `homepage` (string)
- `topics` (string)

## search.all

`GET /api/v1/search` — query, anonymous

Full-text search across every repository the caller may read.

Input:

| field | type | rules |
|---|---|---|
| `q` | string | `required,max=200` |
| `kind` | string | `oneof=checkin\|wiki\|ticket\|forum\|technote\|doc\|file` |
| `repo` | string | `max=64` |
| `limit` | integer | `min=0,max=200,default=50` |

Output fields:

- `results` (array of object {repo: string, SearchResult: object {kind: string, ref: string, label: string, snippet: string, date: string, user: string, branch: string, status: string, hash: string}})
- `count` (integer)
- `searched` (integer)
- `by_repo` (array of object {repo: string, count: integer})

## search.query

`GET /api/v1/r/{repo}/search` — query, anonymous

Full-text search over a repository's check-ins, wiki, and tickets.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `q` | string | `required,max=200` |
| `kind` | string | `oneof=checkin\|wiki\|ticket\|forum\|technote\|doc\|file` |
| `limit` | integer | `min=0,max=200,default=50` |

Output fields:

- `results` (array of object {kind: string, ref: string, label: string, snippet: string, date: string, user: string, branch: string, status: string, hash: string})
- `count` (integer)

## session.end

`POST /api/v1/sessions/end` — command, token

Sign out one session by its id — any session for an admin, only your own otherwise — or every session of an account with account=.

Input:

| field | type | rules |
|---|---|---|
| `id` | string | `max=64` |
| `account` | string | `max=64` |


## session.list

`GET /api/v1/sessions` — query, token

Who is signed in, from where, since when. An admin sees every account's sessions (or one account's with account=); everyone else sees their own.

Input:

| field | type | rules |
|---|---|---|
| `account` | string | `max=64` |

Output fields:

- `sessions` (array of object {id: string, account: string, created: string, expires: string, lastSeen: string, ip: string, agent: string})
- `count` (integer)

## settings.list

`GET /api/v1/r/{repo}/settings/fossil` — query, admin

List the fossil settings in force for a repository's store (RFC 0008 rung 8). On a store with no working tree the reply is supported=false with a note, until zit ships settings -R on bare stores (zit#153).

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `supported` (boolean)
- `note` (string)
- `settings` (array of object {name: string, value: string, versionable: boolean})

## settings.set

`POST /api/v1/r/{repo}/settings/fossil` — command, admin

Set one repository-tier fossil setting on a store, then return the refreshed list (RFC 0008 rung 8). supported=false until zit#153.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `name` | string | `required,max=64` |
| `value` | string | `max=4096` |

Output fields:

- `supported` (boolean)
- `note` (string)
- `settings` (array of object {name: string, value: string, versionable: boolean})

## tag.list

`GET /api/v1/r/{repo}/tag` — query, anonymous

List release tags — symbolic names that are not branches.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `limit` | integer | `min=0,max=500,default=100` |

Output fields:

- `tags` (array of object {name: string, tip: string, date: string})
- `count` (integer)

## technote.add

`POST /api/v1/r/{repo}/technote` — command, token

Record a technote; passing an existing event id records an edit.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `body` | string | `required,max=1048576` |
| `mimetype` | string | `max=64` |
| `comment` | string | `max=200` |
| `event` | string | `max=64` |
| `time` | string | `max=32` |

Output fields:

- `event` (string)
- `hash` (string)

## technote.get

`GET /api/v1/r/{repo}/technote/{event}` — query, anonymous

Fetch one technote, rendered, by event-id prefix; rev selects one edit.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `event` | string | `required,max=64` |
| `rev` | string | `max=64` |

Output fields:

- `event` (string)
- `hash` (string)
- `date` (string)
- `edited` (string)
- `user` (string)
- `comment` (string)
- `body` (string)
- `html` (string)
- `revisions` (integer)
- `historical` (boolean)
- `attachments` (array of object {hash: string, name: string, target: string, src: string, date: string, user: string, mimetype: string, external: boolean, sha256: string, size: integer, held: boolean})

## technote.history

`GET /api/v1/r/{repo}/technote/{event}/history` — query, anonymous

List every recorded edit of one technote, newest first.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `event` | string | `required,max=64` |

Output fields:

- `event` (string)
- `comment` (string)
- `edits` (array of object {hash: string, date: string, edited: string, user: string, comment: string})
- `count` (integer)

## technote.list

`GET /api/v1/r/{repo}/technote` — query, anonymous

List a repository's technotes at their latest edit, newest first.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `notes` (array of object {hash: string, event: string, date: string, user: string, mimetype: string, comment: string, body: string, excerpt: string, revisions: integer})
- `count` (integer)

## ticket.across

`GET /api/v1/me/tickets` — query, token

Every tracker the caller can reach, as one listing: the same report axes as ticket.list asked of all granted repositories at once, with repository as one more browse axis.

Input:

| field | type | rules |
|---|---|---|
| `limit` | integer | `min=0,max=200,default=50` |
| `state` | string | `oneof=open\|closed\|all` |
| `repo` | string | `max=64` |
| `type` | string | `max=64` |
| `severity` | string | `max=64` |
| `priority` | string | `max=64` |
| `subsystem` | string | `max=64` |
| `status` | string | `max=64` |
| `author` | string | `max=64` |
| `title` | string | `max=128` |
| `order` | string | `max=32` |
| `awaiting` | boolean | — |

Output fields:

- `tickets` (array of object {repo: string})
- `count` (integer)
- `open` (integer)
- `closed` (integer)
- `repos` (array of object {value: string, open: integer})
- `subsystems` (array of object {value: string, open: integer})
- `authors` (array of object {value: string, open: integer})
- `truncated` (boolean)

## ticket.config

`GET /api/v1/r/{repo}/ticket-config` — query, anonymous

The repository's ticket vocabulary: .quarry/tickets.json at the tip of trunk, merged over fossil's defaults. List order is sort order.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `vocab` (array of object {field: string, words: array of string})
- `source` (string)
- `problem` (string)

## ticket.discard

`POST /api/v1/r/{repo}/ticket/{id}/discard` — command, token

Delete a local ticket outright — the ability the artifact log can never offer, and the whole answer to tracker spam.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | string | `required,min=4,max=40` |

Output fields:

- `ok` (boolean)

## ticket.get

`GET /api/v1/r/{repo}/ticket/{id}` — query, anonymous

Fetch one ticket: folded state plus change history.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | string | `required,min=4,max=40` |

Output fields:

- `ticket` (object {id: string, created: string, updated: string, fields: array of object {name: string, value: string}, description: string, descriptionMimetype: string, comments: array of object {hash: string, date: string, login: string, username: string, mimetype: string, body: string}, changes: array of object {hash: string, date: string, user: string, fields: array of object {name: string, value: string}}})
- `descriptionHtml` (string)
- `comments` (array of object {date: string, login: string, username: string, html: string})
- `attachments` (array of object {hash: string, name: string, target: string, src: string, date: string, user: string, mimetype: string, external: boolean, sha256: string, size: integer, held: boolean})
- `mentions` (array of object {root: string, title: string, kind: string, state: string, branch: string, target: string, updated: string})
- `chatRefs` (object {count: integer, seq: integer, at: string})
- `local` (boolean)
- `promotedTo` (string)

## ticket.intake

`POST /api/v1/r/{repo}/ticket/intake` — command, anonymous

File a local ticket (docs/local-tickets.md): stored on this forge, promotable into the artifact log by an admin, deletable until then.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `title` | string | `required,max=200` |
| `status` | string | `max=40` |
| `comment` | string | `max=65536` |
| `type` | string | `max=40` |
| `severity` | string | `max=40` |
| `priority` | string | `max=40` |
| `subsystem` | string | `max=80` |
| `foundin` | string | `max=80` |

Output fields:

- `id` (string)

## ticket.list

`GET /api/v1/r/{repo}/ticket` — query, anonymous

List a repository's tickets with their folded current state.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `limit` | integer | `min=0,max=200,default=50` |
| `before` | string | `max=96` |
| `state` | string | `oneof=open\|closed\|all` |
| `type` | string | `max=64` |
| `severity` | string | `max=64` |
| `priority` | string | `max=64` |
| `subsystem` | string | `max=64` |
| `status` | string | `max=64` |
| `author` | string | `max=64` |
| `title` | string | `max=128` |
| `offset` | integer | `min=0,max=100000` |
| `order` | string | `max=32` |
| `origin` | string | `oneof=local\|fossil` |

Output fields:

- `tickets` (array of object {id: string, title: string, status: string, updated: string, created: string, type: string, severity: string, priority: string, subsystem: string, resolution: string, author: string, lastUser: string, comments: integer, refs: integer, local: boolean})
- `count` (integer)
- `open` (integer)
- `closed` (integer)
- `subsystems` (array of object {value: string, open: integer})
- `authors` (array of object {value: string, open: integer})
- `next` (string)

## ticket.new

`POST /api/v1/r/{repo}/ticket` — command, token

Open a new ticket. `attach` lists deferred upload tickets to commit against it; refusals come back in `attachErrors`.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `title` | string | `required,max=200` |
| `status` | string | `max=40` |
| `comment` | string | `max=65536` |
| `type` | string | `max=40` |
| `severity` | string | `max=40` |
| `priority` | string | `max=40` |
| `subsystem` | string | `max=80` |
| `foundin` | string | `max=80` |
| `attach` | array of string | `max=20` |

Output fields:

- `id` (string)
- `hash` (string)
- `attachErrors` (array of string)

## ticket.promote

`POST /api/v1/r/{repo}/ticket/{id}/promote` — command, token

Promote a local ticket into the artifact log: one ticket artifact with the folded state and provenance, one more collating any discussion. The local id becomes a redirect.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | string | `required,min=4,max=40` |

Output fields:

- `id` (string)
- `hash` (string)

## ticket.set

`POST /api/v1/r/{repo}/ticket/{id}` — command, token

Record a ticket change; empty fields are left untouched. `attach` lists deferred upload tickets to commit against the ticket (alone, with no field, they are attached and nothing else is written); refusals come back in `attachErrors`.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `id` | string | `required,min=4,max=40` |
| `title` | string | `max=200` |
| `status` | string | `max=40` |
| `resolution` | string | `max=40` |
| `comment` | string | `max=65536` |
| `description` | string | `max=65536` |
| `type` | string | `max=40` |
| `severity` | string | `max=40` |
| `priority` | string | `max=40` |
| `subsystem` | string | `max=80` |
| `foundin` | string | `max=80` |
| `attach` | array of string | `max=20` |

Output fields:

- `hash` (string)
- `attachErrors` (array of string)

## timeline.list

`GET /api/v1/r/{repo}/timeline` — query, anonymous

List a repository's events — check-ins, wiki edits, ticket changes — newest first, paged by `before`.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `limit` | integer | `min=0,max=200,default=50` |
| `before` | string | `max=96` |
| `branch` | string | `max=64` |
| `tag` | string | `max=64` |
| `user` | string | `max=64` |
| `y` | string | `oneof=all\|ci\|w\|t\|e\|f\|l` |

Output fields:

- `entries` (array of object {kind: string, hash: string, ref: string, date: string, user: string, comment: string, branch: string, isLeaf: boolean, isMerge: boolean, parents: array of string, ci: string, commentOrig: string, hidden: boolean, closed: boolean, landing: string, landingTitle: string, landingApprover: string, excerpt: string})
- `defaultBranch` (string)
- `count` (integer)
- `next` (string)
- `indexBehind` (boolean)

## tree.list

`GET /api/v1/r/{repo}/tree/{version}/{path...}` — query, anonymous

List one directory of a check-in, with the directory's README rendered.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `version` | string | `max=100` |
| `path` | string | `max=1024` |

Output fields:

- `version` (string)
- `ref` (string)
- `path` (string)
- `entries` (array of object {name: string, path: string, dir: boolean, blob: string, files: integer, size: integer, exec: boolean, link: boolean, binary: boolean, lastHash: string, lastDate: string, lastComment: string})
- `readme` (string)
- `readmeIn` (string)

## wiki.get

`GET /api/v1/r/{repo}/wiki/{name}` — query, anonymous

Fetch one wiki page, rendered — latest, or the revision named by rev.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `name` | string | `required,max=100` |
| `rev` | string | `max=64` |

Output fields:

- `hash` (string)
- `title` (string)
- `date` (string)
- `user` (string)
- `mimetype` (string)
- `source` (string)
- `html` (string)
- `outline` (array of object {level: integer, id: string, text: string})
- `deleted` (boolean)
- `attachments` (array of object {hash: string, name: string, target: string, src: string, date: string, user: string, mimetype: string, external: boolean, sha256: string, size: integer, held: boolean})

## wiki.history

`GET /api/v1/r/{repo}/wiki/{name}/history` — query, anonymous

List every recorded edit of one wiki page, newest first.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `name` | string | `required,max=100` |

Output fields:

- `title` (string)
- `edits` (array of object {hash: string, date: string, user: string})
- `count` (integer)

## wiki.list

`GET /api/v1/r/{repo}/wiki` — query, anonymous

List a repository's wiki pages with their latest edit.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |

Output fields:

- `pages` (array of object {title: string, date: string, user: string, revisions: integer})
- `count` (integer)
- `orphans` (array of string)
- `wanted` (array of object {title: string, links: integer, from: array of string})
- `home` (string)

## wiki.save

`POST /api/v1/r/{repo}/wiki/{name}` — command, token

Record a wiki page edit (creates the page on first save). `attach` lists deferred upload tickets to commit against the page; refusals come back in `attachErrors`.

Input:

| field | type | rules |
|---|---|---|
| `repo` | string | `required,max=64` |
| `name` | string | `required,max=100` |
| `body` | string | `required,max=1048576` |
| `mimetype` | string | `max=64` |
| `attach` | array of string | `max=20` |

Output fields:

- `hash` (string)
- `attachErrors` (array of string)