Standards

/api/standards

Read and edit standards (specifications) — the structured documents that define project requirements. Supports revisioning, working copies, and comments.

Endpoints

GET /api/standards

List standards accessible to the authenticated user. Supports filtering by scope, project, company, library, and category.

Required permission: standard:read

Parameters
NameInTypeDescription
subject_typequerystringFilter by scope: "global", "company", or "project"
project_idqueryintegerFilter to standards associated with a specific project
company_idqueryintegerFilter to standards owned by a specific company
library_idqueryintegerFilter to standards in a specific library
category_idqueryintegerFilter to standards in a specific category
searchquerystringSearch by name or description (ILIKE match)
include_deprecatedquerybooleanInclude deprecated standards (default false)
pagequeryintegerPage number (optional, enables pagination)
per_pagequeryintegerResults per page (1-100, default all)
sortquerystringSort by: "name", "created_at", or "category" (default: category, name)
sort_orderquerystring"asc" or "desc" (default: asc)
formatquerystring"verbose" (default, every field) or "compact", which keeps only identity and navigation fields (id, name, description, scope, library, category, current revision) and drops the lineage/audit metadata. Built for token reduction when listing many standards. HTTP CALLERS ONLY — the assistant path does not read this parameter, so sending it here changes nothing about the response and is not a way to alter or shrink a result.

GET /api/standards/:id

Get a standard with its full element content, revision info, and datasheet elements. To LOOK SOMETHING UP (what does a standard require, which element covers X) use GET /search-content first and then read just the element it cites — a full read is for exports, imports, or a complete review of one known standard.

Required permission: standard:read

Parameters
NameInTypeDescription
revisionIdqueryintegerSpecific standard revision ID for historical view (optional, defaults to current)
formatquerystringResponse format: "verbose" (default, all fields) or "compact" (essential fields only, reduces payload size). Invalid values return 400. HTTP CALLERS ONLY — the assistant path does not read this parameter, so sending it here changes nothing about the response and is not a way to alter or shrink a result. To keep a standard read inside the context budget, fetch the one element you need via GET /api/standards/search-content rather than the whole document.

GET /api/standards/:id/revisions

List revision history for a standard

Required permission: standard:view_revisions

GET /api/standards/:id/compare

Compare two revisions of a standard side-by-side

Required permission: standard:view_revisions

Parameters
NameInTypeDescription
fromqueryintegerSource revision ID
toqueryintegerTarget revision ID

GET /api/standards/search-content

Hybrid semantic + keyword search over standards CONTENT — the requirement clauses and datasheet fields themselves, not just standard names. USE THIS FIRST for any question about what a standard requires or contains (e.g. "which standard covers corrosion protection?", "where is the sound-level field?"). It returns a ranked list of compact hits — each carrying the owning standard, a CITABLE element address, and a snippet — so you then fetch just that one element by address (e.g. GET /api/standards/:id) to read it in full. Do NOT pull a whole standard or datasheet into context to look something up; full-document reads are for exports or a complete review. Handles vocabulary gaps (e.g. "rust protection" finds "corrosion-resistant coating") and exact jargon (e.g. "NEMA 3R", "208Y/120V") alike. SCOPE CHOICE MATTERS: omit projectId to search the ENTIRE standards library — that is the right scope for "which standard covers X?", "does our library have anything about X?", and anything phrased about the library/company/wiki. Pass projectId ONLY when the question is about what THIS project's own standards say. A project usually pins just a few standards, so a project-scoped miss means "not in this project" — NEVER conclude the library lacks content until an UNSCOPED search also misses. Hits are standards-library content, not project documents — cite them by standard name + element address.

Required permission: standard:read

Parameters
NameInTypeDescription
queryquerystringWhat to search for — a natural-language phrase or exact jargon (required).
projectIdqueryintegerOptional: restrict to the standards pinned to this project's current revision. OMIT for library-wide questions; if a project-scoped search misses, retry without projectId before concluding the content does not exist.
limitqueryintegerOptional: max hits to return (default 20, max 100).

Request body (JSON)

Required

