openapi: 3.0.3 info: title: Knowtation Hub API description: REST API for the Knowtation Hub (vault read/write, proposals, capture). Same contract as CLI/MCP where applicable. version: 1.0.0 links: - description: API contract (human-readable) url: ./HUB-API.md servers: - url: /api/v1 description: Relative to Hub base URL (e.g. https://hub.example.com) tags: - name: Health - name: Auth - name: Notes - name: Search - name: Proposals - name: Capture - name: Flows - name: Tasks - name: LearningPaths - name: Attachments - name: DocsConnectors - name: Automation security: - BearerAuth: [] paths: /health: get: tags: [Health] summary: Health check security: [] responses: '200': description: Hub is up content: application/json: schema: { type: object, properties: { ok: { type: boolean } }, required: [ok] } /automation/ingest: post: tags: [Automation] summary: AIP — preferred cron ingest description: | Routes a robot write by first-match per-account rules. Requires agent_access with ingest:automation, or a session editor/admin/member. Does not require vault:write. REST-only (no MCP). parameters: - name: X-Vault-Id in: header schema: { type: string } - name: X-Ingest-Idempotency-Key in: header schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [path, body, source_fingerprint] properties: path: { type: string } body: { type: string } frontmatter: { type: object } intent: { type: string } labels: { type: array, items: { type: string } } source: { type: string } source_fingerprint: { type: string } content_class: { type: string, enum: [research, ops, general] } ingest: { type: boolean } responses: '201': description: First apply '200': description: Idempotent replay '400': description: Validation '401': description: Unauthorized '403': description: Vault forbidden '409': description: Path or idempotency conflict /automation/ingest-rules: get: tags: [Automation] summary: List ingest rules and disabled pack templates (session only) responses: '200': description: '{ rules, templates }' '401': description: Unauthorized put: tags: [Automation] summary: Replace ingest rules (session only) responses: '200': description: Replaced '400': description: Validation or cap '401': description: Unauthorized post: tags: [Automation] summary: Append one ingest rule (session only) responses: '201': description: Created '401': description: Unauthorized /automation/ingest-rules/from-template: post: tags: [Automation] summary: Copy a pack template into user rules (stays disabled unless enable=true) responses: '201': description: Copied '401': description: Unauthorized /automation/ingest-rules/{rule_id}: delete: tags: [Automation] summary: Delete one user ingest rule (session only) parameters: - name: rule_id in: path required: true schema: { type: string } responses: '200': description: Remaining rules '401': description: Unauthorized /auth/agent/credentials: post: tags: [Auth] summary: Phase C — mint scoped REST agent credential (secret once) description: | Requires a human session JWT. Returns opaque `kt_agent_.` once. Default scopes: propose + vault:read. No admin scopes. responses: '201': description: Credential minted '401': description: Unauthorized '409': description: Credential limit get: tags: [Auth] summary: Phase C — list agent credentials (metadata only) responses: '200': description: Credential list without secrets; includes health fields and store banner flags content: application/json: schema: type: object required: [credentials, store] properties: credentials: type: array items: type: object properties: id: { type: string } name: { type: string } vault_ids: { type: array, items: { type: string } } scopes: { type: array, items: { type: string } } created_at: { type: number, nullable: true } last_used_at: { type: number, nullable: true } last_failure_code: { type: string, nullable: true, enum: [invalid, revoked, expired] } last_failure_at: { type: number, nullable: true } expires_at: { type: number, nullable: true } revoked: { type: boolean } revoked_at: { type: number, nullable: true } store: type: object required: [wipe_required, inconsistent] properties: wipe_required: { type: boolean } inconsistent: { type: boolean } '503': description: Agent credential store unavailable or inconsistent content: application/json: schema: type: object properties: code: type: string enum: [AGENT_CREDENTIAL_STORE_UNAVAILABLE, AGENT_CREDENTIAL_STORE_INCONSISTENT] store: type: object properties: inconsistent: { type: boolean } /auth/agent/credentials/{id}: delete: tags: [Auth] summary: Phase C — revoke agent credential parameters: - name: id in: path required: true schema: { type: string } responses: '200': description: Revoked (idempotent) /auth/agent/credentials/{id}/rotate: post: tags: [Auth] summary: Phase C — rotate agent credential secret (shown once) parameters: - name: id in: path required: true schema: { type: string } responses: '200': description: New secret issued /auth/agent/token: post: tags: [Auth] summary: Phase C — exchange kt_agent_ credential for agent_access JWT security: [] responses: '200': description: Access token issued '401': description: Invalid credential '503': description: Agent credential store unavailable or inconsistent content: application/json: schema: type: object properties: code: type: string enum: [AGENT_CREDENTIAL_STORE_UNAVAILABLE, AGENT_CREDENTIAL_STORE_INCONSISTENT] /api/v1/auth/session: get: tags: [Auth] summary: C7 Session introspection — current identity and scopes description: | Returns the verified identity and derived API scopes for the caller. Accepts only a `Bearer` JWT in the `Authorization` header — no cookie required, making it safe to call cross-origin from Scooling or any other consumer. The response is derived entirely from the signed token — no database call is made. Scopes are role-derived today (C4 will replace this with per-user explicit grants without changing the response shape). security: - bearerAuth: [] responses: '200': description: Verified session content: application/json: schema: type: object required: [sub, provider, id, name, role, iat, exp, scopes] properties: sub: type: string description: Canonical user ID (`provider:id`) example: google:104164334692309763642 provider: type: string enum: [google, github, apple] id: type: string description: Provider-specific user ID name: type: string description: Display name (empty for refresh-path tokens) role: type: string enum: [admin, member] iat: type: integer description: Token issued-at (Unix seconds) exp: type: integer description: Token expires-at (Unix seconds) scopes: type: array items: { type: string } description: Derived API scopes. `admin` role → `[vault:read, vault:write, admin]`; `member` → `[vault:read, vault:write]` example: [vault:read, vault:write] '401': description: Missing, expired, or tampered token content: application/json: schema: type: object properties: error: { type: string } code: { type: string, enum: [UNAUTHORIZED] } /auth/providers: get: tags: [Auth] summary: OAuth providers configured security: [] responses: '200': content: application/json: schema: type: object properties: google: { type: boolean } github: { type: boolean } apple: { type: boolean } local: { type: boolean } /api/v1/auth/native-apple-exchange: post: tags: [Auth] summary: Sign in with Apple identity-assertion exchange (native) description: | Verifies an Apple `identity_token` (JWKS) and mints a hosted session JWT (`provider: apple`, `type: session`). Not Passport Google/GitHub and not `api/v1/auth/native` PKCE. Does not return `scooling_uid`. security: [] requestBody: required: true content: application/json: schema: type: object required: [identity_token] additionalProperties: false properties: identity_token: { type: string } nonce: { type: string } full_name: { type: string, maxLength: 128 } responses: '200': description: Hosted session minted content: application/json: schema: type: object required: [schema_version, token_type, access_token, expires_in] properties: schema_version: { type: integer, enum: [1] } token_type: { type: string, enum: [Bearer] } access_token: { type: string } expires_in: { type: integer } '400': description: Bad request '401': description: Apple assertion invalid '403': description: Offline-locked OAuth disabled '503': description: Not configured or Apple JWKS unavailable /notes/facets: get: tags: [Notes] summary: Projects, tags, folders for filter dropdowns responses: '200': content: application/json: schema: type: object properties: projects: { type: array, items: { type: string } } tags: { type: array, items: { type: string } } folders: { type: array, items: { type: string } } /notes: get: tags: [Notes] summary: List notes parameters: - name: folder in: query schema: { type: string } - name: project in: query schema: { type: string } - name: tag in: query schema: { type: string } - name: since in: query schema: { type: string } - name: until in: query schema: { type: string } - name: limit in: query schema: { type: integer, minimum: 0, maximum: 100 } - name: offset in: query schema: { type: integer, minimum: 0 } - name: order in: query schema: { type: string, enum: [date, date-asc] } - name: fields in: query schema: { type: string, enum: [path, path+metadata, full] } - name: count_only in: query schema: { type: boolean } - name: content_class in: query schema: { type: string, enum: [research, ops, general] } responses: '200': content: application/json: schema: oneOf: - type: object properties: notes: { type: array, items: { $ref: '#/components/schemas/NoteListItem' } } total: { type: integer } - type: object properties: total: { type: integer } post: tags: [Notes] summary: Write or update a note requestBody: required: true content: application/json: schema: type: object required: [path] properties: path: { type: string } body: { type: string } frontmatter: { type: object } append: { type: boolean } responses: '200': content: application/json: schema: { type: object, properties: { path: { type: string }, written: { type: boolean } } } '400': '500': content: application/json: schema: { $ref: '#/components/schemas/Error' } /notes/{path}: get: tags: [Notes] summary: Get one note by path parameters: - name: path in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/NoteFull' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /note-outline: get: tags: [Notes] summary: Body-free NoteOutline headings for one note description: > Returns knowtation.note_outline/v1 metadata for one authorized vault-relative note. The response excludes note body text, snippets, full frontmatter, absolute paths, provider payloads, MCP resource URIs, summaries, vectors, OCR, PageIndex output, persistence records, and write-back state. parameters: - name: path in: query required: true schema: { type: string } description: Vault-relative Markdown note path. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/NoteOutline' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '502': content: application/json: schema: { $ref: '#/components/schemas/Error' } /document-tree: get: tags: [Notes] summary: Body-free DocumentTree heading hierarchy for one note description: > Returns knowtation.document_tree/v0 metadata for one authorized vault-relative note. The response excludes note body text, snippets, full frontmatter, absolute paths, provider payloads, MCP resource URIs, summaries, vectors, OCR, PageIndex output, persistence records, sidecars, LLM calls, and write-back state. parameters: - name: path in: query required: true schema: { type: string } description: Vault-relative Markdown note path. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/DocumentTree' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '502': content: application/json: schema: { $ref: '#/components/schemas/Error' } /calendar/timeline: get: tags: [Calendar] summary: Merged note-date and external-event timeline (self-hosted) description: > Returns knowtation.calendar_timeline/v0 items for an authorized vault. Merges note-date buckets and stored calendar events. No OAuth tokens or connector secrets. parameters: - name: from in: query required: true schema: { type: string } description: Range start (YYYY-MM-DD or ISO8601). - name: to in: query required: true schema: { type: string } description: Range end (YYYY-MM-DD or ISO8601). - name: layers in: query required: false schema: { type: string } description: Comma-separated layers (`notes`, `events`). Default both. - name: source_calendar_ids in: query required: false schema: { type: string } description: Comma-separated source calendar ids to include for the events layer. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/CalendarTimeline' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /calendar/agent-context: get: tags: [Calendar] summary: Tier-enforced calendar context for agents (self-hosted) description: > Returns knowtation.calendar_agent_context/v0 — redacted calendar events an agent may see. Enforced server-side: calendars with enabled_for_agents=false contribute nothing; per-calendar agent_context_tier_max and the org policy cap clamp the tier; the v0 retrieval ceiling is tier 2. Agent visibility is independent of enabled_for_display. Tier 1 omits the event summary; tier 0 returns no events. Calendar text is untrusted prompt content. parameters: - name: from in: query required: true schema: { type: string } description: Range start (YYYY-MM-DD or ISO8601). - name: to in: query required: true schema: { type: string } description: Range end (YYYY-MM-DD or ISO8601). - name: agent_context_tier in: query required: true schema: { type: integer, minimum: 0, maximum: 2 } description: Requested agent tier (0 none, 1 busy blocks, 2 titles + label). Clamped by caps. - name: source_calendar_ids in: query required: false schema: { type: string } description: Comma-separated source calendar ids to restrict the agent scope. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/CalendarAgentContext' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /calendar/source-calendars: get: tags: [Calendar] summary: List source calendars and display/agent toggles (self-hosted) responses: '200': content: application/json: schema: { $ref: '#/components/schemas/SourceCalendarList' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /calendar/source-calendars/{id}: patch: tags: [Calendar] summary: Update source calendar display/agent toggles (self-hosted) description: > Partial update for enabled_for_display, enabled_for_agents, agent_context_tier_max (0–4), and optional user_group. Org policy may cap agent_context_tier_max via KNOWTATION_CALENDAR_AGENT_TIER_MAX_CAP or data/hub_calendar_policy.json. parameters: - name: id in: path required: true schema: { type: string } description: Source calendar id. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/SourceCalendarPatchRequest' } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/SourceCalendarPatchResult' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /calendar/events/import: post: tags: [Calendar] summary: Import ICS text into the local event store (read-only, self-hosted) requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/CalendarIcsImportRequest' } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/CalendarIcsImportResult' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows: get: tags: [Flows] summary: List scope-visible flows (content-minimized) description: > Returns knowtation.flow_list/v0 summaries for flows visible in the caller's authorized workspace scope. Scope query param narrows only — never widens. Step bodies are never included in list responses. parameters: - name: scope in: query schema: { type: string, enum: [personal, project, org] } description: Narrow within authorized scopes only. - name: tag in: query schema: { type: string } description: Filter by single tag membership. - name: limit in: query schema: { type: integer, minimum: 1, maximum: 200 } description: Max summaries (default 200). responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowListResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } post: tags: [Flows] summary: Propose a new Flow (review-before-write) description: > Validates a knowtation.flow/v0 + flow_step/v0 bundle, resolves write authority server-side (scope × role, deny-by-default), and creates a standard proposal targeting the Flow's mirror note (SD-4). Returns a knowtation.flow_proposal/v0 envelope (pointers/labels only). Gated by FLOW_AUTHORING_WRITES — when off returns 403 FLOW_AUTHORING_DISABLED. No Flow index write happens here; the index changes only at approve→apply. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/FlowProposeRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/FlowProposalResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '409': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/import: post: tags: [Flows] summary: Import a portable Flow bundle as a scope-checked proposal description: > Routes a portable { flow, steps } bundle through the same propose path. The bundle scope is validated against the actor's write tier; unwritable ⇒ 403 FLOW_IMPORT_SCOPE_DENIED, malformed ⇒ 400 FLOW_IMPORT_BUNDLE_MALFORMED. Lineage pointers (external_ref / source_vault_hint) are preserved. Never auto-applied — creates a proposed proposal. Gated by FLOW_AUTHORING_WRITES. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/FlowImportRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/FlowProposalResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '409': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/capture/observe: post: tags: [Flows] summary: Observe content-minimized session signals (capture detection) description: > Runs bounded structural detectors when FLOW_CAPTURE_DETECTION_ENABLED is on. Creates/updates flow_candidate/v0 records; returns content-minimized summaries. When detection is off, returns detection_authorized=false with no store mutation. requestBody: required: true content: application/json: schema: type: object required: [session_id, step_sequence_refs, observed_counts] responses: '200': description: Observe envelope '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/candidates: get: tags: [Flows] summary: List flow capture candidates (read store) description: Returns pending_review candidate summaries; read path does not require detection sub-gate. parameters: - name: scope in: query schema: { type: string, enum: [personal, project, org] } - name: include_low_confidence in: query schema: { type: boolean } - name: limit in: query schema: { type: integer, minimum: 1, maximum: 50 } responses: '200': description: Candidate list envelope '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/candidates/{candidate_id}/propose: post: tags: [Flows] summary: Propose candidate promotion (review-before-write) description: > Creates a flow_candidate_promote or flow_candidate_merge proposal. Gated by FLOW_CAPTURE_WRITES_ENABLED (default off). parameters: - name: candidate_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [confirmed_scope, intent] responses: '201': description: Capture proposal envelope '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '409': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/candidates/{candidate_id}/dismiss: post: tags: [Flows] summary: Propose candidate dismissal description: > Creates a flow_candidate_dismiss proposal; on approve candidate status becomes rejected. Gated by FLOW_CAPTURE_WRITES_ENABLED (default off). parameters: - name: candidate_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [intent] responses: '201': description: Capture proposal envelope '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/{id}/proposals: post: tags: [Flows] summary: Propose an edit to an existing Flow (review-before-write) description: > Like POST /flows but for an edit. Requires base_version + base_state_id (the flowst1_ optimistic-concurrency token); a mismatch at propose or approve time ⇒ 409 FLOW_LINEAGE_CONFLICT. flow.version must be strictly greater than base_version. Editing a flow the actor cannot read ⇒ 404 unknown_flow (no existence leak). Gated by FLOW_AUTHORING_WRITES. parameters: - name: id in: path required: true schema: { type: string } description: Flow id (flow_); must match the bundle's flow_id. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/FlowProposeEditRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/FlowProposalResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '409': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/{id}/projection: get: tags: [Flows] summary: Derive a read-only harness projection of a canonical flow description: > Renders the canonical flow (latest visible, or pinned ?version) into the requested harness as knowtation.flow_project/v0. Derived and read-only — generated_from_canonical is always true and editable is always false. No secrets appear in rendered text. parameters: - name: id in: path required: true schema: { type: string } - name: harness in: query required: true schema: type: string enum: [cursor_rule, cursor_skill, mcp_prompt, cli_runbook, agent_bundle] - name: version in: query schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowProjectResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/{id}/external-grants: post: tags: [Flows] summary: Mint a short-lived external-agent grant (gated; default off) description: > Mints knowtation.flow_external_grant/v0 for a pinned flow version and requested tools. Returns a one-time bearer at mint only. Requires FLOW_EXTERNAL_AGENT_ENABLED. parameters: - name: id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/FlowExternalGrantMintRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/FlowExternalGrantMintResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/external-grants: get: tags: [Flows] summary: List external-agent grant metadata (no bearer) parameters: - name: flow_id in: query schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowExternalGrantListResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/external-grants/{grant_id}: delete: tags: [Flows] summary: Revoke an external-agent grant parameters: - name: grant_id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowExternalGrant' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /tasks: get: tags: [Tasks] summary: List scope-visible tasks (content-minimized) description: > Returns knowtation.task_list/v0 summaries for tasks visible in the caller's authorized workspace scope. Scope query param narrows only — never widens. List responses never include assignee refs or artifact_links. parameters: - name: scope in: query schema: { type: string, enum: [personal, project, org] } description: Narrow within authorized scopes only. - name: workspace_id in: query schema: { type: string } description: Filter by workspace_id equality. - name: status in: query schema: type: string enum: [pending, in_progress, blocked, done, cancelled] - name: kind in: query schema: type: string enum: [personal, assignment, mentor_checkin, org_work_job] - name: limit in: query schema: { type: integer, minimum: 1, maximum: 500 } description: Max summaries (default 500). responses: '200': content: application/json: schema: { $ref: '#/components/schemas/TaskListResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /tasks/{id}: get: tags: [Tasks] summary: Get one authorized task description: > Returns knowtation.task_get/v0 with the full task record when the caller is authorized for the task scope. Missing or invisible tasks return 404 unknown_task (no existence leak). parameters: - name: id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/TaskGetResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /learning-paths: get: tags: [LearningPaths] summary: List scope-visible learning paths (content-minimized) description: > Returns knowtation.learning_path_list/v0 summaries. JWT + vault; no write gate. Default omits archived. Empty list is paths: [] (never a fabricated sample). PATH_WRITES_ENABLED does not affect this read. parameters: - name: scope in: query schema: { type: string, enum: [personal, project, org] } - name: workspace_id in: query schema: { type: string } - name: status in: query schema: { type: string, enum: [active, paused, archived] } - name: limit in: query schema: { type: integer, minimum: 1, maximum: 200 } description: Max summaries (default 200; invalid treated as 200). responses: '200': content: application/json: schema: { $ref: '#/components/schemas/LearningPathListResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /learning-paths/{path_id}: get: tags: [LearningPaths] summary: Get one authorized learning path description: > Missing, invalid path_id, or out-of-scope returns 404 PATH_NOT_FOUND (no existence leak). Archived paths still return when authorized. parameters: - name: path_id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/LearningPathGetResponse' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /learning-paths/proposals: post: tags: [LearningPaths] summary: Propose a learning-path write (SD-4; gated; default off) description: > path_create, path_update, or path_archive. Missing kind defaults to path_create. Requires PATH_WRITES_ENABLED (default off). Propose only — apply stays after approve. Path kinds are not admitted to personal self-apply (T5). requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/LearningPathProposalRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/LearningPathProposalResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /attachments: get: tags: [Attachments] summary: List scope-visible attachments (content-minimized) description: > Returns knowtation.attachment_list/v0 summaries for attachments visible in the caller's authorized workspace scope. Scope query param narrows only — never widens. List responses never include linked_note_refs, byte_size, mime_type, or agent_visible. On hosted gateway→bridge (SEC-SEAM-MEDIA), connector_ref rows from hub_attachment_external_refs.json are included after media external-link apply. parameters: - name: scope in: query schema: { type: string, enum: [personal, project, org] } description: Narrow within authorized scopes only. - name: note_ref in: query schema: { type: string } description: Filter attachments linked to a specific note (note:path). - name: source in: query schema: type: string enum: [vault_file, mist_blob, embedded_url, connector_ref] - name: mime_class in: query schema: type: string enum: [image, video, audio, document, unknown] - name: storage_kind in: query schema: type: string enum: [vault_blob, external_link] - name: agent_visible in: query schema: { type: boolean } description: When true, return only attachments with agent_visible consent. - name: limit in: query schema: { type: integer, minimum: 1, maximum: 500 } description: Max summaries (default 500). responses: '200': content: application/json: schema: { $ref: '#/components/schemas/AttachmentListResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /attachments/link-proposals: post: tags: [Attachments] summary: Propose an external media link (review-before-write) description: > Creates a media_external_link proposal. Gated by MEDIA_EXTERNAL_LINK_ENABLED (default off). Requires an active import consent and allowlisted connector. Never fetches opaque_ref. Available on self-hosted Hub and on hosted gateway→bridge (SEC-SEAM-MEDIA) when the same gate is enabled; hosted creates the proposal on the canister proposal store. requestBody: required: true content: application/json: schema: type: object required: [intent, scope, connector_id, opaque_ref, consent_id] properties: intent: { type: string } scope: { type: string, enum: [personal, project, org] } connector_id: { type: string } opaque_ref: { type: string } consent_id: { type: string } display_label: { type: string } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/MediaProposalResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /attachments/attach-proposals: post: tags: [Attachments] summary: Propose attaching media to a note (review-before-write) description: > Creates a media_attach proposal. Gated by MEDIA_ATTACH_ENABLED (default off). Available on self-hosted Hub and on hosted gateway→bridge (SEC-SEAM-MEDIA) when the same gate is enabled. Hosted propose stamps media_pointer onto the canister row so apply does not require a vault-wide mist walk on the bridge. requestBody: required: true content: application/json: schema: type: object required: [intent, scope, attachment_id, note_ref, base_state_id] properties: intent: { type: string } scope: { type: string, enum: [personal, project, org] } attachment_id: { type: string } note_ref: { type: string } base_state_id: { type: string } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/MediaProposalResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /attachments/import-consents: get: tags: [Attachments] summary: List import consents (read-only) parameters: - name: scope in: query schema: { type: string, enum: [personal, project, org] } responses: '200': content: application/json: schema: type: object required: [schema, vault_id, consents] properties: schema: type: string enum: [knowtation.media_import_consent_list/v0] vault_id: { type: string } consents: type: array items: type: object post: tags: [Attachments] summary: Grant import consent for external linking description: Requires MEDIA_EXTERNAL_LINK_ENABLED. Not exposed as MCP write tool. requestBody: required: true content: application/json: schema: type: object required: [connector_id, scope] properties: connector_id: { type: string } scope: { type: string, enum: [personal, project, org] } expires_at: { type: [string, 'null'] } responses: '201': content: application/json: schema: type: object delete: tags: [Attachments] summary: Revoke import consent (not supported at collection path) responses: '405': content: application/json: schema: { $ref: '#/components/schemas/Error' } /attachments/import-consents/{id}: delete: tags: [Attachments] summary: Revoke an import consent by id parameters: - name: id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: type: object '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /attachments/proposals/{proposal_id}/apply-approved: post: tags: [Attachments] summary: Apply an approved media proposal (hosted Hub-complete / ops recovery) description: > Runs the shared media precheck + reconcile against bridge dataDir stores (and canister note RMW for media_attach). Requires proposal status approved (409 CONFLICT otherwise). The mandatory client path is the gateway post-approve hook (maybeApplyHostedMediaAfterApprove); this route is the ops recovery surface and the hook's bridge target (SEC-SEAM-MEDIA SM-C4 / SM-C12). Self-hosted Hub applies on approve without this route. parameters: - name: proposal_id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: type: object required: [applied, proposal_id, vault_id, proposal_kind] properties: applied: { type: boolean, enum: [true] } proposal_id: { type: string } vault_id: { type: string } proposal_kind: type: string enum: [media_external_link, media_attach] attachment_id: { type: string } connector_id: { type: ['string', 'null'] } note_ref: { type: string } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '409': content: application/json: schema: { $ref: '#/components/schemas/Error' } /attachments/{id}: get: tags: [Attachments] summary: Get one authorized attachment description: > Returns knowtation.attachment_get/v0 with the full attachment record when the caller is authorized. Missing or invisible attachments return 404 unknown_attachment (no existence leak). On hosted gateway→bridge, connector_ref rows derived from hub_attachment_external_refs.json are visible after media external-link apply (blob-backed stores). parameters: - name: id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/AttachmentGetResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /tasks/proposals: post: tags: [Tasks] summary: Propose a one-time task write (SD-4; gated; default off) description: > Typed facade over /proposals for task_create, task_status_update, task_assign, and task_artifact_link. Requires TASK_WRITES_ENABLED (default off). requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/TaskWriteProposalRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/TaskProposalResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '409': content: application/json: schema: { $ref: '#/components/schemas/Error' } /task-loops/proposals: post: tags: [Tasks] summary: Propose a task loop series write (SD-4; gated; default off) description: > task_loop_create, task_loop_pause, or task_loop_cancel proposal. Requires TASK_WRITES_ENABLED. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/TaskLoopWriteProposalRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/TaskProposalResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /task-loops/{loop_id}/instances/proposals: post: tags: [Tasks] summary: Propose materializing one loop occurrence task (SD-4; gated; default off) parameters: - name: loop_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/TaskInstanceMaterializeRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/TaskInstanceProposalResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '409': content: application/json: schema: { $ref: '#/components/schemas/Error' } /agents/identities: post: tags: [Delegation] summary: Propose agent identity registration (SD-4; gated; default off) description: > Creates an SD-4 proposal with intent agent_identity_register. Requires DELEGATION_ENABLED. requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/AgentIdentityRegisterRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/DelegationProposalResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } get: tags: [Delegation] summary: List agent identities (gated; default off) parameters: - name: kind in: query schema: type: string enum: [user_owned, org_owned, delegate] - name: status in: query schema: type: string enum: [active, suspended, revoked] responses: '200': content: application/json: schema: { $ref: '#/components/schemas/AgentIdentityListResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /delegation/consents: post: tags: [Delegation] summary: Propose delegation consent (SD-4; gated; default off) requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/DelegationConsentProposeRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/DelegationConsentProposeResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /delegation/consents/{consent_id}: delete: tags: [Delegation] summary: Revoke delegation consent parameters: - name: consent_id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/DelegationConsent' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /delegation/grants: post: tags: [Delegation] summary: Mint short-lived delegation grant (gated; default off) requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/DelegationGrantMintRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/DelegationGrantMintResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } get: tags: [Delegation] summary: List delegation grant metadata (no bearer) parameters: - name: actor_agent_id in: query schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/DelegationGrantListResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /delegation/grants/{grant_id}: delete: tags: [Delegation] summary: Revoke delegation grant parameters: - name: grant_id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/DelegationGrant' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /delegation/audit: post: tags: [Delegation] summary: Append delegation audit entry (pointer-safe) requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/DelegationAuditAppendRequest' } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/DelegationAudit' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/{id}: get: tags: [Flows] summary: Get one flow definition + ordered steps description: > Returns knowtation.flow_get/v0 with full flow definition and steps in ascending ordinal order. Missing and scope-invisible flows both return 404 unknown_flow. Step text is untrusted input — returned verbatim as data. parameters: - name: id in: path required: true schema: { type: string } description: Flow id (flow_). - name: version in: query schema: { type: string } description: Pin semver version; default latest visible. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowGetResponse' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flow-runs/{run_id}: get: tags: [Flows] summary: Get one flow run by run id or portable run_ref description: > Returns knowtation.flow_run/v0 for a scope-visible run. Accepts canonical run_id (run_…) or portable run_ref (flow_run:…). Missing and scope-invisible runs both return 404 unknown_run. parameters: - name: run_id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowRunResponse' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/{id}/runs: get: tags: [Flows] summary: List runs for a flow parameters: - name: id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowRunListResponse' } post: tags: [Flows] summary: Start a flow run description: > Gated by FLOW_RUN_WRITES_ENABLED (default off). Pins flow_version for the run life. parameters: - name: id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [flow_version] properties: flow_version: { type: string } task_ref: { type: string } external_ref: { type: string } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/FlowRunStartResponse' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } /flows/{id}/runs/{run_id}: get: tags: [Flows] summary: Get one flow run parameters: - name: id in: path required: true schema: { type: string } - name: run_id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowRunResponse' } post: tags: [Flows] summary: Advance, record evidence, execute automatable, or submit review description: > Use dedicated sub-paths (/advance, /evidence, /execute-automatable, /submit-review). Run writes gated by FLOW_RUN_WRITES_ENABLED; automatable by FLOW_AUTOMATABLE_EXECUTION_ENABLED. /flows/{id}/runs/{run_id}/advance: post: tags: [Flows] summary: Advance a step manually parameters: - name: id in: path required: true schema: { type: string } - name: run_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [step_id, to_status] properties: step_id: { type: string } to_status: { type: string } skip_reason: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowRunResponse' } /flows/{id}/runs/{run_id}/evidence: post: tags: [Flows] summary: Record evidence pointer on a step parameters: - name: id in: path required: true schema: { type: string } - name: run_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [step_id, evidence_ref, pointer_kind] properties: step_id: { type: string } evidence_ref: { type: string } pointer_kind: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowRunResponse' } /flows/{id}/runs/{run_id}/execute-automatable: post: tags: [Flows] summary: Execute an automatable step (server orchestration stub) description: Requires valid knowtation.flow_execution_consent/v0. Gated by FLOW_AUTOMATABLE_EXECUTION_ENABLED. parameters: - name: id in: path required: true schema: { type: string } - name: run_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [step_id, consent_id] properties: step_id: { type: string } consent_id: { type: string } model_lane: { type: string } dry_run: { type: boolean } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowExecuteAutomatableResponse' } /flows/{id}/runs/{run_id}/submit-review: post: tags: [Flows] summary: Submit run outcome to review tray parameters: - name: id in: path required: true schema: { type: string } - name: run_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [intent] properties: intent: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/FlowRunSubmitReviewResponse' } /flows/{id}/runs/{run_id}/consent: post: tags: [Flows] summary: Mint execution consent for automatable steps parameters: - name: id in: path required: true schema: { type: string } - name: run_id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [allowed_lanes, cost_cap_units] properties: allowed_lanes: { type: array, items: { type: string } } cost_cap_units: { type: integer } ttl_seconds: { type: integer } responses: '201': content: application/json: schema: { $ref: '#/components/schemas/FlowExecutionConsentMintResponse' } /metadata-facets: get: tags: [Notes] summary: Body-free MetadataFacets hints for one note description: > Returns knowtation.metadata_facets/v0 metadata for one authorized vault-relative note. The response excludes note body text, snippets, full frontmatter, absolute paths, provider payloads, MCP resource URIs, summaries, labels, vectors, OCR, PageIndex output, media metadata, memory events, persistence records, sidecars, LLM calls, and write-back state. parameters: - name: path in: query required: true schema: { type: string } description: Vault-relative Markdown note path. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/MetadataFacets' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '502': content: application/json: schema: { $ref: '#/components/schemas/Error' } /section-source: get: tags: [Notes] summary: Body-free SectionSource metadata for one note description: > Returns knowtation.section_source/v0 metadata for one authorized vault-relative note. The response excludes note body text, section body text, snippets, full frontmatter, line ranges, byte offsets, section body lengths, absolute paths, raw canister payloads, provider payloads, and MCP resource URIs. parameters: - name: path in: query required: true schema: { type: string } description: Vault-relative Markdown note path. responses: '200': content: application/json: schema: { $ref: '#/components/schemas/SectionSource' } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '401': content: application/json: schema: { $ref: '#/components/schemas/Error' } '403': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } '502': content: application/json: schema: { $ref: '#/components/schemas/Error' } /index: post: tags: [Notes] summary: Re-run indexer (vault to vector store) responses: '200': content: application/json: schema: type: object properties: ok: { type: boolean } notesProcessed: { type: integer } chunksIndexed: { type: integer } vectors_deleted: { type: integer, description: Rows removed for this vault before upsert (hosted sqlite-vec) } '500': content: application/json: schema: { $ref: '#/components/schemas/Error' } /export: post: tags: [Notes] summary: Export one note to content (returns body + filename for client download) requestBody: required: true content: application/json: schema: type: object required: [path] properties: path: { type: string } format: { type: string, enum: [md, html] } responses: '200': content: application/json: schema: type: object properties: content: { type: string } filename: { type: string } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '404': content: application/json: schema: { $ref: '#/components/schemas/Error' } /import: post: tags: [Notes] summary: Import from uploaded file or ZIP (multipart: source_type; file except for google-sheets; optional project, tags, spreadsheet_id for google-sheets) requestBody: required: true content: multipart/form-data: schema: type: object required: [source_type] properties: source_type: type: string description: Importer id. For google-sheets, omit file and set spreadsheet_id; optional sheets_range (A1 notation). See lib/import-source-types.mjs. file: { type: string, format: binary, description: Required for all importers except google-sheets. } spreadsheet_id: type: string description: Required when source_type is google-sheets (id from the Google Sheets URL). sheets_range: type: string description: Optional for google-sheets; A1 range. Omit to read the first sheet from A1. project: { type: string } output_dir: { type: string } tags: { type: string } responses: '200': content: application/json: schema: type: object properties: imported: { type: array, items: { type: object } } count: { type: integer } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '500': content: application/json: schema: { $ref: '#/components/schemas/Error' } /import-url: post: tags: [Notes] summary: Import from a public https URL (JSON body; editor/admin) requestBody: required: true content: application/json: schema: type: object required: [url] properties: url: { type: string, description: 'Full https URL' } mode: { type: string, enum: [auto, bookmark, extract], description: 'Capture mode (default auto)' } project: { type: string } output_dir: { type: string } tags: { oneOf: [{ type: string }, { type: array, items: { type: string } }] } responses: '200': content: application/json: schema: type: object properties: imported: { type: array, items: { type: object } } count: { type: integer } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '500': content: application/json: schema: { $ref: '#/components/schemas/Error' } /docs/connectors: get: tags: [DocsConnectors] summary: List docs connectors (Drive + Notion). Gate off → 501 NOT_AUTHORIZED. No secrets. responses: '200': content: application/json: schema: type: object required: [schema, connectors] properties: schema: { type: string, enum: [knowtation.docs_connectors/v0] } connectors: type: array items: type: object properties: connector_id: { type: string } provider: { type: string, enum: [google-drive, notion] } display_name: { type: string } status: { type: string, enum: [pending, connected, needs_reauth, revoked] } last_sync_at: { type: string, nullable: true } last_sync_error: { type: string } file_count: { type: integer } revoked_at: { type: string, nullable: true } '501': description: Gate not authorized post: tags: [DocsConnectors] summary: Begin Drive OAuth or Notion Hub-key connector (editor/admin). Gates hard-coded false. requestBody: required: true content: application/json: schema: type: object required: [provider] properties: provider: { type: string, enum: [google-drive, notion] } display_name: { type: string, maxLength: 128 } return_url: { type: string, description: Required for google-drive; exact-allowlisted } responses: '200': description: Drive returns authorization_url; Notion returns status '400': description: BAD_REQUEST / PROVIDER_DENIED / RETURN_URL_DENIED '501': description: NOT_AUTHORIZED /docs/connectors/callback: get: tags: [DocsConnectors] summary: Google Drive OAuth callback (state-authenticated; no JWT) security: [] parameters: - name: code in: query schema: { type: string } - name: state in: query schema: { type: string } responses: '302': description: Redirect to allowlisted return_url /docs/connectors/{id}/files: get: tags: [DocsConnectors] summary: List importable file metadata (no bodies) parameters: - name: id in: path required: true schema: { type: string } - name: page_token in: query schema: { type: string } - name: q in: query schema: { type: string, pattern: '^[A-Za-z0-9 ._-]{1,128}$' } responses: '200': description: files + optional next_page_token '400': description: BAD_REQUEST (invalid q or ids) '501': description: NOT_AUTHORIZED /docs/connectors/{id}/import: post: tags: [DocsConnectors] summary: Create docs-sync Review proposals for 1–20 file ids (never writeNote) parameters: - name: id in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [file_ids] properties: file_ids: type: array minItems: 1 maxItems: 20 items: { type: string } responses: '200': description: '{ proposed, skipped, proposal_ids }' '501': description: NOT_AUTHORIZED /docs/connectors/{id}/sync: post: tags: [DocsConnectors] summary: Optional cursor sync; rate-limited ≥60s; creates proposals only parameters: - name: id in: path required: true schema: { type: string } responses: '200': description: '{ proposed, skipped, last_sync_at }' '429': description: RATE_LIMITED '501': description: NOT_AUTHORIZED /docs/connectors/{id}: delete: tags: [DocsConnectors] summary: Revoke connector; imported vault notes stay parameters: - name: id in: path required: true schema: { type: string } responses: '200': description: '{ revoked: true }' '501': description: NOT_AUTHORIZED /settings: get: tags: [Notes] summary: Config status for Settings UI (no secrets) responses: '200': content: application/json: schema: type: object properties: vault_path_display: { type: string } vault_git: type: object properties: enabled: { type: boolean } has_remote: { type: boolean } auto_commit: { type: boolean } auto_push: { type: boolean } /vault/sync: post: tags: [Notes] summary: Manual vault backup (git add, commit, push) description: Self-hosted runs local git. Hosted (bridge) pushes notes as Markdown plus `.knowtation/backup/v1/snapshot.json` with full proposals. responses: '200': content: application/json: schema: type: object properties: ok: { type: boolean } message: { type: string } notesCount: { type: integer, description: Hosted bridge only } proposalsCount: { type: integer, description: Hosted bridge only } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } '500': content: application/json: schema: { $ref: '#/components/schemas/Error' } /search: post: tags: [Search] summary: Vault search (semantic or keyword) requestBody: required: true content: application/json: schema: type: object required: [query] properties: query: { type: string } mode: { type: string, enum: [semantic, keyword], description: Omitted or semantic = vector search; keyword = substring/token match on note text } match: { type: string, enum: [phrase, all_terms], description: Keyword only; phrase = full query substring; all_terms = every token must appear } folder: { type: string } project: { type: string } tag: { type: string } since: { type: string } until: { type: string } chain: { type: string } entity: { type: string } episode: { type: string } limit: { type: integer } order: { type: string } fields: { type: string } content_scope: { type: string, enum: [notes, approval_logs], description: Narrow to normal notes vs approvals/ logs } snippetChars: { type: integer } count_only: { type: boolean } countOnly: { type: boolean } responses: '200': content: application/json: schema: type: object properties: results: { type: array, items: { $ref: '#/components/schemas/SearchResult' } } query: { type: string } mode: { type: string, enum: [semantic, keyword] } count: { type: integer, description: Present when count_only keyword search } '400': content: application/json: schema: { $ref: '#/components/schemas/Error' } /proposals: get: tags: [Proposals] summary: List proposals parameters: - name: status in: query schema: { type: string } - name: limit in: query schema: { type: integer } - name: offset in: query schema: { type: integer } - name: label in: query description: Match if proposal labels include this string (case-insensitive) schema: { type: string } - name: source in: query schema: { type: string } - name: path_prefix in: query schema: { type: string } - name: evaluation_status in: query description: Filter by evaluation_status (none, pending, passed, failed, needs_changes) schema: { type: string } - name: review_queue in: query description: Exact match on proposal review_queue schema: { type: string } - name: review_severity in: query description: standard or elevated schema: { type: string } responses: '200': content: application/json: schema: type: object properties: proposals: { type: array, items: { $ref: '#/components/schemas/Proposal' } } total: { type: integer } post: tags: [Proposals] summary: Create proposal requestBody: content: application/json: schema: type: object properties: path: { type: string } body: { type: string } frontmatter: { type: object } intent: { type: string } base_state_id: { type: string } external_ref: { type: string } labels: { type: array, items: { type: string } } source: { type: string } responses: '201': content: application/json: schema: type: object properties: proposal_id: { type: string } path: { type: string } status: { type: string, enum: [proposed] } '400': /proposals/{id}: get: tags: [Proposals] summary: Get one proposal parameters: - name: id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/ProposalDetail' } '404': /proposals/{id}/review-hints: post: tags: [Proposals] summary: Store async LLM review hints (canister; not a merge gate) parameters: - name: id in: path required: true schema: { type: string } requestBody: content: application/json: schema: type: object properties: review_hints: { type: string } review_hints_model: { type: string } responses: '200': content: application/json: schema: type: object properties: proposal_id: { type: string } ok: { type: boolean } /proposals/{id}/evaluation: post: tags: [Proposals] summary: Submit human evaluation (admin or evaluator) parameters: - name: id in: path required: true schema: { type: string } requestBody: content: application/json: schema: type: object required: [outcome] properties: outcome: type: string enum: [pass, fail, needs_changes] checklist: type: array items: type: object properties: id: { type: string } passed: { type: boolean } grade: { type: string } comment: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/ProposalDetail' } '400': '404': /proposals/{id}/approve: post: tags: [Proposals] summary: Apply proposal to vault parameters: - name: id in: path required: true schema: { type: string } requestBody: content: application/json: schema: type: object properties: base_state_id: { type: string } waiver_reason: type: string description: Admin override when evaluation is not passed (min length 3 after trim) external_ref: type: string description: Optional cross-system lineage id (e.g. Muse); server may resolve via MUSE_URL when omitted responses: '200': content: application/json: schema: type: object properties: proposal_id: { type: string } status: { type: string, enum: [approved] } external_ref: { type: string } '403': description: EVALUATION_REQUIRED — pass evaluation or provide waiver_reason '409': description: base_state_id mismatch (CONFLICT) /proposals/{id}/enrich: post: tags: [Proposals] summary: Optional LLM summary and suggested labels (KNOWTATION_HUB_PROPOSAL_ENRICH=1) parameters: - name: id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: { $ref: '#/components/schemas/ProposalDetail' } '400': description: >- ICP canister — suggested_labels_json or assistant_suggested_frontmatter_json is valid JSON but exceeds max length (4000 / 14000 characters) after validation. '404': /proposals/{id}/discard: post: tags: [Proposals] summary: Discard proposal parameters: - name: id in: path required: true schema: { type: string } responses: '200': content: application/json: schema: type: object properties: proposal_id: { type: string } status: { type: string, enum: [discarded] } /capture: post: tags: [Capture] summary: Ingest message into vault inbox (webhook-style) description: Same contract as capture-webhook. If CAPTURE_WEBHOOK_SECRET is set, require X-Webhook-Secret header. security: [] requestBody: content: application/json: schema: type: object required: [body] properties: body: { type: string } source_id: { type: string } source: { type: string } project: { type: string } tags: { type: array, items: { type: string } } responses: '200': content: application/json: schema: { type: object, properties: { ok: { type: boolean }, path: { type: string } } } '400': components: securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: JWT schemas: Error: type: object properties: error: { type: string } code: { type: string } NoteListItem: type: object properties: path: { type: string } title: { type: string, nullable: true } project: { type: string, nullable: true } tags: { type: array, items: { type: string } } date: { type: string, nullable: true } NoteFull: type: object properties: path: { type: string } frontmatter: { type: object } body: { type: string } SearchResult: type: object properties: path: { type: string } snippet: { type: string } score: { type: number } project: { type: string } tags: { type: array, items: { type: string } } NoteOutline: type: object required: [schema, path, headings, truncated] properties: schema: type: string enum: [knowtation.note_outline/v1] path: { type: string } title: { type: string, nullable: true } headings: type: array maxItems: 500 items: { $ref: '#/components/schemas/NoteOutlineHeading' } truncated: { type: boolean } NoteOutlineHeading: type: object required: [level, text, id] properties: level: { type: integer, minimum: 1, maximum: 6 } text: { type: string } id: { type: string } DocumentTree: type: object required: [schema, path, root, truncated] properties: schema: type: string enum: [knowtation.document_tree/v0] path: { type: string } title: { type: string, nullable: true } root: type: object required: [children] properties: children: type: array maxItems: 500 items: { $ref: '#/components/schemas/DocumentTreeNode' } truncated: { type: boolean } DocumentTreeNode: type: object required: [id, level, text, children] properties: id: { type: string } level: { type: integer, minimum: 1, maximum: 6 } text: { type: string } children: type: array items: { $ref: '#/components/schemas/DocumentTreeNode' } MetadataFacets: type: object required: [schema, path, facets, inferred, truncated] properties: schema: type: string enum: [knowtation.metadata_facets/v0] path: { type: string } facets: type: object required: [project, tags, date, updated, causal_chain_id, entity, episode_id] properties: project: { type: string, nullable: true } tags: type: array maxItems: 100 items: { type: string } date: { type: string, nullable: true } updated: { type: string, nullable: true } causal_chain_id: { type: string, nullable: true } entity: type: array maxItems: 100 items: { type: string } episode_id: { type: string, nullable: true } inferred: type: object required: [folder, source_type] properties: folder: { type: string, nullable: true } source_type: { nullable: true, enum: [null] } truncated: { type: boolean } SectionSource: type: object required: [schema, path, sections, truncated] properties: schema: type: string enum: [knowtation.section_source/v0] path: { type: string } title: { type: string, nullable: true } sections: type: array items: { $ref: '#/components/schemas/SectionSourceSection' } truncated: { type: boolean } SectionSourceSection: type: object required: - section_id - heading_id - level - heading_path - heading_text - child_section_ids - body_available - body_returned - snippet_returned properties: section_id: { type: string } heading_id: { type: string } level: { type: integer, minimum: 1, maximum: 6 } heading_path: { type: array, items: { type: string } } heading_text: { type: string } child_section_ids: { type: array, items: { type: string } } body_available: { type: boolean } body_returned: { type: boolean, enum: [false] } snippet_returned: { type: boolean, enum: [false] } Proposal: type: object properties: proposal_id: { type: string } path: { type: string } status: { type: string } intent: { type: string } base_state_id: { type: string } external_ref: { type: string } vault_id: { type: string } proposed_by: { type: string } labels: { type: array, items: { type: string } } source: { type: string } suggested_labels: { type: array, items: { type: string } } assistant_notes: { type: string } assistant_model: { type: string } assistant_at: { type: string } created_at: { type: string } updated_at: { type: string } evaluation_status: type: string enum: [none, pending, passed, failed, needs_changes] evaluation_grade: { type: string } evaluation_comment: { type: string } evaluated_by: { type: string } evaluated_at: { type: string } evaluation_waiver: type: object nullable: true properties: by: { type: string } at: { type: string } reason: { type: string } review_queue: { type: string } review_severity: { type: string, enum: [standard, elevated] } auto_flag_reasons: type: array items: { type: string } auto_flag_reasons_json: { type: string, description: JSON array string on canister } review_hints: { type: string } review_hints_at: { type: string } review_hints_model: { type: string } assistant_suggested_frontmatter: type: object description: Normalized SPEC-aligned suggested note metadata from Enrich (object on GET); omitted or empty on older proposals additionalProperties: true ProposalDetail: allOf: - { $ref: '#/components/schemas/Proposal' } - type: object properties: body: { type: string } frontmatter: { type: object } evaluation_checklist: type: array items: type: object properties: id: { type: string } label: { type: string } passed: { type: boolean } CalendarTimeline: type: object required: [schema, vault_id, from, to, layers, items] properties: schema: type: string enum: [knowtation.calendar_timeline/v0] vault_id: { type: string } from: { type: string } to: { type: string } layers: type: array items: type: string enum: [notes, events] items: type: array items: oneOf: - { $ref: '#/components/schemas/CalendarTimelineNoteItem' } - { $ref: '#/components/schemas/CalendarTimelineEventItem' } CalendarTimelineNoteItem: type: object required: [kind, date, path, title, project, tags, sort_at] properties: kind: type: string enum: [note] date: { type: string } path: { type: string } title: { type: string, nullable: true } project: { type: string, nullable: true } tags: type: array items: { type: string } sort_at: { type: string } CalendarTimelineEventItem: type: object required: [kind, event_id, source_calendar_id, start, end, timezone, summary, busy, status, calendar_label, sort_at] properties: kind: type: string enum: [event] event_id: { type: string } source_calendar_id: { type: string } start: { type: string } end: { type: string } timezone: { type: string } summary: { type: string, nullable: true } busy: { type: boolean } status: type: string enum: [confirmed, cancelled, tentative] calendar_label: { type: string, nullable: true } sort_at: { type: string } CalendarAgentContext: type: object required: [schema, vault_id, from, to, requested_tier, effective_tier, policy_agent_context_tier_max_cap, source_calendars, items] properties: schema: type: string enum: [knowtation.calendar_agent_context/v0] vault_id: { type: string } from: { type: string } to: { type: string } requested_tier: type: integer minimum: 0 maximum: 2 effective_tier: type: integer minimum: 0 maximum: 2 description: Requested tier after the org policy cap is applied. policy_agent_context_tier_max_cap: type: integer minimum: 0 maximum: 4 source_calendars: type: array items: { $ref: '#/components/schemas/AgentContextCalendarSummary' } items: type: array items: { $ref: '#/components/schemas/CalendarAgentContextEventItem' } AgentContextCalendarSummary: type: object required: [source_calendar_id, display_name, user_group, enabled_for_agents, agent_context_tier_max, effective_tier, event_count] properties: source_calendar_id: { type: string } display_name: { type: string } user_group: type: string nullable: true enum: [personal, work, school, other, null] enabled_for_agents: { type: boolean } agent_context_tier_max: type: integer minimum: 0 maximum: 4 effective_tier: type: integer minimum: 0 maximum: 2 event_count: { type: integer } CalendarAgentContextEventItem: type: object required: [event_id, source_calendar_id, external_uid, start, end, timezone, busy, status, agent_tier] description: > Redacted event. `summary` and `calendar_label` are present only at tier 2; tier 1 omits the event title entirely. properties: event_id: { type: string } source_calendar_id: { type: string } external_uid: { type: string } start: { type: string } end: { type: string } timezone: { type: string } busy: { type: boolean } status: type: string enum: [confirmed, cancelled, tentative] agent_tier: type: integer minimum: 1 maximum: 2 summary: { type: string, nullable: true } calendar_label: { type: string, nullable: true } SourceCalendarList: type: object required: [schema, vault_id, source_calendars] properties: schema: type: string enum: [knowtation.source_calendars/v0] vault_id: { type: string } source_calendars: type: array items: { $ref: '#/components/schemas/SourceCalendar' } SourceCalendar: type: object required: [source_calendar_id, connector_id, display_name, enabled_for_sync, enabled_for_display, enabled_for_agents, agent_context_tier_max] properties: source_calendar_id: { type: string } connector_id: { type: string } display_name: { type: string } color: { type: string, nullable: true } user_group: type: string nullable: true enum: [personal, work, school, other, null] enabled_for_sync: { type: boolean } enabled_for_display: { type: boolean } enabled_for_agents: { type: boolean } agent_context_tier_max: type: integer minimum: 0 maximum: 4 provider: { type: string } CalendarIcsImportRequest: type: object required: [ics_text] properties: ics_text: { type: string } display_name: { type: string } source_calendar_id: { type: string } connector_id: { type: string } default_timezone: { type: string } CalendarIcsImportResult: type: object required: [schema, vault_id, source_calendar_id, connector_id, imported, updated] properties: schema: type: string enum: [knowtation.calendar_import/v0] vault_id: { type: string } source_calendar_id: { type: string } connector_id: { type: string } imported: { type: integer } updated: { type: integer } SourceCalendarPatchRequest: type: object minProperties: 1 properties: enabled_for_display: { type: boolean } enabled_for_agents: { type: boolean } agent_context_tier_max: type: integer minimum: 0 maximum: 4 user_group: type: string nullable: true enum: [personal, work, school, other, null] SourceCalendarPatchResult: type: object required: [schema, vault_id, policy_agent_context_tier_max_cap, source_calendar] properties: schema: type: string enum: [knowtation.source_calendar_patch/v0] vault_id: { type: string } policy_agent_context_tier_max_cap: type: integer minimum: 0 maximum: 4 source_calendar: { $ref: '#/components/schemas/SourceCalendar' } FlowListResponse: type: object required: [schema, vault_id, effective_scope, flows, truncated] properties: schema: type: string enum: [knowtation.flow_list/v0] vault_id: { type: string } effective_scope: type: string enum: [personal, project, org] flows: type: array maxItems: 200 items: { $ref: '#/components/schemas/FlowSummary' } truncated: { type: boolean } TaskListResponse: type: object required: [schema, vault_id, effective_scope, tasks, truncated] properties: schema: type: string enum: [knowtation.task_list/v0] vault_id: { type: string } effective_scope: type: string enum: [personal, project, org] tasks: type: array maxItems: 500 items: { $ref: '#/components/schemas/TaskSummary' } truncated: { type: boolean } LearningPathListResponse: type: object required: [schema, vault_id, effective_scope, paths, truncated] properties: schema: type: string enum: [knowtation.learning_path_list/v0] vault_id: { type: string } effective_scope: type: string enum: [personal, project, org] paths: type: array maxItems: 200 items: { $ref: '#/components/schemas/LearningPathSummary' } truncated: { type: boolean } LearningPathSummary: type: object required: [schema, path_id, scope, status, title, goal, current_step_index, step_count, next_step_title, active_decisions, workspace_id, updated] properties: schema: type: string enum: [knowtation.learning_path/v0] path_id: { type: string } scope: type: string enum: [personal, project, org] status: type: string enum: [active, paused, archived] title: { type: string } goal: { type: string } current_step_index: { type: integer } step_count: { type: integer } next_step_title: { type: string } active_decisions: { type: string } workspace_id: { type: string } updated: { type: string, format: date-time } LearningPathGetResponse: type: object required: [schema, vault_id, effective_scope, path] properties: schema: type: string enum: [knowtation.learning_path_get/v0] vault_id: { type: string } effective_scope: type: string enum: [personal, project, org] path: { $ref: '#/components/schemas/LearningPathRecord' } LearningPathRecord: type: object required: [schema, path_id, scope, status, title, summary, goal, steps, current_step_index, step_count, next_step_title, active_decisions, workspace_id, note_path, created, updated] properties: schema: type: string enum: [knowtation.learning_path/v0] path_id: { type: string } scope: type: string enum: [personal, project, org] status: type: string enum: [active, paused, archived] title: { type: string } summary: { type: string } goal: { type: string } steps: type: array minItems: 1 maxItems: 20 items: type: object required: [title, objective, source_document_ids] properties: title: { type: string } objective: { type: string } source_document_ids: type: array items: { type: string } current_step_index: { type: integer } step_count: { type: integer } next_step_title: { type: string } active_decisions: { type: string } workspace_id: { type: string } note_path: type: string nullable: true created: { type: string, format: date-time } updated: { type: string, format: date-time } LearningPathProposalRequest: type: object properties: proposal_kind: type: string enum: [path_create, path_update, path_archive] title: { type: string } summary: { type: string } goal: { type: string } steps: { type: array, items: { type: object } } path_id: { type: string } intent: { type: string } LearningPathProposalResponse: type: object required: [schema, proposal_id, proposal_kind, path_id, status, review_queue] properties: schema: type: string enum: [knowtation.learning_path_proposal/v0] proposal_id: { type: string } proposal_kind: type: string enum: [path_create, path_update, path_archive] path_id: { type: string } scope: type: string enum: [personal, project, org] auto_approvable: { type: boolean } status: { type: string } review_queue: type: string enum: [learning-path] TaskSummary: type: object required: [schema, task_id, kind, scope, status, title, workspace_id, due_at, run_ref, truncated] properties: schema: type: string enum: [knowtation.task/v0] task_id: { type: string } kind: type: string enum: [personal, assignment, mentor_checkin, org_work_job] scope: type: string enum: [personal, project, org] status: type: string enum: [pending, in_progress, blocked, done, cancelled] title: { type: string } workspace_id: { type: string } due_at: type: string nullable: true run_ref: type: string nullable: true truncated: { type: boolean } TaskGetResponse: type: object required: [schema, vault_id, effective_scope, task] properties: schema: type: string enum: [knowtation.task_get/v0] vault_id: { type: string } effective_scope: type: string enum: [personal, project, org] task: { $ref: '#/components/schemas/TaskRecord' } TaskRecord: type: object required: - schema - task_id - kind - scope - status - title - workspace_id - due_at - artifact_links - created - updated - truncated properties: schema: type: string enum: [knowtation.task/v0] task_id: { type: string } kind: type: string enum: [personal, assignment, mentor_checkin, org_work_job] scope: type: string enum: [personal, project, org] status: type: string enum: [pending, in_progress, blocked, done, cancelled] title: { type: string } workspace_id: { type: string } due_at: type: string nullable: true assignee_ref: type: string nullable: true assigner_ref: type: string nullable: true run_ref: type: string nullable: true artifact_links: type: array maxItems: 32 items: type: object required: [kind, ref] properties: kind: type: string enum: [note, media, review_item] ref: { type: string } created: { type: string } updated: { type: string } truncated: { type: boolean } AttachmentListResponse: type: object required: [schema, vault_id, effective_scope, attachments, truncated] properties: schema: type: string enum: [knowtation.attachment_list/v0] vault_id: { type: string } effective_scope: type: string enum: [personal, project, org] attachments: type: array maxItems: 500 items: { $ref: '#/components/schemas/AttachmentSummary' } truncated: { type: boolean } AttachmentSummary: type: object required: - schema - attachment_id - source - storage_kind - mime_class - scope - display_label - created - truncated properties: schema: type: string enum: [knowtation.attachment/v0] attachment_id: { type: string } source: type: string enum: [vault_file, mist_blob, embedded_url] storage_kind: type: string enum: [vault_blob, external_link] mime_class: type: string enum: [image, video, audio, document, unknown] scope: type: string enum: [personal, project, org] display_label: { type: string } created: { type: string } truncated: { type: boolean } AttachmentGetResponse: type: object required: [schema, vault_id, effective_scope, attachment] properties: schema: type: string enum: [knowtation.attachment_get/v0] vault_id: { type: string } effective_scope: type: string enum: [personal, project, org] attachment: { $ref: '#/components/schemas/AttachmentRecord' } MediaProposalResponse: type: object required: [schema, proposal_id, proposal_kind, attachment_id, scope, auto_approvable, status] properties: schema: type: string enum: [knowtation.media_proposal/v0] proposal_id: { type: string } proposal_kind: type: string enum: [media_external_link, media_attach] attachment_id: type: string pattern: '^att_(file|mist|url|link)_[a-f0-9]{32}$' note_ref: { type: [string, 'null'] } connector_id: { type: [string, 'null'] } scope: type: string enum: [personal, project, org] base_state_id: { type: string } external_ref: { type: [string, 'null'] } auto_approvable: type: boolean enum: [false] status: type: string enum: [proposed] review_queue: type: string enum: [media-writes] AttachmentRecord: type: object required: - schema - attachment_id - source - storage_kind - mime_class - mime_type - scope - display_label - byte_size - linked_note_refs - agent_visible - created - updated - truncated properties: schema: type: string enum: [knowtation.attachment/v0] attachment_id: { type: string } source: type: string enum: [vault_file, mist_blob, embedded_url] storage_kind: type: string enum: [vault_blob, external_link] mime_class: type: string enum: [image, video, audio, document, unknown] mime_type: type: string nullable: true scope: type: string enum: [personal, project, org] display_label: { type: string } byte_size: type: integer nullable: true linked_note_refs: type: array items: { type: string } agent_visible: { type: boolean } created: { type: string } updated: { type: string } truncated: { type: boolean } TaskWriteProposalRequest: type: object required: [intent] properties: proposal_kind: type: string enum: [task_create, task_status_update, task_assign, task_artifact_link] intent: { type: string, minLength: 1 } task: { $ref: '#/components/schemas/TaskRecord' } task_id: { type: string } base_state_id: { type: string } status: type: string enum: [pending, in_progress, blocked, done, cancelled] assignee_ref: { type: string, nullable: true } assigner_ref: { type: string, nullable: true } artifact_link: type: object required: [kind, ref] properties: kind: { type: string } ref: { type: string } TaskLoopWriteProposalRequest: type: object required: [intent] properties: proposal_kind: type: string enum: [task_loop_create, task_loop_pause, task_loop_cancel] intent: { type: string, minLength: 1 } loop: { type: object } loop_id: { type: string } base_state_id: { type: string } TaskInstanceMaterializeRequest: type: object required: [intent] properties: intent: { type: string, minLength: 1 } occurrence_key: { type: string } occurrence_at: { type: string } due_at: { type: string } title_override: { type: string } base_state_id: { type: string } TaskProposalResponse: type: object required: - schema - proposal_id - proposal_kind - auto_approvable - status - review_queue properties: schema: type: string enum: [knowtation.task_proposal/v0] proposal_id: { type: string } proposal_kind: { type: string } task_id: { type: string, nullable: true } loop_id: { type: string, nullable: true } base_state_id: { type: string, nullable: true } scope: type: string enum: [personal, project, org] auto_approvable: { type: boolean } status: { type: string } review_queue: { type: string } TaskInstanceProposalResponse: allOf: - $ref: '#/components/schemas/TaskProposalResponse' - type: object required: [occurrence_key] properties: schema: type: string enum: [knowtation.task_instance_proposal/v0] occurrence_key: { type: string } FlowSummary: type: object required: [schema, flow_id, title, version, scope, summary, tags, step_count, updated, truncated] properties: schema: type: string enum: [knowtation.flow/v0] flow_id: { type: string } title: { type: string } version: { type: string } scope: type: string enum: [personal, project, org] summary: { type: string } tags: type: array maxItems: 32 items: { type: string } step_count: { type: integer, minimum: 0 } updated: { type: string } truncated: { type: boolean } FlowGetResponse: type: object required: [schema, vault_id, flow, steps] properties: schema: type: string enum: [knowtation.flow_get/v0] vault_id: { type: string } flow: { $ref: '#/components/schemas/Flow' } steps: type: array maxItems: 100 items: { $ref: '#/components/schemas/FlowStep' } FlowRun: type: object required: [schema, run_id, run_ref, flow_id, flow_version, scope, status, step_states, started, provenance] description: knowtation.flow_run/v0 — canonical run state (pointer-only). properties: schema: type: string enum: [knowtation.flow_run/v0] run_id: { type: string } run_ref: type: string description: Portable cross-system pointer (flow_run:…); resolves get/list lookups. flow_id: { type: string } flow_version: { type: string } scope: type: string enum: [personal, project, org] status: type: string enum: [pending, in_progress, blocked, done, abandoned] step_states: type: array maxItems: 200 items: type: object required: [step_id, status, verified] properties: step_id: { type: string } status: type: string enum: [pending, in_progress, blocked, done, skipped] evidence_ref: { type: string, nullable: true } verified: { type: boolean } started: { type: string, format: date-time } provenance: type: object required: [actor, harness] properties: actor: { type: string, description: Hashed actor id only } harness: { type: string } task_ref: { type: string, nullable: true } external_ref: { type: string, nullable: true } FlowRunGetResponse: type: object required: [schema, vault_id, run] properties: schema: type: string enum: [knowtation.flow_run_get/v0] vault_id: { type: string } run: { $ref: '#/components/schemas/FlowRun' } FlowRunResponse: allOf: - $ref: '#/components/schemas/FlowRunGetResponse' FlowRunListResponse: type: object required: [schema, vault_id, effective_scope, runs, truncated] properties: schema: type: string enum: [knowtation.flow_run_list/v0] vault_id: { type: string } effective_scope: type: string enum: [personal, project, org] runs: type: array maxItems: 200 items: { $ref: '#/components/schemas/FlowRun' } truncated: { type: boolean } FlowRunStartResponse: type: object required: [schema, run] properties: schema: type: string enum: [knowtation.flow_run_start/v0] run: { $ref: '#/components/schemas/FlowRun' } FlowRunSubmitReviewResponse: type: object required: [schema, run, proposal_id] properties: schema: type: string enum: [knowtation.flow_run_submit_review/v0] run: { $ref: '#/components/schemas/FlowRun' } proposal_id: { type: string } FlowProposeRequest: type: object required: [flow, steps, intent] description: Propose a new Flow. intent is untrusted and recorded verbatim. properties: flow: { $ref: '#/components/schemas/Flow' } steps: type: array maxItems: 100 items: { $ref: '#/components/schemas/FlowStep' } intent: { type: string, minLength: 1 } FlowProposeEditRequest: type: object required: [flow, steps, intent, base_version, base_state_id] description: > Propose an edit. base_version + base_state_id (flowst1_ token) gate optimistic concurrency; flow.version must exceed base_version. properties: flow: { $ref: '#/components/schemas/Flow' } steps: type: array maxItems: 100 items: { $ref: '#/components/schemas/FlowStep' } intent: { type: string, minLength: 1 } base_version: { type: string } base_state_id: { type: string } FlowImportRequest: type: object required: [bundle, intent] description: Import a portable bundle through the same scope-checked propose path. properties: bundle: type: object required: [flow, steps] properties: flow: { $ref: '#/components/schemas/Flow' } steps: type: array maxItems: 100 items: { $ref: '#/components/schemas/FlowStep' } intent: { type: string, minLength: 1 } external_ref: { type: string } source_vault_hint: { type: string } FlowProposalResponse: type: object required: [schema, proposal_id, flow_id, scope, auto_approvable, status, review_queue] description: > knowtation.flow_proposal/v0 envelope — pointers/labels only, never a rendered Flow body or secret. base_version/base_state_id are null for new. properties: schema: type: string enum: [knowtation.flow_proposal/v0] proposal_id: { type: string } flow_id: { type: string } base_version: { type: string, nullable: true } base_state_id: { type: string, nullable: true } scope: type: string enum: [personal, project, org] auto_approvable: { type: boolean } status: type: string enum: [proposed] review_queue: { type: string } FlowExternalGrantMintRequest: type: object required: [flow_version, requested_tools] properties: flow_version: { type: string } requested_tools: type: array minItems: 1 items: { type: string } ttl_seconds: { type: integer, minimum: 1 } actor_label: { type: string } FlowExternalGrant: type: object required: - schema - grant_id - vault_id - scope - flow_id - flow_version - allowed_tools - allowed_harnesses - expires_at - issued_at - revoked_at - actor_hash - invocation_count properties: schema: type: string enum: [knowtation.flow_external_grant/v0] grant_id: { type: string } vault_id: { type: string } scope: type: string enum: [personal, project, org] flow_id: { type: string } flow_version: { type: string } allowed_tools: type: array items: { type: string } allowed_harnesses: type: array items: { type: string } expires_at: { type: string, format: date-time } issued_at: { type: string, format: date-time } revoked_at: { type: string, format: date-time, nullable: true } actor_hash: { type: string } max_invocations: { type: integer } invocation_count: { type: integer } FlowExternalGrantMintResponse: type: object required: [schema, grant, bearer, expires_at] properties: schema: type: string enum: [knowtation.flow_external_grant_mint/v0] grant: { $ref: '#/components/schemas/FlowExternalGrant' } bearer: { type: string } expires_at: { type: string, format: date-time } FlowExternalGrantListResponse: type: object required: [schema, vault_id, grants] properties: schema: type: string enum: [knowtation.flow_external_grant_list/v0] vault_id: { type: string } grants: type: array items: { $ref: '#/components/schemas/FlowExternalGrant' } AgentIdentityRegisterRequest: type: object required: [kind] properties: kind: type: string enum: [user_owned, org_owned, delegate] agent_id: { type: string } label: { type: string } scope_ceiling: type: string enum: [personal, project, org] DelegationProposalResponse: type: object required: [schema, proposal_id, intent] properties: schema: type: string enum: [knowtation.delegation_proposal/v0] proposal_id: { type: string } intent: { type: string } agent_id: { type: string } consent_id: { type: string } AgentIdentity: type: object required: - schema - agent_id - kind - owner_ref - vault_id - scope_ceiling - status - created - updated properties: schema: type: string enum: [knowtation.agent_identity/v0] agent_id: { type: string } kind: type: string enum: [user_owned, org_owned, delegate] owner_ref: { type: string } vault_id: { type: string } scope_ceiling: type: string enum: [personal, project, org] label: { type: string } status: type: string enum: [active, suspended, revoked] created: { type: string, format: date-time } updated: { type: string, format: date-time } AgentIdentityListResponse: type: object required: [schema, vault_id, identities] properties: schema: type: string enum: [knowtation.agent_identity_list/v0] vault_id: { type: string } identities: type: array items: { $ref: '#/components/schemas/AgentIdentity' } DelegationConsentProposeRequest: type: object required: [delegate_agent_id, scope] properties: delegate_agent_id: { type: string } scope: type: string enum: [personal, project, org] workspace_id: { type: string } allowed_flow_ids: type: array items: { type: string } allowed_task_kinds: type: array items: { type: string } allowed_task_ids: type: array items: { type: string } expires_at: { type: string, format: date-time } DelegationConsentProposeResponse: type: object required: [schema, proposal_id, intent, consent_id] properties: schema: type: string enum: [knowtation.delegation_proposal/v0] proposal_id: { type: string } intent: { type: string } consent_id: { type: string } consent_preview: { $ref: '#/components/schemas/DelegationConsent' } DelegationConsent: type: object required: - schema - consent_id - principal_ref - delegate_agent_id - scope - revoked_at - evidence_ref - created properties: schema: type: string enum: [knowtation.delegation_consent/v0] consent_id: { type: string } principal_ref: { type: string } delegate_agent_id: { type: string } scope: type: string enum: [personal, project, org] workspace_id: { type: string } allowed_flow_ids: type: array items: { type: string } allowed_task_kinds: type: array items: { type: string } allowed_task_ids: type: array items: { type: string } expires_at: { type: string, format: date-time } revoked_at: { type: string, format: date-time, nullable: true } evidence_ref: { type: string } created: { type: string, format: date-time } DelegationGrantMintRequest: type: object required: [consent_id, actor_agent_id] properties: consent_id: { type: string } actor_agent_id: { type: string } task_ref: { type: string } run_ref: { type: string } flow_id: { type: string } flow_version: { type: string } ttl_seconds: { type: integer, minimum: 1 } DelegationGrant: type: object required: - schema - grant_id - consent_id - actor_agent_id - principal_ref - scope - expires_at - revoked_at - action_count - issued_at properties: schema: type: string enum: [knowtation.delegation_grant/v0] grant_id: { type: string } consent_id: { type: string } actor_agent_id: { type: string } principal_ref: { type: string } scope: type: string enum: [personal, project, org] workspace_id: { type: string } task_ref: { type: string } run_ref: { type: string } flow_id: { type: string } flow_version: { type: string } expires_at: { type: string, format: date-time } revoked_at: { type: string, format: date-time, nullable: true } max_actions: { type: integer } action_count: { type: integer } issued_at: { type: string, format: date-time } DelegationGrantMintResponse: type: object required: [schema, grant, bearer, expires_at] properties: schema: type: string enum: [knowtation.delegation_grant_mint/v0] grant: { $ref: '#/components/schemas/DelegationGrant' } bearer: { type: string } expires_at: { type: string, format: date-time } DelegationGrantListResponse: type: object required: [schema, vault_id, grants] properties: schema: type: string enum: [knowtation.delegation_grant_list/v0] vault_id: { type: string } grants: type: array items: { $ref: '#/components/schemas/DelegationGrant' } DelegationAuditAppendRequest: type: object required: [grant_id, actor_agent_id, action, evidence_refs] properties: grant_id: { type: string } actor_agent_id: { type: string } principal_ref: { type: string } action: type: string enum: [advance_step, complete_task, propose_outcome, invoke_tool, mint_subgrant] evidence_refs: type: array minItems: 1 items: { type: string } task_ref: { type: string } run_ref: { type: string } flow_id: { type: string } flow_version: { type: string } step_id: { type: string } execution_location: type: string enum: [local, hosted, hybrid] DelegationAudit: type: object required: - schema - audit_id - grant_id - actor_agent_id - principal_ref - action - evidence_refs - occurred_at properties: schema: type: string enum: [knowtation.delegation_audit/v0] audit_id: { type: string } grant_id: { type: string } actor_agent_id: { type: string } principal_ref: { type: string } task_ref: { type: string } run_ref: { type: string } flow_id: { type: string } flow_version: { type: string } step_id: { type: string } action: type: string enum: [advance_step, complete_task, propose_outcome, invoke_tool, mint_subgrant] evidence_refs: type: array items: { type: string } occurred_at: { type: string, format: date-time } execution_location: type: string enum: [local, hosted, hybrid] FlowProjectResponse: type: object required: [schema, vault_id, projection, staleness, generator] properties: schema: type: string enum: [knowtation.flow_project/v0] vault_id: { type: string } projection: { $ref: '#/components/schemas/FlowProjection' } staleness: { $ref: '#/components/schemas/FlowProjectionStaleness' } generator: { $ref: '#/components/schemas/FlowProjectionGenerator' } FlowProjection: type: object required: [schema, flow_id, flow_version, harness, rendered, generated_from_canonical, editable] properties: schema: type: string enum: [knowtation.flow_projection/v0] flow_id: { type: string } flow_version: { type: string } harness: type: string enum: [cursor_rule, cursor_skill, mcp_prompt, cli_runbook, agent_bundle] rendered: type: string maxLength: 65536 generated_from_canonical: type: boolean enum: [true] editable: type: boolean enum: [false] fidelity: type: object required: [dropped_fields] properties: dropped_fields: type: array items: { type: string } notes: { type: string } FlowProjectionStaleness: type: object required: [stale, projection_version, latest_version] properties: stale: { type: boolean } projection_version: { type: string } latest_version: { type: string } FlowProjectionGenerator: type: object required: [generator_version, content_hash, generated_at] properties: generator_version: { type: string } content_hash: { type: string } generated_at: { type: string } Flow: type: object required: [schema, flow_id, title, version, scope, summary, steps, updated, truncated] properties: schema: type: string enum: [knowtation.flow/v0] flow_id: { type: string } title: { type: string } version: { type: string } scope: type: string enum: [personal, project, org] summary: { type: string } tags: type: array maxItems: 32 items: { type: string } steps: type: array maxItems: 100 items: { type: string } inputs: type: array items: type: object required: [name, type, required] properties: name: { type: string } type: { type: string } required: { type: boolean } vault_mirror_path: { type: string, nullable: true } updated: { type: string } truncated: { type: boolean } FlowStep: type: object required: [schema, step_id, flow_id, ordinal, owned_job, instruction, trigger, when_not_to_run, boundaries, output_shape, verification, automatable] properties: schema: type: string enum: [knowtation.flow_step/v0] step_id: { type: string } flow_id: { type: string } ordinal: { type: integer, minimum: 1 } owned_job: { type: string } instruction: { type: string } trigger: { type: string } when_not_to_run: { type: string } requires: type: array items: type: object required: [kind, id] properties: kind: type: string enum: [vault_scope, tool, file, artifact] id: { type: string } boundaries: type: array items: { type: string } skill_refs: type: array items: type: object required: [kind, id] properties: kind: type: string enum: [mcp_prompt, skill_pack, cli, external_tool] id: { type: string } inputs: type: array items: type: object required: [name, from] properties: name: { type: string } from: { type: string } outputs: type: array items: type: object required: [name, type] properties: name: { type: string } type: { type: string } output_shape: { type: string } verification: type: object required: [kind, evidence_required, description] properties: kind: type: string enum: [human_review, artifact_exists, value_match, test_pass, agent_check] evidence_required: { type: boolean } description: { type: string } automatable: type: string enum: [manual, agent_assisted, automatable]