{"openapi":"3.1.0","info":{"title":"Garnish HTTP API","summary":"Markdown documents that double as HTTP endpoints.","description":"Garnish exposes every document at two-and-a-half URL shapes:\n\n1. **Surface URL** — `/d/{id}` or `/u/{username}/{slug}`. Browsers see HTML; agents (curl, application/json, text/markdown) see the document. Same URL, content-negotiated.\n2. **API URL** — `/api/doc/d/{id}` / `/api/doc/u/{username}/{slug}`. Same operations, no content negotiation.\n3. **Subresources** — `/.../head`, `/.../versions`, `/.../patch`, `/.../reset-token`, `/.../comments`, `/.../invitees`.\n\nReading is free; writing requires a `write_token` either as the `write_token` query param or an `Authorization: Bearer` header. Every save appends an immutable version row — past versions are always recoverable.\n\nFor the full error envelope catalogue, see [/api/error-reference](/api/error-reference).","version":"1.0.0","contact":{"name":"Garnish","url":"https://garnish.preview.manicule.dev/api/auth/callback"},"license":{"name":"Internal"}},"servers":[{"url":"https://garnish.preview.manicule.dev/api/auth/callback"}],"tags":[{"name":"Documents","description":"Read, write, patch, and delete documents."},{"name":"Versions","description":"Append-only version history and restore."},{"name":"Realtime","description":"Lightweight head polling for collaboration."},{"name":"Comments","description":"Anchored, threaded comments — anonymous, user, or agent authored."},{"name":"Invitees","description":"Per-document ACL for owned docs."},{"name":"Meta","description":"Reference + spec endpoints."}],"security":[{"writeTokenQuery":[]},{"writeTokenHeader":[]},{}],"paths":{"/api/new":{"post":{"summary":"Create a new anonymous document","tags":["Documents"],"description":"No auth required. Returns the public URL plus a freshly minted write_token. Send `Accept: application/json` to get JSON; otherwise the response is a 303 redirect to the edit URL.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"contentMd":{"type":"string"},"title":{"type":"string"}}}},"text/markdown":{"schema":{"type":"string"}}}},"responses":{"200":{"description":"Doc created (JSON path).","content":{"application/json":{"schema":{"type":"object","required":["id","viewUrl","editUrl","writeToken"],"properties":{"id":{"type":"string"},"viewUrl":{"type":"string","format":"uri"},"editUrl":{"type":"string","format":"uri"},"writeToken":{"type":"string"}}}}}},"303":{"description":"Redirect to edit URL (HTML/browser path)."}}},"get":{"summary":"Create-and-redirect (browser convenience)","tags":["Documents"],"description":"Equivalent to a POST with no body, then 303-redirects to the edit URL. Useful from `<a href>` links.","responses":{"303":{"description":"Redirect to edit URL."}}}},"/d/{id}":{"get":{"summary":"Open the document","tags":["Documents"],"description":"Content-negotiated. Browsers receive the editor HTML page; curl/JSON/markdown clients receive the document body. URL extensions `.md/.txt/.json/.html/.pdf/.lines` and `?format=` are accepted aliases.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"format","in":"query","schema":{"type":"string","enum":["md","markdown","json","txt","html","print","numbered"]},"description":"Force a response format regardless of Accept header. `.md/.txt/.json/.html/.pdf/.lines` URL extensions are aliases."},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"responses":{"200":{"description":"Editor HTML or document body.","content":{"text/html":{"schema":{"type":"string"}},"text/markdown":{"schema":{"type":"string"}},"application/json":{"schema":{"$ref":"#/components/schemas/Doc"}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Write (surface URL — rewrites to /api/doc/...)","tags":["Documents"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contentMd"],"properties":{"contentMd":{"type":"string"},"baseVersion":{"type":"integer"},"sessionId":{"type":"string"}}}},"text/markdown":{"schema":{"type":"string"}}}},"responses":{"200":{"description":"Wrote new version."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/u/{username}/{slug}":{"get":{"summary":"Open the document (owned)","tags":["Documents"],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","schema":{"type":"string","enum":["md","markdown","json","txt","html","print","numbered"]},"description":"Force a response format regardless of Accept header. `.md/.txt/.json/.html/.pdf/.lines` URL extensions are aliases."},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"responses":{"200":{"description":"Editor HTML or document body."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/doc/d/{id}":{"get":{"summary":"Read a document","tags":["Documents"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"format","in":"query","schema":{"type":"string","enum":["md","markdown","json","txt","html","print","numbered"]},"description":"Force a response format regardless of Accept header. `.md/.txt/.json/.html/.pdf/.lines` URL extensions are aliases."},{"name":"download","in":"query","schema":{"type":"string","enum":["1"]},"description":"Add Content-Disposition: attachment."},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"responses":{"200":{"description":"Document body in the requested format.","content":{"text/markdown":{"schema":{"type":"string","description":"Markdown source with optional YAML front-matter."}},"application/json":{"schema":{"$ref":"#/components/schemas/Doc"}},"text/html":{"schema":{"type":"string"}},"text/plain":{"schema":{"type":"string"}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Write a new full-document version","tags":["Documents"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contentMd"],"properties":{"contentMd":{"type":"string"},"title":{"type":"string"},"sessionId":{"type":"string","description":"Opaque id grouping rapid autosaves; appears in the version row."},"baseVersion":{"type":"integer","minimum":0,"description":"Optimistic concurrency: pass the seq your edit is based on. If the server head moved, returns 409."}}}},"text/markdown":{"schema":{"type":"string","description":"Raw markdown body. No concurrency check."}}}},"responses":{"200":{"description":"Wrote new version.","content":{"application/json":{"schema":{"type":"object","required":["ok","version","head"],"properties":{"ok":{"type":"boolean"},"version":{"type":"integer"},"head":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}},"patch":{"summary":"Update document settings (title, visibility, fonts)","tags":["Documents"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"visibility":{"type":"string","enum":["public","unlisted","private"]},"title":{"type":"string"},"allowAnonComments":{"type":"boolean"},"allowWriteViaToken":{"type":"boolean"},"font":{"type":"string","enum":["sans","serif","mono"]},"size":{"type":"string","enum":["s","m","l"]}}}}}},"responses":{"200":{"description":"Settings updated."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Soft-delete a document","tags":["Documents"],"description":"Marks `deletedAt`. Reads 404 immediately; versions and comments are retained for recovery. There is no hard delete via API.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"responses":{"200":{"description":"Soft-deleted."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/doc/d/{id}/head":{"get":{"summary":"Cheap version-head poll","tags":["Realtime"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."}],"description":"Lightweight endpoint for clients to detect remote changes. Cache-Control: no-store.","responses":{"200":{"description":"Current head state.","content":{"application/json":{"schema":{"type":"object","required":["head","updatedAt"],"properties":{"head":{"type":"integer","minimum":0},"updatedAt":{"type":"string","format":"date-time"},"lastActor":{"type":["string","null"]},"lastActorKind":{"type":["string","null"],"enum":["user","agent","anon",null]}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/doc/d/{id}/versions":{"get":{"summary":"List all versions","tags":["Versions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."}],"responses":{"200":{"description":"Append-only version chain, newest first.","content":{"application/json":{"schema":{"type":"object","required":["versions","head"],"properties":{"versions":{"type":"array","items":{"$ref":"#/components/schemas/Version"}},"head":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Restore a past version","tags":["Versions"],"description":"Appends a new version whose content is the chosen historical seq. No data is overwritten.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["seq"],"properties":{"seq":{"type":"integer","minimum":1}}}}}},"responses":{"200":{"description":"Restored.","content":{"application/json":{"schema":{"type":"object","required":["ok","version","head","restoredFrom"],"properties":{"ok":{"type":"boolean"},"version":{"type":"integer"},"head":{"type":"integer"},"restoredFrom":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/doc/d/{id}/patch":{"post":{"summary":"Apply line-range edits","tags":["Documents"],"description":"Agent-friendly partial edit. Send an array of `{startLine, endLine, content}` operations; the server applies them highest-first so indices stay valid. Use `?format=numbered` or `.lines` to read with line markers.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["edits"],"properties":{"edits":{"type":"array","minItems":1,"maxItems":500,"items":{"$ref":"#/components/schemas/LineEdit"}},"baseVersion":{"type":"integer","minimum":0},"sessionId":{"type":"string"}}},"examples":{"replaceAndDelete":{"summary":"Replace line 3, delete lines 10-12","value":{"baseVersion":12,"edits":[{"startLine":3,"endLine":3,"content":"## Updated heading"},{"startLine":10,"endLine":12,"content":""}]}},"insertAtEnd":{"summary":"Insert a new paragraph at the end (doc has 47 lines)","value":{"edits":[{"startLine":48,"endLine":47,"content":"\nNew paragraph."}]}}}}}},"responses":{"200":{"description":"Patch applied.","content":{"application/json":{"schema":{"type":"object","required":["ok","version","head","bytes","lines"],"properties":{"ok":{"type":"boolean"},"version":{"type":"integer"},"head":{"type":"integer"},"bytes":{"type":"integer"},"lines":{"type":"integer"}}}}}},"400":{"description":"Invalid edit. Error codes: `invalid_edit`, `out_of_bounds`, `overlapping_edits`. Hint includes the offending edit index and the doc's current line count when applicable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/doc/d/{id}/reset-token":{"post":{"summary":"Rotate the write_token","tags":["Documents"],"description":"Invalidates the current write_token and returns a new one. Owner-only on owned docs; current token-holder on anonymous docs. Read access is unaffected.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"responses":{"200":{"description":"Token rotated.","content":{"application/json":{"schema":{"type":"object","required":["ok","writeToken","editUrl"],"properties":{"ok":{"type":"boolean"},"writeToken":{"type":"string"},"editUrl":{"type":"string","format":"uri"},"message":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/doc/u/{username}/{slug}":{"get":{"summary":"Read a document","tags":["Documents"],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","schema":{"type":"string","enum":["md","markdown","json","txt","html","print","numbered"]},"description":"Force a response format regardless of Accept header. `.md/.txt/.json/.html/.pdf/.lines` URL extensions are aliases."},{"name":"download","in":"query","schema":{"type":"string","enum":["1"]},"description":"Add Content-Disposition: attachment."},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"responses":{"200":{"description":"Document body in the requested format.","content":{"text/markdown":{"schema":{"type":"string","description":"Markdown source with optional YAML front-matter."}},"application/json":{"schema":{"$ref":"#/components/schemas/Doc"}},"text/html":{"schema":{"type":"string"}},"text/plain":{"schema":{"type":"string"}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Write a new full-document version","tags":["Documents"],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contentMd"],"properties":{"contentMd":{"type":"string"},"title":{"type":"string"},"sessionId":{"type":"string","description":"Opaque id grouping rapid autosaves; appears in the version row."},"baseVersion":{"type":"integer","minimum":0,"description":"Optimistic concurrency: pass the seq your edit is based on. If the server head moved, returns 409."}}}},"text/markdown":{"schema":{"type":"string","description":"Raw markdown body. No concurrency check."}}}},"responses":{"200":{"description":"Wrote new version.","content":{"application/json":{"schema":{"type":"object","required":["ok","version","head"],"properties":{"ok":{"type":"boolean"},"version":{"type":"integer"},"head":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}},"patch":{"summary":"Update document settings (title, visibility, fonts)","tags":["Documents"],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"visibility":{"type":"string","enum":["public","unlisted","private"]},"title":{"type":"string"},"allowAnonComments":{"type":"boolean"},"allowWriteViaToken":{"type":"boolean"},"font":{"type":"string","enum":["sans","serif","mono"]},"size":{"type":"string","enum":["s","m","l"]}}}}}},"responses":{"200":{"description":"Settings updated."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Soft-delete a document","tags":["Documents"],"description":"Marks `deletedAt`. Reads 404 immediately; versions and comments are retained for recovery. There is no hard delete via API.","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"responses":{"200":{"description":"Soft-deleted."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/doc/u/{username}/{slug}/head":{"get":{"summary":"Cheap version-head poll","tags":["Realtime"],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"description":"Lightweight endpoint for clients to detect remote changes. Cache-Control: no-store.","responses":{"200":{"description":"Current head state.","content":{"application/json":{"schema":{"type":"object","required":["head","updatedAt"],"properties":{"head":{"type":"integer","minimum":0},"updatedAt":{"type":"string","format":"date-time"},"lastActor":{"type":["string","null"]},"lastActorKind":{"type":["string","null"],"enum":["user","agent","anon",null]}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/doc/u/{username}/{slug}/versions":{"get":{"summary":"List all versions","tags":["Versions"],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Append-only version chain, newest first.","content":{"application/json":{"schema":{"type":"object","required":["versions","head"],"properties":{"versions":{"type":"array","items":{"$ref":"#/components/schemas/Version"}},"head":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Restore a past version","tags":["Versions"],"description":"Appends a new version whose content is the chosen historical seq. No data is overwritten.","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["seq"],"properties":{"seq":{"type":"integer","minimum":1}}}}}},"responses":{"200":{"description":"Restored.","content":{"application/json":{"schema":{"type":"object","required":["ok","version","head","restoredFrom"],"properties":{"ok":{"type":"boolean"},"version":{"type":"integer"},"head":{"type":"integer"},"restoredFrom":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/doc/u/{username}/{slug}/patch":{"post":{"summary":"Apply line-range edits","tags":["Documents"],"description":"Agent-friendly partial edit. Send an array of `{startLine, endLine, content}` operations; the server applies them highest-first so indices stay valid. Use `?format=numbered` or `.lines` to read with line markers.","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["edits"],"properties":{"edits":{"type":"array","minItems":1,"maxItems":500,"items":{"$ref":"#/components/schemas/LineEdit"}},"baseVersion":{"type":"integer","minimum":0},"sessionId":{"type":"string"}}},"examples":{"replaceAndDelete":{"summary":"Replace line 3, delete lines 10-12","value":{"baseVersion":12,"edits":[{"startLine":3,"endLine":3,"content":"## Updated heading"},{"startLine":10,"endLine":12,"content":""}]}},"insertAtEnd":{"summary":"Insert a new paragraph at the end (doc has 47 lines)","value":{"edits":[{"startLine":48,"endLine":47,"content":"\nNew paragraph."}]}}}}}},"responses":{"200":{"description":"Patch applied.","content":{"application/json":{"schema":{"type":"object","required":["ok","version","head","bytes","lines"],"properties":{"ok":{"type":"boolean"},"version":{"type":"integer"},"head":{"type":"integer"},"bytes":{"type":"integer"},"lines":{"type":"integer"}}}}}},"400":{"description":"Invalid edit. Error codes: `invalid_edit`, `out_of_bounds`, `overlapping_edits`. Hint includes the offending edit index and the doc's current line count when applicable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/doc/u/{username}/{slug}/reset-token":{"post":{"summary":"Rotate the write_token","tags":["Documents"],"description":"Invalidates the current write_token and returns a new one. Owner-only on owned docs; current token-holder on anonymous docs. Read access is unaffected.","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"responses":{"200":{"description":"Token rotated.","content":{"application/json":{"schema":{"type":"object","required":["ok","writeToken","editUrl"],"properties":{"ok":{"type":"boolean"},"writeToken":{"type":"string"},"editUrl":{"type":"string","format":"uri"},"message":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/d/{id}/comments":{"get":{"summary":"List comments on a document","tags":["Comments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."}],"responses":{"200":{"description":"All comments — resolved threads included.","content":{"application/json":{"schema":{"type":"object","required":["comments"],"properties":{"comments":{"type":"array","items":{"$ref":"#/components/schemas/Comment"}}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Create a comment (or reply to a thread)","tags":["Comments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"description":"Comment author identity: signed-in owner/invitee → user; agent if a matching write_token is supplied; otherwise the anonymous-cookie identity. `threadId` omitted starts a new thread.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string","minLength":1,"maxLength":10000},"threadId":{"type":"string"},"parentId":{"type":"string"},"anchor":{"type":["object","null"],"properties":{"from":{"type":"integer","minimum":0},"to":{"type":"integer","minimum":1},"quote":{"type":"string","maxLength":10000}}}}}}}},"responses":{"200":{"description":"Comment created.","content":{"application/json":{"schema":{"type":"object","required":["comment"],"properties":{"comment":{"$ref":"#/components/schemas/Comment"}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/d/{id}/comments/{commentId}":{"patch":{"summary":"Edit body or toggle resolved on a comment","tags":["Comments"],"description":"Editing the body requires being the comment author. Toggling resolved requires owner OR the comment author.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"commentId","in":"path","required":true,"schema":{"type":"string"}},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":10000},"resolved":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Delete a comment","tags":["Comments"],"description":"Comment author OR doc owner only.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"commentId","in":"path","required":true,"schema":{"type":"string"}},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"responses":{"200":{"description":"Deleted."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/d/{id}/invitees":{"get":{"summary":"List ACL invitees","tags":["Invitees"],"description":"Owner or any invitee (signed in).","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."}],"responses":{"200":{"description":"Invitee list.","content":{"application/json":{"schema":{"type":"object","required":["invitees"],"properties":{"invitees":{"type":"array","items":{"type":"object","required":["userId","email","username","role"],"properties":{"userId":{"type":"string"},"email":{"type":"string","format":"email"},"username":{"type":"string"},"displayName":{"type":["string","null"]},"role":{"type":"string","enum":["owner","editor","commenter","viewer"]}}}}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Invite a user by email","tags":["Invitees"],"description":"Inviter must be the owner or an existing invitee. The invitee must already have a Garnish account.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["editor","commenter","viewer"],"default":"editor"}}}}}},"responses":{"200":{"description":"Invitee added."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"404 — Email is not registered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"user_not_found","message":"Email is not registered.","hint":"See /api/error-reference for the full envelope spec."}}}}}},"delete":{"summary":"Remove an invitee","tags":["Invitees"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Public id (e.g. `x7k2qp9`)."},{"name":"userId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removed."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/u/{username}/{slug}/comments":{"get":{"summary":"List comments on a document","tags":["Comments"],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"All comments — resolved threads included.","content":{"application/json":{"schema":{"type":"object","required":["comments"],"properties":{"comments":{"type":"array","items":{"$ref":"#/components/schemas/Comment"}}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Create a comment (or reply to a thread)","tags":["Comments"],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"description":"Comment author identity: signed-in owner/invitee → user; agent if a matching write_token is supplied; otherwise the anonymous-cookie identity. `threadId` omitted starts a new thread.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string","minLength":1,"maxLength":10000},"threadId":{"type":"string"},"parentId":{"type":"string"},"anchor":{"type":["object","null"],"properties":{"from":{"type":"integer","minimum":0},"to":{"type":"integer","minimum":1},"quote":{"type":"string","maxLength":10000}}}}}}}},"responses":{"200":{"description":"Comment created.","content":{"application/json":{"schema":{"type":"object","required":["comment"],"properties":{"comment":{"$ref":"#/components/schemas/Comment"}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/u/{username}/{slug}/comments/{commentId}":{"patch":{"summary":"Edit body or toggle resolved on a comment","tags":["Comments"],"description":"Editing the body requires being the comment author. Toggling resolved requires owner OR the comment author.","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"commentId","in":"path","required":true,"schema":{"type":"string"}},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":10000},"resolved":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Delete a comment","tags":["Comments"],"description":"Comment author OR doc owner only.","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"commentId","in":"path","required":true,"schema":{"type":"string"}},{"name":"write_token","in":"query","schema":{"type":"string"},"description":"URL-driven write authorization. Alternative: `Authorization: Bearer <token>` header."}],"responses":{"200":{"description":"Deleted."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/u/{username}/{slug}/invitees":{"get":{"summary":"List ACL invitees","tags":["Invitees"],"description":"Owner or any invitee (signed in).","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Invitee list.","content":{"application/json":{"schema":{"type":"object","required":["invitees"],"properties":{"invitees":{"type":"array","items":{"type":"object","required":["userId","email","username","role"],"properties":{"userId":{"type":"string"},"email":{"type":"string","format":"email"},"username":{"type":"string"},"displayName":{"type":["string","null"]},"role":{"type":"string","enum":["owner","editor","commenter","viewer"]}}}}}}}}},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Invite a user by email","tags":["Invitees"],"description":"Inviter must be the owner or an existing invitee. The invitee must already have a Garnish account.","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["editor","commenter","viewer"],"default":"editor"}}}}}},"responses":{"200":{"description":"Invitee added."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"404 — Email is not registered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"user_not_found","message":"Email is not registered.","hint":"See /api/error-reference for the full envelope spec."}}}}}},"delete":{"summary":"Remove an invitee","tags":["Invitees"],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"userId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removed."},"400":{"$ref":"#/components/responses/InvalidBody"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/error-reference":{"get":{"summary":"Get the live error envelope catalogue","tags":["Meta"],"description":"Machine-readable reference: each error code, status, meaning, plus token/URL shapes and content-negotiation rules.","responses":{"200":{"description":"Reference document.","content":{"application/json":{}}}}}},"/openapi.json":{"get":{"summary":"This OpenAPI spec","tags":["Meta"],"responses":{"200":{"description":"OpenAPI 3.1 document.","content":{"application/json":{}}}}}},"/api/auth/signin":{"get":{"summary":"Begin WorkOS sign-in","tags":["Meta"],"description":"302 redirect to the WorkOS hosted sign-in.","parameters":[{"name":"next","in":"query","schema":{"type":"string"},"description":"Path to land on after sign-in."}],"responses":{"302":{"description":"Redirect to WorkOS."}}}},"/api/auth/signup":{"get":{"summary":"Begin WorkOS sign-up","tags":["Meta"],"responses":{"302":{"description":"Redirect to WorkOS."}}}},"/api/auth/callback":{"get":{"summary":"WorkOS OAuth callback","tags":["Meta"],"description":"Handled by WorkOS AuthKit. Not intended for direct use.","responses":{"302":{"description":"Redirect to landing or `next`."}}}},"/api/auth/logout":{"get":{"summary":"Sign out and redirect to /logout","tags":["Meta"],"responses":{"302":{"description":"Redirect after clearing session."}}}}},"components":{"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"Stable machine code (e.g. not_found)."},"message":{"type":"string","description":"Human-readable summary."},"hint":{"type":"string","description":"Actionable suggestion."},"details":{}}},"Doc":{"type":"object","required":["title","updatedAt","visibility","allowWriteViaToken","contentMd"],"properties":{"id":{"type":"string"},"owner":{"type":"string"},"slug":{"type":"string"},"title":{"type":"string"},"updatedAt":{"type":"string","format":"date-time"},"visibility":{"type":"string","enum":["public","unlisted","private"]},"allowWriteViaToken":{"type":"boolean"},"contentMd":{"type":"string"}}},"Version":{"type":"object","required":["seq","createdAt","actorKind","bytes"],"properties":{"seq":{"type":"integer","minimum":1},"createdAt":{"type":"string","format":"date-time"},"actorKind":{"type":"string","enum":["user","agent","anon"]},"actorLabel":{"type":["string","null"]},"sessionId":{"type":["string","null"]},"bytes":{"type":"integer","minimum":0}}},"LineEdit":{"type":"object","required":["startLine","endLine","content"],"properties":{"startLine":{"type":"integer","minimum":1,"description":"First line to replace, 1-indexed and inclusive."},"endLine":{"type":"integer","minimum":0,"description":"Last line to replace, 1-indexed and inclusive. Set to startLine - 1 to pure-insert before startLine."},"content":{"type":"string","description":"Replacement text. May contain newlines. Empty string deletes the range."}}},"Comment":{"type":"object","required":["id","threadId","body","authorKind","authorLabel","resolved","createdAt"],"properties":{"id":{"type":"string"},"threadId":{"type":"string"},"parentId":{"type":["string","null"]},"body":{"type":"string"},"anchor":{"type":["object","null"],"properties":{"from":{"type":"integer","minimum":0},"to":{"type":"integer","minimum":0},"quote":{"type":"string"}}},"authorKind":{"type":"string","enum":["user","agent","anon"]},"authorId":{"type":["string","null"]},"authorLabel":{"type":["string","null"]},"resolved":{"type":"boolean"},"resolvedAt":{"type":["string","null"],"format":"date-time"},"resolvedBy":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}}}},"securitySchemes":{"writeTokenQuery":{"type":"apiKey","in":"query","name":"write_token","description":"URL-driven write authorization. Pass the doc's writeToken as ?write_token=…"},"writeTokenHeader":{"type":"http","scheme":"bearer","description":"Same write_token, sent as Authorization: Bearer."}},"responses":{"NotFound":{"description":"404 — Resource not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"not_found","message":"Resource not found.","hint":"See /api/error-reference for the full envelope spec."}}}},"Forbidden":{"description":"403 — Insufficient permissions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"forbidden","message":"Insufficient permissions.","hint":"See /api/error-reference for the full envelope spec."}}}},"InvalidBody":{"description":"400 — Body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"invalid_body","message":"Body failed validation.","hint":"See /api/error-reference for the full envelope spec."}}}},"Conflict":{"description":"409 — Stale baseVersion — re-read and reapply.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"conflict","message":"Stale baseVersion — re-read and reapply.","hint":"See /api/error-reference for the full envelope spec."}}}}}}}