SEMANTIC search: it matches MEANING, so send ONE natural-language phrase describing ONE concept (e.g. "rodent and pest protection for switchgear enclosures"). There are NO boolean operators — a query containing bare OR/AND/NOT, a "|" alternation, or a comma/slash-separated keyword list is REJECTED and no search runs, because such a string embeds as literal words and retrieves worse, not broader. To cover several distinct concepts, make SEPARATE calls, one plain phrase each; never widen or repeat a query to compensate for a miss. REPORTING RESULTS: every hit carries a `confidence` tier — "strong" (real evidence) or "related" (adjacent content, offered without a claim) — and the response reports `strong_count`. When `strong_count` is 0, nothing in the searched content directly addresses the question: say that plainly FIRST. You may then point at the closest "related" hits as WHERE such a requirement would live if it existed (e.g. the enclosure/environment sections of the switchgear standards), explicitly labeled as closest-related context — never presented as findings, never as evidence the requirement exists, and never paraphrased into a requirement the standard does not state. WRONG TOOL FOR BROWSING: this searches requirement TEXT. To find out WHICH standards exist — picking imports for a project, covering several trades or categories, "what do we have on X?" — do NOT run one content search per topic. List the catalog instead: GET /api/standards (optionally with search=, category_id=, library_id=, subject_type=) returns the standards themselves, and GET /api/categories returns the category list to pick from. One listing answers what a dozen content searches only guess at, and each search costs a full turn of your limited budget.

PUT /api/standards/:id

Update a standard. Only standards you own or have company-level update access to can be modified.

Required permission: standard:update

Request body (JSON)

Optional

POST /api/standards/:id/edit

Step 1 of editing a standard: start editing by creating a working copy (returns the existing working copy if one already exists). Then save changes with PUT /:id/working-copy, and finalize with POST /:id/publish.

Required permission: standard:update

GET /api/standards/:id/working-copy

Get the current unpublished working copy (draft) for a standard, with all elements and datasheet elements. Returns 404 if no working copy exists.

Required permission: standard:view_drafts

PUT /api/standards/:id/working-copy

Two modes. (1) FULL REPLACE (default): send ALL elements — any element not included is deleted. This is not a patch; to add/modify specific elements, first GET the standard to retrieve all elements, modify the array, then PUT the full array back. If the new element count is less than half the existing count, pass confirmReplace: true to proceed. (2) PATCH MODE — for editing datasheet fields only (the common case: changing a field's options/default/drawing_ref without touching the standard's requirement text): pass datasheetElementsOnly: true and OMIT elements entirely. Every requirement element and every datasheet field you do not mention is carried over unchanged automatically — you never need to re-fetch or resubmit the full document. In datasheetElements, each item is matched to an existing field by (element_address, label); to rename/respec a field (change its label), add match_label with the field's CURRENT label so it is matched to the right existing row instead of being added as a new field. To delete a field, list it in the top-level removeDatasheetFields array as { element_address, label } (using its current label, not match_label).

Required permission: standard:update

Request body (JSON)

Optional

