/api/wiki
Read and edit the open SynC Standards wiki. Reads are public — no API key needed. Writes require the relevant wiki permission.
GET /api/wiki/librariesList wiki standard libraries with standard count.
GET /api/wiki/libraries/:slugGet a wiki library by slug with its full category tree and standards list. Supports search.
| Name | In | Type | Description |
|---|---|---|---|
| slug | path | string | Library URL slug |
| search | query | string | Filter standards by title or description (ILIKE match) |
GET /api/wiki/standards/:librarySlug/:standardSlugGet a wiki standard by library and standard slug, including current revision content (markdown).
| Name | In | Type | Description |
|---|---|---|---|
| librarySlug | path | string | Library URL slug |
| standardSlug | path | string | Standard URL slug |
GET /api/wiki/standards/:id/revisionsList revision history for a wiki standard. Paginated.
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID |
| limit | query | integer | Results per page (default 50) |
| offset | query | integer | Offset for pagination (default 0) |
GET /api/wiki/standards/:id/revisions/:revIdGet a specific wiki standard revision with full markdown content.
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID |
| revId | path | string | Revision UUID |
GET /api/wiki/standards/:id/diff/:fromRev/:toRevCompare two revisions with computed diff. Returns both full documents, a structured hunks array (added/removed/unchanged lines), stats (added/removed counts), and a unified diff patch string.
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID |
| fromRev | path | string | Source revision UUID |
| toRev | path | string | Target revision UUID |
GET /api/wiki/standards/:id/citationsList the documents that cite this standard ("what cites this?"), from the compiled citation index. Rows carry citingEntityType: "wiki_standard" (a wiki document, addressed by librarySlug/slug) or "standard" (a main-site standard, whose citingEntityId is an INTEGER for /standard/:id). Paginated over DOCUMENTS. Returns two distinct counts: citingDocumentCount (distinct citing documents) and citationOccurrenceCount (individual references — one document may cite the standard many times). Both counts and the rows are viewer-scoped, so a caller who cannot read a citing document neither sees it nor sees it counted; the counts may therefore be lower than the unfiltered totals. A third count, privateCitationOccurrenceCount, aggregates the references made by main-site standards the caller may not read: it says such citers exist and never identifies them. A standard the caller cannot read returns 404, identically to one that does not exist.
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID (the CITED standard) |
| limit | query | integer | Citing documents per page, 1-100 (default 25) |
| offset | query | integer | Offset for pagination (default 0) |
GET /api/wiki/standards/:id/adoptedList adopted (stable) revisions for a wiki standard.
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID |
GET /api/wiki/standards/:id/adopted/:revNumGet a specific adopted revision by revision number.
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID |
| revNum | path | integer | Adopted revision number |
POST /api/wiki/standards/validateValidate in-flight standard markdown without saving. Returns blocking and advisory findings (each with a rule, severity, line, field, and message). Content that fails validation still returns 200 with hasBlocking:true — reporting failures is the job of this endpoint, not a 4xx. Cross-reference findings are advisory and appear only when categoryId or standardId is supplied; they never block. When standardId is supplied it also returns impact: the citation consequences of this edit, viewer-scoped and never blocking. impact.removedClauses lists each cited clause the edit disturbs with a disposition of removed (no successor could be traced — NOT a proven deletion), relocated (the requirement is now at impact.removedClauses[].movedTo), or reoccupied (the address survives but numbers a different requirement now).
Required permission: wiki:standard:create
content — stringcategoryId — stringstandardId — stringcategoryId/standardId are used only to resolve the category path and run the DB-aware cross-reference advisory.
POST /api/wiki/standardsCreate a new wiki standard. Content is markdown, optionally with YAML frontmatter. If frontmatter is missing, it is auto-generated from the body params (title, description, category). Frontmatter is the source of truth for metadata — if present, it overrides body params.
Required permission: wiki:standard:create
librarySlug — stringcategoryId — stringtitle — stringcontent — stringslug — stringdescription — stringeditSummary — stringtag_ids — array of tag UUIDs to attach to the new standard (must be an array; anything else is a 400)If content includes YAML frontmatter (---\ntitle: ...\n---), those values take precedence over body params for title, description, and category. If content has no frontmatter, one is prepended automatically from the body params. Every stored revision is guaranteed to have frontmatter.
PUT /api/wiki/standards/:idEdit a wiki standard. Uses baseRevisionId for conflict detection (returns 409 if outdated).
Required permission: wiki:standard:edit
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID |
content — stringbaseRevisionId — stringeditSummary — stringtag_ids — array of tag UUIDs for the revision this edit creates (must be an array; anything else is a 400)baseRevisionId is required for conflict detection. If another user edited since your base, returns 409. Unknown fields are rejected — metadata changes go in the YAML frontmatter (title, description, category). tag_ids attaches tags to the NEW revision this edit creates, and it is the complete set for that revision — send the full list, not just the tags you are adding. Omitting tag_ids carries the base revision's tags forward unchanged; sending an empty array clears them. An edit that says nothing about tags therefore keeps them.
POST /api/wiki/standards/:id/revert/:revIdRevert a wiki standard to a previous revision. Creates a new revision carrying the old content AND the old tag set.
Required permission: wiki:standard:edit
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID |
| revId | path | string | Revision UUID to revert to |
GET /api/wiki/standards/search-usersSearch users for wiki role assignment. Returns masked emails for privacy. Moderators only.
Required permission: wiki:standard:moderate
| Name | In | Type | Description |
|---|---|---|---|
| query | query | string | Search by name or email (min 2 chars). Accepts q as well. Under 2 chars returns an empty list, not an error. |
GET /api/wiki/standards/:id/rolesList wiki role assignments for a standard.
Required permission: wiki:standard:moderate
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID |
POST /api/wiki/standards/:id/rolesAssign a wiki role (adopter or moderator) to a user for a specific standard.
Required permission: wiki:standard:moderate
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID |
userId — integerrole — stringValid roles: adopter, moderator. User must be approved.
DELETE /api/wiki/standards/:id/roles/:roleIdRemove a wiki role assignment.
Required permission: wiki:standard:moderate
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID |
| roleId | path | string | Role assignment UUID |
POST /api/wiki/standards/:id/cancel-reviewCancel an in-progress adoption review. Only the review starter or a moderator can cancel.
Required permission: wiki:standard:adopt
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID |
POST /api/wiki/standards/:id/preview-adoptionRead-only dry-run of an adoption. Reports whether the revision would parse and adopt cleanly (isParseable, errors, elementCount, datasheetCount, resolvedCategoryPath, categoryPathError, isNew) without performing any writes. A revision that fails to parse returns 200 with isParseable:false, not an error.
Required permission: wiki:standard:adopt
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Standard UUID |
sourceRevisionId — stringsourceRevisionId is the revision being previewed; omit it to preview the latest revision (by revision_number).
POST /api/wiki/categoriesCreate a new wiki category in a library.
Required permission: wiki:category:create
librarySlug — stringname — stringparentId — stringslug — stringdescription — stringsortOrder — integerPUT /api/wiki/categories/:idUpdate a wiki category.
Required permission: wiki:category:update
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Category UUID |
name — stringslug — stringdescription — stringsortOrder — integerparentId — string — UUID of the category this one goes under; null makes it top-levelparentId re-parents the category. Omitting it leaves the current parent alone — it is not a reset, so an update that means to keep the nesting does not need to resend it.
DELETE /api/wiki/categories/:idDelete a wiki category. Standards filed directly in it become uncategorized. Sub-categories are NOT handled automatically — if the category has any, you must say what happens to them via childDisposition or the call is refused.
Required permission: wiki:category:delete
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Category UUID |
childDisposition — string — reassign | promote | cascade; REQUIRED when the category has sub-categorieschildReassignTo — string — top-level category UUID; REQUIRED when childDisposition is "reassign"A category with NO sub-categories deletes with an empty body. With sub-categories, childDisposition is REQUIRED: the call returns 400 with requiresChildDisposition:true and the list of children, and that listing is the read to make the choice from. "reassign" re-parents them under childReassignTo, which must be a TOP-LEVEL category in the SAME library; "promote" makes them top-level; "cascade" DELETES the child categories and leaves their standards uncategorized (it destroys categories, never standards). Any other value is a 400. Deletion is permanent — there is no undo.
GET /api/wiki/search-contentSearch the public wiki corpus and return matching clauses with their standard, address, and snippet. No auth, no project scope — the open-corpus twin of GET /api/standards/search-content. The response carries a search_event_id (uuid, or null when logging failed) purely so the browser can report a click back; API callers can ignore it.
| Name | In | Type | Description |
|---|---|---|---|
| query | query | string | Search text (required) |
| limit | query | integer | Max hits, 1-100 (default 20) |
GET /api/wiki/standards/:librarySlug/:standardSlug/elementsGet the standard's current revision as STRUCTURED elements (address / content / toc_content / depth / attributes) rather than raw markdown, by running the shared adoption parser over the stored content. Same content the markdown read and the public wiki page already expose — this is the parsed form, so a client does not have to reimplement the parser to address individual clauses.
| Name | In | Type | Description |
|---|---|---|---|
| librarySlug | path | string | Library URL slug |
| standardSlug | path | string | Standard URL slug |
GET /api/wiki/standards/:librarySlug/:standardSlug/titleGet just { title } for a wiki standard. A deliberately tiny, cacheable read for callers that need the display name without pulling the whole document.
GET /api/wiki/standards/:id/errataFull errata audit trail for a wiki standard — active AND withdrawn, with their affected adopted revisions. Public.
| Name | In | Type | Description |
|---|---|---|---|
| id | path | string | Wiki standard UUID |
GET /api/wiki/errata/:errataIdGet one erratum in full, including every adopted revision and element address it affects. Public.
GET /api/wiki/adopted-revisions/:adoptedRevisionId/errataACTIVE errata for a single adopted revision — the banner hot path. Ask this before treating an adopted revision as correct: an empty array is the only evidence that no defect has been announced against it.
POST /api/wiki/standards/:id/reviewStart an adoption review by selecting a revision as the candidate snapshot. Creates the review record that POST /standards/:id/adopt then adopts; cancel it with POST /standards/:id/cancel-review.
Required permission: wiki:standard:adopt
sourceRevisionId — string — UUID of the wiki revision to reviewRequires the per-standard ADOPTER role in addition to the permission (SiteAdmin bypasses via wiki:standard:moderate). A global permission grant alone is a 403.
POST /api/wiki/standards/:id/adoptAdopt a revision that is already under review, publishing it as the adopted (citable) version of the standard.
Required permission: wiki:standard:adopt
adoptedRevisionId — string — UUID of the ADOPTED-revision record created by /reviewadoptedRevisionId is the id of the adoption record from POST /standards/:id/review, NOT the wiki revision id you passed to it. Requires the per-standard ADOPTER role in addition to the permission (SiteAdmin bypasses via wiki:standard:moderate). A global permission grant alone is a 403.
POST /api/wiki/standards/:id/direct-adoptAdopt a revision in one step, skipping the review record. Same effect and same authority as review + adopt.
Required permission: wiki:standard:adopt
sourceRevisionId — string — UUID of the wiki revision to adoptUse POST /standards/:id/preview-adoption first to confirm the revision parses. Requires the per-standard ADOPTER role in addition to the permission (SiteAdmin bypasses via wiki:standard:moderate). A global permission grant alone is a 403.
POST /api/wiki/standards/:id/errataIssue an erratum against one or more ADOPTED revisions of a standard. An erratum is a sibling annotation: it never edits the adopted revision or changes its content hash. This is the only correct way to announce that published content is wrong.
Required permission: wiki:errata:issue
severity — stringdescription — string — what is wrongrecommended_action — stringaffected — array of { adopted_revision_id, element_address? }recommended_action_note — stringcorrected_by_revision_id — string — UUID of the revision that fixes it, if one exists yetOmitting element_address scopes the erratum to the whole adopted revision. Requires the per-standard ADOPTER role in addition to the permission (SiteAdmin bypasses via wiki:standard:moderate). A global permission grant alone is a 403.
POST /api/wiki/standards/:id/errata/:errataId/revisionsExtend an existing erratum to additional adopted revisions once the defect is found to reach further back.
Required permission: wiki:errata:issue
affected — array of { adopted_revision_id, element_address? }Requires the per-standard ADOPTER role in addition to the permission (SiteAdmin bypasses via wiki:standard:moderate). A global permission grant alone is a 403.
PATCH /api/wiki/standards/:id/errata/:errataId/correctionPoint an erratum at the revision that corrects it, or clear that pointer by sending null. State change only — it does not withdraw the erratum.
Required permission: wiki:errata:issue
corrected_by_revision_id — string | null — UUID of the correcting revision, or null to clearRequires the per-standard ADOPTER role in addition to the permission (SiteAdmin bypasses via wiki:standard:moderate). A global permission grant alone is a 403.
POST /api/wiki/standards/:id/errata/:errataId/withdrawWithdraw an erratum that should not have been issued. A state change, not a delete: the erratum stays in the audit trail and stops appearing as an active banner.
Required permission: wiki:errata:withdraw
reason — string — why it is being withdrawnRequires the per-standard ADOPTER role in addition to the permission (SiteAdmin bypasses via wiki:standard:moderate). A global permission grant alone is a 403.
POST /api/wiki/standards/:id/archiveArchive (soft-delete) a wiki standard. 409 if it is already archived.
Required permission: wiki:standard:archive
reason_code — string — one of: spam, vandalism, test_content, copyvio, out_of_scope, merged_into, other. Any other value is a 400.reason_note — string — REQUIRED when reason_code is "other"merge_target_standard_id — string — UUID; REQUIRED when reason_code is "merged_into"Requires the per-standard MODERATOR role in addition to the permission (SiteAdmin bypasses via wiki:standard:moderate). Every moderation action is written to the moderation log with its reason — there is no silent form of this call.
POST /api/wiki/standards/:id/restoreRestore an archived wiki standard. One permission gates both directions, and a reason is still recorded. 409 if the standard is not currently archived.
Required permission: wiki:standard:archive
reason_code — string — one of: spam, vandalism, test_content, copyvio, out_of_scope, merged_into, other. Any other value is a 400.reason_note — string — REQUIRED when reason_code is "other"Requires the per-standard MODERATOR role in addition to the permission (SiteAdmin bypasses via wiki:standard:moderate). Every moderation action is written to the moderation log with its reason — there is no silent form of this call.
POST /api/wiki/standards/:id/revisions/:revId/hideHide a single wiki revision (soft moderation) without touching the rest of the history. If the hidden revision was current, the standard is repointed to the highest non-hidden revision. 409 if it is already hidden.
Required permission: wiki:revision:hide
reason_code — string — one of: spam, vandalism, test_content, copyvio, out_of_scope, merged_into, other. Any other value is a 400.reason_note — string — REQUIRED when reason_code is "other"Returns { revision, currentRevisionRepointed, newCurrentRevisionId, activeAdoption, moderationLogEntry }. Requires the per-standard MODERATOR role in addition to the permission (SiteAdmin bypasses via wiki:standard:moderate). Every moderation action is written to the moderation log with its reason — there is no silent form of this call.
POST /api/wiki/standards/:id/revisions/:revId/restoreUn-hide a hidden wiki revision, auto-promoting current_revision_id forward to the highest non-hidden revision.
Required permission: wiki:revision:hide
reason_code — string — one of: spam, vandalism, test_content, copyvio, out_of_scope, merged_into, other. Any other value is a 400.reason_note — string — REQUIRED when reason_code is "other"Requires the per-standard MODERATOR role in addition to the permission (SiteAdmin bypasses via wiki:standard:moderate). Every moderation action is written to the moderation log with its reason — there is no silent form of this call.
POST /api/wiki/standards/:standardId/revisions/:revId/tag-only-revisionChange a standard's tags by appending a NEW revision that carries the parent's content and content hash unchanged, with the new tag set attached. The wiki has no working-copy concept, so this is the ONLY correct way to change tags: writing tags onto an existing revision would edit a record that has already been read and cited.
Required permission: wiki:standard:edit
tag_ids — array of tag UUIDs — the COMPLETE new set; anything other than an array is a 400The array REPLACES the tag set rather than adding to it, so send every tag the revision should end up with, including ones it already has. An empty array clears all tags. :revId is the PARENT revision the new revision descends from — normally the standard's current revision.
POST /api/wiki/revisions/:revisionId/report-attributionReport a contribution-attribution problem on a wiki revision (impersonation, a wrong employer byline, an undisclosed conflict). Any authenticated reader may report. A hidden or non-existent revision returns 404 rather than confirming it exists.
reason_note — string — non-empty; an empty note is a 400POST /api/wiki/standards/:librarySlug/:standardSlug/import-to-new-projectCreate a placeholder project from a wiki standard in one atomic call: mirrors the standard's category into project scope, attaches the ADOPTED revision to the new project's draft, and returns the deep link to its datasheet. The signed-in half of the wiki "use this standard" call to action.
Required permission: project:create
idempotency_key — string — UUID; a repeat with the same key returns the first result, not a second projectsource — string — provenance label, e.g. "wiki_import"Returns { success, projectId, standardId, datasheetUrl }. Generate a fresh idempotency_key per intended import and REUSE it on retry — that is what stops a network retry from creating a duplicate project.