Files
itworx-knowledge-hub-Public/docs/api/openapi.yaml
T

6134 lines
204 KiB
YAML

openapi: 3.1.0
info:
title: ItWorx Knowledge Hub API
version: 0.1.0-draft
description: |
Versioned API for the ItWorx Knowledge Hub web client and scoped automation.
This is not an MCP transport. The future MCP service will consume this API.
servers:
- url: /api/v1
tags:
- name: System
- name: Setup
- name: Auth
- name: Admin
- name: Search
- name: Rag
- name: Pages
- name: Drafts
- name: Templates
- name: Attachments
- name: Imports
- name: Source batch purge
- name: Projects
- name: Decisions
- name: Runbooks
- name: Service accounts
- name: Backups
- name: Infrastructure
- name: Activity
- name: Workspace
- name: Ingestion proposals
security:
- cookieAuth: []
- bearerAuth: []
paths:
/ingestion-proposals:
get:
tags: [Ingestion proposals]
operationId: listIngestionProposals
description: Human review queue only. Page/pageSize and all filters are evaluated server-side; cursor/limit remain supported for existing clients. Project and risk are accepted but return no matches until this version persists those attributes, rather than fabricating filter results.
parameters:
- name: status
in: query
schema: { type: string }
- $ref: "#/components/parameters/Cursor"
- $ref: "#/components/parameters/Limit"
- name: page
in: query
schema: { type: integer, minimum: 1, maximum: 10000, default: 1 }
- name: pageSize
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
- name: projectId
in: query
schema: { type: string, format: uuid }
- name: submitterId
in: query
schema: { type: string, format: uuid }
- name: minAgeDays
in: query
description: Minimum proposal age, calculated from the first immutable proposal revision.
schema: { type: integer, minimum: 0, maximum: 3650 }
- name: risk
in: query
schema: { type: string, enum: [low, medium, high] }
- name: documentType
in: query
schema: { type: string, maxLength: 64 }
responses:
"200": { description: Review queue, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalListResponse" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
post:
tags: [Ingestion proposals]
operationId: createIngestionProposal
description: >-
Requires an Idempotency-Key and an editor cookie or an ingestion:write service
token. Repeating the same request with the same caller and key replays the
original 201 response. Reusing that key with a different request body returns
a 409 problem with type https://itworx.dev/problems/idempotency-key-conflict.
parameters:
- $ref: "#/components/parameters/RequiredIdempotencyKey"
requestBody:
required: true
content: { application/json: { schema: { $ref: "#/components/schemas/CreateIngestionProposalRequest" } } }
responses:
"201": { description: Proposal created, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
"400": { description: Idempotency-Key is missing or invalid, content: { application/problem+json: { schema: { $ref: "#/components/schemas/ProblemDetails" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
"409":
description: Idempotency-Key was already used with a different request body.
content:
application/problem+json:
schema: { $ref: "#/components/schemas/ProblemDetails" }
examples:
idempotencyKeyConflict:
value:
type: https://itworx.dev/problems/idempotency-key-conflict
title: Idempotency-Key hergebruikt met een andere aanvraag.
status: 409
correlationId: pilot-contract
detail: Deze Idempotency-Key is eerder gebruikt voor een andere aanvraag-inhoud.
"422": { $ref: "#/components/responses/BadRequest" }
/ingestion-proposals/{proposalId}:
parameters:
- name: proposalId
in: path
required: true
schema: { type: string, format: uuid }
get:
tags: [Ingestion proposals]
operationId: getIngestionProposal
description: A human reviewer may read any workspace proposal. An ingestion:write token may read only the proposal it submitted.
responses:
"200": { description: Complete metadata-only review/status dossier, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
"404": { $ref: "#/components/responses/NotFound" }
patch:
tags: [Ingestion proposals]
operationId: patchIngestionProposal
description: >-
Requires If-Match. Human editors may change manifest/rationale; an edit to an
Approved proposal clears its approval and returns it to ReadyForReview. Only
the submitting ingestion:write account may submit analysis, which advances the
proposal to ReadyForReview. A stale proposal ETag returns 412 with type
https://itworx.local/problems/ingestion-concurrency-conflict.
parameters:
- $ref: "#/components/parameters/IfMatch"
requestBody:
required: true
content: { application/json: { schema: { $ref: "#/components/schemas/PatchIngestionProposalRequest" } } }
responses:
"200": { description: Updated proposal, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
"412":
description: Proposal ETag does not match the current version.
content:
application/problem+json:
schema: { $ref: "#/components/schemas/ProblemDetails" }
examples:
ingestionConcurrencyConflict:
value:
type: https://itworx.local/problems/ingestion-concurrency-conflict
title: Versieconflict.
status: 412
correlationId: pilot-contract
/ingestion-proposals/{proposalId}/sources:
post:
tags: [Ingestion proposals]
operationId: stageIngestionProposalSource
description: Requires If-Match and Idempotency-Key. Upload is quarantined, signature-validated and stored only as immutable metadata plus a content-addressed attachment reference; neither bytes nor storage paths are returned.
parameters:
- name: proposalId
in: path
required: true
schema: { type: string, format: uuid }
- $ref: "#/components/parameters/IfMatch"
- $ref: "#/components/parameters/RequiredIdempotencyKey"
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties: { file: { type: string, format: binary } }
responses:
"200": { description: Source staged, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
"413": { description: Source exceeds the configured size limit }
"422": { $ref: "#/components/responses/BadRequest" }
/ingestion-proposals/{proposalId}/submit:
post:
tags: [Ingestion proposals]
operationId: submitManualIngestionProposal
description: >-
Human submitter only. Moves a proposal with publication details and at least
one staged source into ReadyForReview without claiming automated analysis.
Requires If-Match. A separate human review and approval remain mandatory.
parameters:
- name: proposalId
in: path
required: true
schema: { type: string, format: uuid }
- $ref: "#/components/parameters/IfMatch"
responses:
"200": { description: Submitted for review, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
"412": { $ref: "#/components/responses/PreconditionFailed" }
"428": { $ref: "#/components/responses/PreconditionRequired" }
/ingestion-proposals/{proposalId}/request-changes:
post:
tags: [Ingestion proposals]
operationId: requestIngestionProposalChanges
description: >-
Human reviewer only. Requires If-Match and returns the updated metadata-only
dossier. Stale ETags return 412 with type
https://itworx.local/problems/ingestion-concurrency-conflict.
parameters:
- name: proposalId
in: path
required: true
schema: { type: string, format: uuid }
- $ref: "#/components/parameters/IfMatch"
requestBody:
required: true
content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDecisionRequest" } } }
responses:
"200": { description: Change request recorded, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
"412": { $ref: "#/components/responses/PreconditionFailed" }
"428": { $ref: "#/components/responses/PreconditionRequired" }
/ingestion-proposals/{proposalId}/approve:
post:
tags: [Ingestion proposals]
operationId: approveIngestionProposal
description: >-
Human reviewer only. Service accounts cannot approve. Requires If-Match and
approves a ReadyForReview proposal without publishing it. Stale ETags return
412 with type https://itworx.local/problems/ingestion-concurrency-conflict.
parameters:
- name: proposalId
in: path
required: true
schema: { type: string, format: uuid }
- $ref: "#/components/parameters/IfMatch"
requestBody:
required: true
content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDecisionRequest" } } }
responses:
"200": { description: Proposal approved for later publication, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
"412": { $ref: "#/components/responses/PreconditionFailed" }
"428": { $ref: "#/components/responses/PreconditionRequired" }
/ingestion-proposals/{proposalId}/reject:
post:
tags: [Ingestion proposals]
operationId: rejectIngestionProposal
description: >-
Human reviewer only. Service accounts cannot reject. Requires If-Match and
records the rejection comment on the metadata-only proposal dossier. Stale
ETags return 412 with type
https://itworx.local/problems/ingestion-concurrency-conflict.
parameters:
- name: proposalId
in: path
required: true
schema: { type: string, format: uuid }
- $ref: "#/components/parameters/IfMatch"
requestBody:
required: true
content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDecisionRequest" } } }
responses:
"200": { description: Proposal rejected, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
"412": { $ref: "#/components/responses/PreconditionFailed" }
"428": { $ref: "#/components/responses/PreconditionRequired" }
/ingestion-proposals/{proposalId}/retry:
post:
tags: [Ingestion proposals]
operationId: retryIngestionProposal
description: Requires If-Match. An ingestion:write caller can retry only its own eligible failed proposal; a human editor may retry an eligible proposal. An optional Idempotency-Key safely replays a successful retry response.
parameters:
- name: proposalId
in: path
required: true
schema: { type: string, format: uuid }
- $ref: "#/components/parameters/IfMatch"
- $ref: "#/components/parameters/IdempotencyKey"
responses:
"200": { description: Eligible proposal reset for re-analysis, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
"412": { $ref: "#/components/responses/PreconditionFailed" }
"428": { $ref: "#/components/responses/PreconditionRequired" }
/ingestion-proposals/{proposalId}/publish:
post:
tags: [Ingestion proposals]
operationId: publishIngestionProposal
description: >-
Human review only. Requires a human editor cookie session, If-Match and
Idempotency-Key; service accounts are forbidden from publishing. A replay with
the same key and request body returns the original success response even when
it carries the original now-stale If-Match value, and does not create a second
page revision. Reusing the key with a different body returns a typed
idempotency conflict. A stale target page ETag returns 409 with type
https://itworx.local/problems/ingestion-target-stale and moves the proposal
back to ReadyForReview.
parameters:
- name: proposalId
in: path
required: true
schema: { type: string, format: uuid }
- $ref: "#/components/parameters/IfMatch"
- $ref: "#/components/parameters/RequiredIdempotencyKey"
responses:
"200": { description: Approved proposal published, headers: { ETag: { $ref: "#/components/headers/ETag" } }, content: { application/json: { schema: { $ref: "#/components/schemas/IngestionProposalDetail" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
"409":
description: >-
Invalid state transition, stale target, or idempotency-key conflict. Typed
problem types are https://itworx.local/problems/ingestion-invalid-transition,
https://itworx.local/problems/ingestion-target-stale, and
https://itworx.dev/problems/idempotency-key-conflict.
content:
application/problem+json:
schema: { $ref: "#/components/schemas/ProblemDetails" }
examples:
staleTarget:
value:
type: https://itworx.local/problems/ingestion-target-stale
title: Publicatiedoel is gewijzigd.
status: 409
correlationId: pilot-contract
detail: Het voorstel is teruggezet naar de reviewwachtrij.
invalidTransition:
value:
type: https://itworx.local/problems/ingestion-invalid-transition
title: Conflict.
status: 409
correlationId: pilot-contract
idempotencyKeyConflict:
value:
type: https://itworx.dev/problems/idempotency-key-conflict
title: Idempotency-Key hergebruikt met een andere aanvraag.
status: 409
correlationId: pilot-contract
detail: Deze Idempotency-Key is eerder gebruikt voor een andere aanvraag-inhoud.
"412":
description: Proposal ETag is stale.
content:
application/problem+json:
schema: { $ref: "#/components/schemas/ProblemDetails" }
examples:
ingestionConcurrencyConflict:
value:
type: https://itworx.local/problems/ingestion-concurrency-conflict
title: Versieconflict.
status: 412
correlationId: pilot-contract
"428": { $ref: "#/components/responses/PreconditionRequired" }
/system/info:
get:
tags: [System]
operationId: getSystemInfo
responses:
"200":
description: Permission-filtered system information
content:
application/json:
schema:
$ref: "#/components/schemas/SystemInfo"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/setup/status:
get:
tags: [Setup]
operationId: getSetupStatus
security: []
description: >-
Reveals only whether first-run setup is still required. Never reveals any other
installation detail to an unauthenticated caller.
responses:
"200":
description: Setup status
content:
application/json:
schema:
$ref: "#/components/schemas/SetupStatus"
/setup/complete:
post:
tags: [Setup]
operationId: completeSetup
security: []
description: >-
Single-use first-run owner setup. Requires CSRF protection and is rate limited.
Permanently fails once setup has completed.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CompleteSetupRequest"
responses:
"200":
description: Installation completed; caller is signed in
content:
application/json:
schema:
$ref: "#/components/schemas/CompleteSetupResponse"
"409":
description: Setup already completed
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"422":
description: Validation failed (email/password/workspace name)
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"429":
$ref: "#/components/responses/RateLimited"
/auth/csrf:
get:
tags: [Auth]
operationId: getCsrfToken
security: []
description: Issues the double-submit anti-forgery token required on cookie-authenticated mutations.
responses:
"200":
description: Anti-forgery token
content:
application/json:
schema:
$ref: "#/components/schemas/CsrfToken"
/auth/session:
get:
tags: [Auth]
operationId: getSession
security: []
description: >-
Always returns 200. An unauthenticated caller only ever sees
`authenticated: false` plus whether setup is required — never whether a
specific account exists.
responses:
"200":
description: Current session state
content:
application/json:
schema:
$ref: "#/components/schemas/SessionResponse"
/auth/login:
post:
tags: [Auth]
operationId: login
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/LoginRequest"
responses:
"200":
description: Signed in
content:
application/json:
schema:
$ref: "#/components/schemas/SessionResponse"
"401":
description: Invalid credentials (also returned for an unknown account and a disabled account)
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"429":
$ref: "#/components/responses/RateLimited"
/auth/logout:
post:
tags: [Auth]
operationId: logout
responses:
"200":
description: Signed out
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
/auth/recovery/redeem:
post:
tags: [Auth]
operationId: redeemRecoveryToken
security: []
description: Consumes a single-use operator-issued recovery token and sets a new password.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RecoveryRedeemRequest"
responses:
"200":
description: Password reset; all prior sessions for this account are invalidated
"400":
description: Invalid or expired token
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"422":
description: New password rejected
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"429":
$ref: "#/components/responses/RateLimited"
/admin/users:
get:
tags: [Admin]
operationId: listUsers
description: Administrator only.
responses:
"200":
description: Workspace members
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/UserSummary"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
post:
tags: [Admin]
operationId: createUser
description: >-
Administrator only. There is no invite email; the administrator sets an initial
password and communicates it out of band.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUserRequest"
responses:
"200":
description: User created
content:
application/json:
schema:
$ref: "#/components/schemas/UserSummary"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"422":
$ref: "#/components/responses/BadRequest"
/admin/users/{userId}:
patch:
tags: [Admin]
operationId: updateUser
description: >-
Administrator only. Changes role and/or enabled state. Refuses a change that
would leave zero enabled administrators.
parameters:
- $ref: "#/components/parameters/UserId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateUserRequest"
responses:
"200":
description: Updated
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: Would remove the last enabled administrator
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/admin/users/{userId}/recovery-tokens:
post:
tags: [Admin]
operationId: issueRecoveryToken
description: >-
Administrator only. Issues a single-use, one-hour password-recovery token for
another account. The raw token is returned exactly once, in this response.
parameters:
- $ref: "#/components/parameters/UserId"
responses:
"200":
description: Token issued
content:
application/json:
schema:
$ref: "#/components/schemas/IssueRecoveryTokenResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/admin/trash/purge:
post:
tags: [Admin]
operationId: purgeTrash
description: >-
Administrator only. The explicit administrative purge
(docs/09-data-safety: never a permanent delete through a normal UI flow).
Permanently removes every trashed page in the workspace whose retention
window has already elapsed; never a page still within retention, never a
page that was never trashed.
responses:
"200":
description: Purge completed
content:
application/json:
schema:
$ref: "#/components/schemas/TrashPurgeResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/admin/staging-cleanup/status:
get:
tags: [Admin]
operationId: getStagingCleanupStatus
description: >-
Administrator only (docs/adr/ADR-029-v2-r5-final-hardening.md). Live counts of
currently-open and expired-pending-cleanup page-creation staging sessions, plus
the most recent cleanup run (scheduled or manual) and its result. The "last run"
fields are in-memory only and reset on application restart — an accepted,
documented limitation; the cleanup guarantee itself does not depend on this
history.
responses:
"200":
description: Current staging-cleanup status
content:
application/json:
schema:
$ref: "#/components/schemas/StagingCleanupStatusResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/admin/staging-cleanup/run:
post:
tags: [Admin]
operationId: runStagingCleanup
description: >-
Administrator only, CSRF-protected (docs/adr/ADR-029-v2-r5-final-hardening.md).
Runs the exact same reclamation logic as the periodic background worker, once,
synchronously. `dryRun: true` only counts currently-expired, claimable sessions
— it claims nothing, deletes nothing and is safe to call as often as wanted;
never expose a destructive "run now" action without this dry-run mode.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RunStagingCleanupRequest"
responses:
"200":
description: Cleanup run (or dry-run preview) result
content:
application/json:
schema:
$ref: "#/components/schemas/RunStagingCleanupResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/activity:
get:
tags: [Activity]
operationId: listActivity
description: >-
Requires a reader cookie session or an `audit:read` service-token scope. The
most recent audit/activity events across the workspace (page/project/decision
mutations, imports, backups, staging-session cleanup, etc.), newest first —
the same durable `audit_events` trail every mutation writes to
(docs/12-auth-security.md "Logging and privacy": never a password, raw token or
session identifier).
parameters:
- name: limit
in: query
required: false
description: Defaults to 100.
schema:
type: integer
minimum: 1
maximum: 500
responses:
"200":
description: Recent activity, newest first
content:
application/json:
schema:
$ref: "#/components/schemas/ActivityListResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/workspace/members:
get:
tags: [Workspace]
operationId: searchWorkspaceMembers
description: >-
Requires a reader cookie session — deliberately not reachable by a
service-account bearer token (no automation use case has been named for this
yet). Backs the accessible owner picker (docs/adr/ADR-029-v2-r5-final-hardening.md,
replacing manual `OwnerId`/GUID entry): only enabled members of the current
workspace, matched case-insensitively against display name or email, capped at
25 results. Never discloses accounts outside the workspace or disabled
accounts. An empty/omitted `q` returns the first 25 members alphabetically (the
picker's initial open state, before typing).
parameters:
- name: q
in: query
required: false
schema:
type: string
responses:
"200":
description: Matching workspace members
content:
application/json:
schema:
$ref: "#/components/schemas/WorkspaceMemberListResponse"
"401":
$ref: "#/components/responses/Unauthorized"
/search:
get:
tags: [Search]
operationId: searchKnowledge
description: >-
PostgreSQL full-text (weighted title/tags/body) plus trigram typo-tolerant
title matching. Authorization is entirely SQL-level: always scoped to the
caller's workspace, always excludes trashed pages
(docs/10-search-and-indexing.md). An empty or absent `q` returns all
permitted, non-trashed pages (browsing by tag/type filter alone).
parameters:
- $ref: "#/components/parameters/CorrelationId"
- name: q
in: query
schema:
type: string
maxLength: 500
- name: type
in: query
description: A single DocumentType value.
schema:
$ref: "#/components/schemas/DocumentType"
- name: tag
in: query
description: A single exact tag match.
schema:
type: string
- $ref: "#/components/parameters/Cursor"
- $ref: "#/components/parameters/Limit"
responses:
"200":
description: Permission-filtered search results
content:
application/json:
schema:
$ref: "#/components/schemas/SearchResponse"
"401":
$ref: "#/components/responses/Unauthorized"
/rag/publication-manifest:
get:
tags: [Rag]
operationId: getRagPublicationManifest
description: >-
A narrow, read-only view of every page tagged `rag:publish`
(docs/32-rag-publication-convention.md), built for RAGcore's n8n sync — the
one reliable way to discover the full curated publication set, since this API
has no `GET /pages` enumeration (only `/pages/roots` plus a tree walk). Reuses
the existing `pages:read` scope; no new scope was added. A page that still
carries `rag:publish` but is trashed, still a draft, awaiting review, archived,
or deprecated without the `rag:allow-deprecated` tag comes back with
`retired: true` and a `retiredReason`, and its `markdown`/`checksum` are
`null` — so a sync consumer can safely withdraw previously ingested content
instead of only ever learning about new content.
parameters:
- $ref: "#/components/parameters/CorrelationId"
- name: space
in: query
description: >-
A single `rag:space:` tag suffix. Case-sensitive lowercase slug; an
invalid shape is rejected with 422 rather than passed into the array-
containment filter.
schema:
type: string
maxLength: 64
- $ref: "#/components/parameters/Cursor"
- name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 200
default: 100
responses:
"200":
description: Publication manifest page
content:
application/json:
schema:
$ref: "#/components/schemas/RagPublicationManifestResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"422":
description: Invalid `space` parameter
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/pages/roots:
get:
tags: [Pages]
operationId: listRootPages
description: Top-level pages (no parent), cursor-paginated.
parameters:
- $ref: "#/components/parameters/Cursor"
- $ref: "#/components/parameters/Limit"
responses:
"200":
description: Root pages
content:
application/json:
schema:
$ref: "#/components/schemas/PageChildrenResponse"
"401":
$ref: "#/components/responses/Unauthorized"
/pages/preview:
post:
tags: [Pages]
operationId: previewMarkdown
description: >-
Requires a reader cookie session or a `pages:read` service-token scope. Renders
arbitrary Markdown through the exact same server-side pipeline (Markdig with
`.DisableHtml()` plus the allowlist `HtmlSanitizer` pass) used for a published
page revision — safe to call with unsaved editor content, never persists
anything. The same safe pipeline is reused for project free-text Markdown
fields (`currentStateMarkdown`, `summaryMarkdown`, etc. —
docs/adr/ADR-029-v2-r5-final-hardening.md).
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PreviewMarkdownRequest"
responses:
"200":
description: Rendered, sanitized HTML
content:
application/json:
schema:
$ref: "#/components/schemas/PreviewMarkdownResponse"
"401":
$ref: "#/components/responses/Unauthorized"
/pages:
post:
tags: [Pages]
operationId: createPage
parameters:
- $ref: "#/components/parameters/CorrelationId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreatePageRequest"
responses:
"200":
description: Page created and published as revision 1
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/PageDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"422":
description: Validation failed, or an unknown templateKey was supplied
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/pages/{pageId}:
parameters:
- $ref: "#/components/parameters/PageId"
- $ref: "#/components/parameters/CorrelationId"
get:
tags: [Pages]
operationId: getPage
responses:
"200":
description: Current page, with server-sanitized rendered HTML
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/PageDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
put:
tags: [Pages]
operationId: updatePage
description: Publishes a new revision. Requires If-Match with the page's current ETag.
parameters:
- $ref: "#/components/parameters/IfMatch"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdatePageRequest"
responses:
"200":
description: Updated page
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/PageDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: Page is trashed and must be restored before it can be edited
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"412":
$ref: "#/components/responses/PreconditionFailed"
"422":
$ref: "#/components/responses/BadRequest"
"428":
$ref: "#/components/responses/PreconditionRequired"
/pages/{pageId}/children:
get:
tags: [Pages]
operationId: listPageChildren
parameters:
- $ref: "#/components/parameters/PageId"
- $ref: "#/components/parameters/Cursor"
- $ref: "#/components/parameters/Limit"
responses:
"200":
description: Direct children, cursor-paginated (proven at 10,000-child scale, docs/27-acceptance-matrix.md M2-A07)
content:
application/json:
schema:
$ref: "#/components/schemas/PageChildrenResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
/pages/{pageId}/move:
post:
tags: [Pages]
operationId: movePage
description: >-
Reparents and/or reorders a page among its siblings. Rejects a move that would
make a page its own descendant. Requires If-Match with the page's current ETag.
parameters:
- $ref: "#/components/parameters/PageId"
- $ref: "#/components/parameters/CorrelationId"
- $ref: "#/components/parameters/IfMatch"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/MovePageRequest"
responses:
"200":
description: Moved page
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/PageDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: The requested move would make the page its own descendant (a cycle)
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/pages/{pageId}/trash:
post:
tags: [Pages]
operationId: trashPage
description: >-
Moves a page to the trash/retention model. Never a permanent delete
(docs/09-data-safety). Requires If-Match with the page's current ETag and
accepts either an editor cookie session or a `pages:write` service token,
enabling bounded import rollback without permanent deletion.
parameters:
- $ref: "#/components/parameters/PageId"
- $ref: "#/components/parameters/CorrelationId"
- $ref: "#/components/parameters/IfMatch"
responses:
"200":
description: Page trashed
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/pages/{pageId}/restore:
post:
tags: [Pages]
operationId: restorePage
description: Restores a trashed page. Requires If-Match with the page's current ETag.
parameters:
- $ref: "#/components/parameters/PageId"
- $ref: "#/components/parameters/CorrelationId"
- $ref: "#/components/parameters/IfMatch"
responses:
"200":
description: Page restored
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/pages/{pageId}/revisions:
get:
tags: [Pages]
operationId: listPageRevisions
description: Immutable revision history, most recent first (M3-A01/M3-A02).
parameters:
- $ref: "#/components/parameters/PageId"
- $ref: "#/components/parameters/CorrelationId"
responses:
"200":
description: Revisions
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/PageRevisionSummary"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
/pages/{pageId}/revisions/{revisionNumber}:
get:
tags: [Pages]
operationId: getPageRevision
parameters:
- $ref: "#/components/parameters/PageId"
- $ref: "#/components/parameters/RevisionNumber"
- $ref: "#/components/parameters/CorrelationId"
responses:
"200":
description: One revision's full content, server-rendered
content:
application/json:
schema:
$ref: "#/components/schemas/PageRevisionDetailResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
/pages/{pageId}/revisions/{revisionNumber}/restore:
post:
tags: [Pages]
operationId: restorePageRevision
description: >-
Publishes a new current revision whose content matches the chosen past
snapshot. Never rewrites history: the state right before this call is itself
preserved as its own revision. Requires If-Match with the page's current ETag.
parameters:
- $ref: "#/components/parameters/PageId"
- $ref: "#/components/parameters/RevisionNumber"
- $ref: "#/components/parameters/CorrelationId"
- $ref: "#/components/parameters/IfMatch"
responses:
"200":
description: Restored page
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/PageDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: Page is trashed and must be restored before it can be edited
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/pages/{pageId}/backlinks:
get:
tags: [Pages]
operationId: getPageBacklinks
description: >-
"What links here": pages whose most recently published content contains a
resolved internal link to this page (M3-A03).
parameters:
- $ref: "#/components/parameters/PageId"
- $ref: "#/components/parameters/CorrelationId"
responses:
"200":
description: Backlinks
content:
application/json:
schema:
$ref: "#/components/schemas/BacklinksResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
/drafts/{draftKey}:
parameters:
- $ref: "#/components/parameters/DraftKey"
- $ref: "#/components/parameters/CorrelationId"
get:
tags: [Drafts]
operationId: getDraft
description: >-
Private per-user autosaved draft. A draft never overwrites the published
revision (docs/27-acceptance-matrix.md, M2-A04).
responses:
"200":
description: Draft
content:
application/json:
schema:
$ref: "#/components/schemas/DraftResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
put:
tags: [Drafts]
operationId: saveDraft
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SaveDraftRequest"
responses:
"200":
description: >-
Draft saved. `baseRevisionIsStale` is true when the page has been published
again since this draft's base revision, without blocking the save.
content:
application/json:
schema:
$ref: "#/components/schemas/DraftResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
delete:
tags: [Drafts]
operationId: discardDraft
responses:
"200":
description: Draft discarded
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
/templates:
get:
tags: [Templates]
operationId: listTemplates
description: Starter document templates available when creating a new page.
responses:
"200":
description: Available templates
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/TemplateSummary"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/pages/{pageId}/attachments:
parameters:
- $ref: "#/components/parameters/PageId"
- $ref: "#/components/parameters/CorrelationId"
get:
tags: [Attachments]
operationId: listPageAttachments
description: Requires a reader cookie session or an `attachments:read` service-token scope.
responses:
"200":
description: Attachments referenced by this page
content:
application/json:
schema:
$ref: "#/components/schemas/AttachmentListResponse"
"401":
$ref: "#/components/responses/Unauthorized"
post:
tags: [Attachments]
operationId: uploadAttachment
description: >-
Requires an editor cookie session or an `attachments:write` service-token
scope.
The upload pipeline from docs/13-attachments.md: streams once to quarantine
while hashing, verifies size, detects the real signature (never trusting the
declared type/extension alone), applies delivery policy, then commits.
Identical content deduplicates to one physical blob while still creating an
independent logical reference.
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
responses:
"200":
description: Uploaded
content:
application/json:
schema:
$ref: "#/components/schemas/AttachmentSummary"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"413":
description: File exceeds the configured maximum upload size
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"422":
description: Rejected content (unsafe signature) or an invalid filename
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/pages/staged:
post:
tags: [Pages]
operationId: beginPageCreationSession
description: >-
Opens a staging session for creating a new page that may need attachments
inserted before the page is ever visible to a reader
(docs/adr/ADR-025-v2-r1-data-integrity-remediation.md). Requires an editor
cookie session or a `pages:write` service-token scope.
parameters:
- $ref: "#/components/parameters/CorrelationId"
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/BeginPageCreationSessionRequest"
responses:
"200":
description: Session opened
content:
application/json:
schema:
$ref: "#/components/schemas/PageCreationSessionResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"422":
description: The supplied parentPageId does not exist
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/pages/staged/{sessionId}/attachments:
parameters:
- $ref: "#/components/parameters/SessionId"
- $ref: "#/components/parameters/CorrelationId"
post:
tags: [Attachments]
operationId: uploadStagedAttachment
description: >-
The same real, immediately-addressable attachment as
`POST /pages/{pageId}/attachments` — owned by an open page-creation session
instead of an existing page until that session's commit re-points ownership
atomically. Requires an editor cookie session or an `attachments:write`
service-token scope.
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
responses:
"200":
description: Uploaded
content:
application/json:
schema:
$ref: "#/components/schemas/AttachmentSummary"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
description: The session does not exist, is no longer open, or has expired
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"413":
description: File exceeds the configured maximum upload size
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"422":
description: Rejected content (unsafe signature) or an invalid filename
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/pages/staged/{sessionId}/commit:
parameters:
- $ref: "#/components/parameters/SessionId"
- $ref: "#/components/parameters/CorrelationId"
post:
tags: [Pages]
operationId: commitPageCreationSession
description: >-
The single explicit publication action: atomically creates the page and
re-points every attachment already uploaded into this session to it, in one
transaction — no half-published page with broken attachment links can ever
become visible (docs/adr/ADR-025-v2-r1-data-integrity-remediation.md).
Requires an editor cookie session or a `pages:write` service-token scope.
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CommitPageCreationSessionRequest"
responses:
"200":
description: Page created and published, with every staged attachment now owned by it
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/PageDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
description: The session does not exist
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"409":
description: The session is no longer open (already committed, discarded, or expired)
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"422":
$ref: "#/components/responses/BadRequest"
/pages/staged/{sessionId}/discard:
parameters:
- $ref: "#/components/parameters/SessionId"
- $ref: "#/components/parameters/CorrelationId"
post:
tags: [Pages]
operationId: discardPageCreationSession
description: >-
Explicit compensation for an abandoned session: removes every attachment
uploaded into it that is not used elsewhere, then marks the session
discarded. Idempotent — discarding an already-discarded session is a safe
no-op. Requires an editor cookie session or a `pages:write` service-token
scope.
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
responses:
"200":
description: Session discarded (or already was)
content:
application/json:
schema:
$ref: "#/components/schemas/DiscardPageCreationSessionResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
description: The session does not exist
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"409":
description: The session was already committed and can no longer be discarded
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/infrastructure/overview:
get:
tags: [Infrastructure]
operationId: getInfrastructureOverview
description: >-
Requires a reader cookie session or an `infrastructure:read` service-token
scope. The dashboard summary: the most recent inventory snapshot (if any),
every known host, and aggregate service counts (total, proposed/unreviewed,
missing, unhealthy, unprotected-by-backup). Returns an all-zero/empty response
rather than an error when no workspace exists yet.
responses:
"200":
description: Infrastructure overview
content:
application/json:
schema:
$ref: "#/components/schemas/InfrastructureOverviewResponse"
"401":
$ref: "#/components/responses/Unauthorized"
/infrastructure/services:
get:
tags: [Infrastructure]
operationId: listInfrastructureServices
description: >-
Requires a reader cookie session or an `infrastructure:read` service-token
scope. Cursor-paginated services catalog
(docs/adr/ADR-026-v2-r2-infrastructure-scale-and-sync-durability.md) — proven
at real production scale (InfrastructureCatalogReleaseScaleTests.cs, 50,000
services) with an index-order scan, never a full in-memory load. `projectId`,
`search` and `status` are applied server-side on the same paginated query;
filter results never depend on pages already loaded by the client.
parameters:
- name: includeHidden
in: query
required: false
description: Defaults to false — hidden/archived services are excluded from the default catalog view.
schema:
type: boolean
- name: projectId
in: query
required: false
description: Only services whose editorial profile links this project.
schema:
type: string
format: uuid
- name: search
in: query
required: false
description: Case-insensitive match against detected name, image reference, and editorial display name/category.
schema:
type: string
- name: status
in: query
required: false
description: Server-side operational status filter.
schema:
type: string
enum: [attention, healthy, missing]
- $ref: "#/components/parameters/Cursor"
- $ref: "#/components/parameters/Limit"
responses:
"200":
description: A page of services, newest-`(DetectedName, Id)`-ordered
content:
application/json:
schema:
$ref: "#/components/schemas/InfrastructureServiceListResponse"
"401":
$ref: "#/components/responses/Unauthorized"
/infrastructure/storage:
get:
tags: [Infrastructure]
operationId: listInfrastructureStorage
description: >-
Requires a reader cookie session or an `infrastructure:read` service-token
scope. Cursor-paginated storage listing (V2-R5,
docs/adr/ADR-029-v2-r5-final-hardening.md) — every known volume across every
service, each row carrying its owning service's ID/name for display.
parameters:
- name: search
in: query
required: false
description: Case-insensitive match against the owning service's detected name and the volume's own name/mount path, applied server-side.
schema:
type: string
- $ref: "#/components/parameters/Cursor"
- $ref: "#/components/parameters/Limit"
responses:
"200":
description: A page of storage volumes
content:
application/json:
schema:
$ref: "#/components/schemas/InfrastructureStorageListResponse"
"401":
$ref: "#/components/responses/Unauthorized"
/infrastructure/services/{serviceId}:
get:
tags: [Infrastructure]
operationId: getInfrastructureService
description: >-
Requires a reader cookie session or an `infrastructure:read` service-token
scope. The full fact graph for one service: editorial profile, manual
overrides, components, endpoints, networks, volumes, databases, dependencies,
health observations, deployment references and backup coverage, each fact
carrying its own provenance (source key, confidence, last-seen snapshot,
observed-at). The `ETag` response header is the editorial profile's
optimistic-concurrency token, required as `If-Match` on
`PUT .../editorial-profile`.
parameters:
- name: serviceId
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Full service detail
headers:
ETag:
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/InfrastructureServiceDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
/infrastructure/services/{serviceId}/editorial-profile:
put:
tags: [Infrastructure]
operationId: updateInfrastructureEditorialProfile
description: >-
Editor role or `infrastructure:write` service-token scope required, CSRF- and
`If-Match`-protected. Updates the human-authored editorial layer for a service
(display name, summary, category, purpose, owning project, procedures, tags,
favorite/hidden/archived flags) — automatically-detected facts are never
touched by this endpoint. A stale `If-Match` is a 412, never a silent overwrite.
parameters:
- name: serviceId
in: path
required: true
schema:
type: string
format: uuid
- $ref: "#/components/parameters/IfMatch"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateEditorialProfileRequest"
responses:
"200":
description: Updated service detail
headers:
ETag:
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/InfrastructureServiceDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"412":
description: If-Match did not match the current editorial profile ETag
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"422":
$ref: "#/components/responses/ValidationFailed"
"428":
description: If-Match header is required and was missing
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/infrastructure/services/{serviceId}/overrides:
post:
tags: [Infrastructure]
operationId: setManualOverride
description: >-
Editor role or `infrastructure:write` service-token scope required,
CSRF-protected. Sets (creates or replaces) one field-level manual correction
over an automatically-detected fact, validated against the exact allowlist
`GET /infrastructure/overrides/catalog` returns for this object kind — an
inventory sync never overwrites or removes an existing override
(docs/adr/ADR-025-v2-r1-data-integrity-remediation.md, audit finding H2).
parameters:
- name: serviceId
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SetManualOverrideRequest"
responses:
"200":
description: The created/updated override's ID
content:
application/json:
schema:
type: object
properties:
id:
type: string
format: uuid
required: [id]
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"422":
$ref: "#/components/responses/ValidationFailed"
/infrastructure/overrides:
post:
tags: [Infrastructure]
operationId: setInfrastructureOverride
description: >-
Editor role or `infrastructure:write` service-token scope required,
CSRF-protected. The generic sibling of
`POST /infrastructure/services/{serviceId}/overrides` (V2-R5,
docs/adr/ADR-029-v2-r5-final-hardening.md): sets a field-level manual
correction for any `InfrastructureObjectKind` (Host, Network, Volume, Database,
Endpoint, Component, Deployment, Backup), not only a Service reached through
its own nested route — validated against the exact allowlist
`GET /infrastructure/overrides/catalog?targetKind=...` returns for that kind.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SetInfrastructureOverrideRequest"
responses:
"200":
description: The created/updated override's ID
content:
application/json:
schema:
type: object
properties:
id:
type: string
format: uuid
required: [id]
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"422":
$ref: "#/components/responses/ValidationFailed"
/infrastructure/overrides/{overrideId}:
delete:
tags: [Infrastructure]
operationId: removeManualOverride
description: >-
Editor role or `infrastructure:write` service-token scope required,
CSRF-protected. Removes one manual override, reverting that field to its
automatically-detected value (if any).
parameters:
- name: overrideId
in: path
required: true
schema:
type: string
format: uuid
responses:
"204":
description: Override removed
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/infrastructure/services/{serviceId}/review:
post:
tags: [Infrastructure]
operationId: reviewInfrastructureService
description: >-
Administrator only, CSRF-protected. Accepts or rejects a `Proposed` (newly
detected, unreviewed) service — the deliberate human-in-the-loop gate before an
automatically-detected container becomes part of the confirmed catalog. A
name match alone is never treated as a high-confidence relationship
(docs/adr/ADR-029-v2-r5-final-hardening.md).
parameters:
- name: serviceId
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ReviewInfrastructureServiceRequest"
responses:
"200":
description: The service's resulting presence state
content:
application/json:
schema:
$ref: "#/components/schemas/ReviewInfrastructureServiceResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"422":
$ref: "#/components/responses/ValidationFailed"
/infrastructure/inventory/snapshots:
post:
tags: [Infrastructure]
operationId: synchronizeInfrastructureInventory
description: >-
Administrator only, CSRF- and `Idempotency-Key`-protected. Imports one
inventory snapshot (hosts + services + every nested fact) captured by the
external, read-only inventory tooling — never destructive: existing editorial
profiles and manual overrides are always preserved, a previously-seen service
that is missing from this snapshot is marked missing (not deleted), and
replaying the same `sourceKey`/`sourceHash` is a safe no-op
(`alreadyImported: true`), not a duplicate import.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SynchronizeInventoryRequest"
responses:
"200":
description: Synchronization result
content:
application/json:
schema:
$ref: "#/components/schemas/InventorySyncResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"409":
description: Idempotency-Key reused with a different request body
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"422":
$ref: "#/components/responses/ValidationFailed"
/infrastructure/inventory/curated-package:
post:
tags: [Infrastructure]
operationId: importCuratedInventoryPackage
description: >-
Administrator only, CSRF- and `Idempotency-Key`-protected. A single bounded
call that both synchronizes a raw inventory snapshot and applies a curated set
of editorial profiles/review decisions on top of it in one step — the
mechanism the curated-infrastructure-wiki publication used
(docs/deployments/2026-08-02-curated-infrastructure-wiki.md). Never overwrites
an editorial profile a human has since hand-edited.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ImportCuratedInventoryRequest"
responses:
"200":
description: Combined synchronization + curation result
content:
application/json:
schema:
$ref: "#/components/schemas/ImportCuratedInventoryResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"409":
description: Idempotency-Key reused with a different request body
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"422":
$ref: "#/components/responses/ValidationFailed"
/infrastructure/changes:
get:
tags: [Infrastructure]
operationId: listInfrastructureChanges
description: >-
Requires a reader cookie session or an `infrastructure:read` service-token
scope. The most recent inventory-sync change log entries (new/changed/missing
objects), newest first, each naming its target kind/ID, source key, change
kind and the specific fields that changed.
parameters:
- name: limit
in: query
required: false
description: Defaults to 100.
schema:
type: integer
minimum: 1
maximum: 500
responses:
"200":
description: Recent inventory changes, newest first
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/InventoryChangeResponse"
"401":
$ref: "#/components/responses/Unauthorized"
/infrastructure/overrides/catalog:
get:
tags: [Infrastructure]
operationId: getManualOverrideFieldCatalog
description: >-
The exact, server-authoritative set of fields a ManualOverride may target for
the given InfrastructureObjectKind, with each field's JSON value kind, max
length and (for enum fields) allowed values
(docs/adr/ADR-025-v2-r1-data-integrity-remediation.md, audit finding H3). The
frontend fetches this instead of keeping its own list, so the two can never
independently drift. Requires a reader cookie session or an
`infrastructure:read` service-token scope.
parameters:
- name: targetKind
in: query
required: false
description: Defaults to Service, the only kind reachable through the current UI/API.
schema:
type: string
enum: [Host, Service, Component, Endpoint, Network, Volume, Database, Deployment, Backup]
responses:
"200":
description: Field catalog for the requested target kind
content:
application/json:
schema:
$ref: "#/components/schemas/ManualOverrideFieldCatalogResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"422":
description: Unknown targetKind
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/attachments/{attachmentId}/content:
get:
tags: [Attachments]
operationId: getAttachmentContent
description: >-
Requires a reader cookie session or an `attachments:read` service-token scope.
Every request re-authorizes the logical reference (docs/13-attachments.md) —
a content hash alone is never sufficient permission to retrieve a blob. Served
with a safe Content-Disposition (inline only for a known-safe raster image,
attachment otherwise) and X-Content-Type-Options: nosniff.
parameters:
- name: attachmentId
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: The raw file content
content:
application/octet-stream: {}
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
/admin/imports/preflight:
post:
tags: [Imports]
operationId: preflightImport
description: >-
Administrator only. Discovers and fingerprints a read-only source beneath the
operator-mounted imports directory, selects an adapter, and builds a plan —
no target mutation occurs (docs/17-leafwiki-migration.md).
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PreflightImportRequest"
responses:
"200":
description: Plan ready (or an idempotent repeat of an already-preflighted request)
content:
application/json:
schema:
$ref: "#/components/schemas/ImportPreflightResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"422":
description: Invalid source path, or no adapter can interpret this source
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/admin/imports/{jobId}:
get:
tags: [Imports]
operationId: getImportJob
parameters:
- $ref: "#/components/parameters/ImportJobId"
responses:
"200":
description: Import job status
content:
application/json:
schema:
$ref: "#/components/schemas/ImportJobSummaryResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/admin/imports/{jobId}/execute:
post:
tags: [Imports]
operationId: executeImport
description: >-
Administrator only. Re-verifies the source fingerprint and plan hash have not
changed since preflight, then creates each planned page — parents before
children — with full provenance for rollback. Never overwrites an existing
page; an item already imported with unchanged content is skipped idempotently.
parameters:
- $ref: "#/components/parameters/ImportJobId"
- name: sourceRelativePath
in: query
required: true
description: The same relative path supplied to preflight, re-verified against its recorded fingerprint.
schema:
type: string
responses:
"200":
description: Execution result
content:
application/json:
schema:
$ref: "#/components/schemas/ExecuteImportResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: The source or plan changed since preflight, or the job is not in a plan-ready state
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/admin/imports/{jobId}/rollback:
post:
tags: [Imports]
operationId: rollbackImport
description: >-
Administrator only. Removes exactly the pages/attachments this job's own
provenance created — never a page a user has since edited, which is preserved
instead (docs/17-leafwiki-migration.md).
parameters:
- $ref: "#/components/parameters/ImportJobId"
responses:
"200":
description: Rollback result
content:
application/json:
schema:
$ref: "#/components/schemas/RollbackImportResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: There is nothing to roll back for this job
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/admin/source-batches/purge/dry-run:
post:
tags: [Source batch purge]
operationId: dryRunSourceBatchPurge
description: >-
Administrator only. Inspects an explicitly supplied, independently recorded
page-ID list for one source batch. It blocks on missing or unlisted source
pages, derived content outside the list, retained backlinks or project links.
This endpoint never writes data.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SourceBatchPurgeRequest"
responses:
"200":
description: Immutable-scope dry-run and confirmation hash
content:
application/json:
schema:
$ref: "#/components/schemas/SourceBatchPurgeDryRunResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/admin/source-batches/purge/execute:
post:
tags: [Source batch purge]
operationId: executeSourceBatchPurge
description: >-
Administrator only. Permanently removes only the pages and attachment
references proven by the preceding dry-run. The confirmation hash binds the
request to the current page hashes and attachment graph; any change requires a
new dry-run. Physical blobs are hash-verified and removed only after their
database references have been committed as absent.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ExecuteSourceBatchPurgeRequest"
responses:
"200":
description: Completed source-batch purge
content:
application/json:
schema:
$ref: "#/components/schemas/SourceBatchPurgeResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"409":
description: The dry-run changed or the source scope is ambiguous
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/projects:
get:
tags: [Projects]
operationId: listProjects
parameters:
- $ref: "#/components/parameters/Cursor"
- $ref: "#/components/parameters/Limit"
- name: status
in: query
schema:
$ref: "#/components/schemas/ProjectStatus"
- name: priority
in: query
schema:
$ref: "#/components/schemas/ProjectPriority"
- name: ownerId
in: query
schema:
type: string
format: uuid
- name: search
in: query
description: Case-insensitive match against project name or summary, applied before cursor pagination.
schema:
type: string
- name: updatedAfter
in: query
description: Only projects updated at or after this ISO-8601 timestamp.
schema:
type: string
format: date-time
- name: sort
in: query
schema:
type: string
enum: [name, updatedAt]
responses:
"200":
description: Project collection
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectListResponse"
"401":
$ref: "#/components/responses/Unauthorized"
post:
tags: [Projects]
operationId: createProject
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateProjectRequest"
responses:
"200":
description: Created project (full bundle)
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectDetail"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/projects/totals:
get:
tags: [Projects]
operationId: getProjectCatalogTotals
description: Authoritative workspace totals for dashboard KPI rendering; independent of cursor-paginated catalog pages.
responses:
"200":
description: Total and active project counts
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectCatalogTotalsResponse"
"401":
$ref: "#/components/responses/Unauthorized"
/projects/{projectId}:
parameters:
- $ref: "#/components/parameters/ProjectId"
get:
tags: [Projects]
operationId: getProject
description: >-
The project's bounded context bundle — structured data plus stable citations
into linked pages, never a copy of their content (docs/15-project-hub.md
"Project context bundle", M5-A05).
responses:
"200":
description: Project detail bundle (documents, recent updates, milestones, decisions)
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
put:
tags: [Projects]
operationId: updateProject
parameters:
- $ref: "#/components/parameters/IfMatch"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateProjectRequest"
responses:
"200":
description: Updated project
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectDetail"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/projects/{projectId}/context:
parameters:
- $ref: "#/components/parameters/ProjectId"
get:
tags: [Projects]
operationId: getProjectContext
description: >-
A bounded, single-call context bundle for automation and future MCP-server
consumers (docs/09-api-contract.md "Project context", docs/24-mcp-readiness.md,
M6-A04). Update/decision/document counts are clamped; the response reports
what was truncated instead of ever growing unbounded. Distinct from the
unbounded `GET /projects/{projectId}` detail bundle.
responses:
"200":
description: Bounded project context bundle with stable citations
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectContextResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/projects/{projectId}/icon:
parameters:
- $ref: "#/components/parameters/ProjectId"
post:
tags: [Projects]
operationId: uploadProjectIcon
description: >-
Replaces the current project icon using the attachment quarantine and
byte-signature validation pipeline. Accepts only PNG, JPEG, GIF or WebP up to
2 MiB. The response points to same-origin, workspace-authorized attachment
content. Requires the project's current ETag.
parameters:
- $ref: "#/components/parameters/IfMatch"
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
responses:
"200":
description: Project icon replaced
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectIconResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"412":
$ref: "#/components/responses/PreconditionFailed"
"413":
description: Icon file exceeds 2 MiB
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"422":
description: Unsupported or invalid image content
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"428":
$ref: "#/components/responses/PreconditionRequired"
/projects/{projectId}/status:
parameters:
- $ref: "#/components/parameters/ProjectId"
post:
tags: [Projects]
operationId: changeProjectStatus
description: Only the documented lifecycle transitions are accepted (docs/15-project-hub.md).
parameters:
- $ref: "#/components/parameters/IfMatch"
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [status]
properties:
status:
$ref: "#/components/schemas/ProjectStatus"
responses:
"200":
description: Updated project
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: The requested transition is not allowed from the project's current status
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/projects/{projectId}/documents:
parameters:
- $ref: "#/components/parameters/ProjectId"
post:
tags: [Projects]
operationId: linkProjectDocument
description: >-
Links a page to the project with a role; the page remains canonical, only the
relationship is stored (docs/15-project-hub.md).
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/LinkProjectDocumentRequest"
responses:
"200":
description: Created link
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectDocumentSummary"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/projects/documents/{documentId}:
parameters:
- name: documentId
in: path
required: true
schema:
type: string
format: uuid
delete:
tags: [Projects]
operationId: unlinkProjectDocument
description: Removes only the relationship — the linked page is never touched or deleted.
responses:
"200":
description: Unlinked
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/projects/{projectId}/updates:
parameters:
- $ref: "#/components/parameters/ProjectId"
get:
tags: [Projects]
operationId: getProjectUpdates
description: Ordered, attributable, linkable timeline (M5-A02).
parameters:
- $ref: "#/components/parameters/Cursor"
- $ref: "#/components/parameters/Limit"
responses:
"200":
description: Update timeline page
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectUpdatesResponse"
"401":
$ref: "#/components/responses/Unauthorized"
post:
tags: [Projects]
operationId: createProjectUpdate
description: >-
Immutable, append-only entry. Setting supersedesUpdateId marks the earlier
update Superseded without rewriting it (docs/15-project-hub.md).
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateProjectUpdateRequest"
responses:
"200":
description: Created update
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectUpdateSummary"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/projects/{projectId}/milestones:
parameters:
- $ref: "#/components/parameters/ProjectId"
get:
tags: [Projects]
operationId: getProjectMilestones
responses:
"200":
description: Milestones, in their stable sort order
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ProjectMilestoneSummary"
"401":
$ref: "#/components/responses/Unauthorized"
post:
tags: [Projects]
operationId: createProjectMilestone
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateProjectMilestoneRequest"
responses:
"200":
description: Created milestone
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectMilestoneSummary"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/projects/milestones/{milestoneId}:
parameters:
- name: milestoneId
in: path
required: true
schema:
type: string
format: uuid
put:
tags: [Projects]
operationId: updateProjectMilestone
parameters:
- $ref: "#/components/parameters/IfMatch"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateProjectMilestoneRequest"
responses:
"200":
description: Updated milestone
content:
application/json:
schema:
$ref: "#/components/schemas/ProjectMilestoneSummary"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/decisions:
get:
tags: [Decisions]
operationId: listDecisions
parameters:
- $ref: "#/components/parameters/Cursor"
- $ref: "#/components/parameters/Limit"
- name: projectId
in: query
schema:
type: string
format: uuid
- name: status
in: query
schema:
$ref: "#/components/schemas/DecisionStatus"
responses:
"200":
description: Decision collection
content:
application/json:
schema:
$ref: "#/components/schemas/DecisionListResponse"
"401":
$ref: "#/components/responses/Unauthorized"
post:
tags: [Decisions]
operationId: createDecision
description: >-
Creates one ArchitectureDecision page plus its lifecycle profile in one step
(docs/16-runbooks-and-adrs.md "unique page profile").
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateDecisionRequest"
responses:
"200":
description: Created decision (full detail, including rendered content)
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/DecisionDetail"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/decisions/{decisionId}:
parameters:
- name: decisionId
in: path
required: true
schema:
type: string
format: uuid
get:
tags: [Decisions]
operationId: getDecision
description: Includes lineage (supersedes/supersededBy) — links are bidirectional and cycle-free.
responses:
"200":
description: Decision detail with lineage
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/DecisionDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
/decisions/{decisionId}/accept:
parameters:
- name: decisionId
in: path
required: true
schema:
type: string
format: uuid
post:
tags: [Decisions]
operationId: acceptDecision
description: >-
If this decision supersedes another, that decision must currently be Accepted
and is atomically flipped to Superseded with bidirectional links
(docs/16-runbooks-and-adrs.md).
parameters:
- $ref: "#/components/parameters/IfMatch"
responses:
"200":
description: Accepted decision
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/DecisionDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: Only a Proposed decision can be accepted
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/decisions/{decisionId}/reject:
parameters:
- name: decisionId
in: path
required: true
schema:
type: string
format: uuid
post:
tags: [Decisions]
operationId: rejectDecision
parameters:
- $ref: "#/components/parameters/IfMatch"
responses:
"200":
description: Rejected decision
content:
application/json:
schema:
$ref: "#/components/schemas/DecisionDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: Only a Proposed decision can be rejected
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/decisions/{decisionId}/withdraw:
parameters:
- name: decisionId
in: path
required: true
schema:
type: string
format: uuid
post:
tags: [Decisions]
operationId: withdrawDecision
parameters:
- $ref: "#/components/parameters/IfMatch"
responses:
"200":
description: Withdrawn decision
content:
application/json:
schema:
$ref: "#/components/schemas/DecisionDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: Only a Proposed or Accepted decision can be withdrawn
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/runbooks:
get:
tags: [Runbooks]
operationId: listRunbooks
parameters:
- $ref: "#/components/parameters/Cursor"
- $ref: "#/components/parameters/Limit"
- name: riskLevel
in: query
schema:
$ref: "#/components/schemas/RunbookRiskLevel"
- name: status
in: query
schema:
$ref: "#/components/schemas/RunbookStatus"
responses:
"200":
description: Runbook collection
content:
application/json:
schema:
$ref: "#/components/schemas/RunbookListResponse"
"401":
$ref: "#/components/responses/Unauthorized"
post:
tags: [Runbooks]
operationId: createRunbook
description: >-
Creates one Runbook page plus its validation/risk profile in one step
(docs/16-runbooks-and-adrs.md "unique page profile").
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateRunbookRequest"
responses:
"200":
description: Created runbook (full detail, including rendered content)
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/RunbookDetail"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/runbooks/{runbookId}:
parameters:
- name: runbookId
in: path
required: true
schema:
type: string
format: uuid
get:
tags: [Runbooks]
operationId: getRunbook
description: >-
isStale is true whenever the linked page has a newer revision than the last
validation, or was never validated (docs/16-runbooks-and-adrs.md).
responses:
"200":
description: Runbook detail
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/RunbookDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
/runbooks/{runbookId}/validate:
parameters:
- name: runbookId
in: path
required: true
schema:
type: string
format: uuid
post:
tags: [Runbooks]
operationId: validateRunbook
description: >-
Records that a human reviewed the current revision — the platform never
executes any step (docs/16-runbooks-and-adrs.md).
parameters:
- $ref: "#/components/parameters/IfMatch"
responses:
"200":
description: Validated runbook
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/RunbookDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/runbooks/{runbookId}/profile:
parameters:
- name: runbookId
in: path
required: true
schema:
type: string
format: uuid
put:
tags: [Runbooks]
operationId: updateRunbookProfile
parameters:
- $ref: "#/components/parameters/IfMatch"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateRunbookProfileRequest"
responses:
"200":
description: Updated runbook
headers:
ETag:
$ref: "#/components/headers/ETag"
content:
application/json:
schema:
$ref: "#/components/schemas/RunbookDetail"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"412":
$ref: "#/components/responses/PreconditionFailed"
"428":
$ref: "#/components/responses/PreconditionRequired"
/service-accounts:
get:
tags: [Service accounts]
operationId: listServiceAccounts
description: Administrator-only, cookie-authenticated — never reachable by a service token itself (docs/09-api-contract.md).
responses:
"200":
description: Service accounts and their (secret-free) tokens
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceAccountListResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
post:
tags: [Service accounts]
operationId: createServiceAccount
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateServiceAccountRequest"
responses:
"200":
description: Service account and its one-time raw token — never retrievable again
content:
application/json:
schema:
$ref: "#/components/schemas/CreatedServiceAccountToken"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"422":
description: One or more requested scopes are not recognized
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
/service-accounts/{serviceAccountId}/tokens:
parameters:
- name: serviceAccountId
in: path
required: true
schema:
type: string
format: uuid
post:
tags: [Service accounts]
operationId: issueServiceAccountToken
description: >-
Rotation pattern: issue a new token, update the client, then revoke the old
one — a credential is never briefly unusable.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/IssueServiceAccountTokenRequest"
responses:
"200":
description: New token — its one-time raw value
content:
application/json:
schema:
$ref: "#/components/schemas/CreatedServiceAccountToken"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/service-accounts/tokens/{tokenId}/revoke:
parameters:
- name: tokenId
in: path
required: true
schema:
type: string
format: uuid
post:
tags: [Service accounts]
operationId: revokeServiceAccountToken
description: Takes effect on the very next request using this token — tokens are verified live against the database, never cached as valid.
responses:
"200":
description: Revoked
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/admin/backups:
get:
tags: [Backups]
operationId: listBackups
description: >-
Administrator-only (docs/18-backup-restore.md). Never includes a filesystem
path or connection string — only what an administrator needs to judge backup
health.
responses:
"200":
description: Completed backups, newest first
content:
application/json:
schema:
$ref: "#/components/schemas/BackupListResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
post:
tags: [Backups]
operationId: createBackup
description: >-
Synchronously creates and verifies a full backup (every table plus every
content-addressed attachment blob), then applies retention. A failed backup
never replaces the last known-good one. Restore is intentionally not exposed
here — it runs as a separate maintenance-mode invocation of the same
deployable image (docs/18-backup-restore.md; ADR-016 decision 4).
responses:
"200":
description: The newly created, verified backup
content:
application/json:
schema:
$ref: "#/components/schemas/BackupSummaryResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"409":
description: A backup is already in progress
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
components:
securitySchemes:
cookieAuth:
type: apiKey
in: cookie
name: ikh_auth
bearerAuth:
type: http
scheme: bearer
bearerFormat: IKH service token
parameters:
CorrelationId:
name: X-Correlation-ID
in: header
required: false
schema:
type: string
maxLength: 100
IdempotencyKey:
name: Idempotency-Key
in: header
required: false
schema:
type: string
minLength: 16
maxLength: 200
RequiredIdempotencyKey:
name: Idempotency-Key
in: header
required: true
schema:
type: string
minLength: 1
maxLength: 200
Cursor:
name: cursor
in: query
required: false
schema:
type: string
maxLength: 2048
Limit:
name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 25
PageId:
name: pageId
in: path
required: true
schema:
type: string
format: uuid
SessionId:
name: sessionId
in: path
required: true
schema:
type: string
format: uuid
RevisionNumber:
name: revisionNumber
in: path
required: true
schema:
type: integer
format: int64
minimum: 1
ImportJobId:
name: jobId
in: path
required: true
schema:
type: string
format: uuid
ProjectId:
name: projectId
in: path
required: true
schema:
type: string
format: uuid
UserId:
name: userId
in: path
required: true
schema:
type: string
format: uuid
DraftKey:
name: draftKey
in: path
required: true
description: The page ID in edit mode, or a client-generated key for an unpublished new page.
schema:
type: string
IfMatch:
name: If-Match
in: header
required: true
schema:
type: string
headers:
ETag:
description: Strong entity version tag
schema:
type: string
responses:
BadRequest:
description: Invalid request
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
Unauthorized:
description: Authentication required
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
Forbidden:
description: Insufficient permission
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
NotFound:
description: Resource not found or not disclosed
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
Conflict:
description: Idempotency or domain conflict
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
PreconditionFailed:
description: ETag does not match the current resource
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
PreconditionRequired:
description: If-Match is required
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
ValidationFailed:
description: Request failed validation
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
RateLimited:
description: Request rate limited
headers:
Retry-After:
schema:
type: integer
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
schemas:
CreateIngestionProposalRequest:
type: object
required: [manifest]
properties:
manifest: { type: string, minLength: 1, maxLength: 100000 }
correlationId: { type: [string, "null"], maxLength: 200 }
publication: { oneOf: [{ $ref: "#/components/schemas/IngestionPublicationRequest" }, { type: "null" }] }
IngestionPublicationRequest:
type: object
required: [title, markdown, documentType, documentStatus, mutationKind]
properties:
title: { type: string, minLength: 1, maxLength: 200 }
markdown: { type: string, minLength: 1, maxLength: 100000 }
documentType: { $ref: "#/components/schemas/DocumentType" }
documentStatus: { $ref: "#/components/schemas/DocumentStatus" }
mutationKind: { type: string, enum: [CreatePage, UpdatePage] }
parentPageId: { type: [string, "null"], format: uuid }
targetPageId: { type: [string, "null"], format: uuid }
expectedTargetETag: { type: [string, "null"], maxLength: 32 }
PatchIngestionProposalRequest:
type: object
description: Human callers use manifest/rationale; the submitting service account uses analysis only.
properties:
manifest: { type: [string, "null"], maxLength: 100000 }
rationale: { type: [string, "null"], maxLength: 2000 }
analysis: { type: [string, "null"], maxLength: 100000 }
IngestionProposalDecisionRequest:
type: object
properties:
comment: { type: [string, "null"], maxLength: 2000 }
IngestionProposalSourceDetail:
type: object
required: [id, fileName, mimeType, byteSize, digestSha256, scanResult, attachmentReferenceId]
properties:
id: { type: string, format: uuid }
fileName: { type: string }
mimeType: { type: string }
byteSize: { type: integer, format: int64 }
digestSha256: { type: string, pattern: '^[a-f0-9]{64}$' }
scanResult: { type: string }
attachmentReferenceId: { type: string, format: uuid }
IngestionProposalRevisionDetail:
type: object
required: [revisionNumber, status, operation, actorId, occurredAt]
properties:
revisionNumber: { type: integer, format: int64 }
status: { type: string }
operation: { type: string }
actorId: { type: string, format: uuid }
occurredAt: { type: string, format: date-time }
IngestionProposalSummary:
type: object
required: [id, correlationId, status, revisionNumber, sourceCount, submitterId, createdAt, destination, metadata, mutationKind]
properties:
id: { type: string, format: uuid }
correlationId: { type: string }
status: { type: string }
revisionNumber: { type: integer, format: int64 }
sourceCount: { type: integer }
submitterId: { type: string, format: uuid }
createdAt: { type: string, format: date-time }
destination: { oneOf: [{ $ref: "#/components/schemas/IngestionProposalDestination" }, { type: "null" }] }
metadata: { $ref: "#/components/schemas/IngestionProposalMetadata" }
mutationKind: { type: [string, "null"], enum: [CreatePage, UpdatePage, null] }
IngestionProposalListResponse:
type: object
required: [items, nextCursor, page, pageSize, total, correlationId]
properties:
items: { type: array, items: { $ref: "#/components/schemas/IngestionProposalSummary" } }
nextCursor: { type: [string, "null"] }
page: { type: integer, minimum: 1 }
pageSize: { type: integer, minimum: 1, maximum: 100 }
total: { type: integer, format: int64, minimum: 0 }
correlationId: { type: string }
IngestionProposalDestination:
type: object
required: [pageId, parentPageId, title]
properties:
pageId: { type: [string, "null"], format: uuid }
parentPageId: { type: [string, "null"], format: uuid }
title: { type: [string, "null"] }
IngestionProposalMetadata:
type: object
description: Project and risk are currently unavailable persisted attributes and are therefore explicitly null.
required: [documentType, documentStatus, projectId, risk]
properties:
documentType: { type: [string, "null"] }
documentStatus: { type: [string, "null"] }
projectId: { type: [string, "null"], format: uuid }
risk: { type: [string, "null"], enum: [low, medium, high, null] }
IngestionProposalCitation:
type: object
required: [label, pageId, excerpt, confidence]
properties:
label: { type: string }
pageId: { type: [string, "null"], format: uuid }
excerpt: { type: [string, "null"] }
confidence: { type: [number, "null"] }
IngestionProposalWarning:
type: object
required: [code, message, severity]
properties:
code: { type: string }
message: { type: string }
severity: { type: [string, "null"], enum: [low, medium, high, null] }
IngestionProposalCandidate:
type: object
required: [pageId, title, kind]
properties:
pageId: { type: [string, "null"], format: uuid }
title: { type: [string, "null"] }
kind: { type: [string, "null"] }
IngestionProposalDiff:
type: object
required: [before, after, summary]
properties:
before: { type: [string, "null"] }
after: { type: [string, "null"] }
summary: { type: [string, "null"] }
IngestionProposalPermissions:
type: object
required: [canEdit, canDecide, canPublish]
properties:
canEdit: { type: boolean }
canDecide: { type: boolean }
canPublish: { type: boolean }
IngestionProposalDetail:
type: object
description: Safe dossier metadata only; it never contains source bytes, paths, URLs, credentials, or tokens.
required: [id, correlationId, status, revisionNumber, manifest, retryCount, maxPublishRetries, isRetryEligible, sources, revisions, etag, submitterId, createdAt, destination, metadata, mutationKind, attachments, expectedTargetETag, citations, warnings, duplicateCandidates, conflictCandidates, diff, permissions]
properties:
id: { type: string, format: uuid }
correlationId: { type: string }
status: { type: string }
revisionNumber: { type: integer, format: int64 }
manifest: { type: string }
analysis: { type: [string, "null"] }
reviewerComment: { type: [string, "null"] }
retryCount: { type: integer }
maxPublishRetries: { type: integer }
isRetryEligible: { type: boolean }
failedLifecycleStatus: { type: [string, "null"] }
failedProcessingStage: { type: [string, "null"] }
failureErrorType: { type: [string, "null"] }
failureMessage: { type: [string, "null"] }
sources: { type: array, items: { $ref: "#/components/schemas/IngestionProposalSourceDetail" } }
revisions: { type: array, items: { $ref: "#/components/schemas/IngestionProposalRevisionDetail" } }
etag: { type: string }
publishedPageId: { type: [string, "null"], format: uuid }
publishedPageRevisionNumber: { type: [integer, "null"], format: int64 }
submitterId: { type: string, format: uuid }
createdAt: { type: string, format: date-time }
destination: { oneOf: [{ $ref: "#/components/schemas/IngestionProposalDestination" }, { type: "null" }] }
metadata: { $ref: "#/components/schemas/IngestionProposalMetadata" }
mutationKind: { type: [string, "null"], enum: [CreatePage, UpdatePage, null] }
attachments: { type: array, items: { $ref: "#/components/schemas/IngestionProposalSourceDetail" } }
expectedTargetETag: { type: [string, "null"] }
citations: { type: array, items: { $ref: "#/components/schemas/IngestionProposalCitation" } }
warnings: { type: array, items: { $ref: "#/components/schemas/IngestionProposalWarning" } }
duplicateCandidates: { type: array, items: { $ref: "#/components/schemas/IngestionProposalCandidate" } }
conflictCandidates: { type: array, items: { $ref: "#/components/schemas/IngestionProposalCandidate" } }
diff:
description: after contains the proposed publication Markdown. before is null when no current target snapshot is available.
oneOf: [{ $ref: "#/components/schemas/IngestionProposalDiff" }, { type: "null" }]
permissions: { $ref: "#/components/schemas/IngestionProposalPermissions" }
SystemInfo:
type: object
required: [product, version, commit, schemaVersion, environment, primaryLocale, timeZone, uptimeSeconds, release]
properties:
product:
type: string
const: ItWorx Knowledge Hub
version:
type: string
commit:
type: string
schemaVersion:
type: string
environment:
type: string
primaryLocale:
type: string
timeZone:
type: string
uptimeSeconds:
type: integer
release:
$ref: "#/components/schemas/ReleaseIdentity"
ReleaseIdentity:
type: object
description: >-
The canonical, machine-readable release identity
(docs/adr/ADR-029-v2-r5-final-hardening.md). Every field is honestly nullable —
a value not actually known at build or deploy time is never fabricated.
required: [semanticVersion, releaseTag, sourceCommit, imageRepository, imageTag, imageId, imageDigest, buildTimestamp, deploymentTimestamp, openApiVersion, openApiHash, frontendBuildHash, databaseMigrationRange, inventorySnapshotId, backupId]
properties:
semanticVersion:
type: [string, "null"]
releaseTag:
type: [string, "null"]
sourceCommit:
type: [string, "null"]
imageRepository:
type: [string, "null"]
imageTag:
type: [string, "null"]
imageId:
type: [string, "null"]
imageDigest:
type: [string, "null"]
buildTimestamp:
type: [string, "null"]
format: date-time
deploymentTimestamp:
type: [string, "null"]
format: date-time
openApiVersion:
type: [string, "null"]
openApiHash:
type: [string, "null"]
frontendBuildHash:
type: [string, "null"]
databaseMigrationRange:
type: [string, "null"]
description: "The first and last applied EF Core migration for the database this instance is actually connected to, formatted as 'first..last' (or a single migration name if only one has been applied)."
inventorySnapshotId:
type: [string, "null"]
backupId:
type: [string, "null"]
format: int64
minimum: 0
DocumentType:
type: string
enum:
- WikiPage
- StudyNote
- CourseModule
- ProjectDocument
- ArchitectureDecision
- Runbook
- Troubleshooting
- Reference
- ChangeLog
- SessionNote
- Checklist
DocumentStatus:
type: string
enum: [Draft, Active, NeedsReview, Deprecated, Archived]
PageSummary:
type: object
required: [id, parentPageId, title, slug, documentType, documentStatus, hasChildren, updatedAt]
properties:
id:
type: string
format: uuid
parentPageId:
type: [string, "null"]
format: uuid
title:
type: string
slug:
type: string
documentType:
$ref: "#/components/schemas/DocumentType"
documentStatus:
$ref: "#/components/schemas/DocumentStatus"
hasChildren:
type: boolean
updatedAt:
type: string
format: date-time
PageChildrenResponse:
type: object
required: [items, nextCursor]
properties:
items:
type: array
items:
$ref: "#/components/schemas/PageSummary"
nextCursor:
type: [string, "null"]
BreadcrumbEntry:
type: object
required: [id, title]
properties:
id:
type: string
format: uuid
title:
type: string
PageDetail:
type: object
required:
- id
- workspaceId
- parentPageId
- title
- slug
- markdown
- renderedHtml
- documentType
- documentStatus
- currentRevisionNumber
- tags
- breadcrumb
- createdBy
- updatedBy
- createdAt
- updatedAt
- publishedAt
- trashedAt
- eTag
properties:
id:
type: string
format: uuid
workspaceId:
type: string
format: uuid
parentPageId:
type: [string, "null"]
format: uuid
title:
type: string
slug:
type: string
markdown:
type: string
renderedHtml:
type: string
description: Server-rendered and sanitized. Never render untrusted Markdown as HTML client-side.
documentType:
$ref: "#/components/schemas/DocumentType"
documentStatus:
$ref: "#/components/schemas/DocumentStatus"
currentRevisionNumber:
type: integer
format: int64
tags:
type: array
items:
type: string
breadcrumb:
type: array
items:
$ref: "#/components/schemas/BreadcrumbEntry"
createdBy:
type: [string, "null"]
format: uuid
updatedBy:
type: [string, "null"]
format: uuid
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
publishedAt:
type: [string, "null"]
format: date-time
trashedAt:
type: [string, "null"]
format: date-time
description: Null unless the page is in the trash/retention model.
eTag:
type: string
description: Also present as the response ETag header; included in the body so clients need not parse headers.
CreatePageRequest:
type: object
required: [parentPageId, title, documentType, markdown, templateKey]
description: >-
Either supply documentType and markdown directly, or supply templateKey to seed
the page from a starter template (in which case documentType/markdown are
ignored and may be null).
properties:
parentPageId:
type: [string, "null"]
format: uuid
title:
type: string
minLength: 1
maxLength: 300
documentType:
oneOf:
- $ref: "#/components/schemas/DocumentType"
- type: "null"
markdown:
type: [string, "null"]
maxLength: 2097152
templateKey:
type: [string, "null"]
tags:
type: array
items:
type: string
maxItems: 25
BeginPageCreationSessionRequest:
type: object
properties:
parentPageId:
type: [string, "null"]
format: uuid
PageCreationSessionResponse:
type: object
required: [sessionId, expiresAt]
properties:
sessionId:
type: string
format: uuid
expiresAt:
type: string
format: date-time
CommitPageCreationSessionRequest:
type: object
required: [title]
description: >-
No parentPageId (fixed at session-begin time) or templateKey (a template and
pre-publish attachments are mutually exclusive, matching the existing
product behavior).
properties:
title:
type: string
minLength: 1
maxLength: 300
documentType:
oneOf:
- $ref: "#/components/schemas/DocumentType"
- type: "null"
markdown:
type: [string, "null"]
maxLength: 2097152
tags:
type: array
items:
type: string
maxItems: 25
DiscardPageCreationSessionResponse:
type: object
required: [attachmentsRemoved]
properties:
attachmentsRemoved:
type: integer
UpdatePageRequest:
type: object
required: [title, markdown, documentType, documentStatus, changeSummary]
properties:
title:
type: string
minLength: 1
maxLength: 300
markdown:
type: string
maxLength: 2097152
documentType:
$ref: "#/components/schemas/DocumentType"
documentStatus:
$ref: "#/components/schemas/DocumentStatus"
changeSummary:
type: [string, "null"]
maxLength: 500
tags:
type: array
items:
type: string
maxItems: 25
description: Omitted or null leaves the page's tags unchanged.
MovePageRequest:
type: object
required: [newParentPageId, afterSiblingId]
properties:
newParentPageId:
type: [string, "null"]
format: uuid
afterSiblingId:
type: [string, "null"]
format: uuid
description: Null means "first among the new siblings".
TemplateSummary:
type: object
required: [key, name, documentType, description]
properties:
key:
type: string
name:
type: string
documentType:
$ref: "#/components/schemas/DocumentType"
description:
type: string
DraftResponse:
type: object
required: [draftKey, pageId, baseRevisionNumber, title, markdown, updatedAt, expiresAt, baseRevisionIsStale]
properties:
draftKey:
type: string
pageId:
type: [string, "null"]
format: uuid
baseRevisionNumber:
type: [integer, "null"]
format: int64
title:
type: string
markdown:
type: string
updatedAt:
type: string
format: date-time
expiresAt:
type: string
format: date-time
baseRevisionIsStale:
type: boolean
description: True when the page has published a newer revision since this draft's base.
SaveDraftRequest:
type: object
required: [pageId, baseRevisionNumber, title, markdown]
properties:
pageId:
type: [string, "null"]
format: uuid
baseRevisionNumber:
type: [integer, "null"]
format: int64
title:
type: string
markdown:
type: string
PageRevisionSummary:
type: object
required: [revisionNumber, title, documentType, documentStatus, authoredBy, changeSummary, createdAt]
properties:
revisionNumber:
type: integer
format: int64
title:
type: string
documentType:
$ref: "#/components/schemas/DocumentType"
documentStatus:
$ref: "#/components/schemas/DocumentStatus"
authoredBy:
type: [string, "null"]
format: uuid
changeSummary:
type: [string, "null"]
createdAt:
type: string
format: date-time
PageRevisionDetailResponse:
type: object
required: [revisionNumber, title, markdown, renderedHtml, documentType, documentStatus, authoredBy, changeSummary, createdAt]
properties:
revisionNumber:
type: integer
format: int64
title:
type: string
markdown:
type: string
renderedHtml:
type: string
description: >-
Server-rendered and sanitized without re-resolving internal links against
today's link table (a historical snapshot's links are not live).
documentType:
$ref: "#/components/schemas/DocumentType"
documentStatus:
$ref: "#/components/schemas/DocumentStatus"
authoredBy:
type: [string, "null"]
format: uuid
changeSummary:
type: [string, "null"]
createdAt:
type: string
format: date-time
BacklinkEntry:
type: object
required: [id, title, slug, documentType]
properties:
id:
type: string
format: uuid
title:
type: string
slug:
type: string
documentType:
$ref: "#/components/schemas/DocumentType"
BacklinksResponse:
type: object
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/BacklinkEntry"
TrashPurgeResponse:
type: object
required: [purgedCount]
properties:
purgedCount:
type: integer
AttachmentDeliveryPolicy:
type: string
enum: [InlinePreviewEligible, DownloadOnly, Unknown]
AttachmentSummary:
type: object
required: [id, filename, mediaType, deliveryPolicy, byteLength, description, altText, createdAt]
properties:
id:
type: string
format: uuid
filename:
type: string
description: The sanitized, stored display filename — never necessarily identical to what the client declared.
mediaType:
type: string
description: The detected media type from the byte signature, not the caller-declared Content-Type.
deliveryPolicy:
$ref: "#/components/schemas/AttachmentDeliveryPolicy"
byteLength:
type: integer
format: int64
description:
type: [string, "null"]
altText:
type: [string, "null"]
createdAt:
type: string
format: date-time
AttachmentListResponse:
type: object
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/AttachmentSummary"
ManualOverrideFieldCatalogEntry:
type: object
required: [fieldName, displayLabel, valueKind, maxStringLength, allowedEnumValues]
properties:
fieldName:
type: string
displayLabel:
type: string
valueKind:
type: string
enum: [Text, Enum]
maxStringLength:
type: integer
description: 0 for an Enum-kind field, where length is governed by allowedEnumValues instead.
allowedEnumValues:
type: [array, "null"]
items:
type: string
ManualOverrideFieldCatalogResponse:
type: object
required: [targetKind, fields]
properties:
targetKind:
type: string
fields:
type: array
items:
$ref: "#/components/schemas/ManualOverrideFieldCatalogEntry"
FactProvenance:
type: object
required: [sourceKey, confidence, lastSnapshotId, observedAt]
properties:
sourceKey:
type: string
confidence:
type: string
enum: [Observed, Inferred, Declared, Unknown]
lastSnapshotId:
type: string
format: uuid
observedAt:
type: string
format: date-time
InventorySnapshotSummary:
type: object
required: [id, sourceKey, sourceHash, capturedAt, importedAt, hostCount, serviceCount]
properties:
id:
type: string
format: uuid
sourceKey:
type: string
sourceHash:
type: string
capturedAt:
type: string
format: date-time
importedAt:
type: string
format: date-time
hostCount:
type: integer
serviceCount:
type: integer
InfrastructureHostSummary:
type: object
required: [id, sourceKey, hostname, operatingSystem, kernelVersion, architecture, logicalCpuCount, totalMemoryBytes, presence, lastSeenAt]
properties:
id:
type: string
format: uuid
sourceKey:
type: string
hostname:
type: string
operatingSystem:
type: string
kernelVersion:
type: string
architecture:
type: string
logicalCpuCount:
type: integer
totalMemoryBytes:
type: integer
format: int64
presence:
type: string
enum: [Present, Missing]
lastSeenAt:
type: string
format: date-time
InfrastructureOverviewResponse:
type: object
required: [latestSnapshot, hosts, serviceCount, proposedServiceCount, missingServiceCount, unhealthyServiceCount, unprotectedServiceCount]
properties:
latestSnapshot:
type: [object, "null"]
allOf:
- $ref: "#/components/schemas/InventorySnapshotSummary"
hosts:
type: array
items:
$ref: "#/components/schemas/InfrastructureHostSummary"
serviceCount:
type: integer
proposedServiceCount:
type: integer
missingServiceCount:
type: integer
unhealthyServiceCount:
type: integer
unprotectedServiceCount:
type: integer
InfrastructureServiceSummary:
type: object
required: [id, sourceKey, name, detectedName, category, summary, runtimeKind, runtimeStatus, health, presence, confidence, imageReference, projectId, projectName, backupStatus, primaryEndpointUrl, isFavorite, isHidden, componentCount, endpointCount, volumeCount, databaseCount, lastSeenAt]
properties:
id:
type: string
format: uuid
sourceKey:
type: string
name:
type: string
description: Editorial display name if set, otherwise the detected name.
detectedName:
type: string
category:
type: string
summary:
type: string
runtimeKind:
type: string
runtimeStatus:
type: string
health:
type: string
enum: [Healthy, Degraded, Unhealthy, Unknown]
presence:
type: string
enum: [Present, Proposed, Missing]
confidence:
type: string
enum: [Observed, Inferred, Declared, Unknown]
imageReference:
type: string
projectId:
type: [string, "null"]
format: uuid
projectName:
type: [string, "null"]
backupStatus:
type: string
primaryEndpointUrl:
type: [string, "null"]
isFavorite:
type: boolean
isHidden:
type: boolean
componentCount:
type: integer
endpointCount:
type: integer
volumeCount:
type: integer
databaseCount:
type: integer
lastSeenAt:
type: string
format: date-time
InfrastructureServiceListResponse:
type: object
required: [items, nextCursor, totalCount]
properties:
items:
type: array
items:
$ref: "#/components/schemas/InfrastructureServiceSummary"
nextCursor:
type: [string, "null"]
totalCount:
type: integer
description: >-
How many services match the current `search`/`status`/`projectId`/
`includeHidden` filters in total (OUX-4). Counted server-side before
cursor pagination, so it is the size of the filtered catalog rather than
of the returned page, and stays stable while paging through it.
InfrastructureStorageRow:
type: object
required: [id, serviceId, serviceName, name, mountPath, storageClass, readOnly, sizeBytes, provenance]
properties:
id:
type: string
format: uuid
serviceId:
type: string
format: uuid
serviceName:
type: string
name:
type: string
mountPath:
type: string
storageClass:
type: string
readOnly:
type: boolean
sizeBytes:
type: [integer, "null"]
format: int64
provenance:
$ref: "#/components/schemas/FactProvenance"
InfrastructureStorageListResponse:
type: object
required: [items, nextCursor]
properties:
items:
type: array
items:
$ref: "#/components/schemas/InfrastructureStorageRow"
nextCursor:
type: [string, "null"]
EditorialProfileResponse:
type: object
required: [id, displayName, summary, category, purpose, ownerId, projectId, repositoryUrl, liveUrl, backupStatus, monitoringUrl, maintenanceNotes, knownRisks, updateProcedure, rollbackProcedure, tags, isFavorite, isHidden, isArchived, updatedAt, etag]
properties:
id:
type: string
format: uuid
displayName:
type: string
summary:
type: string
category:
type: string
purpose:
type: string
ownerId:
type: [string, "null"]
format: uuid
projectId:
type: [string, "null"]
format: uuid
repositoryUrl:
type: string
liveUrl:
type: string
backupStatus:
type: string
monitoringUrl:
type: string
maintenanceNotes:
type: string
knownRisks:
type: string
updateProcedure:
type: string
rollbackProcedure:
type: string
tags:
type: array
items:
type: string
isFavorite:
type: boolean
isHidden:
type: boolean
isArchived:
type: boolean
updatedAt:
type: string
format: date-time
etag:
type: string
ManualOverrideResponse:
type: object
required: [id, fieldName, value, reason, createdBy, updatedAt]
properties:
id:
type: string
format: uuid
fieldName:
type: string
value:
description: The overridden value — any JSON scalar, array or object, matching the field's ManualOverrideFieldCatalogEntry.valueKind.
reason:
type: string
createdBy:
type: string
format: uuid
updatedAt:
type: string
format: date-time
ServiceComponentResponse:
type: object
required: [id, name, kind, runtimeId, status, imageReference, provenance]
properties:
id:
type: string
format: uuid
name:
type: string
kind:
type: string
runtimeId:
type: string
status:
type: string
imageReference:
type: string
provenance:
$ref: "#/components/schemas/FactProvenance"
InfrastructureEndpointResponse:
type: object
required: [id, scheme, host, publishedPort, targetPort, url, isPublic, provenance]
properties:
id:
type: string
format: uuid
scheme:
type: string
host:
type: string
publishedPort:
type: [integer, "null"]
targetPort:
type: [integer, "null"]
url:
type: string
isPublic:
type: boolean
provenance:
$ref: "#/components/schemas/FactProvenance"
InfrastructureNetworkResponse:
type: object
required: [id, name, driver, scope, internal, provenance]
properties:
id:
type: string
format: uuid
name:
type: string
driver:
type: string
scope:
type: string
internal:
type: boolean
provenance:
$ref: "#/components/schemas/FactProvenance"
InfrastructureVolumeResponse:
type: object
required: [id, name, mountPath, storageClass, readOnly, sizeBytes, provenance]
properties:
id:
type: string
format: uuid
name:
type: string
mountPath:
type: string
storageClass:
type: string
readOnly:
type: boolean
sizeBytes:
type: [integer, "null"]
format: int64
provenance:
$ref: "#/components/schemas/FactProvenance"
InfrastructureDatabaseResponse:
type: object
required: [id, name, engine, version, host, port, hostPortPublished, provenance]
properties:
id:
type: string
format: uuid
name:
type: string
engine:
type: string
version:
type: string
host:
type: string
port:
type: [integer, "null"]
hostPortPublished:
type: boolean
provenance:
$ref: "#/components/schemas/FactProvenance"
ServiceDependencyResponse:
type: object
required: [id, dependsOnServiceId, dependsOnSourceKey, kind, provenance]
properties:
id:
type: string
format: uuid
dependsOnServiceId:
type: [string, "null"]
format: uuid
dependsOnSourceKey:
type: string
kind:
type: string
provenance:
$ref: "#/components/schemas/FactProvenance"
HealthObservationResponse:
type: object
required: [id, state, checkKind, responseTimeMilliseconds, provenance]
properties:
id:
type: string
format: uuid
state:
type: string
enum: [Healthy, Degraded, Unhealthy, Unknown]
checkKind:
type: string
responseTimeMilliseconds:
type: [integer, "null"]
provenance:
$ref: "#/components/schemas/FactProvenance"
DeploymentReferenceResponse:
type: object
required: [id, repositoryUrl, commit, imageReference, imageDigest, deployedAt, provenance]
properties:
id:
type: string
format: uuid
repositoryUrl:
type: string
commit:
type: string
imageReference:
type: string
imageDigest:
type: string
deployedAt:
type: [string, "null"]
format: date-time
provenance:
$ref: "#/components/schemas/FactProvenance"
BackupCoverageResponse:
type: object
required: [id, method, schedule, lastSuccessfulAt, restoreTested, provenance]
properties:
id:
type: string
format: uuid
method:
type: string
schedule:
type: string
lastSuccessfulAt:
type: [string, "null"]
format: date-time
restoreTested:
type: boolean
provenance:
$ref: "#/components/schemas/FactProvenance"
InfrastructureServiceDetail:
type: object
required: [service, editorialProfile, editorialETag, overrides, components, endpoints, networks, volumes, databases, dependencies, healthObservations, deployments, backups]
properties:
service:
$ref: "#/components/schemas/InfrastructureServiceSummary"
editorialProfile:
type: [object, "null"]
allOf:
- $ref: "#/components/schemas/EditorialProfileResponse"
editorialETag:
type: string
overrides:
type: array
items:
$ref: "#/components/schemas/ManualOverrideResponse"
components:
type: array
items:
$ref: "#/components/schemas/ServiceComponentResponse"
endpoints:
type: array
items:
$ref: "#/components/schemas/InfrastructureEndpointResponse"
networks:
type: array
items:
$ref: "#/components/schemas/InfrastructureNetworkResponse"
volumes:
type: array
items:
$ref: "#/components/schemas/InfrastructureVolumeResponse"
databases:
type: array
items:
$ref: "#/components/schemas/InfrastructureDatabaseResponse"
dependencies:
type: array
items:
$ref: "#/components/schemas/ServiceDependencyResponse"
healthObservations:
type: array
items:
$ref: "#/components/schemas/HealthObservationResponse"
deployments:
type: array
items:
$ref: "#/components/schemas/DeploymentReferenceResponse"
backups:
type: array
items:
$ref: "#/components/schemas/BackupCoverageResponse"
UpdateEditorialProfileRequest:
type: object
required: [isFavorite, isHidden, isArchived]
description: Every field besides the three booleans is optional — omit a field to leave it unchanged.
properties:
displayName:
type: [string, "null"]
summary:
type: [string, "null"]
category:
type: [string, "null"]
purpose:
type: [string, "null"]
ownerId:
type: [string, "null"]
format: uuid
projectId:
type: [string, "null"]
format: uuid
repositoryUrl:
type: [string, "null"]
liveUrl:
type: [string, "null"]
backupStatus:
type: [string, "null"]
monitoringUrl:
type: [string, "null"]
maintenanceNotes:
type: [string, "null"]
knownRisks:
type: [string, "null"]
updateProcedure:
type: [string, "null"]
rollbackProcedure:
type: [string, "null"]
tags:
type: [array, "null"]
items:
type: string
isFavorite:
type: boolean
isHidden:
type: boolean
isArchived:
type: boolean
SetManualOverrideRequest:
type: object
required: [fieldName, value]
properties:
fieldName:
type: string
description: Must be one of the field names GET /infrastructure/overrides/catalog returns for the target's object kind.
value:
description: Any JSON scalar, array or object, validated server-side against the field's catalog entry (valueKind, maxStringLength, allowedEnumValues).
reason:
type: [string, "null"]
SetInfrastructureOverrideRequest:
type: object
required: [targetKind, targetId, fieldName, value]
properties:
targetKind:
type: string
enum: [Host, Service, Component, Endpoint, Network, Volume, Database, Deployment, Backup]
targetId:
type: string
format: uuid
fieldName:
type: string
description: Must be one of the field names GET /infrastructure/overrides/catalog?targetKind=... returns for this targetKind.
value:
description: Any JSON scalar, array or object, validated server-side against the field's catalog entry (valueKind, maxStringLength, allowedEnumValues).
reason:
type: [string, "null"]
ReviewInfrastructureServiceRequest:
type: object
required: [decision]
properties:
decision:
type: string
enum: [Accept, Reject]
reason:
type: [string, "null"]
ReviewInfrastructureServiceResponse:
type: object
required: [serviceId, presence]
properties:
serviceId:
type: string
format: uuid
presence:
type: string
enum: [Present, Missing]
InventoryChangeResponse:
type: object
required: [id, snapshotId, targetKind, targetId, sourceKey, changeKind, changedFields, recordedAt]
properties:
id:
type: string
format: uuid
snapshotId:
type: string
format: uuid
targetKind:
type: string
targetId:
type: string
format: uuid
sourceKey:
type: string
changeKind:
type: string
enum: [Added, Updated, Missing, Unchanged]
changedFields:
type: array
items:
type: string
recordedAt:
type: string
format: date-time
InventoryHostRequest:
type: object
required: [sourceKey, hostname, logicalCpuCount, totalMemoryBytes]
properties:
sourceKey:
type: string
hostname:
type: string
operatingSystem:
type: [string, "null"]
kernelVersion:
type: [string, "null"]
architecture:
type: [string, "null"]
logicalCpuCount:
type: integer
totalMemoryBytes:
type: integer
format: int64
InventoryFactConfidence:
type: string
enum: [Observed, Inferred, Declared, Unknown]
InventoryComponentRequest:
type: object
required: [sourceKey, name, confidence]
properties:
sourceKey:
type: string
name:
type: string
kind:
type: [string, "null"]
runtimeId:
type: [string, "null"]
status:
type: [string, "null"]
imageReference:
type: [string, "null"]
confidence:
$ref: "#/components/schemas/InventoryFactConfidence"
InventoryEndpointRequest:
type: object
required: [sourceKey, isPublic, confidence]
properties:
sourceKey:
type: string
scheme:
type: [string, "null"]
host:
type: [string, "null"]
publishedPort:
type: [integer, "null"]
targetPort:
type: [integer, "null"]
url:
type: [string, "null"]
isPublic:
type: boolean
confidence:
$ref: "#/components/schemas/InventoryFactConfidence"
InventoryNetworkRequest:
type: object
required: [sourceKey, name, internal, confidence]
properties:
sourceKey:
type: string
name:
type: string
driver:
type: [string, "null"]
scope:
type: [string, "null"]
internal:
type: boolean
confidence:
$ref: "#/components/schemas/InventoryFactConfidence"
InventoryVolumeRequest:
type: object
required: [sourceKey, name, readOnly, confidence]
properties:
sourceKey:
type: string
name:
type: string
mountPath:
type: [string, "null"]
storageClass:
type: [string, "null"]
readOnly:
type: boolean
sizeBytes:
type: [integer, "null"]
format: int64
confidence:
$ref: "#/components/schemas/InventoryFactConfidence"
InventoryDatabaseRequest:
type: object
required: [sourceKey, name, hostPortPublished, confidence]
properties:
sourceKey:
type: string
name:
type: string
engine:
type: [string, "null"]
version:
type: [string, "null"]
host:
type: [string, "null"]
port:
type: [integer, "null"]
hostPortPublished:
type: boolean
confidence:
$ref: "#/components/schemas/InventoryFactConfidence"
InventoryDependencyRequest:
type: object
required: [sourceKey, dependsOnSourceKey, confidence]
properties:
sourceKey:
type: string
dependsOnSourceKey:
type: string
kind:
type: [string, "null"]
confidence:
$ref: "#/components/schemas/InventoryFactConfidence"
InventoryHealthRequest:
type: object
required: [sourceKey, state, confidence]
properties:
sourceKey:
type: string
state:
type: string
enum: [Healthy, Degraded, Unhealthy, Unknown]
checkKind:
type: [string, "null"]
responseTimeMilliseconds:
type: [integer, "null"]
confidence:
$ref: "#/components/schemas/InventoryFactConfidence"
InventoryDeploymentRequest:
type: object
required: [sourceKey, confidence]
properties:
sourceKey:
type: string
repositoryUrl:
type: [string, "null"]
commit:
type: [string, "null"]
imageReference:
type: [string, "null"]
imageDigest:
type: [string, "null"]
deployedAt:
type: [string, "null"]
format: date-time
confidence:
$ref: "#/components/schemas/InventoryFactConfidence"
InventoryBackupRequest:
type: object
required: [sourceKey, restoreTested, confidence]
properties:
sourceKey:
type: string
method:
type: [string, "null"]
schedule:
type: [string, "null"]
lastSuccessfulAt:
type: [string, "null"]
format: date-time
restoreTested:
type: boolean
confidence:
$ref: "#/components/schemas/InventoryFactConfidence"
InventoryServiceRequest:
type: object
required: [sourceKey, detectedName, health, readOnlyRootFilesystem, noNewPrivileges, privileged, confidence]
properties:
sourceKey:
type: string
hostSourceKey:
type: [string, "null"]
detectedName:
type: string
runtimeKind:
type: [string, "null"]
runtimeStatus:
type: [string, "null"]
health:
type: string
enum: [Healthy, Degraded, Unhealthy, Unknown]
imageReference:
type: [string, "null"]
imageDigest:
type: [string, "null"]
runtimeUser:
type: [string, "null"]
readOnlyRootFilesystem:
type: boolean
noNewPrivileges:
type: boolean
privileged:
type: boolean
confidence:
$ref: "#/components/schemas/InventoryFactConfidence"
components:
type: [array, "null"]
items:
$ref: "#/components/schemas/InventoryComponentRequest"
endpoints:
type: [array, "null"]
items:
$ref: "#/components/schemas/InventoryEndpointRequest"
networks:
type: [array, "null"]
items:
$ref: "#/components/schemas/InventoryNetworkRequest"
volumes:
type: [array, "null"]
items:
$ref: "#/components/schemas/InventoryVolumeRequest"
databases:
type: [array, "null"]
items:
$ref: "#/components/schemas/InventoryDatabaseRequest"
dependencies:
type: [array, "null"]
items:
$ref: "#/components/schemas/InventoryDependencyRequest"
healthObservations:
type: [array, "null"]
items:
$ref: "#/components/schemas/InventoryHealthRequest"
deployments:
type: [array, "null"]
items:
$ref: "#/components/schemas/InventoryDeploymentRequest"
backups:
type: [array, "null"]
items:
$ref: "#/components/schemas/InventoryBackupRequest"
SynchronizeInventoryRequest:
type: object
required: [sourceKey, sourceHash, capturedAt]
properties:
sourceKey:
type: string
sourceHash:
type: string
capturedAt:
type: string
format: date-time
hosts:
type: [array, "null"]
items:
$ref: "#/components/schemas/InventoryHostRequest"
services:
type: [array, "null"]
items:
$ref: "#/components/schemas/InventoryServiceRequest"
InventorySyncResponse:
type: object
required: [snapshotId, alreadyImported, addedServices, updatedServices, missingServices, preservedEditorialProfiles, preservedOverrides]
properties:
snapshotId:
type: string
format: uuid
alreadyImported:
type: boolean
addedServices:
type: integer
updatedServices:
type: integer
missingServices:
type: integer
preservedEditorialProfiles:
type: integer
preservedOverrides:
type: integer
CuratedServiceProfileRequest:
type: object
required: [sourceKey, reviewed, archived]
properties:
sourceKey:
type: string
displayName:
type: [string, "null"]
summary:
type: [string, "null"]
category:
type: [string, "null"]
purpose:
type: [string, "null"]
repositoryUrl:
type: [string, "null"]
liveUrl:
type: [string, "null"]
tags:
type: [array, "null"]
items:
type: string
reviewed:
type: boolean
archived:
type: boolean
ImportCuratedInventoryRequest:
type: object
required: [inventory]
properties:
inventory:
$ref: "#/components/schemas/SynchronizeInventoryRequest"
services:
type: [array, "null"]
items:
$ref: "#/components/schemas/CuratedServiceProfileRequest"
ImportCuratedInventoryResponse:
type: object
required: [inventory, createdEditorialProfiles, confirmedServices, archivedServices]
properties:
inventory:
$ref: "#/components/schemas/InventorySyncResponse"
createdEditorialProfiles:
type: integer
confirmedServices:
type: integer
archivedServices:
type: integer
ActivityItemResponse:
type: object
required: [id, actorKind, actorId, source, action, targetType, targetId, summary, occurredAt]
properties:
id:
type: integer
format: int64
actorKind:
type: string
enum: [System, User, ServiceAccount]
actorId:
type: [string, "null"]
format: uuid
source:
type: string
action:
type: string
targetType:
type: [string, "null"]
targetId:
type: [string, "null"]
summary:
type: string
occurredAt:
type: string
format: date-time
ActivityListResponse:
type: object
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/ActivityItemResponse"
WorkspaceMemberSummary:
type: object
required: [id, displayName, email]
properties:
id:
type: string
format: uuid
displayName:
type: string
email:
type: string
WorkspaceMemberListResponse:
type: object
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/WorkspaceMemberSummary"
PreviewMarkdownRequest:
type: object
required: [markdown]
properties:
markdown:
type: string
PreviewMarkdownResponse:
type: object
required: [renderedHtml]
properties:
renderedHtml:
type: string
description: Sanitized HTML — safe to render directly, matching the same pipeline a published page revision uses.
StagingCleanupRunSummary:
type: object
required: [sessionsExpired, sessionsClaimed, sessionsLostRace, attachmentReferencesRemoved, blobsRemoved, warnings]
properties:
sessionsExpired:
type: integer
sessionsClaimed:
type: integer
sessionsLostRace:
type: integer
description: Expired sessions this run observed but a concurrent run claimed first — never lost, just processed elsewhere.
attachmentReferencesRemoved:
type: integer
blobsRemoved:
type: integer
warnings:
type: array
items:
type: string
StagingCleanupStatusResponse:
type: object
required: [activeSessionCount, expiredPendingCleanupCount, runInProgress, lastRunStartedAt, lastRunCompletedAt, lastRunTrigger, lastResult]
properties:
activeSessionCount:
type: integer
expiredPendingCleanupCount:
type: integer
runInProgress:
type: boolean
lastRunStartedAt:
type: [string, "null"]
format: date-time
lastRunCompletedAt:
type: [string, "null"]
format: date-time
lastRunTrigger:
type: [string, "null"]
enum: [scheduled, manual, null]
lastResult:
type: [object, "null"]
allOf:
- $ref: "#/components/schemas/StagingCleanupRunSummary"
RunStagingCleanupRequest:
type: object
required: [dryRun]
properties:
dryRun:
type: boolean
RunStagingCleanupResponse:
type: object
required: [dryRun, result]
properties:
dryRun:
type: boolean
result:
$ref: "#/components/schemas/StagingCleanupRunSummary"
PreflightImportRequest:
type: object
required: [sourceRelativePath, idempotencyKey]
properties:
sourceRelativePath:
type: string
description: Resolved beneath the operator-mounted KnowledgeHub:Paths:Imports directory — never an arbitrary filesystem path.
idempotencyKey:
type: string
ImportPlanItemResponse:
type: object
required: [sourceIdentity, kind, proposedTitle, proposedParentSourceIdentity, conflict, conflictDetail]
properties:
sourceIdentity:
type: string
kind:
type: string
enum: [Markdown, SqliteRow, Asset, Unsupported]
proposedTitle:
type: string
proposedParentSourceIdentity:
type: [string, "null"]
conflict:
type: string
enum: [NoConflict, ExactRerunDuplicate, SamePathDifferentContent, ExistingUserPage, AmbiguousLink, Unsupported]
conflictDetail:
type: [string, "null"]
ImportPreflightResponse:
type: object
required: [status, jobId, adapterName, discoveredCount, plannedCount, items, warnings, blockers]
properties:
status:
type: string
enum: [Success, NoApplicableAdapter, AlreadyPreflighted]
jobId:
type: [string, "null"]
format: uuid
adapterName:
type: [string, "null"]
discoveredCount:
type: integer
plannedCount:
type: integer
items:
type: array
items:
$ref: "#/components/schemas/ImportPlanItemResponse"
warnings:
type: array
items:
type: string
blockers:
type: array
items:
type: string
ExecuteImportResponse:
type: object
required: [status, imported, skipped, failed, sourceUnchangedAfterExecution, diagnostics]
properties:
status:
type: string
enum: [Success, JobNotFound, NotPlanReady, SourceChanged, PlanChanged]
imported:
type: integer
skipped:
type: integer
failed:
type: integer
sourceUnchangedAfterExecution:
type: boolean
description: A re-fingerprint comparison proving the source was never mutated during import (M4-A08).
diagnostics:
type: array
items:
type: string
RollbackImportResponse:
type: object
required: [status, removedPages, preservedPages, removedAttachments]
properties:
status:
type: string
enum: [Success, JobNotFound, NothingToRollBack]
removedPages:
type: integer
preservedPages:
type: integer
description: Pages this import created but a user has since edited — never deleted by rollback.
removedAttachments:
type: integer
ImportJobSummaryResponse:
type: object
required: [id, adapterName, status, discoveredCount, plannedCount, importedCount, skippedCount, failedCount, createdAt, startedAt, completedAt]
properties:
id:
type: string
format: uuid
adapterName:
type: string
status:
type: string
enum: [Discovered, PlanReady, Executing, Completed, Failed, RolledBack]
discoveredCount:
type: integer
plannedCount:
type: integer
importedCount:
type: integer
skippedCount:
type: integer
failedCount:
type: integer
createdAt:
type: string
format: date-time
startedAt:
type: [string, "null"]
format: date-time
completedAt:
type: [string, "null"]
format: date-time
SourceBatchPurgeRequest:
type: object
required: [batchId, source, pages]
properties:
batchId:
type: string
minLength: 1
source:
type: string
pattern: "^[a-z0-9-]{1,64}$"
pages:
type: array
minItems: 1
items:
$ref: "#/components/schemas/SourceBatchPurgePageEvidence"
SourceBatchPurgePageEvidence:
type: object
required: [pageId, contentHash]
properties:
pageId:
type: string
format: uuid
contentHash:
type: string
pattern: "^[a-fA-F0-9]{64}$"
ExecuteSourceBatchPurgeRequest:
allOf:
- $ref: "#/components/schemas/SourceBatchPurgeRequest"
- type: object
required: [confirmationHash]
properties:
confirmationHash:
type: string
pattern: "^[a-f0-9]{64}$"
SourceBatchPurgeDryRunResponse:
type: object
required: [safe, confirmationHash, pages, descendantPages, revisions, drafts, links, attachmentUsages, attachmentReferences, orphanedBlobs, sharedBlobs, importJobs, importItems, provenanceRecords, sourceTagAssignments, searchIndexRecords, generatedSourceDerivedPages, retainedPages, blockers]
properties:
safe:
type: boolean
confirmationHash:
type: string
pages:
type: integer
descendantPages:
type: integer
revisions:
type: integer
drafts:
type: integer
links:
type: integer
attachmentUsages:
type: integer
attachmentReferences:
type: integer
orphanedBlobs:
type: integer
sharedBlobs:
type: integer
importJobs:
type: integer
importItems:
type: integer
provenanceRecords:
type: integer
sourceTagAssignments:
type: integer
searchIndexRecords:
type: integer
generatedSourceDerivedPages:
type: integer
retainedPages:
type: integer
blockers:
type: array
items:
type: string
SourceBatchPurgeResponse:
type: object
required: [status, dryRun, removedPages, removedRevisions, removedDrafts, removedLinks, removedAttachmentUsages, removedAttachmentReferences, removedImportJobs, removedImportItems, removedProvenanceRecords, removedBlobs]
properties:
status:
type: string
enum: [purged]
dryRun:
$ref: "#/components/schemas/SourceBatchPurgeDryRunResponse"
removedPages:
type: integer
removedRevisions:
type: integer
removedDrafts:
type: integer
removedLinks:
type: integer
removedAttachmentUsages:
type: integer
removedAttachmentReferences:
type: integer
removedImportJobs:
type: integer
removedImportItems:
type: integer
removedProvenanceRecords:
type: integer
removedBlobs:
type: integer
SearchResultItem:
type: object
required: [id, parentPageId, title, slug, documentType, documentStatus, tags, updatedAt, snippet]
properties:
id:
type: string
format: uuid
parentPageId:
type: [string, "null"]
format: uuid
title:
type: string
slug:
type: string
documentType:
$ref: "#/components/schemas/DocumentType"
documentStatus:
$ref: "#/components/schemas/DocumentStatus"
tags:
type: array
items:
type: string
updatedAt:
type: string
format: date-time
snippet:
type: string
description: >-
May contain the safe server-generated highlight markers `**` around
matched terms. Never raw HTML — clients must escape this text before
rendering and only then interpret the `**` markers.
SearchResponse:
type: object
required: [items, nextCursor]
properties:
items:
type: array
items:
$ref: "#/components/schemas/SearchResultItem"
nextCursor:
type: [string, "null"]
RagPublicationManifestItem:
type: object
description: >-
docs/32-rag-publication-convention.md. `markdown` and `checksum` are always
`null` when `retired` is `true`.
required:
- contentId
- documentType
- documentStatus
- title
- slug
- canonicalUrl
- markdown
- revision
- checksum
- locale
- space
- updatedAt
- publishedAt
- retired
- retiredReason
properties:
contentId:
type: string
format: uuid
documentType:
$ref: "#/components/schemas/DocumentType"
documentStatus:
$ref: "#/components/schemas/DocumentStatus"
title:
type: string
slug:
type: string
canonicalUrl:
type: string
markdown:
type: [string, "null"]
revision:
type: integer
format: int64
checksum:
type: [string, "null"]
description: Lowercase-hex SHA-256 of `markdown` (Page.ContentHash). Null when retired.
locale:
type: string
description: >-
The page's own `locale:` tag, or the configured
`KnowledgeHub:PrimaryLocale` when it declares none. Never silently `en`.
space:
type: [string, "null"]
description: The page's own `rag:space:` tag suffix, if any.
updatedAt:
type: string
format: date-time
publishedAt:
type: [string, "null"]
format: date-time
retired:
type: boolean
retiredReason:
type: [string, "null"]
enum: [null, trashed, draft, needs-review, deprecated, archived]
RagPublicationManifestResponse:
type: object
required: [items, nextCursor]
properties:
items:
type: array
items:
$ref: "#/components/schemas/RagPublicationManifestItem"
nextCursor:
type: [string, "null"]
ProjectStatus:
type: string
description: docs/15-project-hub.md lifecycle diagram. Archived is terminal in v1 (ADR-014 decision 6).
enum: [Concept, Planned, Active, Blocked, Maintenance, Paused, Completed, Archived]
ProjectPriority:
type: string
enum: [Low, Medium, High, Critical]
ProjectDocumentRole:
type: string
enum:
- Overview
- CurrentState
- Architecture
- Deployment
- Infrastructure
- Security
- Testing
- Troubleshooting
- Release
- UserDocumentation
- Other
ProjectUpdateCategory:
type: string
enum: [Progress, Feature, Fix, Deployment, Validation, Release, Decision, Risk, Blocker, Documentation, Maintenance]
ProjectUpdateVerificationStatus:
type: string
description: The UI must distinguish reported claims from validated evidence (docs/15-project-hub.md).
enum: [Reported, Verified, Superseded]
ProjectMilestoneState:
type: string
enum: [Planned, InProgress, Completed, Cancelled]
DecisionStatus:
type: string
enum: [Proposed, Accepted, Rejected, Superseded, Withdrawn]
RunbookRiskLevel:
type: string
enum: [Low, Medium, High, Critical]
RunbookStatus:
type: string
enum: [Draft, Active, Deprecated]
ProjectSummary:
type: object
required: [id, name, slug, summary, status, priority, ownerId, repositoryUrl, liveUrl, updatedAt, iconAttachmentId]
properties:
id: { type: string, format: uuid }
name: { type: string }
slug: { type: string }
summary: { type: string }
status: { $ref: "#/components/schemas/ProjectStatus" }
priority: { $ref: "#/components/schemas/ProjectPriority" }
ownerId: { type: string, format: uuid }
repositoryUrl: { type: [string, "null"], format: uri }
liveUrl: { type: [string, "null"], format: uri }
updatedAt: { type: string, format: date-time }
iconAttachmentId: { type: [string, "null"], format: uuid, description: "Same-origin attachment content is served at /api/v1/attachments/{iconAttachmentId}/content." }
ProjectListResponse:
type: object
required: [items, nextCursor, totalCount]
properties:
items:
type: array
items:
$ref: "#/components/schemas/ProjectSummary"
nextCursor:
type: [string, "null"]
totalCount:
type: integer
minimum: 0
description: >-
How many projects match the current filters in total (OUX-4), counted
server-side before cursor pagination. Not a substitute for
`/projects/totals`, which answers the unfiltered dashboard question.
ProjectCatalogTotalsResponse:
type: object
required: [totalCount, activeCount]
properties:
totalCount: { type: integer, minimum: 0 }
activeCount: { type: integer, minimum: 0 }
ProjectDocumentSummary:
type: object
required: [id, pageId, pageTitle, pageSlug, pageDocumentType, role]
properties:
id: { type: string, format: uuid }
pageId: { type: string, format: uuid }
pageTitle: { type: string }
pageSlug: { type: string }
pageDocumentType: { $ref: "#/components/schemas/DocumentType" }
role: { $ref: "#/components/schemas/ProjectDocumentRole" }
ProjectUpdateSummary:
type: object
required: [id, category, title, summaryMarkdown, verificationStatus, actorId, occurredAt]
properties:
id: { type: string, format: uuid }
category: { $ref: "#/components/schemas/ProjectUpdateCategory" }
title: { type: string }
summaryMarkdown: { type: string }
verificationStatus: { $ref: "#/components/schemas/ProjectUpdateVerificationStatus" }
actorId: { type: string, format: uuid }
occurredAt: { type: string, format: date-time }
commitSha: { type: [string, "null"] }
releaseTag: { type: [string, "null"] }
issueIdentifier: { type: [string, "null"] }
url: { type: [string, "null"] }
pageId: { type: [string, "null"], format: uuid }
decisionRecordId: { type: [string, "null"], format: uuid }
milestoneId: { type: [string, "null"], format: uuid }
attachmentReferenceId: { type: [string, "null"], format: uuid }
supersedesUpdateId: { type: [string, "null"], format: uuid }
ProjectUpdatesResponse:
type: object
required: [items, nextCursor]
properties:
items:
type: array
items:
$ref: "#/components/schemas/ProjectUpdateSummary"
nextCursor:
type: [string, "null"]
ProjectMilestoneSummary:
type: object
required: [id, name, state, summary, successCriteria, eTag]
properties:
id: { type: string, format: uuid }
name: { type: string }
state: { $ref: "#/components/schemas/ProjectMilestoneState" }
targetDate: { type: [string, "null"], format: date }
completionDate: { type: [string, "null"], format: date }
summary: { type: string }
successCriteria: { type: string }
eTag:
type: string
description: Also present as the response ETag header.
ProjectDecisionSummary:
type: object
required: [id, decisionNumber, status, summary]
properties:
id: { type: string, format: uuid }
decisionNumber: { type: integer }
status: { $ref: "#/components/schemas/DecisionStatus" }
summary: { type: string }
ProjectDetail:
type: object
required:
- id
- name
- slug
- summary
- status
- priority
- currentStateMarkdown
- ownerId
- technologies
- documents
- recentUpdates
- milestones
- decisions
- createdAt
- updatedAt
- eTag
- iconAttachmentId
properties:
id: { type: string, format: uuid }
name: { type: string }
slug: { type: string }
summary: { type: string }
status: { $ref: "#/components/schemas/ProjectStatus" }
priority: { $ref: "#/components/schemas/ProjectPriority" }
currentStateMarkdown: { type: string }
ownerId: { type: string, format: uuid }
startDate: { type: [string, "null"], format: date }
targetDate: { type: [string, "null"], format: date }
completionDate: { type: [string, "null"], format: date }
repositoryUrl: { type: [string, "null"] }
liveUrl: { type: [string, "null"] }
documentationUrl: { type: [string, "null"] }
issueTrackerUrl: { type: [string, "null"] }
technologies:
type: array
items: { type: string }
documents:
type: array
items:
$ref: "#/components/schemas/ProjectDocumentSummary"
recentUpdates:
type: array
items:
$ref: "#/components/schemas/ProjectUpdateSummary"
milestones:
type: array
items:
$ref: "#/components/schemas/ProjectMilestoneSummary"
decisions:
type: array
items:
$ref: "#/components/schemas/ProjectDecisionSummary"
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
eTag:
type: string
description: Also present as the response ETag header; included in the body so clients need not parse headers.
iconAttachmentId: { type: [string, "null"], format: uuid }
ProjectIconResponse:
type: object
required: [iconAttachmentId, eTag]
properties:
iconAttachmentId: { type: string, format: uuid }
eTag: { type: string, description: Current project ETag after the icon change. }
CreateProjectRequest:
type: object
required: [name, summary, priority, currentStateMarkdown, ownerId]
properties:
name: { type: string, minLength: 1, maxLength: 200 }
summary: { type: string }
priority: { $ref: "#/components/schemas/ProjectPriority" }
currentStateMarkdown: { type: string }
ownerId: { type: string, format: uuid }
UpdateProjectRequest:
type: object
required: [name, summary, priority, currentStateMarkdown, ownerId]
properties:
name: { type: string, minLength: 1, maxLength: 200 }
summary: { type: string }
priority: { $ref: "#/components/schemas/ProjectPriority" }
currentStateMarkdown: { type: string }
ownerId: { type: string, format: uuid }
startDate: { type: [string, "null"], format: date }
targetDate: { type: [string, "null"], format: date }
completionDate: { type: [string, "null"], format: date }
repositoryUrl: { type: [string, "null"] }
liveUrl: { type: [string, "null"] }
documentationUrl: { type: [string, "null"] }
issueTrackerUrl: { type: [string, "null"] }
technologies:
type: [array, "null"]
items: { type: string }
LinkProjectDocumentRequest:
type: object
required: [pageId, role]
properties:
pageId: { type: string, format: uuid }
role: { $ref: "#/components/schemas/ProjectDocumentRole" }
CreateProjectUpdateRequest:
type: object
required: [category, title, summaryMarkdown, verificationStatus]
properties:
category: { $ref: "#/components/schemas/ProjectUpdateCategory" }
title: { type: string, maxLength: 200 }
summaryMarkdown: { type: string }
verificationStatus: { $ref: "#/components/schemas/ProjectUpdateVerificationStatus" }
occurredAt: { type: [string, "null"], format: date-time }
commitSha: { type: [string, "null"] }
releaseTag: { type: [string, "null"] }
issueIdentifier: { type: [string, "null"] }
url: { type: [string, "null"] }
pageId: { type: [string, "null"], format: uuid }
decisionRecordId: { type: [string, "null"], format: uuid }
milestoneId: { type: [string, "null"], format: uuid }
attachmentReferenceId: { type: [string, "null"], format: uuid }
supersedesUpdateId: { type: [string, "null"], format: uuid }
CreateProjectMilestoneRequest:
type: object
required: [name, summary, successCriteria]
properties:
name: { type: string, minLength: 1 }
targetDate: { type: [string, "null"], format: date }
summary: { type: string }
successCriteria: { type: string }
UpdateProjectMilestoneRequest:
type: object
required: [name, summary, successCriteria, state]
properties:
name: { type: string, minLength: 1 }
targetDate: { type: [string, "null"], format: date }
summary: { type: string }
successCriteria: { type: string }
state: { $ref: "#/components/schemas/ProjectMilestoneState" }
DecisionLineageEntry:
type: object
required: [id, decisionNumber, status, summary]
properties:
id: { type: string, format: uuid }
decisionNumber: { type: integer }
status: { $ref: "#/components/schemas/DecisionStatus" }
summary: { type: string }
DecisionDetail:
type: object
required:
- id
- workspaceId
- pageId
- decisionNumber
- status
- decisionDate
- title
- markdown
- renderedHtml
- summary
- createdAt
- updatedAt
- eTag
properties:
id: { type: string, format: uuid }
workspaceId: { type: string, format: uuid }
projectId: { type: [string, "null"], format: uuid }
pageId: { type: string, format: uuid }
decisionNumber: { type: integer }
status: { $ref: "#/components/schemas/DecisionStatus" }
decisionDate: { type: string, format: date }
title: { type: string }
markdown: { type: string }
renderedHtml:
type: string
description: Server-sanitized HTML — the same rendering pipeline as an ordinary Page.
summary: { type: string }
supersedes:
oneOf:
- $ref: "#/components/schemas/DecisionLineageEntry"
- type: "null"
supersededBy:
oneOf:
- $ref: "#/components/schemas/DecisionLineageEntry"
- type: "null"
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
eTag: { type: string }
DecisionSummary:
type: object
required: [id, decisionNumber, status, title, decisionDate, summary, updatedAt]
properties:
id: { type: string, format: uuid }
projectId: { type: [string, "null"], format: uuid }
decisionNumber: { type: integer }
status: { $ref: "#/components/schemas/DecisionStatus" }
title: { type: string }
decisionDate: { type: string, format: date }
summary: { type: string }
updatedAt: { type: string, format: date-time }
DecisionListResponse:
type: object
required: [items, nextCursor]
properties:
items:
type: array
items:
$ref: "#/components/schemas/DecisionSummary"
nextCursor:
type: [string, "null"]
CreateDecisionRequest:
type: object
required: [title, summary]
properties:
parentPageId: { type: [string, "null"], format: uuid }
projectId: { type: [string, "null"], format: uuid }
title: { type: string, minLength: 1 }
markdown: { type: [string, "null"] }
summary: { type: string }
supersedesDecisionId: { type: [string, "null"], format: uuid }
RunbookDetail:
type: object
required:
- id
- pageId
- owner
- riskLevel
- status
- requiresMaintenanceWindow
- isStale
- title
- markdown
- renderedHtml
- currentRevisionNumber
- createdAt
- updatedAt
- eTag
properties:
id: { type: string, format: uuid }
pageId: { type: string, format: uuid }
owner: { type: string, format: uuid }
riskLevel: { $ref: "#/components/schemas/RunbookRiskLevel" }
status: { $ref: "#/components/schemas/RunbookStatus" }
requiresMaintenanceWindow: { type: boolean }
estimatedDurationMinutes: { type: [integer, "null"] }
reviewDueAt: { type: [string, "null"], format: date-time }
lastValidatedAt: { type: [string, "null"], format: date-time }
validatedBy: { type: [string, "null"], format: uuid }
isStale:
type: boolean
description: True whenever the linked page has a newer revision than the last validation, or was never validated.
title: { type: string }
markdown: { type: string }
renderedHtml:
type: string
description: Server-sanitized HTML — the same rendering pipeline as an ordinary Page. Never executed.
currentRevisionNumber: { type: integer, format: int64 }
createdAt: { type: string, format: date-time }
updatedAt: { type: string, format: date-time }
eTag: { type: string }
RunbookSummary:
type: object
required: [id, pageId, title, riskLevel, status, isStale, updatedAt]
properties:
id: { type: string, format: uuid }
pageId: { type: string, format: uuid }
title: { type: string }
riskLevel: { $ref: "#/components/schemas/RunbookRiskLevel" }
status: { $ref: "#/components/schemas/RunbookStatus" }
isStale: { type: boolean }
lastValidatedAt: { type: [string, "null"], format: date-time }
updatedAt: { type: string, format: date-time }
RunbookListResponse:
type: object
required: [items, nextCursor]
properties:
items:
type: array
items:
$ref: "#/components/schemas/RunbookSummary"
nextCursor:
type: [string, "null"]
CreateRunbookRequest:
type: object
required: [title, riskLevel]
properties:
parentPageId: { type: [string, "null"], format: uuid }
title: { type: string, minLength: 1 }
markdown: { type: [string, "null"] }
templateKey: { type: [string, "null"] }
riskLevel: { $ref: "#/components/schemas/RunbookRiskLevel" }
requiresMaintenanceWindow: { type: boolean }
estimatedDurationMinutes: { type: [integer, "null"] }
UpdateRunbookProfileRequest:
type: object
required: [owner, riskLevel, status, requiresMaintenanceWindow]
properties:
owner: { type: string, format: uuid }
riskLevel: { $ref: "#/components/schemas/RunbookRiskLevel" }
status: { $ref: "#/components/schemas/RunbookStatus" }
requiresMaintenanceWindow: { type: boolean }
estimatedDurationMinutes: { type: [integer, "null"] }
reviewDueAt: { type: [string, "null"], format: date-time }
CreateServiceAccountRequest:
type: object
required: [name, description, scopes]
properties:
name:
type: string
minLength: 1
maxLength: 200
description:
type: string
maxLength: 1000
scopes:
type: array
minItems: 1
uniqueItems: true
items:
type: string
description: "One of the known scope constants, e.g. pages:read, pages:write, projects:read, runbooks:write (see docs/09-api-contract.md)."
IssueServiceAccountTokenRequest:
type: object
required: [scopes]
properties:
scopes:
type: array
minItems: 1
uniqueItems: true
items:
type: string
CreatedServiceAccountToken:
type: object
description: The raw token is only ever present in this one response — it is never persisted in the clear and is not retrievable again.
required: [serviceAccountId, tokenId, rawToken]
properties:
serviceAccountId:
type: string
format: uuid
tokenId:
type: string
format: uuid
rawToken:
type: string
description: "Format ikh_<publicId>_<secret>. Present exactly once."
ServiceAccountTokenSummary:
type: object
required: [id, publicId, scopes, createdAt]
properties:
id:
type: string
format: uuid
publicId:
type: string
description: Safe, non-secret identifier — never the raw secret.
scopes:
type: array
items:
type: string
createdAt:
type: string
format: date-time
lastUsedAt:
type: [string, "null"]
format: date-time
expiresAt:
type: [string, "null"]
format: date-time
revokedAt:
type: [string, "null"]
format: date-time
ServiceAccountSummary:
type: object
required: [id, name, description, enabled, createdAt, tokens]
properties:
id:
type: string
format: uuid
name:
type: string
description:
type: string
enabled:
type: boolean
createdAt:
type: string
format: date-time
tokens:
type: array
items:
$ref: "#/components/schemas/ServiceAccountTokenSummary"
ServiceAccountListResponse:
type: object
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/ServiceAccountSummary"
CitationResponse:
type: object
description: A stable, citable pointer to a document or decision included in a project context bundle.
required: [kind, id, title, canonicalPath, updatedAt]
properties:
kind:
type: string
enum: [Page, DecisionRecord, RunbookProfile]
id:
type: string
format: uuid
revision:
type: [integer, "null"]
format: int64
title:
type: string
canonicalPath:
type: string
updatedAt:
type: string
format: date-time
ProjectContextProjectSummary:
type: object
required: [id, name, status, summary, currentStateMarkdown, updatedAt]
properties:
id:
type: string
format: uuid
name:
type: string
status:
type: string
summary:
type: string
currentStateMarkdown:
type: string
updatedAt:
type: string
format: date-time
ProjectContextTruncation:
type: object
required: [truncated, omittedCounts]
properties:
truncated:
type: boolean
omittedCounts:
type: object
additionalProperties:
type: integer
description: "Counts of items omitted per collection (e.g. updates, decisions), keyed by collection name."
ProjectContextResponse:
type: object
description: "A bounded, permission-filtered bundle of the current project state, suitable for a single-call context fetch (docs/09-api-contract.md 'Project context', M6-A04). Unlike the unbounded project overview, this response always reports what was truncated rather than growing unbounded."
required: [project, recentUpdates, activeMilestones, decisions, runbooks, documents, citations, truncation]
properties:
project:
$ref: "#/components/schemas/ProjectContextProjectSummary"
recentUpdates:
type: array
items:
$ref: "#/components/schemas/ProjectUpdateSummary"
activeMilestones:
type: array
items:
$ref: "#/components/schemas/ProjectMilestoneSummary"
decisions:
type: array
items:
$ref: "#/components/schemas/ProjectDecisionSummary"
runbooks:
type: array
items:
$ref: "#/components/schemas/ProjectDocumentSummary"
documents:
type: array
items:
$ref: "#/components/schemas/ProjectDocumentSummary"
citations:
type: array
items:
$ref: "#/components/schemas/CitationResponse"
truncation:
$ref: "#/components/schemas/ProjectContextTruncation"
BackupSummaryResponse:
type: object
description: Never includes a filesystem path, connection string or other private detail.
required: [backupId, createdAt, totalSizeBytes, tableCount, totalRowCount, attachmentCount, schemaVersion, productVersion]
properties:
backupId:
type: string
createdAt:
type: string
format: date-time
totalSizeBytes:
type: integer
format: int64
tableCount:
type: integer
totalRowCount:
type: integer
format: int64
attachmentCount:
type: integer
schemaVersion:
type: string
productVersion:
type: string
BackupListResponse:
type: object
required: [items]
properties:
items:
type: array
items:
$ref: "#/components/schemas/BackupSummaryResponse"
ProblemDetails:
type: object
required: [type, title, status, correlationId]
properties:
type:
type: string
format: uri-reference
title:
type: string
status:
type: integer
detail:
type: string
instance:
type: string
format: uri-reference
correlationId:
type: string
errors:
type: object
additionalProperties:
type: array
items:
type: string
SetupStatus:
type: object
required: [setupRequired]
properties:
setupRequired:
type: boolean
CompleteSetupRequest:
type: object
required: [workspaceName, displayName, email, password]
properties:
workspaceName:
type: string
minLength: 1
maxLength: 200
displayName:
type: string
minLength: 1
maxLength: 200
email:
type: string
format: email
password:
type: string
minLength: 12
CompleteSetupResponse:
type: object
required: [workspaceId, userId]
properties:
workspaceId:
type: string
format: uuid
userId:
type: string
format: uuid
CsrfToken:
type: object
required: [token, headerName]
properties:
token:
type: string
headerName:
type: string
WorkspaceRole:
type: string
enum: [Reader, Editor, Administrator]
SessionResponse:
type: object
required: [authenticated, setupRequired]
properties:
authenticated:
type: boolean
setupRequired:
type: boolean
userId:
type: [string, "null"]
format: uuid
displayName:
type: [string, "null"]
email:
type: [string, "null"]
workspaceId:
type: [string, "null"]
format: uuid
role:
oneOf:
- $ref: "#/components/schemas/WorkspaceRole"
- type: "null"
LoginRequest:
type: object
required: [email, password]
properties:
email:
type: string
format: email
password:
type: string
RecoveryRedeemRequest:
type: object
required: [token, newPassword]
properties:
token:
type: string
newPassword:
type: string
minLength: 12
UserSummary:
type: object
required: [id, displayName, email, role, enabled, createdAt]
properties:
id:
type: string
format: uuid
displayName:
type: string
email:
type: string
role:
$ref: "#/components/schemas/WorkspaceRole"
enabled:
type: boolean
createdAt:
type: string
format: date-time
lastLoginAt:
type: [string, "null"]
format: date-time
CreateUserRequest:
type: object
required: [displayName, email, password, role]
properties:
displayName:
type: string
minLength: 1
maxLength: 200
email:
type: string
format: email
password:
type: string
minLength: 12
role:
$ref: "#/components/schemas/WorkspaceRole"
UpdateUserRequest:
type: object
properties:
role:
oneOf:
- $ref: "#/components/schemas/WorkspaceRole"
- type: "null"
enabled:
type: [boolean, "null"]
IssueRecoveryTokenResponse:
type: object
required: [token, expiresAt]
properties:
token:
type: string
description: Shown once, at issuance. Never retrievable again.
expiresAt:
type: string
format: date-time