The `markdown` field is a frontend-editor input mode (parsed server-side, full-replace only, parse errors return 422) and is NOT available to this assistant: send `elements`, or `datasheetElementsOnly: true` for a fields-only patch. A markdown-only body is rejected with "Elements array is required". DATASHEET FIELD DEFAULTS vs DRAWING REFERENCES: default_value is a literal value pre-filled into the field — for select/radio/checkbox/listbox/combobox it MUST be one of the values in options, or it silently matches nothing ("As indicated on the drawings" is NOT a valid default_value). To make a field defer to the drawings instead of carrying a default, set starting_disposition: 'per_drawings' (this is what actually seeds the "per drawings" state; legacy starts_deferred: true still works) plus drawing_ref: true and optionally drawing_ref_locator, and leave default_value null/omitted — the field can still keep its full options list for when it IS specified later. To make a field start as "Manufacturer's standard" (terminal — the vendor's standard design value governs, no coordination follow-up), set starting_disposition: 'manufacturer_standard'; no drawing_ref needed. ADDRESS HIERARCHY (enforced): element addresses are dot-separated numeric outline addresses forming a heading tree — every dotted address requires its parent to be in the submission (submitting '1.1' without '1' is a 400; levels cannot be skipped), addresses must be unique, and top-level sections are plain integers. ATTACHMENT (enforced): every datasheetElements item's element_address must equal the address of an element in the same submission — datasheet fields attach to an existing requirement/heading element; a field pointing at a nonexistent address is a 400, never a silent drop.

POST /api/standards/:id/publish

Publish the current working copy as a new immutable revision — the final step after POST /:id/edit and PUT /:id/working-copy. Once published, the revision content is frozen; further edits require opening a new working copy.

Required permission: standard:update

Request body (JSON)

Required
Optional

severity is optional and defaults to minor; accepted values are major|minor|patch (case-insensitive).

POST /api/standards

Create a new standard FROM SCRATCH. Standards are reusable assets — before using this, search for an existing wiki/company/personal standard covering the subject (GET /api/standards/search-content or the standards list) and prefer importing or customizing it into the project. Create from scratch only when the user explicitly asks for a brand-new standard or no usable existing standard was found (and say which of those it was).

Required permission: standard:create

Request body (JSON)

Required
Optional

subject_id scopes ownership: for subject_type 'user' it MUST be your own user id, for 'company' your own company id. Another account's id is rejected before anything is written, and the rejection names the id to use — do not retry with a guessed id, and never create a standard in someone else's scope.

POST /api/standards/:id/customize

Create a standalone customization. WARNING: Does not add to project draft. Use POST /api/projects/:projectId/standards with action: "customize" instead for project workflows.

Required permission: standard:customize

Request body (JSON)

Required
Optional

Snake_case (category_id, project_id, company_id) is accepted for all three, and is the spelling the validation errors use — an error reading "category_id is required" refers to the categoryId you sent, not a second field. Exactly one of projectId or companyId: sending both is a 400, sending neither is a 400.

PATCH /api/standards/:id/deprecate

Deprecate a standard (soft removal without deleting). Deprecated standards remain accessible in projects that already use them but are hidden from standard lists by default.

Required permission: standard:delete

Request body (JSON)

Required

PATCH /api/standards/:id/reactivate

Reactivate a deprecated standard. Fails if an active standard with the same name already exists in the same scope.

Required permission: standard:delete

DELETE /api/standards/:id

Delete a standard. Refused with 409 if the standard is used in any PUBLISHED project revision. Customizations derived from it are not deleted; they lose their parent reference.

Required permission: standard:delete

Request body (JSON)

Required

confirmName is MANDATORY and must match the standard's name EXACTLY (character for character) — there is no unconfirmed form of this call. Anything else returns 400 with requiresConfirmation:true plus the real name and the number of customizations that will lose their parent reference. Read the standard first (GET /api/standards/:id) for its exact name, and tell the user that customization count before deleting.

DELETE /api/standards/:id/working-copy

Discard a working copy without publishing. WARNING: If the standard has never been published, this permanently deletes the standard (there is no revision to revert to).

Required permission: standard:update

POST /api/standards/:id/comments

Add review comment(s) to standard requirement elements. Accepts single or batch format. element_id is the numeric element id from the standard's elements (GET /api/standards/:id) — NOT the printed section number. Comments annotate the standard; they never change its requirements.

Required permission: standard:comment

Request body (JSON)

Optional

GET /api/standards/:id/comments

List comments on a standard. Omit element_id for all comments, or filter by specific element.

Required permission: standard:read

Parameters
NameInTypeDescription
element_idqueryintegerFilter to a specific element (optional)
project_idqueryintegerFilter by project (optional)

PUT /api/standards/:id/comments/:commentId/resolve

Resolve or reopen a comment on a standard.

Required permission: standard:comment:resolve

Request body (JSON)

Optional

Send resolution_id to resolve. Send { reopen: true } to reopen a resolved comment.

GET /api/standards/:id/changelog

For a CUSTOMIZED standard, show what was changed relative to the standard it was customized from — the delta, clause by clause. Returns nothing meaningful for a standard that is not a customization.

Required permission: standard:view_revisions

Parameters
NameInTypeDescription
revisionIdqueryintegerCompare a specific revision instead of the current one (optional)

GET /api/standards/:id/upgrade-status

List the datasheets built on this standard that are behind its current revision. The batch counterpart of GET /api/datasheet/:id/upgrade-status, and the read to run BEFORE POST /:id/batch-upgrade.

Required permission: standard:read

Parameters
NameInTypeDescription
projectIdqueryintegerLimit to datasheets in one project (optional)

GET /api/standards/:id/comments/counts

Comment counts per element for a standard. Subject to the same visibility rule as the comment list: comments from other companies are only counted when they were marked external.

Required permission: standard:read

Parameters
NameInTypeDescription
project_idqueryintegerFilter by project (optional)

GET /api/standards/:id/comment-resolutions

List the available comment-resolution types (the vocabulary a resolution_id comes from). Site-wide reference data — the :id in the path is vestigial and no standard is read.

Required permission: standard:read

POST /api/standards/resolve-references

Batch-resolve `[[library/slug]]` wiki-style cross-references to the standards they point at. A reference that resolves to nothing comes back marked as such rather than omitted, which is how a dangling link is detected.

Required permission: standard:read

Request body (JSON)

Required
Optional

POST /api/standards/errata-banners

Resolve active errata for many standard revisions at once, returning { banners: { [standard_revision_id]: errata[] } }. Public, no auth — announced defects in adopted content are public by design. A revision with no active errata is simply absent from the map.

Request body (JSON)

Required

Non-numeric entries are dropped rather than failing the batch. A non-array is a 400.

POST /api/standards/:id/batch-upgrade

Upgrade many datasheets to a newer revision of this standard in one call, each with its own conflict resolutions. Gated on project:draft:edit, not a standard permission — it writes datasheet revisions into a project's draft.

Required permission: project:draft:edit

Request body (JSON)

Required

Run GET /:id/upgrade-status first to learn which datasheets are behind and GET /api/datasheet/:id/upgrade-plan for each one's conflicts. REQUIRES AN OPEN DRAFT: this change lands in the project's current unpublished draft. If the project has no open draft the call is rejected outright — check GET /api/projects/:id/revisions (a revision with is_draft true) and, if there is none, propose POST /api/projects/:id/draft/create first, then make this call. Opening a draft is its own user-approved change; never publish or revert a project to bring a draft into existence.

PUT /api/standards/:id/comments/:commentId

Edit your OWN comment on a standard. Editing somebody else's is refused.

Required permission: standard:comment

Request body (JSON)

Optional

DELETE /api/standards/:id/comments/:commentId

Delete your OWN comment on a standard. Ownership is enforced server-side.

Required permission: standard:comment

GET /api/standards/tags

List the active tag vocabulary (paginated) — the pool a standard's tags are chosen from. Gated on standard:update because the only reason to read it is to compose a tag set.

Required permission: standard:update

Parameters
NameInTypeDescription
limitqueryintegerPage size
offsetqueryintegerPage offset
searchquerystringFilter by label (optional)

POST /api/standards/tags/suggest

Ask the tagging model to suggest tags for standard content. Returns { tags: [{ id, label, confidence }] } — suggestions only; nothing is attached until you PUT the revision tag set.

Required permission: standard:update

Request body (JSON)

Required
Optional

GET /api/standards/:standardId/revisions/:revId/tags

List the tags attached to one standard revision. Tags live on the REVISION, so a tag set only means anything paired with the revision it was recorded against.

Required permission: standard:read

PUT /api/standards/:standardId/revisions/:revId/tags

Replace the tag set on a standard revision. Only the WORKING COPY may be tagged — a published revision is a 403, because a tag change on a standard is tracked through the normal revision flow rather than written over a record that has already been issued.

Required permission: standard:update

Request body (JSON)

Required

The array REPLACES the tag set rather than adding to it, so send every tag the revision should end up with. An empty array clears all tags. Start a working copy with POST /:id/edit first, then publish it to make the tag change part of a revision.