openapi: 3.1.0
info:
  title: Marrow Hosted API
  version: 0.1.5
  summary: Public Marrow API for account setup, API keys, ingest, cited query, and native memory.
servers:
  - url: https://api.marrow.id
    description: Hosted API
security:
  - marrowApiKey: []
  - marrowCliSession: []
tags:
  - name: Auth
  - name: Account
  - name: API Keys
  - name: Ingest
  - name: Query
  - name: Memory
  - name: MCP
  - name: Usage
paths:
  /v1/auth/login:
    post:
      tags: [Auth]
      summary: Sign in and open a browser account session.
      security: []
      parameters:
        - $ref: "#/components/parameters/ConsoleCsrfRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginRequest"
      responses:
        "200":
          description: Authenticated. A browser account session cookie is set.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/auth/session:
    get:
      tags: [Auth]
      summary: Inspect the active browser account session cookie.
      security: []
      responses:
        "200":
          description: Account session state for the current browser cookie. A deletion-pending session has recovery authority only for deletion retry and sign-out.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthSessionStatus"
        default:
          $ref: "#/components/responses/Error"
    delete:
      tags: [Auth]
      summary: Revoke the active browser session and clear the account session cookie.
      security: []
      parameters:
        - $ref: "#/components/parameters/ConsoleCsrfRequired"
      responses:
        "200":
          description: Browser session revoked when present. The account session cookie is cleared.
          content:
            application/json:
              schema:
                type: object
                required: [schemaVersion, ok]
                properties:
                  schemaVersion:
                    const: marrow-auth-session-v2
                  ok:
                    type: boolean
        default:
          $ref: "#/components/responses/Error"
  /v1/auth/cli/device:
    post:
      tags: [Auth]
      summary: Start CLI sign-in with a browser approval code.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CliDeviceAuthorizationRequest"
      responses:
        "201":
          description: CLI sign-in code created. The device code is used only by the CLI polling flow.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CliDeviceAuthorizationResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/auth/cli/device/approve:
    post:
      tags: [Auth]
      summary: Approve a pending CLI sign-in code from the browser account session.
      security:
        - marrowSession: []
      parameters:
        - $ref: "#/components/parameters/ConsoleCsrfRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CliDeviceApprovalRequest"
      responses:
        "200":
          description: CLI sign-in code approved.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CliDeviceApprovalResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/auth/cli/token:
    post:
      tags: [Auth]
      summary: Poll for a CLI session after browser approval.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CliDeviceTokenRequest"
      responses:
        "200":
          description: CLI session created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CliSessionResponse"
        "202":
          description: CLI sign-in approval is still pending.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CliDeviceTokenPendingResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/auth/cli/session:
    get:
      tags: [Auth]
      summary: Read CLI sign-in state.
      security:
        - marrowCliSession: []
      responses:
        "200":
          description: Current CLI sign-in state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CliAuthStatus"
        default:
          $ref: "#/components/responses/Error"
    delete:
      tags: [Auth]
      summary: Sign out of the CLI on this device.
      security:
        - marrowCliSession: []
      responses:
        "200":
          description: CLI session cleared. API keys are not revoked.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CliLogoutResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/account/api-keys:
    get:
      tags: [API Keys]
      summary: List API keys for the authenticated account.
      security:
        - marrowCliSession: []
        - marrowSession: []
      responses:
        "200":
          description: API key metadata. Raw keys are not returned by list responses.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyListResponse"
        default:
          $ref: "#/components/responses/Error"
    post:
      tags: [API Keys]
      summary: Create another API key for the authenticated account.
      security:
        - marrowCliSession: []
        - marrowSession: []
      parameters:
        - $ref: "#/components/parameters/ConsoleCsrfWhenBrowserSession"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateApiKeyRequest"
      responses:
        "201":
          description: API key created. The raw key is returned once.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/account/overview:
    get:
      tags: [Account]
      summary: Read account overview with a first-party account session.
      description: Requires a browser Console session or first-party CLI session. Customer API keys do not authorize account-management routes.
      security:
        - marrowCliSession: []
        - marrowSession: []
      parameters:
        - name: sourceLimit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 6
        - name: ingestJobLimit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 6
      responses:
        "200":
          description: Account profile, credit balance, record activity, and recent record processing jobs.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountOverview"
        default:
          $ref: "#/components/responses/Error"
  /v1/account/usage:
    get:
      tags: [Account, Usage]
      summary: Read account usage and credit counters with a first-party account session.
      description: Requires a browser Console session or first-party CLI session. Customer API keys do not authorize account-management routes.
      security:
        - marrowCliSession: []
        - marrowSession: []
      parameters:
        - name: ledgerLimit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 8
      responses:
        "200":
          description: Account credit balance, quota counters, recent ledger rows, and daily usage buckets.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountUsage"
        default:
          $ref: "#/components/responses/Error"
  /v1/account/answers/query:
    post:
      tags: [Account, Query]
      summary: Ask a question from the account page.
      description: Requires a browser account session. Customer API keys must use /v1/query instead and do not authorize this account-management route.
      security:
        - marrowSession: []
      parameters:
        - $ref: "#/components/parameters/ConsoleCsrfRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QueryRequest"
      responses:
        "200":
          description: Evidence-backed query result for the signed-in account.
          headers:
            x-marrow-credit-cost:
              schema:
                type: integer
            x-marrow-credit-remaining:
              schema:
                type: integer
            x-marrow-credit-monthly-remaining:
              schema:
                type: integer
            x-marrow-credit-onboarding-remaining:
              schema:
                type: integer
            x-marrow-credit-monthly-period:
              schema:
                type: string
                pattern: "^\\d{4}-\\d{2}-\\d{2}$"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QueryResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/account/api-keys/{apiKeyId}:
    delete:
      tags: [API Keys]
      summary: Revoke an API key for the authenticated account.
      security:
        - marrowCliSession: []
        - marrowSession: []
      parameters:
        - $ref: "#/components/parameters/ConsoleCsrfWhenBrowserSession"
        - $ref: "#/components/parameters/ApiKeyId"
      responses:
        "200":
          description: Revoked API key metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/account/api-keys/{apiKeyId}/rotate:
    post:
      tags: [API Keys]
      summary: Create a replacement API key and revoke the previous key.
      security:
        - marrowCliSession: []
        - marrowSession: []
      parameters:
        - $ref: "#/components/parameters/ConsoleCsrfWhenBrowserSession"
        - $ref: "#/components/parameters/ApiKeyId"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RotateApiKeyRequest"
      responses:
        "201":
          description: Replacement API key. The raw key is returned once.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyRotationResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/account:
    delete:
      tags: [Account]
      summary: Permanently delete the signed-in browser account.
      description: The target account is derived only from the active browser session. CLI sessions, customer API keys, and caller-supplied account IDs cannot authorize or select it.
      security:
        - marrowSession: []
      parameters:
        - $ref: "#/components/parameters/ConsoleCsrfRequired"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AccountDeletionRequest"
      responses:
        "200":
          description: Account data was committed as deleted and the browser session cookie was cleared.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountDeletionResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/ingest/url:
    post:
      tags: [Ingest]
      summary: Preview or queue ingest for a public URL.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UrlIngestRequest"
      responses:
        "200":
          description: URL source preview plan, or an existing queued job for a reused idempotency key.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/IngestPlanResponse"
                  - $ref: "#/components/schemas/IngestJobResponse"
        "202":
          description: Live URL source submission accepted.
          headers:
            x-marrow-quota-metric:
              schema:
                type: string
            x-marrow-quota-daily-limit:
              schema:
                type: integer
            x-marrow-quota-daily-remaining:
              schema:
                type: integer
            x-marrow-quota-monthly-limit:
              schema:
                type: integer
            x-marrow-quota-monthly-remaining:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestJobResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/ingest/file:
    post:
      tags: [Ingest]
      summary: Preview or queue ingest for an uploaded file.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FileIngestRequest"
      responses:
        "200":
          description: File source preview plan, or an existing queued job for a reused idempotency key.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/IngestPlanResponse"
                  - $ref: "#/components/schemas/IngestJobResponse"
        "202":
          description: Live file source submission accepted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestJobResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/ingest/jobs:
    get:
      tags: [Ingest]
      summary: List recent record processing jobs.
      parameters:
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: Recent record processing jobs.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestJobListResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/ingest/jobs/{jobId}:
    get:
      tags: [Ingest]
      summary: Read one record processing job.
      parameters:
        - $ref: "#/components/parameters/JobId"
      responses:
        "200":
          description: Record processing status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestJobResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/query:
    post:
      tags: [Query]
      summary: Retrieve context.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QueryRequest"
      responses:
        "200":
          description: Evidence-backed query result.
          headers:
            x-marrow-credit-cost:
              schema:
                type: integer
            x-marrow-credit-remaining:
              schema:
                type: integer
            x-marrow-credit-monthly-remaining:
              schema:
                type: integer
            x-marrow-credit-onboarding-remaining:
              schema:
                type: integer
            x-marrow-credit-monthly-period:
              schema:
                type: string
                pattern: "^\\d{4}-\\d{2}-\\d{2}$"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QueryResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces:
    post:
      tags: [Memory]
      summary: Get-or-create a workspace label.
      security:
        - marrowApiKey: []
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Workspace"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/list:
    post:
      tags: [Memory]
      summary: List workspaces.
      security:
        - marrowApiKey: []
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PageWorkspace"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/peers:
    post:
      tags: [Memory]
      summary: Get-or-create a peer (a memory subject).
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LabelUpsertRequest"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Peer"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/peers/list:
    post:
      tags: [Memory]
      summary: List peers.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PagePeer"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/peers/{peer}:
    get:
      tags: [Memory]
      summary: Fetch a peer.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemoryPeer"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Peer"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/peers/{peer}/ask:
    post:
      tags: [Memory]
      summary: Ask a peer's memory for span-cited evidence or insufficient_evidence.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemoryPeer"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AskRequest"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MemoryContext"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/peers/{peer}/context:
    get:
      tags: [Memory]
      summary: Fetch a peer's assembled, fail-closed context.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemoryPeer"
        - $ref: "#/components/parameters/MemorySessionQuery"
        - $ref: "#/components/parameters/MemoryQueryText"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MemoryContext"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/peers/{peer}/representation:
    post:
      tags: [Memory]
      summary: Fetch a peer's full current knowledge snapshot.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemoryPeer"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RepresentationResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/sessions:
    post:
      tags: [Memory]
      summary: Get-or-create a session.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LabelUpsertRequest"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Session"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/sessions/list:
    post:
      tags: [Memory]
      summary: List sessions.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PageSession"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/sessions/{s}:
    get:
      tags: [Memory]
      summary: Fetch a session.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemorySession"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Session"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/sessions/{s}/messages:
    post:
      tags: [Memory]
      summary: Add peer-attributed messages; the engine derives a claim asynchronously.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemorySession"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessagesCreateRequest"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessagesCreateReceipt"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/sessions/{s}/messages/list:
    post:
      tags: [Memory]
      summary: List a session's messages.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemorySession"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PageMessage"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/sessions/{s}/context:
    get:
      tags: [Memory]
      summary: Fetch a peer's assembled context within a session.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemorySession"
        - $ref: "#/components/parameters/MemoryPeerQueryRequired"
        - $ref: "#/components/parameters/MemoryQueryText"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MemoryContext"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/claims/query:
    post:
      tags: [Memory]
      summary: Ranked search over a peer's claims.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClaimsQueryRequest"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PageClaim"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/claims/list:
    post:
      tags: [Memory]
      summary: List a peer's current claims.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClaimsListRequest"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PageClaim"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/claims/{id}:
    get:
      tags: [Memory]
      summary: Fetch one claim by its stable id.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemoryId"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Claim"
        default:
          $ref: "#/components/responses/Error"
    put:
      tags: [Memory]
      summary: Correct a claim under its stable id.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemoryId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClaimUpdateRequest"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClaimUpdateResult"
        default:
          $ref: "#/components/responses/Error"
    delete:
      tags: [Memory]
      summary: Withdraw a claim by its stable id.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemoryId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClaimDeleteRequest"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClaimDeleteReceipt"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/claims/{id}/history:
    get:
      tags: [Memory]
      summary: Read a claim's supersede-and-retain change history.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemoryId"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClaimHistoryResponse"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/events/{id}:
    get:
      tags: [Memory]
      summary: Poll the status of an asynchronous memory write.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
        - $ref: "#/components/parameters/MemoryId"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventStatus"
        default:
          $ref: "#/components/responses/Error"
  /v1/workspaces/{ws}/queue/status:
    get:
      tags: [Memory]
      summary: Read the async reasoning backlog.
      security:
        - marrowApiKey: []
      parameters:
        - $ref: "#/components/parameters/MemoryWorkspace"
      responses:
        "200":
          description: OK.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QueueStatus"
        default:
          $ref: "#/components/responses/Error"
  /.well-known/oauth-protected-resource:
    get:
      tags: [MCP]
      summary: Discover the hosted MCP OAuth protected resource.
      servers:
        - url: https://mcp.marrow.id
          description: Hosted MCP
      security: []
      responses:
        "200":
          description: RFC 9728 protected-resource metadata for https://mcp.marrow.id.
          content:
            application/json:
              schema:
                type: object
                required: [resource, authorization_servers, bearer_methods_supported]
        default:
          $ref: "#/components/responses/Error"
  /.well-known/oauth-authorization-server:
    get:
      tags: [MCP]
      summary: Discover the WorkOS authorization server used by hosted MCP.
      servers:
        - url: https://mcp.marrow.id
          description: Hosted MCP
      security: []
      responses:
        "200":
          description: Allowlisted authorization-server metadata from the configured WorkOS environment.
          content:
            application/json:
              schema:
                type: object
        default:
          $ref: "#/components/responses/Error"
  /:
    get:
      tags: [MCP]
      summary: Report that this stateless MCP endpoint accepts POST requests only.
      description: Authenticated GET requests return 405 because Marrow does not open an unsolicited SSE stream.
      servers:
        - url: https://mcp.marrow.id
          description: Hosted MCP
      security:
        - marrowApiKey: []
        - marrowMcpOAuth: []
      parameters:
        - $ref: "#/components/parameters/McpOrigin"
      responses:
        "405":
          description: Use POST for each stateless Streamable HTTP message.
          headers:
            Allow:
              schema:
                type: string
                const: POST
            MCP-Protocol-Version:
              $ref: "#/components/headers/McpProtocolVersion"
        default:
          $ref: "#/components/responses/Error"
    post:
      tags: [MCP]
      summary: Exchange one stateless MCP Streamable HTTP message.
      description: Initialize without MCP-Protocol-Version, then send the negotiated version on every subsequent request. Marrow prefers 2025-11-25 and accepts 2025-06-18 for compatible clients. A Marrow API key or WorkOS-issued MCP OAuth token authenticates each request independently.
      servers:
        - url: https://mcp.marrow.id
          description: Hosted MCP
      security:
        - marrowApiKey: []
        - marrowMcpOAuth: []
      parameters:
        - $ref: "#/components/parameters/McpAccept"
        - $ref: "#/components/parameters/McpProtocolVersion"
        - $ref: "#/components/parameters/McpOrigin"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/McpJsonRpcMessage"
      responses:
        "200":
          description: JSON-RPC response to initialize, ping, tools/list, or tools/call.
          headers:
            MCP-Protocol-Version:
              $ref: "#/components/headers/McpProtocolVersion"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/McpJsonRpcResponse"
        "202":
          description: Notification or client response accepted; no response body is returned.
          headers:
            MCP-Protocol-Version:
              $ref: "#/components/headers/McpProtocolVersion"
        "400":
          description: Invalid JSON-RPC envelope or MCP protocol version.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/McpJsonRpcResponse"
        "401":
          $ref: "#/components/responses/Error"
        "403":
          $ref: "#/components/responses/Error"
        "406":
          description: Accept does not include both application/json and text/event-stream.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/McpJsonRpcResponse"
        "415":
          description: Request body is not UTF-8 application/json.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/McpJsonRpcResponse"
        "429":
          $ref: "#/components/responses/Error"
