Artifact 3a1e89b1aeb3b918813f9a3fc44ef479dfd13aea23f4e8d765b7878660fd8e80:
- File
docs/api.md
— part of check-in
[86880118d5]
at
2026-08-18 02:09:54
on branch trunk
— account: manage identity through the API, and stop lying about why
Six ops (account.list/add/passwd/delete/grant/revoke, admin) plus quarry accounts on top of them. Creating a login or granting someone read on a repository no longer needs shell access on the server.
quarry account still opens OLTP directly and has to: the FIRST account must exist before any server runs or any admin exists to authenticate as. Everything after that is the API, so the CLI has no privileged path.
The verifier never crosses the seam in either direction, and account.add /passwd scrub the password out of any error before it travels — an error is the one value that reaches a log, an HTTP body and a terminal at once.
Verify returns a three-valued Decision instead of a bool. Denied and Insufficient are not the same thing and the wire has separate codes for them: an authenticated client short of a capability was being told 401 with a challenge, which invites it to retry the same password forever. Now 403, and deliberately without a challenge. A harness test pinned the old behaviour and was updated with the reason. (user: dmestas, size: 17484) [annotate] [blame] [check-ins using]
# 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.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.grant `POST /api/v1/accounts/grant` — command, admin Grant an account read, write or admin on one repository. Input: | field | type | rules | |---|---|---| | `name` | string | `required,max=64` | | `repo` | string | `required,max=64` | | `role` | string | `required,oneof=read\|write\|admin` | Output fields: - `name` (string) - `repo` (string) - `role` (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}, 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.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) ## archive.get `GET /api/v1/r/{repo}/archive/{version}` — query, anonymous Download a check-in's files as a reproducible gzip tar archive. Input: | field | type | rules | |---|---|---| | `repo` | string | `required,max=64` | | `version` | string | `max=100` | Output fields: - `name` (string) - `size` (integer) - `content` (string) ## 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) ## 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` | Output fields: - `branches` (array of object {name: string, tip: string, date: string}) - `count` (integer) ## 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` | Output fields: - `from` (string) - `to` (string) - `files` (array of object {status: string, name: string}) - `unified` (string) - `note` (string) ## 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` | Output fields: - `hash` (string) - `ci` (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}) ## 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` | Output fields: - `runs` (array of object {runId: string, checkin: string, workflow: string, pipeline: string, status: string, started: string}) - `count` (integer) ## 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` | Output fields: - `hash` (string) - `path` (string) - `size` (integer) - `binary` (boolean) - `content` (string) - `html` (string) ## 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.status `GET /api/v1/fleet` — query, admin Report every served store's ingest health. Output fields: - `repos` (array of object {name: string, code: string, checkins: integer, indexing: boolean, health: object {lastOk: string, roundSeconds: number, lastCount: integer, total: integer, fallbacks: integer, lastErr: string, lastErrAt: string, failures: integer}}) - `count` (integer) - `unhealthy` (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) - `posts` (array of object {hash: string, parent: string, date: string, user: string, html: string}) ## 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` | Output fields: - `threads` (array of object {root: string, title: string, posts: integer, updated: string, user: string}) - `count` (integer) ## forum.post `POST /api/v1/r/{repo}/forum` — command, token Open a new forum thread. Input: | field | type | rules | |---|---|---| | `repo` | string | `required,max=64` | | `title` | string | `required,max=200` | | `body` | string | `required,max=1048576` | | `mimetype` | string | `max=64` | Output fields: - `hash` (string) ## forum.reply `POST /api/v1/r/{repo}/forum/{root}` — command, token Reply to a forum post within its thread. Input: | field | type | rules | |---|---|---| | `repo` | string | `required,max=64` | | `root` | string | `required,min=4,max=64` | | `body` | string | `required,max=1048576` | | `mimetype` | string | `max=64` | Output fields: - `hash` (string) ## 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) ## mirror.delete `POST /api/v1/mirrors/delete` — command, admin Stop mirroring a store and forget its upstream. 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}) - `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}) ## 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` | | `create` | boolean | — | | `clone` | string | `max=2048` | | `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: - `checkins` (integer) - `wikiPages` (integer) - `tickets` (integer) - `forumThreads` (integer) - `technotes` (integer) ## repo.list `GET /api/v1/repos` — query, anonymous List the served repositories with their tip check-in. Output fields: - `repos` (array of object {name: string, description: string, checkins: integer, tipDate: string, tipComment: string, indexing: boolean}) - `count` (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) ## 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` | | `limit` | integer | `min=0,max=200,default=50` | Output fields: - `results` (array of object {kind: string, ref: string, label: string, snippet: 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` | 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. Input: | field | type | rules | |---|---|---| | `repo` | string | `required,max=64` | | `event` | string | `required,max=64` | Output fields: - `event` (string) - `date` (string) - `user` (string) - `comment` (string) - `body` (string) - `html` (string) ## 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}) - `count` (integer) ## 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, src: string, date: string, user: 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` | Output fields: - `tickets` (array of object {id: string, title: string, status: string, updated: string}) - `count` (integer) - `next` (string) ## ticket.new `POST /api/v1/r/{repo}/ticket` — command, token Open a new ticket. Input: | field | type | rules | |---|---|---| | `repo` | string | `required,max=64` | | `title` | string | `required,max=200` | | `status` | string | `max=40` | | `comment` | string | `max=65536` | 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. 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` | Output fields: - `hash` (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` | | `y` | string | `oneof=all\|ci\|w\|t\|e\|f` | 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}) - `count` (integer) - `next` (string) ## 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) - `path` (string) - `entries` (array of object {name: string, path: string, dir: boolean, blob: string, files: integer, size: integer, exec: boolean, link: boolean, binary: boolean}) - `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) ## 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}) - `count` (integer) ## wiki.save `POST /api/v1/r/{repo}/wiki/{name}` — command, token Record a wiki page edit (creates the page on first save). Input: | field | type | rules | |---|---|---| | `repo` | string | `required,max=64` | | `name` | string | `required,max=100` | | `body` | string | `required,max=1048576` | | `mimetype` | string | `max=64` | Output fields: - `hash` (string)