Files

6587 lines
227 KiB
YAML

openapi: 3.1.0
info:
title: ItWorx Knowledge Hub API
version: 1.0.0-rc.1
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
- name: Preferences
- name: DataSources
- name: Jobs
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"
/unified-search:
get:
tags: [Search]
operationId: searchWorkspaceContent
description: >-
Human reader session only. Searches visible knowledge, projects, reviewed
services, runbooks and decisions in the current workspace. Trashed content
and hidden or archived services are excluded. Counts cover all five kinds;
`kind` filters the returned page. A `tag` filters exact, case-insensitive
page tags on knowledge, runbook and decision results and excludes projects
and services. An empty query returns no results unless `tag` is supplied. The
endpoint shares the search rate limit of 60 requests per effective remote IP
per minute. An invalid `kind` returns 422.
parameters:
- name: q
in: query
schema:
type: string
maxLength: 500
- name: kind
in: query
schema:
type: string
enum: [knowledge, projects, services, runbooks, decisions]
- name: tag
in: query
description: Exact, case-insensitive page tag; applies to knowledge, runbooks and decisions.
schema:
type: string
maxLength: 64
- $ref: "#/components/parameters/Cursor"
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 25
responses:
"200":
description: Permission-filtered, ranked results and complete grouped counts
content:
application/json:
schema:
$ref: "#/components/schemas/UnifiedSearchResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"422":
description: Invalid kind
"429":
description: Search request budget exhausted
/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"
/me/preferences:
get:
tags: [Preferences]
operationId: getMyPreferences
security: [{ cookieAuth: [] }]
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/UserPreferences" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
put:
tags: [Preferences]
operationId: updateMyPreferences
security: [{ cookieAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/UserPreferencesRequest" } } } }
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/UserPreferences" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
"412": { $ref: "#/components/responses/PreconditionFailed" }
"428": { $ref: "#/components/responses/PreconditionRequired" }
/admin/platform-settings:
get:
tags: [Admin]
operationId: getPlatformSettings
security: [{ cookieAuth: [] }]
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/PlatformSettings" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
put:
tags: [Admin]
operationId: updatePlatformSettings
security: [{ cookieAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/PlatformSettingsRequest" } } } }
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/PlatformSettings" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
"412": { $ref: "#/components/responses/PreconditionFailed" }
"428": { $ref: "#/components/responses/PreconditionRequired" }
/admin/data-sources:
get:
tags: [DataSources]
operationId: listDataSources
security: [{ cookieAuth: [] }]
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/DataSourceList" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
post:
tags: [DataSources]
operationId: registerDataSource
security: [{ cookieAuth: [] }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/DataSourceRegistration" } } } }
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/DataSource" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/admin/data-sources/{id}:
put:
tags: [DataSources]
operationId: configureDataSource
security: [{ cookieAuth: [] }]
parameters: [{ name: id, in: path, required: true, schema: { type: string, format: uuid } }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/DataSourceConfiguration" } } } }
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/DataSource" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
"412": { $ref: "#/components/responses/PreconditionFailed" }
"428": { $ref: "#/components/responses/PreconditionRequired" }
/admin/data-sources/{id}/observations:
post:
tags: [DataSources]
operationId: reportDataSourceObservation
security: [{ cookieAuth: [] }]
parameters: [{ name: id, in: path, required: true, schema: { type: string, format: uuid } }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/DataSourceObservation" } } } }
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/DataSource" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/admin/backups/status:
get:
tags: [Backups]
operationId: getBackupStatus
security: [{ cookieAuth: [] }]
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackupStatus" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/admin/backups/{backupId}/protection:
put:
tags: [Backups]
operationId: setBackupProtection
security: [{ cookieAuth: [] }]
parameters: [{ name: backupId, in: path, required: true, schema: { type: string } }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/BackupProtectionRequest" } } } }
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackupEvidence" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
"412": { $ref: "#/components/responses/PreconditionFailed" }
"428": { $ref: "#/components/responses/PreconditionRequired" }
/admin/audit-log:
get:
tags: [Admin]
operationId: listAdminAuditLog
security: [{ cookieAuth: [] }]
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/AdminAuditList" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/jobs:
get:
tags: [Jobs]
operationId: listBackgroundJobs
security: [{ cookieAuth: [] }]
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJobList" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/jobs/course-tagging:
post:
tags: [Jobs]
operationId: queueCourseTaggingJob
security: [{ cookieAuth: [] }]
parameters: [{ $ref: "#/components/parameters/RequiredIdempotencyKey" }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/QueueCourseTaggingJobRequest" } } } }
responses:
"202": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJob" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/jobs/backups:
post:
tags: [Jobs]
operationId: queueBackupJob
security: [{ cookieAuth: [] }]
parameters: [{ $ref: "#/components/parameters/RequiredIdempotencyKey" }]
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/QueueBackupJobRequest" } } } }
responses:
"202": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJob" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/jobs/{jobId}:
get:
tags: [Jobs]
operationId: getBackgroundJob
security: [{ cookieAuth: [] }]
parameters: [{ name: jobId, in: path, required: true, schema: { type: string, format: uuid } }]
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJob" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/jobs/{jobId}/cancel:
post:
tags: [Jobs]
operationId: cancelBackgroundJob
security: [{ cookieAuth: [] }]
parameters: [{ name: jobId, in: path, required: true, schema: { type: string, format: uuid } }]
responses:
"200": { description: Persisted result, content: { application/json: { schema: { $ref: "#/components/schemas/BackgroundJob" } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
/jobs/{jobId}/events:
get:
tags: [Jobs]
operationId: streamBackgroundJobEvents
security: [{ cookieAuth: [] }]
parameters: [{ name: jobId, in: path, required: true, schema: { type: string, format: uuid } }]
responses:
"200": { description: Job and progress Server-Sent Events with heartbeat and terminal close, content: { text/event-stream: { schema: { type: string } } } }
"401": { $ref: "#/components/responses/Unauthorized" }
"403": { $ref: "#/components/responses/Forbidden" }
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:
UserPreferencesRequest:
type: object
required: [theme, density, primaryNavigationCollapsed, contextPanelOpen, navigationOrder, dashboardWidgetOrder, hiddenDashboardWidgets, defaultLandingPage, catalogView, contextPanelWidth, navigationWidth]
properties:
theme: { type: string, enum: [system, light, dark] }
density: { type: string, enum: [comfortable, compact] }
primaryNavigationCollapsed: { type: boolean }
contextPanelOpen: { type: boolean }
navigationOrder: { type: array, items: { type: string } }
dashboardWidgetOrder: { type: array, items: { type: string } }
hiddenDashboardWidgets: { type: array, items: { type: string } }
defaultLandingPage: { type: string }
catalogView: { type: string, enum: [grid, list] }
contextPanelWidth: { type: integer, minimum: 256, maximum: 440 }
navigationWidth: { type: integer, minimum: 192, maximum: 320 }
UserPreferences:
allOf:
- $ref: "#/components/schemas/UserPreferencesRequest"
- type: object
required: [schemaVersion, eTag, isDefault, updatedAt]
properties:
schemaVersion: { type: integer, enum: [1] }
eTag: { type: string }
isDefault: { type: boolean }
updatedAt: { type: [string, "null"], format: date-time }
PlatformSettingsRequest:
type: object
required: [displayName, primaryLocale, timeZone, ingestionProposals, backupPolicyExpectation, inventoryFreshnessPolicy]
properties:
displayName: { type: string, minLength: 1, maxLength: 200 }
primaryLocale: { type: string }
timeZone: { type: string }
ingestionProposals: { type: object, required: [enabled, maximumSourceCount, maximumSourceBytes, failedRetryLimit], properties: { enabled: { type: boolean }, maximumSourceCount: { type: integer, minimum: 1, maximum: 20 }, maximumSourceBytes: { type: integer, format: int64 }, failedRetryLimit: { type: integer, minimum: 1, maximum: 3 } } }
backupPolicyExpectation: { type: object, required: [expectedIntervalHours, scheduleDescription, dailyRetentionCount, weeklyRetentionCount, monthlyRetentionCount, protectedBackupCount, restoreVerificationMaxAgeDays], properties: { expectedIntervalHours: { type: integer }, scheduleDescription: { type: string }, dailyRetentionCount: { type: integer }, weeklyRetentionCount: { type: integer }, monthlyRetentionCount: { type: integer }, protectedBackupCount: { type: integer }, restoreVerificationMaxAgeDays: { type: integer } } }
inventoryFreshnessPolicy: { type: object, required: [warningAfterHours], properties: { warningAfterHours: { type: integer } } }
PlatformSettings:
allOf:
- $ref: "#/components/schemas/PlatformSettingsRequest"
- type: object
required: [schemaVersion, eTag, isDefault, updatedAt]
properties:
schemaVersion: { type: integer, enum: [1] }
eTag: { type: string }
isDefault: { type: boolean }
updatedAt: { type: [string, "null"], format: date-time }
backupPolicyExpectation: { type: object, description: Includes enforcement External; configured schedule is an expectation, not a scheduler run. }
DataSourceRegistration:
type: object
required: [sourceKey, displayName, kind, expectedIntervalHours]
properties:
sourceKey: { type: string, maxLength: 200 }
displayName: { type: string, maxLength: 120 }
kind: { type: string, enum: [Inventory, Gitea, KnowledgeClient, ManualIntake, LeafWiki, CuratedPackage] }
expectedIntervalHours: { type: integer, minimum: 1, maximum: 8760 }
DataSourceConfiguration:
type: object
required: [displayName, enabled, expectedIntervalHours]
properties:
displayName: { type: string, maxLength: 120 }
enabled: { type: boolean }
expectedIntervalHours: { type: integer, minimum: 1, maximum: 8760 }
DataSourceObservation:
type: object
required: [success]
properties:
success: { type: boolean }
recordCount: { type: [integer, "null"], format: int64 }
errorCode: { type: [string, "null"] }
correlationId: { type: [string, "null"], maxLength: 100 }
DataSource:
type: object
required: [id, sourceKey, displayName, kind, enabled, state, configurationState, syncAuthority, expectedIntervalHours, eTag, actions]
properties:
id: { type: string, format: uuid }
sourceKey: { type: string }
displayName: { type: string }
kind: { type: string }
enabled: { type: boolean }
state: { type: string, enum: [Unknown, Current, Stale, Failed, Disabled] }
configurationState: { type: string }
syncAuthority: { type: string }
expectedIntervalHours: { type: integer }
lastAttemptAt: { type: [string, "null"], format: date-time }
lastSuccessAt: { type: [string, "null"], format: date-time }
lastFailureAt: { type: [string, "null"], format: date-time }
dataObservedAt: { type: [string, "null"], format: date-time }
recordCount: { type: [integer, "null"], format: int64 }
lastErrorCode: { type: [string, "null"] }
lastCorrelationId: { type: [string, "null"] }
eTag: { type: string }
actions: { type: array, items: { type: string } }
DataSourceList:
type: object
required: [items, nextCursor]
properties:
items: { type: array, items: { $ref: "#/components/schemas/DataSource" } }
nextCursor: { type: [string, "null"] }
BackupProtectionRequest:
type: object
required: [protected]
properties:
protected: { type: boolean }
BackupEvidence:
type: object
required: [protected, protectionVersion]
properties:
protected: { type: boolean }
protectionVersion: { type: integer }
integrityVerifiedAt: { type: [string, "null"], format: date-time }
restoreVerifiedAt: { type: [string, "null"], format: date-time }
lastRestoreSucceeded: { type: [boolean, "null"] }
BackupStatus:
type: object
required: [state, scheduleAuthority, scheduleDescription, restoreConfidence, storedBackupBytes, protectedCount, expectedIntervalHours, applicationRetentionCount, dailyRetentionExpectation, weeklyRetentionExpectation, monthlyRetentionExpectation, restoreVerificationMaxAgeDays]
properties:
state: { type: string, enum: [Unknown, Current, Stale] }
scheduleAuthority: { type: string, enum: [External] }
scheduleDescription: { type: string }
nextScheduledRun: { type: [string, "null"], format: date-time }
nextExpectedBy: { type: [string, "null"], format: date-time }
lastSuccessfulBackupAt: { type: [string, "null"], format: date-time }
lastIntegrityVerifiedAt: { type: [string, "null"], format: date-time }
lastVerifiedRestoreAt: { type: [string, "null"], format: date-time }
restoreConfidence: { type: string, enum: [Unknown, Stale, Verified] }
storedBackupBytes: { type: integer, format: int64, description: Sum of stored backup manifest sizes; not filesystem capacity. }
protectedCount: { type: integer }
expectedIntervalHours: { type: integer }
applicationRetentionCount: { type: integer }
dailyRetentionExpectation: { type: integer }
weeklyRetentionExpectation: { type: integer }
monthlyRetentionExpectation: { type: integer }
restoreVerificationMaxAgeDays: { type: integer }
AdminAuditList:
type: object
required: [items, nextCursor]
properties:
items: { type: array, items: { $ref: "#/components/schemas/AdminAuditItem" } }
nextCursor: { type: [integer, "null"], format: int64 }
AdminAuditItem:
type: object
required: [id, actorKind, source, action, summary, occurredAt]
properties:
id: { type: integer, format: int64 }
actorKind: { type: string }
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 }
correlationId: { type: [string, "null"] }
QueueCourseTaggingJobRequest:
type: object
required: [rootPageId, tag]
properties:
rootPageId: { type: string, format: uuid }
tag: { type: string }
QueueBackupJobRequest: { type: object, description: Empty object; the job snapshots the current server-side backup policy. }
BackgroundJob:
type: object
required: [id, workspaceId, actorId, type, status, createdAt, cancelRequested, progress, total, processed, succeeded, skipped, failed, errors, version]
properties:
id: { type: string, format: uuid }
workspaceId: { type: string, format: uuid }
actorId: { type: string, format: uuid }
type: { type: string }
status: { type: string, enum: [queued, running, succeeded, partially_succeeded, failed, cancelled] }
createdAt: { type: string, format: date-time }
startedAt: { type: [string, "null"], format: date-time }
completedAt: { type: [string, "null"], format: date-time }
cancelRequestedAt: { type: [string, "null"], format: date-time }
cancelRequested: { type: boolean }
progress: { type: integer, minimum: 0, maximum: 100 }
total: { type: integer }
processed: { type: integer }
succeeded: { type: integer }
skipped: { type: integer }
failed: { type: integer }
errors: { type: array, items: { type: object, properties: { code: { type: string }, message: { type: string }, itemId: { type: [string, "null"], format: uuid } } } }
result: { type: [object, "null"] }
correlationId: { type: [string, "null"] }
version: { type: integer, format: int64 }
BackgroundJobList:
type: object
required: [items]
properties:
items: { type: array, items: { $ref: "#/components/schemas/BackgroundJob" } }
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"]
UnifiedSearchResponse:
type: object
required: [items, counts, nextCursor]
properties:
items:
type: array
items:
$ref: "#/components/schemas/UnifiedSearchResultItem"
counts:
$ref: "#/components/schemas/UnifiedSearchCounts"
nextCursor:
type: [string, "null"]
UnifiedSearchCounts:
type: object
required: [knowledge, projects, services, runbooks, decisions, total]
properties:
knowledge: { type: integer, minimum: 0 }
projects: { type: integer, minimum: 0 }
services: { type: integer, minimum: 0 }
runbooks: { type: integer, minimum: 0 }
decisions: { type: integer, minimum: 0 }
total: { type: integer, minimum: 0 }
UnifiedSearchResultItem:
type: object
required: [kind, id, title, summary, snippet, updatedAt, href]
properties:
kind:
type: string
enum: [knowledge, projects, services, runbooks, decisions]
id: { type: string, format: uuid }
title: { type: string }
summary: { type: string }
snippet: { type: string }
updatedAt: { type: string, format: date-time }
href: { type: string, description: Same-origin relative route to the result. }
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