components:
  securitySchemes:
    marrowApiKey:
      type: http
      scheme: bearer
      bearerFormat: Marrow API key
      description: Application credential for direct HTTP integrations. Scopes are explicit (`ingest`, `query`, `memory.read`, and `memory.write`); account-management routes do not accept it.
    marrowCliSession:
      type: http
      scheme: bearer
      bearerFormat: Marrow CLI session token
      description: First-party session from `marrow auth login`, accepted for account management plus classic query, URL/file ingest, and ingest-job status. Native memory routes do not accept it; it is not an application credential. The 1.0 CLI uses `MARROW_API_KEY` for product commands.
    marrowSession:
      type: apiKey
      in: cookie
      name: marrow_session
      description: Browser Console account session for browser account management and Console Answers. It is never a product API credential.
    marrowMcpOAuth:
      type: oauth2
      description: Interactive OAuth for the exact hosted MCP resource https://mcp.marrow.id. Current WorkOS authorized-application state determines effective scopes on every request.
      flows:
        authorizationCode:
          authorizationUrl: https://promising-bazaar-45.authkit.app/oauth2/authorize
          tokenUrl: https://promising-bazaar-45.authkit.app/oauth2/token
          scopes:
            memory.read: Read Marrow memory.
            memory.write: Create and correct Marrow memory.
  parameters:
    ApiKeyId:
      name: apiKeyId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    JobId:
      name: jobId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    Limit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    MemoryWorkspace:
      name: ws
      in: path
      required: true
      description: Workspace label. Marrow serves the account's `default` workspace.
      schema:
        type: string
        enum: [default]
    MemoryPeer:
      name: peer
      in: path
      required: true
      description: Peer label inside the authenticated account partition.
      schema:
        type: string
        minLength: 1
    MemoryPeerQueryRequired:
      name: peer
      in: query
      required: true
      description: Peer label required because session context is subject-partitioned.
      schema:
        type: string
        minLength: 1
    MemorySession:
      name: s
      in: path
      required: true
      description: Session label inside the authenticated account partition.
      schema:
        type: string
        minLength: 1
    MemorySessionQuery:
      name: session
      in: query
      required: false
      description: Optional session label used to narrow peer context.
      schema:
        type: string
        minLength: 1
    MemoryQueryText:
      name: q
      in: query
      required: false
      description: Optional query text for context ranking. Defaults to current context.
      schema:
        type: string
        minLength: 1
    MemoryId:
      name: id
      in: path
      required: true
      description: Public stable memory handle or event id.
      schema:
        type: string
        minLength: 1
    McpAccept:
      name: Accept
      in: header
      required: true
      description: Streamable HTTP clients advertise both response media types even though this stateless endpoint returns JSON or an empty 202 response.
      schema:
        type: string
        examples:
          - application/json, text/event-stream
    McpProtocolVersion:
      name: MCP-Protocol-Version
      in: header
      required: false
      description: Omit for initialize. Send the negotiated `2025-11-25` or compatible `2025-06-18` value on every subsequent request.
      schema:
        type: string
        enum: ["2025-11-25", "2025-06-18"]
    McpOrigin:
      name: Origin
      in: header
      required: false
      description: When present, must be a canonical Marrow origin. Localhost origins are accepted only by an explicitly local development server.
      schema:
        type: string
        format: uri
    ConsoleCsrfRequired:
      name: x-marrow-csrf
      in: header
      required: true
      description: Required first-party browser write marker. Send the literal value `1`; the request `Origin` must also be an allowed Marrow browser origin.
      schema:
        type: string
        const: "1"
    ConsoleCsrfWhenBrowserSession:
      name: x-marrow-csrf
      in: header
      required: false
      description: Required when authorizing this write with a browser `marrowSession`; send the literal value `1` with an allowed Marrow `Origin`. Omit this header when authorizing with `marrowCliSession`.
      schema:
        type: string
        const: "1"
  headers:
    McpProtocolVersion:
      description: Negotiated MCP protocol version.
      schema:
        type: string
        enum: ["2025-11-25", "2025-06-18"]
  responses:
    Error:
      description: Versioned API error.
      headers:
        x-marrow-credit-cost:
          schema:
            type: integer
        x-marrow-credit-remaining:
          schema:
            type: integer
        retry-after:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
  schemas:
    McpJsonRpcMessage:
      description: A single JSON-RPC 2.0 MCP request, notification, or client response. Batch arrays are rejected. initialize, ping, tools/list, and tools/call are the supported request methods; notifications/initialized is the supported notification.
      oneOf:
        - $ref: "#/components/schemas/McpInitializeRequest"
        - $ref: "#/components/schemas/McpPingRequest"
        - $ref: "#/components/schemas/McpToolsListRequest"
        - $ref: "#/components/schemas/McpToolsCallRequest"
        - $ref: "#/components/schemas/McpInitializedNotification"
        - $ref: "#/components/schemas/McpJsonRpcClientSuccess"
        - $ref: "#/components/schemas/McpJsonRpcClientError"
    McpInitializeRequest:
      type: object
      required: [jsonrpc, id, method, params]
      additionalProperties: false
      properties:
        jsonrpc:
          const: "2.0"
        id:
          oneOf:
            - type: string
            - type: integer
        method:
          const: initialize
        params:
          type: object
          required: [protocolVersion, capabilities, clientInfo]
          properties:
            protocolVersion:
              type: string
            capabilities:
              type: object
            clientInfo:
              type: object
              required: [name, version]
              properties:
                name:
                  type: string
                version:
                  type: string
    McpPingRequest:
      type: object
      required: [jsonrpc, id, method]
      additionalProperties: false
      properties:
        jsonrpc:
          const: "2.0"
        id:
          oneOf:
            - type: string
            - type: integer
        method:
          const: ping
        params:
          type: object
    McpToolsListRequest:
      type: object
      required: [jsonrpc, id, method]
      additionalProperties: false
      properties:
        jsonrpc:
          const: "2.0"
        id:
          oneOf:
            - type: string
            - type: integer
        method:
          const: tools/list
        params:
          type: object
    McpToolsCallRequest:
      type: object
      required: [jsonrpc, id, method, params]
      additionalProperties: false
      properties:
        jsonrpc:
          const: "2.0"
        id:
          oneOf:
            - type: string
            - type: integer
        method:
          const: tools/call
        params:
          type: object
          required: [name]
          properties:
            name:
              type: string
              minLength: 1
            arguments:
              type: object
    McpInitializedNotification:
      type: object
      required: [jsonrpc, method]
      additionalProperties: false
      properties:
        jsonrpc:
          const: "2.0"
        method:
          const: notifications/initialized
        params:
          type: object
    McpJsonRpcClientSuccess:
      type: object
      required: [jsonrpc, id, result]
      additionalProperties: false
      properties:
        jsonrpc:
          const: "2.0"
        id:
          oneOf:
            - type: string
            - type: integer
        result: {}
    McpJsonRpcClientError:
      type: object
      required: [jsonrpc, id, error]
      additionalProperties: false
      properties:
        jsonrpc:
          const: "2.0"
        id:
          oneOf:
            - type: string
            - type: integer
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: integer
            message:
              type: string
            data: {}
    McpJsonRpcResponse:
      description: One JSON-RPC 2.0 success or error response. Customer keys and internal Marrow records are never response fields.
      oneOf:
        - $ref: "#/components/schemas/McpJsonRpcSuccessResponse"
        - $ref: "#/components/schemas/McpJsonRpcErrorResponse"
    McpJsonRpcSuccessResponse:
      type: object
      required: [jsonrpc, id, result]
      additionalProperties: false
      properties:
        jsonrpc:
          const: "2.0"
        id:
          oneOf:
            - type: string
            - type: integer
        result: {}
    McpJsonRpcErrorResponse:
      type: object
      required: [jsonrpc, id, error]
      additionalProperties: false
      properties:
        jsonrpc:
          const: "2.0"
        id:
          oneOf:
            - type: string
            - type: integer
            - type: "null"
        error:
          type: object
          required: [code, message]
          additionalProperties: false
          properties:
            code:
              type: integer
            message:
              type: string
            data: {}
    LoginRequest:
      type: object
      required: [email, password]
      additionalProperties: false
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 15
          maxLength: 128
    AuthResponse:
      type: object
      required: [schemaVersion, account, session, auth]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-email-auth-v1
        account:
          $ref: "#/components/schemas/AuthAccount"
        session:
          type: object
          required: [issuedAt, expiresAt]
          additionalProperties: false
          properties:
            issuedAt:
              type: string
              format: date-time
            expiresAt:
              type: string
              format: date-time
        auth:
          type: object
          required: [provider, email, mode]
          additionalProperties: false
          properties:
            provider:
              const: email
            email:
              type: string
              format: email
            mode:
              const: login
    AuthSessionStatus:
      type: object
      required: [schemaVersion, signedIn, accountState, account, session, linkedProviders]
      additionalProperties: false
      oneOf:
        - properties:
            signedIn:
              const: false
            accountState:
              const: signed_out
            account:
              type: "null"
            session:
              type: "null"
            linkedProviders:
              type: "null"
        - properties:
            signedIn:
              const: true
            accountState:
              const: active
            account:
              $ref: "#/components/schemas/AuthAccount"
            session:
              type: object
            linkedProviders:
              type: object
        - properties:
            signedIn:
              const: true
            accountState:
              const: deletion_pending
            account:
              $ref: "#/components/schemas/AuthAccount"
            session:
              type: object
            linkedProviders:
              type: "null"
      properties:
        schemaVersion:
          const: marrow-auth-session-v2
        signedIn:
          type: boolean
        accountState:
          type: string
          enum: [signed_out, active, deletion_pending]
          description: Active sessions have normal account authority. Deletion-pending sessions retain only deletion-retry and sign-out authority.
        account:
          oneOf:
            - $ref: "#/components/schemas/AuthAccount"
            - type: "null"
        session:
          oneOf:
            - type: object
              required: [issuedAt, expiresAt]
              additionalProperties: false
              properties:
                issuedAt:
                  type: string
                  format: date-time
                expiresAt:
                  type: string
                  format: date-time
            - type: "null"
        linkedProviders:
          oneOf:
            - type: object
              required: [google, apple]
              additionalProperties: false
              properties:
                google:
                  type: boolean
                apple:
                  type: boolean
            - type: "null"
    AuthAccount:
      type: object
      required: [email, displayName]
      additionalProperties: false
      properties:
        email:
          type: string
          format: email
        displayName:
          type:
            - string
            - "null"
    AccountDeletionRequest:
      type: object
      required: [confirmation]
      additionalProperties: false
      properties:
        confirmation:
          type: string
          format: email
          description: Exact email address of the signed-in browser account.
    AccountDeletionResponse:
      type: object
      required: [schemaVersion, status]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-account-deletion-v1
        status:
          const: deleted
    CliDeviceAuthorizationRequest:
      type: object
      additionalProperties: false
      properties:
        clientName:
          type: string
          minLength: 1
          maxLength: 80
    CliDeviceAuthorizationResponse:
      type: object
      required: [schemaVersion, deviceCode, userCode, verificationUrl, expiresAt, intervalSeconds]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-cli-device-authorization-v1
        deviceCode:
          type: string
          readOnly: true
        userCode:
          type: string
          pattern: "^[A-Z0-9]{4}-[A-Z0-9]{4}$"
        verificationUrl:
          type: string
          format: uri
        expiresAt:
          type: string
          format: date-time
        intervalSeconds:
          type: integer
          minimum: 0
    CliDeviceApprovalRequest:
      type: object
      required: [userCode]
      additionalProperties: false
      properties:
        userCode:
          type: string
          minLength: 1
          maxLength: 32
    CliDeviceApprovalResponse:
      type: object
      required: [schemaVersion, approved, userCode, expiresAt]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-cli-device-approval-v1
        approved:
          const: true
        userCode:
          type: string
        expiresAt:
          type: string
          format: date-time
    CliDeviceTokenRequest:
      type: object
      required: [deviceCode]
      additionalProperties: false
      properties:
        deviceCode:
          type: string
          minLength: 1
          writeOnly: true
    CliDeviceTokenPendingResponse:
      type: object
      required: [schemaVersion, status, expiresAt, intervalSeconds]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-cli-device-token-v1
        status:
          const: pending
        expiresAt:
          type: string
          format: date-time
        intervalSeconds:
          type: integer
          minimum: 0
    CliSessionResponse:
      type: object
      required: [schemaVersion, cliSession, account]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-cli-session-v1
        cliSession:
          type: object
          required: [token, expiresAt]
          additionalProperties: false
          properties:
            token:
              type: string
              readOnly: true
            expiresAt:
              type: string
              format: date-time
        account:
          $ref: "#/components/schemas/CliAccount"
    CliAuthStatus:
      type: object
      required: [schemaVersion, signedIn, account, expiresAt]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-cli-auth-v1
        signedIn:
          type: boolean
        account:
          oneOf:
            - $ref: "#/components/schemas/CliAccount"
            - type: "null"
        expiresAt:
          type:
            - string
            - "null"
          format: date-time
    CliLogoutResponse:
      type: object
      required: [schemaVersion, signedOut, sessionCleared]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-cli-auth-v1
        signedOut:
          const: true
        sessionCleared:
          const: true
    CliAccount:
      type: object
      required: [email, displayName]
      additionalProperties: false
      properties:
        email:
          type: string
          format: email
        displayName:
          type:
            - string
            - "null"
    AccountProfile:
      type: object
      required: [email]
      additionalProperties: false
      properties:
        email:
          type: string
          format: email
        displayName:
          type: string
    AccountOverview:
      type: object
      required: [schemaVersion, generatedAt, account, recordCounts, credits, sources, ingestJobs]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-account-overview-v2
        generatedAt:
          type: string
          format: date-time
        account:
          $ref: "#/components/schemas/AccountProfile"
        memoryRecoveryBinding:
          type: string
          minLength: 43
          maxLength: 43
          pattern: "^[A-Za-z0-9_-]+$"
          description: Server-opaque browser recovery binding. Present only for Console browser sessions; it grants no API authority.
        recordCounts:
          $ref: "#/components/schemas/AccountRecordCounts"
        credits:
          $ref: "#/components/schemas/AccountCreditBalance"
        sources:
          type: array
          items:
            $ref: "#/components/schemas/AccountSourceSummary"
        ingestJobs:
          type: array
          items:
            $ref: "#/components/schemas/AccountIngestJobSummary"
        latestActivityAt:
          type:
            - string
            - "null"
          format: date-time
    AccountRecordCounts:
      type: object
      required: [total, sourceExcerpts, facts, links]
      additionalProperties: false
      properties:
        total:
          type: integer
        sourceExcerpts:
          type: integer
        facts:
          type: integer
        links:
          type: integer
    AccountCreditBalance:
      type: object
      required: [monthlyPeriodStart, monthlyCreditsGranted, monthlyCreditsRemaining, onboardingCreditsGranted, onboardingCreditsRemaining, totalCreditsGranted, totalCreditsRemaining, updatedAt]
      additionalProperties: false
      properties:
        monthlyPeriodStart:
          type: string
          pattern: "^\\d{4}-\\d{2}-\\d{2}$"
        monthlyCreditsGranted:
          type: integer
        monthlyCreditsRemaining:
          type: integer
        onboardingCreditsGranted:
          type: integer
        onboardingCreditsRemaining:
          type: integer
        totalCreditsGranted:
          type: integer
        totalCreditsRemaining:
          type: integer
        updatedAt:
          type: string
          format: date-time
    AccountSourceSummary:
      type: object
      required: [sourceKinds, lastIngestedAt, counts]
      additionalProperties: false
      properties:
        sourceKinds:
          type: array
          items:
            type: string
        sourceName:
          type: string
        sourceUrl:
          type: string
        lastIngestedAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
        counts:
          $ref: "#/components/schemas/AccountSourceCounts"
    AccountSourceCounts:
      type: object
      required: [sourceExcerpts, facts, links]
      additionalProperties: false
      properties:
        sourceExcerpts:
          type: integer
        facts:
          type: integer
        links:
          type: integer
    AccountIngestJobSummary:
      type: object
      required: [id, kind, status, attempts, maxAttempts, createdAt, updatedAt]
      additionalProperties: false
      properties:
        id:
          type: string
          format: uuid
        kind:
          type: string
          enum: [url, file]
        status:
          type: string
          enum: [queued, running, succeeded, failed, quarantined]
        sourceName:
          type: string
        sourceUrl:
          type: string
        attempts:
          type: integer
        maxAttempts:
          type: integer
        completedAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    AccountUsage:
      type: object
      required: [schemaVersion, generatedAt, credits, quotas, recentCreditLedger, dailyUsage]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-account-usage-v1
        generatedAt:
          type: string
          format: date-time
        credits:
          $ref: "#/components/schemas/AccountUsageCredits"
        quotas:
          type: object
          additionalProperties: false
          properties:
            query:
              $ref: "#/components/schemas/QuotaPair"
            ingest:
              $ref: "#/components/schemas/QuotaPair"
        recentCreditLedger:
          $ref: "#/components/schemas/CreditLedgerPage"
        dailyUsage:
          $ref: "#/components/schemas/DailyUsage"
    AccountUsageCredits:
      type: object
      required: [balance, monthly, onboarding]
      additionalProperties: false
      properties:
        balance:
          type: object
          additionalProperties: false
          properties:
            totalGranted:
              type: integer
            totalUsed:
              type: integer
            totalRemaining:
              type: integer
            updatedAt:
              type: string
              format: date-time
        monthly:
          type: object
          additionalProperties: false
          properties:
            granted:
              type: integer
            used:
              type: integer
            remaining:
              type: integer
            periodStart:
              type: string
              pattern: "^\\d{4}-\\d{2}-\\d{2}$"
            resetAt:
              type: string
              format: date-time
        onboarding:
          type: object
          additionalProperties: false
          properties:
            granted:
              type: integer
            used:
              type: integer
            remaining:
              type: integer
    QuotaPair:
      type: object
      required: [day, month]
      additionalProperties: false
      properties:
        day:
          $ref: "#/components/schemas/QuotaCounter"
        month:
          $ref: "#/components/schemas/QuotaCounter"
    QuotaCounter:
      type: object
      required: [period, periodStart, limit, used, remaining, resetAt]
      additionalProperties: false
      properties:
        period:
          type: string
          enum: [day, month]
        periodStart:
          type: string
          pattern: "^\\d{4}-\\d{2}-\\d{2}$"
        limit:
          type: integer
        used:
          type: integer
        remaining:
          type: integer
        resetAt:
          type: string
          format: date-time
    CreditLedgerPage:
      type: object
      required: [limit, returned, rows]
      additionalProperties: false
      properties:
        limit:
          type: integer
        returned:
          type: integer
        rows:
          type: array
          items:
            $ref: "#/components/schemas/CreditLedgerRow"
    CreditLedgerRow:
      type: object
      required: [eventType, source, credits, monthlyCreditsDelta, onboardingCreditsDelta, createdAt]
      additionalProperties: false
      properties:
        eventType:
          type: string
          enum: [debit, refund]
        source:
          type: string
          enum: [query, ingest]
        credits:
          type: integer
        monthlyCreditsDelta:
          type: integer
        onboardingCreditsDelta:
          type: integer
        createdAt:
          type: string
          format: date-time
    DailyUsage:
      type: object
      required: [days, buckets]
      additionalProperties: false
      properties:
        days:
          type: integer
        buckets:
          type: array
          items:
            $ref: "#/components/schemas/DailyUsageBucket"
    DailyUsageBucket:
      type: object
      required: [date, queryCreditsUsed, ingestCreditsUsed, queryCreditsRefunded, ingestCreditsRefunded]
      additionalProperties: false
      properties:
        date:
          type: string
          pattern: "^\\d{4}-\\d{2}-\\d{2}$"
        queryCreditsUsed:
          type: integer
        ingestCreditsUsed:
          type: integer
        queryCreditsRefunded:
          type: integer
        ingestCreditsRefunded:
          type: integer
    ApiKeyScope:
      type: string
      enum: [ingest, query, memory.read, memory.write]
    ApiKeyMetadata:
      type: object
      required: [id, name, scopes, keyPrefix, createdAt]
      additionalProperties: false
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        scopes:
          type: array
          items:
            $ref: "#/components/schemas/ApiKeyScope"
        keyPrefix:
          type: string
        createdAt:
          type: string
          format: date-time
        lastUsedAt:
          type:
            - string
            - "null"
          format: date-time
        expiresAt:
          type:
            - string
            - "null"
          pattern: "^\\d{4}-\\d{2}-\\d{2}$"
        revokedAt:
          type:
            - string
            - "null"
          format: date-time
    CreatedApiKey:
      allOf:
        - $ref: "#/components/schemas/ApiKeyMetadata"
        - type: object
          required: [key]
          additionalProperties: false
          properties:
            key:
              type: string
              writeOnly: true
    ApiKeyListResponse:
      type: object
      required: [schemaVersion, apiKeys]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-account-api-key-list-v1
        apiKeys:
          type: array
          items:
            $ref: "#/components/schemas/ApiKeyMetadata"
    CreateApiKeyRequest:
      type: object
      required: [name]
      additionalProperties: false
      properties:
        name:
          type: string
          minLength: 1
        scopes:
          type: array
          items:
            $ref: "#/components/schemas/ApiKeyScope"
        expiresAt:
          type: string
          pattern: "^\\d{4}-\\d{2}-\\d{2}$"
    RotateApiKeyRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
          minLength: 1
        scopes:
          type: array
          items:
            $ref: "#/components/schemas/ApiKeyScope"
        expiresAt:
          type: string
          pattern: "^\\d{4}-\\d{2}-\\d{2}$"
    ApiKeyResponse:
      type: object
      required: [schemaVersion, apiKey]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-account-api-key-v1
        apiKey:
          oneOf:
            - $ref: "#/components/schemas/ApiKeyMetadata"
            - $ref: "#/components/schemas/CreatedApiKey"
        warning:
          type: string
    ApiKeyRotationResponse:
      type: object
      required: [schemaVersion, apiKey, revokedApiKey]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-account-api-key-rotation-v1
        apiKey:
          $ref: "#/components/schemas/CreatedApiKey"
        revokedApiKey:
          $ref: "#/components/schemas/ApiKeyMetadata"
        warning:
          type: string
    UrlIngestRequest:
      type: object
      required: [url]
      additionalProperties: false
      properties:
        url:
          type: string
          format: uri
          description: Public http or https URL.
        dryRun:
          type: boolean
          default: true
        datedAt:
          type: string
          pattern: "^\\d{4}-\\d{2}-\\d{2}$"
          description: Required for live source submission.
        idempotencyKey:
          type: string
          minLength: 8
          maxLength: 160
          description: Required when dryRun is false. Reuse the same caller-owned key for an exact retry.
    FileIngestRequest:
      type: object
      required: [filename, contentBase64]
      additionalProperties: false
      properties:
        filename:
          type: string
          minLength: 1
        contentType:
          type: string
        contentBase64:
          type: string
          contentEncoding: base64
        dryRun:
          type: boolean
          default: true
        datedAt:
          type: string
          pattern: "^\\d{4}-\\d{2}-\\d{2}$"
          description: Required for live source submission.
        idempotencyKey:
          type: string
          minLength: 8
          maxLength: 160
          description: Required when dryRun is false. Reuse the same caller-owned key for an exact retry.
    IngestPlanResponse:
      type: object
      required: [schemaVersion, mode, plan]
      additionalProperties: false
      properties:
        schemaVersion:
          enum: [marrow-url-ingest-plan-v1, marrow-file-ingest-plan-v1]
        mode:
          const: dry-run
        plan:
          type: object
          required: [dryRun, status, source, records, review, warnings, nextAction]
          additionalProperties: false
          properties:
            dryRun:
              const: true
            status:
              const: preview_ready
            source:
              $ref: "#/components/schemas/IngestSource"
            records:
              $ref: "#/components/schemas/RecordSummary"
            sourcePreviews:
              type: array
              items:
                type: object
                required: [wordCount]
                additionalProperties: false
                properties:
                  title:
                    type: string
                  wordCount:
                    type: integer
                  textPreview:
                    type: string
            review:
              $ref: "#/components/schemas/IngestReview"
            warnings:
              type: array
              items:
                type: string
            nextAction:
              type: string
        warnings:
          type: array
          items:
            type: string
    IngestJob:
      type: object
      required: [id, kind, status, createdAt]
      additionalProperties: false
      properties:
        id:
          type: string
          format: uuid
        kind:
          enum: [url, file]
        status:
          enum: [queued, running, succeeded, failed, quarantined]
        request:
          type: object
          required: [source]
          additionalProperties: false
          properties:
            source:
              type: object
              additionalProperties: false
              properties:
                kind:
                  type: string
                url:
                  type: string
                filename:
                  type: string
                contentType:
                  type: string
                datedAt:
                  type: string
                bytes:
                  type: integer
        result:
          oneOf:
            - type: object
              required: [mode]
              additionalProperties: false
              properties:
                mode:
                  type: string
                  enum: [completed, review_required, unknown]
                records:
                  $ref: "#/components/schemas/RecordSummary"
                warnings:
                  type: array
                  items:
                    type: string
                source:
                  $ref: "#/components/schemas/IngestSource"
                review:
                  $ref: "#/components/schemas/IngestReview"
            - type: "null"
        failure:
          oneOf:
            - type: object
              required: [message, retryable, failedAt]
              additionalProperties: false
              properties:
                message:
                  type: string
                retryable:
                  type: boolean
                  description: When false, do not resubmit the source until account usage or Marrow support resolves the stop.
                failedAt:
                  type: string
                  format: date-time
            - type: "null"
        attempts:
          type: integer
        maxAttempts:
          type: integer
        completedAt:
          type:
            - string
            - "null"
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    IngestJobResponse:
      type: object
      required: [schemaVersion, job]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-ingest-job-v1
        mode:
          enum: [queued]
        created:
          type: boolean
        job:
          $ref: "#/components/schemas/IngestJob"
    IngestJobListResponse:
      type: object
      required: [schemaVersion, jobs, pagination]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-ingest-job-list-v1
        jobs:
          type: array
          items:
            $ref: "#/components/schemas/IngestJob"
        pagination:
          $ref: "#/components/schemas/Pagination"
    IngestSource:
      type: object
      additionalProperties: false
      properties:
        kind:
          type: string
        path:
          type: string
        bytes:
          type: integer
        pages:
          type: integer
        lines:
          type: integer
        url:
          type: string
        title:
          type: string
        contentType:
          type: string
        datedAt:
          type: string
    RecordSummary:
      type: object
      additionalProperties: false
      properties:
        total:
          type: integer
        sourceExcerpts:
          type: integer
        facts:
          type: integer
    IngestReview:
      type: object
      additionalProperties: false
      properties:
        status:
          type: string
          enum: [ready, not_recommended, needs_review]
        warnings:
          type: array
          items:
            type: string
    QueryRequest:
      type: object
      additionalProperties: false
      required: [query]
      properties:
        query:
          type: string
          minLength: 1
        limit:
          type: integer
          minimum: 1
          maximum: 20
          default: 5
        evidenceLimit:
          type: integer
          minimum: 1
          maximum: 10
          default: 3
    QueryResponse:
      type: object
      required: [schemaVersion, generatedAt, query, answer, results, warnings]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-evidence-query-v2
        generatedAt:
          type: string
          format: date-time
        query:
          type: string
        answer:
          type: object
          required: [status, text, citations, limitations]
          additionalProperties: false
          properties:
            status:
              type: string
              enum: [partial, insufficient_evidence]
              description: The API returns partial for relevant cited excerpts that do not prove a complete answer, or insufficient_evidence when verified support is missing.
            text:
              type: string
            citations:
              type: array
              items:
                $ref: "#/components/schemas/PublicEvidenceCitation"
            limitations:
              type: array
              items:
                type: string
        results:
          type: array
          items:
            type: object
            required: [rank, title, snippet, evidence]
            additionalProperties: false
            properties:
              rank:
                type: integer
              title:
                type: string
              snippet:
                type: string
              evidence:
                type: array
                items:
                  $ref: "#/components/schemas/PublicEvidenceCitation"
        warnings:
          type: array
          items:
            type: string
    PublicEvidenceCitation:
      type: object
      required: [citationId, excerpt, sourceLabel]
      additionalProperties: false
      dependentRequired:
        charOffsetStart: [charOffsetEnd]
        charOffsetEnd: [charOffsetStart]
      properties:
        citationId:
          type: integer
          minimum: 1
        excerpt:
          type: string
        sourceLabel:
          type: string
        support:
          type: string
          enum: [quotation, compression, inference]
          description: How the cited claim is supported by the excerpt. Omitted when no typed claim-support relation applies.
        charOffsetStart:
          type: integer
          minimum: 0
        charOffsetEnd:
          type: integer
          minimum: 0
    Pagination:
      type: object
      required: [limit, returned, hasMore, nextCursor]
      additionalProperties: false
      properties:
        limit:
          type: integer
        returned:
          type: integer
        hasMore:
          type: boolean
        nextCursor:
          type:
            - string
            - "null"
    LabelUpsertRequest:
      type: object
      required: [id]
      additionalProperties: false
      properties:
        id:
          type: string
          minLength: 1
    AskRequest:
      type: object
      required: [query]
      additionalProperties: false
      properties:
        query:
          type: string
          minLength: 1
        session_id:
          type: string
          minLength: 1
        top_k:
          type: integer
          minimum: 1
    MessagesCreateRequest:
      type: object
      required: [peer_id, messages]
      additionalProperties: false
      properties:
        peer_id:
          type: string
          minLength: 1
        agent_id:
          type: string
          minLength: 1
        messages:
          type: array
          minItems: 1
          maxItems: 100
          items:
            $ref: "#/components/schemas/MessageInput"
        infer:
          type: boolean
        created_at:
          type: string
          format: date-time
        metadata:
          $ref: "#/components/schemas/MessageMetadata"
        source_ids:
          type: array
          maxItems: 100
          items:
            type: string
            format: uuid
        idempotency_key:
          type: string
          minLength: 1
    MessageInput:
      type: object
      required: [role, content]
      additionalProperties: false
      properties:
        role:
          type: string
          enum: [user, assistant, system, tool]
        content:
          type: string
    ClaimsQueryRequest:
      type: object
      required: [peer_id, query]
      additionalProperties: false
      properties:
        peer_id:
          type: string
          minLength: 1
        query:
          type: string
          minLength: 1
        agent_id:
          type: string
          minLength: 1
        session_id:
          type: string
          minLength: 1
        top_k:
          type: integer
          minimum: 1
    ClaimsListRequest:
      type: object
      required: [peer_id]
      additionalProperties: false
      properties:
        peer_id:
          type: string
          minLength: 1
        agent_id:
          type: string
          minLength: 1
        session_id:
          type: string
          minLength: 1
        page:
          type: integer
          minimum: 1
        page_size:
          type: integer
          minimum: 1
    ClaimUpdateRequest:
      type: object
      required: [content, head_revision]
      additionalProperties: false
      properties:
        content:
          type: string
          minLength: 1
        head_revision:
          type: string
          minLength: 1
          maxLength: 128
          description: Opaque concurrency token. Echo the current value unchanged.
        reason:
          type: string
          minLength: 1
    ClaimDeleteRequest:
      type: object
      required: [head_revision]
      additionalProperties: false
      properties:
        head_revision:
          type: string
          minLength: 1
          maxLength: 128
          description: Opaque concurrency token. Echo the current value unchanged.
        reason:
          type: string
          minLength: 1
    Workspace:
      type: object
      required: [id, created_at]
      additionalProperties: false
      properties:
        id:
          type: string
        created_at:
          type:
            - string
            - "null"
          format: date-time
    Peer:
      type: object
      required: [id, metadata, created_at]
      additionalProperties: false
      properties:
        id:
          type: string
        metadata:
          $ref: "#/components/schemas/MemoryMetadata"
        created_at:
          type:
            - string
            - "null"
          format: date-time
    Session:
      type: object
      required: [id, metadata, peer_ids, created_at]
      additionalProperties: false
      properties:
        id:
          type: string
        metadata:
          $ref: "#/components/schemas/MemoryMetadata"
        peer_ids:
          oneOf:
            - type: array
              items:
                type: string
            - type: "null"
        created_at:
          type:
            - string
            - "null"
          format: date-time
    Message:
      type: object
      required: [role, content, peer_id, created_at]
      additionalProperties: false
      properties:
        role:
          type: string
          enum: [user, assistant, system, tool]
        content:
          type: string
        peer_id:
          type:
            - string
            - "null"
        created_at:
          type:
            - string
            - "null"
          format: date-time
    Claim:
      type: object
      required: [id, content, peer_id, agent_id, session_id, authority, state, eligibility, support_status, valid_at, invalid_at, created_at, updated_at, head_revision, categories, citations]
      additionalProperties: false
      properties:
        id:
          type: string
        content:
          type: string
        peer_id:
          type:
            - string
            - "null"
        agent_id:
          type:
            - string
            - "null"
        session_id:
          type:
            - string
            - "null"
        authority:
          type: string
        state:
          type: string
        eligibility:
          type: string
        support_status:
          type: string
        valid_at:
          type:
            - string
            - "null"
          format: date-time
        invalid_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type:
            - string
            - "null"
          format: date-time
        updated_at:
          type:
            - string
            - "null"
          format: date-time
        head_revision:
          type: string
          description: Opaque concurrency token. Echo this value unchanged when correcting or withdrawing the claim.
        metadata:
          $ref: "#/components/schemas/MemoryMetadata"
        categories:
          type: array
          items:
            type: string
        citations:
          type: array
          items:
            $ref: "#/components/schemas/ClaimCitation"
    ClaimCitation:
      type: object
      required: [excerpt, source_label, char_offset_start, char_offset_end]
      additionalProperties: false
      properties:
        excerpt:
          type: string
        source_label:
          type: string
        char_offset_start:
          type: integer
        char_offset_end:
          type: integer
    ClaimHistoryEntry:
      type: object
      required: [id, event, prior_claim, successor_claim, valid_at, invalid_at, created_at, deterministic]
      additionalProperties: false
      properties:
        id:
          type: string
        event:
          type: string
          enum: [ADD, UPDATE, DELETE]
        prior_claim:
          type:
            - string
            - "null"
        successor_claim:
          type:
            - string
            - "null"
        valid_at:
          type:
            - string
            - "null"
          format: date-time
        invalid_at:
          type:
            - string
            - "null"
          format: date-time
        created_at:
          type:
            - string
            - "null"
          format: date-time
        deterministic:
          type: boolean
    ClaimHistoryResponse:
      type: array
      items:
        $ref: "#/components/schemas/ClaimHistoryEntry"
    ClaimUpdateResult:
      type: object
      required: [id, content, event, message, valid_at, invalid_at, deterministic, authority, state, eligibility, head_revision]
      additionalProperties: false
      properties:
        id:
          type: string
        content:
          type: string
        event:
          type: string
          enum: [UPDATE]
        message:
          type: string
        valid_at:
          type: string
          format: date-time
        invalid_at:
          type:
            - string
            - "null"
          format: date-time
        deterministic:
          type: boolean
        authority:
          type: string
          enum: [correction_memory]
        state:
          type: string
          enum: [current]
        eligibility:
          type: string
          enum: [pending_review]
        head_revision:
          type: string
          description: Opaque concurrency token. Echo this value unchanged when correcting or withdrawing the claim.
    ClaimDeleteReceipt:
      type: object
      required: [id, invalid_at, expired_at]
      additionalProperties: false
      properties:
        id:
          type: string
        invalid_at:
          type: string
          format: date-time
        expired_at:
          type: string
          format: date-time
    MemoryContext:
      type: object
      required: [status, placement, context, messages, blocks, citations, conflicts, warnings]
      additionalProperties: false
      properties:
        status:
          type: string
          enum: [ready, partial, insufficient_evidence, conflicted, withheld]
        placement:
          type: string
        context:
          type: string
        messages:
          type: array
          items:
            $ref: "#/components/schemas/ContextMessage"
        blocks:
          type: array
          items:
            $ref: "#/components/schemas/ContextBlock"
        citations:
          type: array
          items:
            $ref: "#/components/schemas/ClaimCitation"
        conflicts:
          type: array
          items:
            type: string
        warnings:
          type: array
          items:
            type: string
    ContextBlock:
      type: object
      required: [authority, state, text, support_status, citations]
      additionalProperties: false
      properties:
        authority:
          type: string
        state:
          type: string
        text:
          type: string
        support_status:
          type: string
        citations:
          type: array
          items:
            $ref: "#/components/schemas/ClaimCitation"
    ContextMessage:
      type: object
      required: [role, content]
      additionalProperties: false
      properties:
        role:
          type: string
        content:
          type: string
    MessagesCreateReceipt:
      type: object
      required: [event_id, status, results, authority, eligibility]
      additionalProperties: false
      properties:
        event_id:
          type: string
        status:
          type: string
          enum: [processing]
        results:
          type: array
          items:
            $ref: "#/components/schemas/ClaimRef"
        authority:
          type: string
        eligibility:
          type: string
    ClaimRef:
      type: object
      required: [id]
      additionalProperties: false
      properties:
        id:
          type: string
    RepresentationResponse:
      type: object
      required: [peer_id, generated_at, count, claims]
      additionalProperties: false
      properties:
        peer_id:
          type: string
        generated_at:
          type: string
          format: date-time
        count:
          type: integer
        claims:
          type: array
          items:
            $ref: "#/components/schemas/Claim"
    EventStatus:
      type: object
      required: [event_id, status]
      additionalProperties: false
      properties:
        event_id:
          type: string
        status:
          type: string
          enum: [queued, running, succeeded, failed, quarantined]
    QueueStatus:
      type: object
      required: [queued, running, succeeded, failed]
      additionalProperties: false
      properties:
        queued:
          type: integer
        running:
          type: integer
        succeeded:
          type: integer
        failed:
          type: integer
    MemoryMetadata:
      type: object
      additionalProperties: false
    MessageMetadata:
      type: object
      description: Application metadata stored with the message event. The reserved marrow property is rejected.
      propertyNames:
        not:
          const: marrow
      additionalProperties: {}
    PageClaim:
      type: object
      required: [items, total, page, size]
      additionalProperties: false
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Claim"
        total:
          type: integer
        page:
          type: integer
        size:
          type: integer
    PagePeer:
      type: object
      required: [items, total, page, size]
      additionalProperties: false
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Peer"
        total:
          type: integer
        page:
          type: integer
        size:
          type: integer
    PageSession:
      type: object
      required: [items, total, page, size]
      additionalProperties: false
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Session"
        total:
          type: integer
        page:
          type: integer
        size:
          type: integer
    PageMessage:
      type: object
      required: [items, total, page, size]
      additionalProperties: false
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Message"
        total:
          type: integer
        page:
          type: integer
        size:
          type: integer
    PageWorkspace:
      type: object
      required: [items, total, page, size]
      additionalProperties: false
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/Workspace"
        total:
          type: integer
        page:
          type: integer
        size:
          type: integer
    ErrorEnvelope:
      type: object
      required: [schemaVersion, requestId, error]
      additionalProperties: false
      properties:
        schemaVersion:
          const: marrow-api-error-v1
        requestId:
          type: string
        error:
          type: object
          required: [code, message]
          additionalProperties: false
          properties:
            code:
              type: string
            message:
              type: string
            details: {}
            issues: {}
