/api/standards
Read and edit standards (specifications) — the structured documents that define project requirements. Supports revisioning, working copies, and comments.
GET /api/standardsList standards accessible to the authenticated user. Supports filtering by scope, project, company, library, and category.
Required permission: standard:read
| Name | In | Type | Description |
|---|---|---|---|
| subject_type | query | string | Filter by scope: "global", "company", or "project" |
| project_id | query | integer | Filter to standards associated with a specific project |
| company_id | query | integer | Filter to standards owned by a specific company |
| library_id | query | integer | Filter to standards in a specific library |
| category_id | query | integer | Filter to standards in a specific category |
| search | query | string | Search by name or description (ILIKE match) |
| include_deprecated | query | boolean | Include deprecated standards (default false) |
| page | query | integer | Page number (optional, enables pagination) |
| per_page | query | integer | Results per page (1-100, default all) |
| sort | query | string | Sort by: "name", "created_at", or "category" (default: category, name) |
| sort_order | query | string | "asc" or "desc" (default: asc) |
| format | query | string | "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/:idGet 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
| Name | In | Type | Description |
|---|---|---|---|
| revisionId | query | integer | Specific standard revision ID for historical view (optional, defaults to current) |
| format | query | string | Response 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/revisionsList revision history for a standard
Required permission: standard:view_revisions
GET /api/standards/:id/compareCompare two revisions of a standard side-by-side
Required permission: standard:view_revisions
| Name | In | Type | Description |
|---|---|---|---|
| from | query | integer | Source revision ID |
| to | query | integer | Target revision ID |
GET /api/standards/search-contentHybrid 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
| Name | In | Type | Description |
|---|---|---|---|
| query | query | string | What to search for — a natural-language phrase or exact jargon (required). |
| projectId | query | integer | Optional: 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. |
| limit | query | integer | Optional: max hits to return (default 20, max 100). |
query — stringSEMANTIC 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/:idUpdate a standard. Only standards you own or have company-level update access to can be modified.
Required permission: standard:update
name — stringdescription — stringcategory_id — integerPOST /api/standards/:id/editStep 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-copyGet 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-copyTwo 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
elements — array (required unless datasheetElementsOnly is true)datasheetElements — arraydatasheetElementsOnly — boolean — patch mode, see descriptionremoveDatasheetFields — array of { element_address, label } — patch-mode-only field deletionsconfirmReplace — booleanmarkdown — string — raw-markdown input mode, HTTP callers only; not accepted on the assistant path (see notes)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/publishPublish 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
summary — stringseverity — stringseverity is optional and defaults to minor; accepted values are major|minor|patch (case-insensitive).
POST /api/standardsCreate 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
name — stringcategory_id — integersubject_type — stringdescription — stringlibrary_id — integersubject_id — integersubject_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/customizeCreate 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
name — stringcategoryId — integerprojectId — integercompanyId — integerSnake_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/deprecateDeprecate 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
reason — stringPATCH /api/standards/:id/reactivateReactivate 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/:idDelete 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
confirmName — string — the standard's exact nameconfirmName 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-copyDiscard 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/commentsAdd 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
element_id — integer (single comment format)comment_text — string (single comment format)comments — array of { element_id, comment_text, disposition?, visibility? } (batch format)disposition — string (Comply, Deviation, Exception, N/A, Comment — default: Comment)project_id — integervisibility — string (internal or external — default: internal)GET /api/standards/:id/commentsList comments on a standard. Omit element_id for all comments, or filter by specific element.
Required permission: standard:read
| Name | In | Type | Description |
|---|---|---|---|
| element_id | query | integer | Filter to a specific element (optional) |
| project_id | query | integer | Filter by project (optional) |
PUT /api/standards/:id/comments/:commentId/resolveResolve or reopen a comment on a standard.
Required permission: standard:comment:resolve
resolution_id — integerreopen — booleanSend resolution_id to resolve. Send { reopen: true } to reopen a resolved comment.
GET /api/standards/:id/changelogFor 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
| Name | In | Type | Description |
|---|---|---|---|
| revisionId | query | integer | Compare a specific revision instead of the current one (optional) |
GET /api/standards/:id/upgrade-statusList 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
| Name | In | Type | Description |
|---|---|---|---|
| projectId | query | integer | Limit to datasheets in one project (optional) |
GET /api/standards/:id/comments/countsComment 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
| Name | In | Type | Description |
|---|---|---|---|
| project_id | query | integer | Filter by project (optional) |
GET /api/standards/:id/comment-resolutionsList 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-referencesBatch-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
references — array of "library/slug" stringsprojectId — integer — also return the lineage-matched project standard, where one existsPOST /api/standards/:id/batch-upgradeUpgrade 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
toStandardRevisionId — integer — the standard revision to move todatasheetResolutions — object keyed by datasheet id: { [datasheetId]: { resolutions: [...], summary } }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/:commentIdEdit your OWN comment on a standard. Editing somebody else's is refused.
Required permission: standard:comment
comment_text — stringdisposition — stringvisibility — string — 'internal' | 'external'DELETE /api/standards/:id/comments/:commentIdDelete your OWN comment on a standard. Ownership is enforced server-side.
Required permission: standard:comment