openapi: 3.1.0
info:
  title: Xberg Pro API
  description: Self-hosted, single-container document extraction API powered by Xberg. Audio/video transcription is currently available only through Xberg Core/CLI; this API rejects those inputs.
  license:
    name: Proprietary
  version: 1.0.0
paths:
  /auth/account:
    delete:
      tags:
        - auth
      summary: |-
        `DELETE /auth/account` — session-JWT authenticated (verified directly here
        rather than via the `/v1/*` middleware, since `/auth/*` is mounted at the
        root outside that nest). Revokes API keys, soft-deletes owned projects, and
        removes memberships for the authenticated `sub`, then erases what those
        projects held outside their rows. Pro has no invitations/webhooks-by-owner
        concept to also clean up (unlike Enterprise's `delete_account`).
      description: |-
        The 500 is not an edge case here. Erasure runs after the transaction
        commits, so a caller can see it: the rows are gone and the bucket or the
        vector store still holds the documents. The caller must repeat the request,
        which is why the endpoint reports the failure instead of answering 204.

        Repeating it takes a fresh session. The record written before the deletion
        refuses the token that drove it, and signing in again issues one the record
        admits. The soft delete matches every owned project rather than only the
        live ones, so the second request names the same projects and finishes the
        erasure the first one started.
      operationId: delete_account
      responses:
        "204":
          description: Account deleted successfully
        "400":
          description: Malformed request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Account authentication is disabled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: The account rows were deleted but the stored documents, cached results or vector collections were not fully erased. Sign in again and repeat the request to finish the erasure.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: The record that refuses the account's session tokens could not be written, so nothing was deleted. Retry the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /auth/config:
    get:
      tags:
        - auth
      summary: |-
        `GET /auth/config` — unauthenticated; tells the frontend which auth
        methods this instance accepts.
      description: |-
        The response cannot name an issuer while reporting `enabled: false`:
        start-up refuses a half-configured provider (an issuer without an
        audience, or either without `JWT_SECRET`), so a served issuer implies
        login is enabled.
      operationId: get_auth_config
      responses:
        "200":
          description: Authentication configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthConfigResponse'
        "400":
          description: Malformed request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - {}
  /auth/login:
    post:
      tags:
        - auth
      summary: '`POST /auth/login` — exchange a verified OIDC ID token for a Pro session JWT.'
      description: |-
        404s when OIDC login is disabled. On success, auto-provisions a "Default"
        project (as OWNER) the first time this `sub` is seen — this *is* Pro's
        signup path; there is no separate signup endpoint.
      operationId: login
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
        required: true
      responses:
        "200":
          description: Successful login
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        "400":
          description: Malformed request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Invalid OIDC ID token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Account not permitted to sign in
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: OIDC login is disabled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Identity provider service unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - {}
  /healthz:
    get:
      tags:
        - health
      summary: Check whether the process is alive.
      description: |-
        Always answers `200` once the process has started; it does not check any
        dependency (database, storage) — use `GET /readyz` for that.
        Unauthenticated: intended for a container orchestrator's liveness probe,
        not for API clients. The body also reports the deployment `tier`,
        license state, and whether RAG and auto-tune are enabled on this
        instance, which the frontend uses for feature gating.
      operationId: healthz
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
      security:
        - {}
  /readyz:
    get:
      tags:
        - health
      summary: Check whether the service is ready to serve traffic.
      description: |-
        Probes the primary database, the maintenance database login, the object
        store (a HEAD on a key that never exists — any answer, found or refused,
        proves the store is reachable) and the embedded vector-index worker.
        Answers `200` with each dependency's status only when all are healthy; any
        failure answers `503` with the same body shape so the caller can see which
        check failed. Unauthenticated: intended for a container orchestrator's
        readiness probe, not for API clients.
      operationId: readyz
      responses:
        "200":
          description: Service can serve traffic
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReadinessResponse'
        "503":
          description: A dependency is unreachable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReadinessResponse'
      security:
        - {}
  /v1/audit:
    get:
      tags:
        - audit
      summary: GET /v1/audit — list audit events for the caller's own project (admin or project owner).
      description: |-
        Supports optional `?action=` filtering and pagination via `?limit=` and
        `?offset=` (limit default 50, accepted range 1..=100; offset accepted range 0..=1,000,000).
      operationId: list_audit_entries
      parameters:
        - name: action
          in: query
          description: |-
            Filter to entries matching a known audit action exactly (e.g. `"job.submit"`).
            Unknown actions are rejected with a 400 response.
          required: false
          schema:
            type: string
            maxLength: 64
        - name: limit
          in: query
          description: Page size. Values outside 1..=100 are rejected.
          required: false
          schema:
            type: integer
            format: int64
            default: 50
            maximum: 100
            minimum: 1
        - name: offset
          in: query
          description: Number of items to skip. Values outside 0..=1,000,000 are rejected.
          required: false
          schema:
            type: integer
            format: int64
            default: 0
            maximum: 1000000
            minimum: 0
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Audit entries for the project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListAuditEntriesResponse'
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden — not an admin or the project owner
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Selected project not found or unavailable to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/auto-tune:
    get:
      tags:
        - auto-tune
      summary: List auto-tune jobs
      description: |-
        Returns a page of job summaries (status and timestamps, not the ground
        truth or the winning configuration), most recently submitted first. Fetch
        `GET /v1/auto-tune/{id}` for one job's live status or
        `GET /v1/auto-tune/{id}/result` for a completed job's result.
      operationId: list_auto_tune_jobs
      parameters:
        - name: limit
          in: query
          description: Page size (default 50, accepted range 1..=100).
          required: false
          schema:
            type: integer
            format: int64
            default: 50
            maximum: 100
            minimum: 1
        - name: offset
          in: query
          description: Pagination offset (default 0, accepted range 0..=1,000,000).
          required: false
          schema:
            type: integer
            format: int64
            default: 0
            maximum: 1000000
            minimum: 0
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Auto-tune jobs for the current project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListAutoTuneJobsResponse'
        "400":
          description: Invalid request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Auto-tune is disabled or the project is unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    post:
      tags:
        - auto-tune
      summary: Submit a new auto-tune job
      description: |-
        Accepts `multipart/form-data`: a `request` text part carrying a
        JSON-encoded `CreateAutoTuneJobRequest`, plus one binary part per
        document referenced by `documents[].filename`.
      operationId: submit_auto_tune
      parameters:
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/AutoTuneMultipartForm'
        required: true
      responses:
        "201":
          description: Auto-tune job created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateAutoTuneJobResponse'
        "400":
          description: Invalid request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Selected project not found or unavailable to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Auto-tune admission limit exceeded
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence or dispatch error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/auto-tune/capabilities:
    get:
      tags:
        - auto-tune
      summary: Get auto-tune capabilities (OCR backends and tunable knobs) for this deployment
      description: |-
        Requires the same auth as its sibling endpoints even though the response itself is
        project-agnostic, so it sits behind the same access boundary as the rest of the surface.
      operationId: get_auto_tune_capabilities
      parameters:
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Auto-tune capabilities
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutoTuneCapabilitiesResponse'
        "400":
          description: Invalid request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Selected project not found or unavailable to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/auto-tune/{id}:
    get:
      tags:
        - auto-tune
      summary: Get auto-tune job status
      description: |-
        Auto-tune runs a search over extraction configurations against a
        caller-supplied ground truth, scoring each candidate and tracking the
        best one seen so far. Returns the job's current lifecycle state and
        progress; once the state reaches a terminal value, fetch
        `GET /v1/auto-tune/{id}/result` for the winning configuration and its
        score.
      operationId: get_auto_tune_status
      parameters:
        - name: id
          in: path
          description: Auto-tune job ID
          required: true
          schema:
            type: string
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Auto-tune job status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutoTuneJobStatus'
        "400":
          description: Invalid request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    delete:
      tags:
        - auto-tune
      summary: Delete an auto-tune job
      description: |-
        Irreversible: there is no soft-delete. Rejected with `409` while the job
        is still running: stop it first with `POST /v1/auto-tune/{id}/stop`, which
        drives it to a terminal state, or wait for it to finish.
        Rejected with `403` for a read-only API key.
      operationId: delete_auto_tune_job
      parameters:
        - name: id
          in: path
          description: Auto-tune job ID
          required: true
          schema:
            type: string
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Auto-tune job deleted
        "400":
          description: Invalid request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: Job is still running and cannot be deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/auto-tune/{id}/promote:
    post:
      tags:
        - auto-tune
      summary: Promote an auto-tune result to a tuning profile
      description: |-
        Persists the job's best-found configuration under the name in
        `PromoteProfileRequest`, so it can be applied to future extractions
        without re-running the search. Rejected with `409` when the job has not
        reached a successful terminal state, when no trial in the run scored any
        document (`allow_unscored` promotes such a run anyway), or when the project
        already has a tuning profile with this name; rejected with `403` for a
        read-only API key.
      operationId: promote_auto_tune_profile
      parameters:
        - name: id
          in: path
          description: Auto-tune job ID
          required: true
          schema:
            type: string
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PromoteProfileRequest'
        required: true
      responses:
        "201":
          description: Tuning profile created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TuningProfileDetail'
        "400":
          description: Invalid request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: The job has not completed, has failed, scored no document, or the name is already taken
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/auto-tune/{id}/result:
    get:
      tags:
        - auto-tune
      summary: Get auto-tune job result
      description: |-
        Returns the best extraction configuration the search found, its score
        against the supplied ground truth, and the provenance of how it was
        reached. Rejected with `409` when the job has not yet reached a
        successful terminal state — including when it failed, since there is no
        winning configuration to return in that case.
      operationId: get_auto_tune_result
      parameters:
        - name: id
          in: path
          description: Auto-tune job ID
          required: true
          schema:
            type: string
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Auto-tune job result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutoTuneResult'
        "400":
          description: Invalid request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: Job exists but has not completed (or has failed)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/auto-tune/{id}/stop:
    post:
      tags:
        - auto-tune
      summary: Stop a running auto-tune job
      description: |-
        The stop is cooperative: work already in flight may finish before the run
        observes the request at a progress boundary and completes with its partial
        leaderboard. `GET /v1/auto-tune/{id}/result` still serves the best
        configuration found. A terminal job answers `204` unchanged, so callers
        may safely retry.
      operationId: stop_auto_tune_job
      parameters:
        - name: id
          in: path
          description: Auto-tune job ID
          required: true
          schema:
            type: string
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Stop requested, or the job had already finished
        "400":
          description: Invalid request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: The job cannot be stopped
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/crawl-jobs/{crawl_job_id}/events:
    get:
      tags:
        - jobs
      summary: Stream crawl job events
      description: |-
        Emits one `text/event-stream` message per event — `discovered` when a URL
        enters the frontier, `page` when one is fetched, `error` when one fails,
        and a single `complete` carrying the job's total page count. The stream
        closes after `complete`.

        A crawl job this instance is not running — one that finished earlier, or
        was interrupted by a restart — answers with one event describing its
        recorded outcome and then closes, rather than holding a connection open for
        events that can no longer arrive.
      operationId: stream_crawl_events
      parameters:
        - name: crawl_job_id
          in: path
          description: Crawl job ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: project_id
          in: query
          description: Selected project; defaults to the authenticated project. Requires access to the selected project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Crawl events SSE stream
          content:
            text/event-stream:
              schema:
                $ref: '#/components/schemas/CrawlEventV1'
        "400":
          description: Invalid crawl job ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: No such crawl job in this project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Too many concurrent event streams for this project
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Failed to read the crawl job
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Crawl extraction is not available on this instance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/enrich:
    post:
      tags:
        - enrich
      summary: Submit text for enrichment.
      description: |-
        Validates text and refuses unsupported label classification, then runs
        keyword extraction and NER in-process, returning a job id to poll for the result.
      operationId: submit_enrich
      parameters:
        - name: project_id
          in: query
          description: Selected project; defaults to the authenticated project. Requires access to the selected project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EnrichTextRequest'
        required: true
      responses:
        "202":
          description: Enrichment job accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EnrichJobSubmitted'
        "400":
          description: Text is invalid, labels requested unavailable classification (`rag.label_classification.unsupported`), a classification/chunk_classification/captioning stage's labels, chunks, definitions, or images are empty or exceed their count/length bounds, or a classification/captioning stage names an endpoint the egress policy refuses or a model this deployment cannot authenticate
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Mutations are disabled in read-only mode
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Selected project not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Enrichment queue is full. The response carries Retry-After with the number of seconds to wait, and it is the only 503 in this API that carries the header
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/enrich/{job_id}:
    get:
      tags:
        - enrich
      summary: Poll an enrichment job's status.
      description: Returns the current state and completed output of the asynchronous job, scoped to the selected project; unknown or inaccessible jobs return 404.
      operationId: get_enrich_status
      parameters:
        - name: job_id
          in: path
          description: Enrichment job ID (ULID)
          required: true
          schema:
            type: string
        - name: project_id
          in: query
          description: Selected project; defaults to the authenticated project. Requires access to the selected project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Job status, with the result when completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EnrichJobStatus'
        "400":
          description: Job ID is not a ULID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/extract:
    post:
      tags:
        - extract
      summary: Submit documents, seed URLs, or both for extraction.
      description: |-
        Accepts at most 10 documents inline or by integration reference because it
        can decode inline bytes and retains the full batch while creating jobs. The
        cap bounds request memory and immediate job dispatch. Use presigned uploads
        to stage up to 100 document metadata entries and upload their bytes directly
        to storage.
        <!-- public batch-limit contract -->
        A request also accepts at most 100 seed URLs; that separate cap bounds crawl
        planning and per-seed validation. Seed URLs that share one effective crawl
        configuration become a single crawl job, and the response names every crawl
        job created.
        A webhook on a crawl request receives one `crawl.completed` or `crawl.failed`
        callback per crawl parent. Extracting crawls retain the existing per-document
        `job.completed` or `job.failed` callbacks for their children.

        The crawl fetches over plain HTTP. A configuration that only means
        something with a browser — `browser.mode` of `always`, `browser.stealth`,
        a named `browser.backend` — or that names a proxy or a bypass provider, is
        refused with `422` rather than answered with an unrendered page.

        Accepts `application/json` or the equivalent `multipart/form-data` form
        shape; a multipart body carries documents only.
      operationId: extract
      parameters:
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID, honoured for both JSON and multipart requests. Defaults to the authenticated or local workspace project; project API keys cannot select another project. A JSON body project_id takes precedence over this header.
          required: false
          schema:
            type:
              - string
              - "null"
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExtractRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/ExtractMultipartForm'
        required: true
      responses:
        "202":
          description: Job(s) accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExtractResponse'
        "400":
          description: Invalid request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Integration or project not visible to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: Decoded document exceeds the configured upload limit
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: Content-Type is neither application/json nor multipart/form-data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: The request is well-formed but asks for a capability this deployment does not have, such as browser rendering during a crawl
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: A project monthly extraction quota is exhausted; Retry-After gives seconds until the next UTC calendar month
          headers:
            Retry-After:
              schema:
                type: integer
                format: int64
                minimum: 0
              description: Seconds until the next UTC calendar month.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Crawl extraction is not available on this instance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/jobs:
    get:
      tags:
        - jobs
      summary: List jobs for the authenticated project.
      description: |-
        Returns a page of the project's extraction jobs and crawl parents, together with the
        `total` count of jobs in the project. `project_id` lets the system-wide
        admin key, or a session-JWT caller whose own project is fixed at login,
        read a different project they can see; naming a project the caller
        cannot read answers `404` rather than an empty page. A bare data-plane
        API key names no human, so it can only read the project its own
        credential carries.
      operationId: list_jobs
      parameters:
        - name: limit
          in: query
          description: Page size (default 50, max 100).
          required: false
          schema:
            type: integer
            format: int64
            default: 50
            maximum: 100
            minimum: 1
        - name: offset
          in: query
          description: Offset for pagination (default 0, clamped to a maximum of 1,000,000).
          required: false
          schema:
            type: integer
            format: int64
            default: 0
            maximum: 1000000
            minimum: 0
        - name: project_id
          in: query
          description: Project to read. Absent reads the credential's own project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Jobs for the project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListJobsResponse'
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Requested project not visible to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/jobs/{id}:
    get:
      tags:
        - jobs
      summary: Fetch one job's metadata and status by ID.
      description: |-
        Returns the job's status and processing statistics, not its extraction
        content — fetch `GET /v1/jobs/{id}/result` for that once the job is
        terminal. `project_id` scopes the lookup the same way as
        `GET /v1/jobs`'s own `project_id`; a job outside the resolved project
        answers `404`.
      operationId: get_job
      parameters:
        - name: id
          in: path
          description: Job ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: project_id
          in: query
          description: Project to read. Absent reads the credential's own project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Job metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GetJobResponse'
        "400":
          description: Invalid job ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Job or project not visible to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    delete:
      tags:
        - jobs
      summary: Cancel a job, idempotently.
      description: |-
        Requests cancellation of a non-terminal job; a job that has already
        reached a terminal state (completed, failed, or already cancelled)
        answers `204` without changing anything, so a caller may retry this
        call freely. There is no response body on success — poll
        `GET /v1/jobs/{id}` to see the job's final status.
      operationId: cancel_job
      parameters:
        - name: id
          in: path
          description: Job ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: project_id
          in: query
          description: Project to mutate. Absent uses the credential's own project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Job cancelled or already terminal
        "400":
          description: Invalid job ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Mutations are disabled in read-only mode
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Job or project not visible to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Cancellation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/jobs/{id}/result:
    get:
      tags:
        - jobs
      summary: GET /v1/jobs/:id/result — fetch the canonical extraction result.
      description: Returns the stored canonical result only after the scoped extraction job completes or partially succeeds; other job states return 409.
      operationId: get_job_result
      parameters:
        - name: id
          in: path
          description: Job ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: project_id
          in: query
          description: Project to read. Absent reads the credential's own project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Extraction result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobResult'
        "400":
          description: Invalid job ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Job or project not visible to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: Job has not reached a terminal successful state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence or storage error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/jobs/{job_id}/pages/{page_number}:
    get:
      tags:
        - jobs
      summary: Retrieve a rasterized page PNG from a completed extraction job.
      description: |-
        Page numbers are 1-indexed, matching Enterprise's own operation. Images
        are cached for 1 hour.
      operationId: get_job_page
      parameters:
        - name: job_id
          in: path
          description: Extraction job ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: page_number
          in: path
          description: 1-indexed page number
          required: true
          schema:
            type: integer
            format: int32
            minimum: 0
        - name: project_id
          in: query
          description: Project to read; defaults to the credential project
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Page PNG image
          content:
            image/png: {}
        "400":
          description: Invalid job ID or page number
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Job or page not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/license:
    get:
      tags:
        - license
      summary: Authenticated, admin-only license detail (ADR-0087 §5).
      description: |-
        Reports licensee, license id, plan, expiry, grace window, and days remaining;
        these values are re-evaluated hourly by the API process rather than snapshotted
        at boot.
      operationId: get_license_info
      responses:
        "200":
          description: License detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LicenseInfoResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: 'Forbidden: admin API key or admin session required'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/oauth/callback:
    get:
      tags:
        - integrations
      summary: GET /v1/oauth/callback — OAuth callback (PUBLIC, no auth required).
      description: |-
        This endpoint receives the authorization code and state token from the OAuth provider.
        On success, redirects to the frontend landing URL.
        On failure, returns a generic error (no oracle to leak valid projects).
      operationId: oauth_callback
      parameters:
        - name: code
          in: query
          description: Authorization code from the provider
          required: false
          schema:
            type: string
        - name: state
          in: query
          description: Signed state token issued by the connect call
          required: false
          schema:
            type: string
        - name: error
          in: query
          description: Provider error code when the user denied access
          required: false
          schema:
            type: string
      responses:
        "303":
          description: Redirect to the dashboard landing URL, on success and on a provider-reported denial
          headers:
            Location:
              schema:
                type: string
              description: Dashboard landing URL
        "400":
          description: Invalid OAuth callback request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: OAuth authentication refused
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Callback rate limit exceeded for this client address
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: The code exchange failed; the response carries no detail about the flow
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Integration hub is not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - {}
  /v1/presets:
    get:
      tags:
        - presets
      summary: List the curated preset registry.
      description: |-
        Returns every built-in preset's summary (id, name, description) in one
        unpaginated response — the registry is small and fixed at build time, not
        a per-project resource. Public: no authentication required, since the
        preset library itself is not tenant-specific.
      operationId: list_presets
      responses:
        "200":
          description: Curated preset library
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PresetSummary'
      security:
        - {}
  /v1/presets/{id}:
    get:
      tags:
        - presets
      summary: Get the full detail of one curated preset.
      description: |-
        Returns the preset's system prompt, output schema, and content
        fingerprint (a hash callers can use to detect that a preset's definition
        changed between releases). `404` when `id` does not match a preset in the
        registry. Public: no authentication required.
      operationId: get_preset
      parameters:
        - name: id
          in: path
          description: Preset identifier
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Preset detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PresetDetail'
        "400":
          description: Malformed request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Preset not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - {}
  /v1/presets/{id}/sample/{name}:
    get:
      tags:
        - presets
      summary: Get a bundled sample file for a preset's landing-page preview.
      description: |-
        Streams the raw sample bytes with a `Content-Type` guessed from `name`'s
        extension; there is no wrapping envelope. `404` when either the preset or
        the named sample does not exist. Public: no authentication required.
      operationId: get_preset_sample
      parameters:
        - name: id
          in: path
          description: Preset identifier
          required: true
          schema:
            type: string
        - name: name
          in: path
          description: Sample file name
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Sample bytes
          content:
            '*/*':
              schema:
                type: string
                format: binary
        "400":
          description: Malformed request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Preset or sample not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - {}
  /v1/projects:
    get:
      tags:
        - projects
      summary: List the caller's own projects.
      description: |-
        A session-JWT human sees the projects owned by `ctx.user_id`; the admin
        API key sees `ADMIN_USER_ID`'s projects. A bare data-plane API key (no
        user and not admin) is refused with `403` — there is no project-picker
        use case for it, since its own credential already names one project.
      operationId: list_projects
      parameters:
        - name: limit
          in: query
          description: |-
            Page size. A value outside 1..=1000 is rejected with a 400, on both
            tiers.
          required: false
          schema:
            type: integer
            format: int64
            default: 100
            maximum: 1000
            minimum: 1
          example: 100
        - name: offset
          in: query
          description: |-
            Number of items to skip. A negative value is rejected with a 400; a
            value above 1,000,000 is accepted but clamped to 1,000,000, which
            selects an empty page rather than erroring.
          required: false
          schema:
            type: integer
            format: int64
            default: 0
            maximum: 1000000
            minimum: 0
          example: 0
      responses:
        "200":
          description: Projects visible to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListProjectsResponse'
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    post:
      tags:
        - projects
      summary: Create a project.
      description: |-
        The owner is the caller: the authenticated human (`ctx.user_id`) when
        present, else `ADMIN_USER_ID` for the admin API key. A request from
        neither is a `403`. Unlike Enterprise, which derives a slug from `name`,
        Pro requires the caller to supply `slug` explicitly; a missing slug is a
        `400`, and a slug that collides with an existing project is a `409` that names
        the slug. Projects are unique on the slug, not the name. The name is stored
        trimmed. The project starts `ACTIVE` with no API keys, webhooks, or usage.
      operationId: create_project
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateProjectRequest'
        required: true
      responses:
        "201":
          description: Project created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectResponse'
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: Project slug already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/projects/{project_id}/api-keys:
    get:
      tags:
        - projects
      summary: List API keys for a project.
      description: |-
        Returns each key's metadata (name, prefix, scope, kind, timestamps) —
        never the plaintext key itself, which is shown only once, at creation.
        Gated the same way as the rest of the project's control-plane operations:
        the admin key, or the project's session-JWT owner.
      operationId: list_api_keys
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          description: |-
            Page size. A value outside 1..=1000 is rejected with a 400, on both
            tiers.
          required: false
          schema:
            type: integer
            format: int64
            default: 100
            maximum: 1000
            minimum: 1
          example: 100
        - name: offset
          in: query
          description: |-
            Number of items to skip. A negative value is rejected with a 400; a
            value above 1,000,000 is accepted but clamped to 1,000,000, which
            selects an empty page rather than erroring.
          required: false
          schema:
            type: integer
            format: int64
            default: 0
            maximum: 1000000
            minimum: 0
          example: 0
      responses:
        "200":
          description: API keys for the project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListApiKeysResponse'
        "400":
          description: Invalid project ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    post:
      tags:
        - projects
      summary: Create an API key for a project.
      description: |-
        `name` is optional on Pro (unlike Enterprise, which requires one).
        `scope` defaults to `write`; a `read` key is refused on every mutating
        method (`POST`/`PUT`/`PATCH`/`DELETE`) at the extractor level, before the
        handler runs. `expires_at`, when set, must parse as ISO 8601. The
        plaintext key is returned only in this response — it is not recoverable
        afterward, only its prefix is.
      operationId: create_api_key
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateApiKeyRequest'
        required: true
      responses:
        "201":
          description: API key created; the plaintext key is returned once
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateApiKeyResponse'
        "400":
          description: Invalid project ID or request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/projects/{project_id}/api-keys/{key_id}:
    delete:
      tags:
        - projects
      summary: Revoke an API key.
      description: |-
        Irreversible: a revoked key cannot be un-revoked, only replaced by
        creating a new one. Gated the same way as the rest of the project's
        control-plane operations: the admin key, or the project's session-JWT
        owner.
      operationId: revoke_api_key
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: key_id
          in: path
          description: API key ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: API key revoked
        "400":
          description: Invalid project or key ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/projects/{project_id}/integrations:
    get:
      tags:
        - integrations
      summary: List a project's source integrations.
      description: |-
        Returns each integration's metadata (name, kind, auth type) — never any
        credential material, sealed or otherwise. Gated to a system-wide admin or
        the project's session-JWT owner.
      operationId: list_integrations
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          description: |-
            Page size. A value outside 1..=1000 is rejected with a 400, on both
            tiers.
          required: false
          schema:
            type: integer
            format: int64
            default: 100
            maximum: 1000
            minimum: 1
          example: 100
        - name: offset
          in: query
          description: |-
            Number of items to skip. A negative value is rejected with a 400; a
            value above 1,000,000 is accepted but clamped to 1,000,000, which
            selects an empty page rather than erroring.
          required: false
          schema:
            type: integer
            format: int64
            default: 0
            maximum: 1000000
            minimum: 0
          example: 0
      responses:
        "200":
          description: Integrations for the project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProIntegrationListResponse'
        "400":
          description: Invalid pagination
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    post:
      tags:
        - integrations
      summary: Create a source integration for a project.
      description: |-
        `kind` must name a connector this deployment supports; `auth_type` must
        be one of `none`, `static`, `api_key`, `oauth2`, or `oidc`. For
        `oauth2`/`oidc`, an optional `credentials` object of caller-supplied
        client credentials is sealed (encrypted) before storage — only a masked
        form is ever read back. Establishing the actual OAuth connection is a
        separate step: see `POST .../connect`. Gated to a system-wide admin or
        the project's session-JWT owner.
      operationId: create_integration
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateIntegrationRequest'
        required: true
      responses:
        "201":
          description: Integration created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationResponse'
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: An integration with this name already exists in the project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Integration OAuth secrets are not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/projects/{project_id}/integrations/{iid}:
    get:
      tags:
        - integrations
      summary: Get one source integration's metadata.
      description: |-
        Returns the same shape as a `list_integrations` entry — never any
        credential material. `404` when `integration_id` does not name an
        integration in the given project. Gated to a system-wide admin or the
        project's session-JWT owner.
      operationId: get_integration
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: iid
          in: path
          description: Integration ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Integration metadata; credentials are masked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationResponse'
        "400":
          description: Invalid project or integration ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project or integration not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    delete:
      tags:
        - integrations
      summary: Delete a source integration.
      description: |-
        Irreversible. Its OAuth connection, if any, is revoked with the provider
        first — best-effort, so a provider-side failure does not block deleting
        the local record. Gated to a system-wide admin or the project's
        session-JWT owner.
      operationId: delete_integration
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: iid
          in: path
          description: Integration ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Integration deleted; its OAuth connection is revoked first
        "400":
          description: Invalid project or integration ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/projects/{project_id}/integrations/{iid}/connect:
    post:
      tags:
        - integrations
      summary: Begin the OAuth connection flow for an integration.
      description: |-
        Returns the provider's authorization URL to redirect the end user to; the
        flow completes at the public OAuth callback route, not this endpoint.
        Only meaningful for an integration whose `auth_type` is `oauth2` or
        `oidc`. Gated to a system-wide admin or the project's session-JWT owner.
      operationId: begin_oauth_connect
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: iid
          in: path
          description: Integration ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Authorization URL to redirect the user to
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BeginOAuthResponse'
        "400":
          description: Invalid ID, or the provider refused to start the flow
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project or integration not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Provider or persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: 'Integration hub is not configured: INTEGRATION_KEK and INTEGRATION_STATE_SECRET are unset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/projects/{project_id}/integrations/{iid}/disconnect:
    post:
      tags:
        - integrations
      summary: Revoke an integration's OAuth connection.
      description: |-
        Best-effort at the provider: the provider is asked to revoke first, then
        the local connection record is cleared regardless of whether the
        provider call succeeded. Idempotent — an already-disconnected integration
        still answers `204` and still records the request in the audit log. The
        integration record itself is not deleted, only its connection. Gated to
        a system-wide admin or the project's session-JWT owner.
      operationId: disconnect_integration
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: iid
          in: path
          description: Integration ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Connection revoked locally and, best effort, at the provider
        "400":
          description: Invalid project or integration ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/projects/{project_id}/integrations/{iid}/documents:
    get:
      tags:
        - integrations
      summary: List documents visible through a connected source integration.
      description: |-
        Delegates to the integration's own connector, so behavior and pagination
        depend on the connected source (Google Drive, SharePoint, etc.), not on
        this service's own database. `mime_types` and `folder_id` are passed
        through to the connector as filters; `limit` bounds the returned count and
        `offset` skips matching source documents. A source with more matching
        documents than the connector's bounded page walk allows may still answer `413` if
        satisfying the filter needs more pages than the connector reads. Errors
        distinguish this service's own failures from the upstream source's:
        `502` for an unreachable or malformed upstream response, `429` for
        upstream rate-limiting.
      operationId: list_integration_documents
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: iid
          in: path
          description: Integration ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: mime_types
          in: query
          description: Comma-separated MIME types to filter by (empty = all types).
          required: false
          schema:
            type: string
        - name: folder_id
          in: query
          description: Source-specific folder ID to scope listing.
          required: false
          schema:
            type: string
        - name: limit
          in: query
          description: Maximum number of documents to return.
          required: false
          schema:
            type: integer
            format: int64
            default: 100
            maximum: 1000
            minimum: 1
        - name: offset
          in: query
          description: Number of matching documents to skip.
          required: false
          schema:
            type: integer
            format: int64
            default: 0
            maximum: 1000000
            minimum: 0
      responses:
        "200":
          description: Documents visible through the connected source
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListDocumentsResponse'
        "400":
          description: Invalid project or integration ID, or the integration has no connector for its kind
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Session authentication failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden, or the source denied access to the folder
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: The source connection is missing or revoked and must be connected again
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The listing needs more pages than the connector reads; narrow `mime_types` or `folder_id`, or reduce `offset`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: The source is rate-limiting the connector; retry after the `Retry-After` interval
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "502":
          description: The source returned an unexpected status, an unreadable response, or could not be reached
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/projects/{project_id}/integrations/{iid}/documents/{doc_id}:
    get:
      tags:
        - integrations
      summary: Fetch one document's raw bytes through a connected source integration.
      description: |-
        Streams the document bytes with the source's MIME type in `Content-Type`
        and a sanitized filename in
        `Content-Disposition` — there is no JSON envelope on success. `document_id`
        is the source's own identifier, not one this service assigns. As with
        listing, errors distinguish this service's own failures from the
        upstream source's: `502` for an unreachable or malformed upstream
        response, `429` for upstream rate-limiting, `413` when the document
        exceeds what the connector reads.
      operationId: fetch_integration_document
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: iid
          in: path
          description: Integration ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: doc_id
          in: path
          description: Source-specific document ID
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Document bytes; Content-Type is the source's MIME type and Content-Disposition names the file
          content:
            '*/*':
              schema:
                type: string
                format: binary
        "400":
          description: Invalid project or integration ID, the integration has no connector for its kind, or the document type cannot be exported
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Session authentication failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden, or the source denied the download
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project or document not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: The source connection is missing or revoked and must be connected again
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The document is larger than the connector reads
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: The source is rate-limiting the connector; retry after the `Retry-After` interval
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "502":
          description: The source returned an unexpected status, an unreadable response, or could not be reached
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/projects/{project_id}/integrations/{iid}/sync:
    get:
      tags:
        - integrations
      summary: Report the durable synchronization state for one source integration.
      description: Requires project access and returns the persisted per-integration opt-in state.
      operationId: get_integration_sync
      parameters:
        - name: project_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: iid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Synchronization preference
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationSyncResponse'
        "400":
          description: 'The request is malformed: a path parameter, a query parameter or the body did not parse'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Integration not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    put:
      tags:
        - integrations
      summary: Enable or disable durable synchronization for one source integration.
      description: Requires project access and preserves the choice across scheduler restarts.
      operationId: set_integration_sync
      parameters:
        - name: project_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: iid
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetIntegrationSyncRequest'
        required: true
      responses:
        "204":
          description: Synchronization preference updated
        "400":
          description: Integration kind does not support durable synchronization
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Integration not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/projects/{project_id}/rag-config:
    get:
      tags:
        - projects
      summary: Read a project's RAG configuration.
      description: |-
        Returns the project's current retrieval settings (chunking, embedding
        model, reranker) regardless of whether RAG is enabled on this Pro
        instance — the configuration is stored per-project even when the RAG
        runtime itself is not built. Gated the same way as the rest of the
        project's control-plane operations: the admin key, or the project's
        session-JWT owner.
      operationId: get_rag_config
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Project RAG configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RagConfigResponse'
        "400":
          description: Invalid project ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    put:
      tags:
        - projects
      summary: Replace a project's RAG configuration.
      description: |-
        A full replacement, not a partial merge — every field in
        `SetRagConfigRequest` is applied, including `enabled`. Persists
        regardless of whether the RAG runtime is built on this instance; setting
        `enabled: true` on a deployment with no RAG runtime configured does not
        make `/v1/rag/*` start answering. Gated the same way as the rest of the
        project's control-plane operations: the admin key, or the project's
        session-JWT owner.
      operationId: set_rag_config
      parameters:
        - name: project_id
          in: path
          description: Project ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SetRagConfigRequest'
        required: true
      responses:
        "200":
          description: Updated project RAG configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RagConfigResponse'
        "400":
          description: Invalid project ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Project not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/rag/collections:
    get:
      tags:
        - rag
      summary: List RAG collections
      description: Returns paginated list of collections ordered by creation time (newest first).
      operationId: list_collections
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            format: int64
            default: 50
            maximum: 100
            minimum: 1
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            format: int64
            default: 0
            maximum: 1000000
            minimum: 0
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: List of collections
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListCollectionsResponse'
        "400":
          description: Invalid request parameters; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: RAG is not enabled for this project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    post:
      tags:
        - rag
      summary: Create a new RAG collection
      description: |-
        Creates a vector collection with the specified embedding and index configuration.
        Collection names must be unique per project.
        Returns 201 on success, 409 if a collection with that name already exists.
      operationId: create_collection
      parameters:
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCollectionRequest'
        required: true
      responses:
        "201":
          description: Collection created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateCollectionResponse'
        "400":
          description: 'Invalid request parameters, or a label classification field was supplied: `rag.label_classification.unsupported`; X-Project-ID must be a UUID'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: RAG is not enabled for this project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: Collection name already exists in this project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/rag/collections/{name}:
    get:
      tags:
        - rag
      summary: Get a RAG collection
      description: Returns 404 if the collection does not exist or belongs to another project.
      operationId: get_collection
      parameters:
        - name: name
          in: path
          description: Collection name
          required: true
          schema:
            type: string
          example: my-docs
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Collection details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CollectionResponse'
        "400":
          description: Invalid request parameters; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Collection not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    delete:
      tags:
        - rag
      summary: Delete a RAG collection
      description: |-
        Marking the collection as deleted prevents new document uploads but
        does not immediately remove data.
        Returns 204 on success, 404 if the collection does not exist.
      operationId: delete_collection
      parameters:
        - name: name
          in: path
          description: Collection name
          required: true
          schema:
            type: string
          example: my-docs
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Collection deleted
        "400":
          description: Invalid request parameters; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Collection not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/rag/collections/{name}/documents:
    post:
      tags:
        - rag
      summary: Create a document in a RAG collection
      description: |-
        Supports two ingest paths:
        - **Text source** → server chunks, enriches, embeds → 202 async
        - **Chunks source with every embedding supplied** → 200 synchronous
        - **Chunks source with any embedding missing** → worker handles embedding and enrichment → 202 async

        Quota checks apply: if the collection has `quota_max_documents` set and
        the active document count would exceed it, returns 429.
      operationId: create_document
      parameters:
        - name: name
          in: path
          description: Collection name
          required: true
          schema:
            type: string
          example: my-docs
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateDocumentRequest'
        required: true
      responses:
        "200":
          description: Document ingested synchronously with caller-supplied embeddings
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateDocumentResponse'
              example:
                document_id: 550e8400-e29b-41d4-a716-446655440000
                external_id: kb-article-42
                collection: my-docs
                chunk_count: 2
                embedding_dim: 768
                ingested_at: "2026-09-22T12:00:00Z"
        "202":
          description: Document ingest queued asynchronously
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateDocumentAsyncResponse'
              example:
                job_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV
                document_id: 550e8400-e29b-41d4-a716-446655440000
                status: pending
                poll: /v1/rag/jobs/01ARZ3NDEKTSV4RRFFQ69G5FAV
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Collection not found or RAG disabled on project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Collection quota exceeded
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence or dispatch error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    delete:
      tags:
        - rag
      summary: Delete documents from a RAG collection
      description: Accepts either a list of document IDs or a metadata filter expression.
      operationId: delete_documents
      parameters:
        - name: name
          in: path
          description: Collection name
          required: true
          schema:
            type: string
          example: my-docs
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeleteDocumentsRequest'
        required: true
      responses:
        "200":
          description: Documents deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeleteDocumentsResponse'
              example:
                deleted_count: 5
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Collection not found or RAG disabled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/rag/collections/{name}/documents/{id}/reindex:
    post:
      tags:
        - rag
      summary: Reindex a document
      description: |-
        When `re_extract: true`, the worker re-pulls the source object from storage
        and re-runs xberg extraction before re-chunking.
      operationId: reindex_document
      parameters:
        - name: name
          in: path
          description: Collection name
          required: true
          schema:
            type: string
          example: my-docs
        - name: id
          in: path
          description: Document ID
          required: true
          schema:
            type: string
            format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReindexRequest'
        required: true
      responses:
        "202":
          description: Reindex job queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateDocumentAsyncResponse'
              example:
                job_id: 01ARZ3NDEKTSV4RRFFQ69G5FAV
                document_id: 550e8400-e29b-41d4-a716-446655440000
                status: pending
                poll: /v1/rag/jobs/01ARZ3NDEKTSV4RRFFQ69G5FAV
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Document or collection not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence or dispatch error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/rag/collections/{name}/migrate-embeddings:
    post:
      tags:
        - rag
      summary: Initiate embedding source migration
      description: |-
        Validates that the target version is greater than the current version
        and that the target embedding source has the same dimension as the
        current embedding source (cross-dimension migration is not supported).

        Returns 202 with a job_id for polling progress.
      operationId: migrate_embeddings
      parameters:
        - name: name
          in: path
          description: Collection name
          required: true
          schema:
            type: string
          example: my-docs
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MigrateEmbeddingsRequest'
        required: true
      responses:
        "202":
          description: Migration queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MigrateEmbeddingsResponse'
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Collection not found or RAG disabled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Invalid migration parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/rag/collections/{name}/migrate-embeddings/{job_id}:
    get:
      tags:
        - rag
      summary: Get migration job status
      description: Returns the current status, progress details, or error information.
      operationId: migrate_status
      parameters:
        - name: name
          in: path
          description: Collection name
          required: true
          schema:
            type: string
        - name: job_id
          in: path
          description: Migration job ID (ULID)
          required: true
          schema:
            type: string
            maxLength: 26
            minLength: 26
            pattern: ^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Migration status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MigrateStatusResponse'
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/rag/collections/{name}/retrieve:
    post:
      tags:
        - rag
      summary: Retrieve relevance-ranked chunks from a RAG collection
      description: '`mode` selects vector, full-text or hybrid retrieval. `top_k` (1..=200) bounds the returned chunk count, and `candidate_multiplier` (1..=20) widens the pre-rerank candidate pool. `501` indicates that the collection''s reranker is unavailable. Pro retrieves in process and returns `503` only when its per-project retrieve limiter is unavailable.'
      operationId: retrieve
      parameters:
        - name: name
          in: path
          description: Collection name
          required: true
          schema:
            type: string
          example: my-docs
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RetrieveRequest'
        required: true
      responses:
        "200":
          description: Retrieve completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RetrieveResponse'
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Collection not found or RAG disabled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Database error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "501":
          description: Reranker kind not available in this deployment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: In-process retrieve limiter unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/rag/embedding-presets:
    get:
      tags:
        - rag
      summary: List available RAG embedding presets
      description: |-
        Returns the compile-time `xberg::embeddings::EMBEDDING_PRESETS` registry, capped at
        `MAX_MANAGED_EMBEDDING_PRESETS` entries, with each preset's `embedding_source` string and
        whether it is the server's default. The set is the same for every project and request.
      operationId: list_managed_embedding_presets
      parameters:
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Managed embedding presets
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListManagedEmbeddingPresetsResponse'
        "400":
          description: X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: Project access is forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Selected project not found or unavailable to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Invalid server preset configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/rag/jobs/{job_id}:
    get:
      tags:
        - rag
      summary: Poll async RAG operation status
      description: |-
        Returns the current job state, operation type, and any available progress details.
        Status is determined by:
        - For ingest/reindex: document status in vector_documents (pending→indexed or failed)
        - For migrate: job status in NATS KV bucket (rag-migrate-status)

        Returns 404 if the job_id does not exist or belongs to another project (RLS enforcement).
      operationId: get_rag_job
      parameters:
        - name: job_id
          in: path
          description: Job ID (ULID)
          required: true
          schema:
            type: string
            maxLength: 26
            minLength: 26
            pattern: ^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$
          example: 01ARZ3NDEKTSV4RRFFQ69G5FAV
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Job status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RagJobStatusResponse'
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Job not found or access denied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Invalid job ID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence or dispatch error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/saved_presets:
    get:
      tags:
        - saved-presets
      summary: List saved presets
      description: |-
        Returns a page of `SavedPresetSummary` entries — not the full schema each
        preset carries; fetch `GET /v1/saved_presets/{preset_id}` for that. `page` in the
        response is a zero-based page index derived from `offset / limit`, not an
        independent cursor.
      operationId: list_saved_presets
      parameters:
        - name: limit
          in: query
          description: Page size (default 50, accepted range 1..=100).
          required: false
          schema:
            type: integer
            format: int64
            default: 50
            maximum: 100
            minimum: 1
        - name: offset
          in: query
          description: Pagination offset (default 0, accepted range 0..=1,000,000).
          required: false
          schema:
            type: integer
            format: int64
            default: 0
            maximum: 1000000
            minimum: 0
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Saved presets for the current project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListSavedPresetsResponse'
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Selected project not found or unavailable to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    post:
      tags:
        - saved-presets
      summary: Create a saved preset
      description: |-
        A saved preset is a project's own reusable extraction configuration — a
        name, a JSON Schema (Draft 2020-12) the extraction output must satisfy,
        and a preferred call mode — distinct from the curated, cross-project
        preset registry served at `GET /v1/presets`. The schema is validated for
        well-formedness at creation time, not for reachability against any
        particular document. Returns `201` with `{success, id, name, message}`.
        Rejected with `403` for a read-only API key.
      operationId: create_saved_preset
      parameters:
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSavedPresetRequest'
        required: true
      responses:
        "201":
          description: Preset created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateSavedPresetResponse'
        "400":
          description: Invalid request (bad name, schema, or call mode); X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Selected project not found or unavailable to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: A preset with this name already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/saved_presets/{preset_id}:
    get:
      tags:
        - saved-presets
      summary: Get a saved preset
      description: |-
        `404` when `preset_id` does not name a preset in the caller's own project — a
        preset belonging to a different project is never visible, project
        membership is not checked separately.
      operationId: get_saved_preset
      parameters:
        - name: preset_id
          in: path
          description: Preset ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Saved preset details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SavedPresetDetail'
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Preset not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    delete:
      tags:
        - saved-presets
      summary: Delete a saved preset
      description: |-
        Irreversible: there is no soft-delete or recovery. `404` when `preset_id` does
        not name a preset in the caller's project. Rejected with `403` for a
        read-only API key.
      operationId: delete_saved_preset
      parameters:
        - name: preset_id
          in: path
          description: Preset ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Preset deleted
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Preset not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    patch:
      tags:
        - saved-presets
      summary: Update a saved preset
      description: |-
        Omitted fields are preserved. Explicit null clears description, system prompt,
        or context template; non-nullable fields reject null. Provided fields are
        validated exactly as on create. `404` when `preset_id` does not name a preset in the
        caller's project. Rejected with `403` for a read-only API key.
      operationId: update_saved_preset
      parameters:
        - name: preset_id
          in: path
          description: Preset ID (UUID)
          required: true
          schema:
            type: string
            format: uuid
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSavedPresetRequest'
        required: true
      responses:
        "200":
          description: Preset updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdateSavedPresetResponse'
        "400":
          description: Invalid request (bad name, schema, or call mode); X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid bearer token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Preset not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: A preset with this name already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/tuning-profiles:
    get:
      tags:
        - auto-tune
      summary: List tuning profiles
      description: |-
        A tuning profile is an extraction configuration saved by
        `POST /v1/auto-tune/{id}/promote`. Returns a page of profile summaries,
        most recently created first; fetch `GET /v1/tuning-profiles/{id}` for one
        profile's full configuration.
      operationId: list_tuning_profiles
      parameters:
        - name: limit
          in: query
          description: Page size (default 50, accepted range 1..=100).
          required: false
          schema:
            type: integer
            format: int64
            default: 50
            maximum: 100
            minimum: 1
        - name: offset
          in: query
          description: Pagination offset (default 0, accepted range 0..=1,000,000).
          required: false
          schema:
            type: integer
            format: int64
            default: 0
            maximum: 1000000
            minimum: 0
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Tuning profiles for the current project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListTuningProfilesResponse'
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Auto-tune is disabled or the project is unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/tuning-profiles/{id}:
    get:
      tags:
        - auto-tune
      summary: Get a tuning profile
      description: '`404` when `id` does not name a profile in the caller''s project.'
      operationId: get_tuning_profile
      parameters:
        - name: id
          in: path
          description: Tuning profile ID
          required: true
          schema:
            type: string
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Tuning profile details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TuningProfileDetail'
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Tuning profile not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
    delete:
      tags:
        - auto-tune
      summary: Delete a tuning profile
      description: |-
        Irreversible: there is no soft-delete. Deleting a profile does not affect
        the auto-tune job that produced it, only the promoted profile record.
        `404` when `id` does not name a profile in the caller's project. Rejected
        with `403` for a read-only API key.
      operationId: delete_tuning_profile
      parameters:
        - name: id
          in: path
          description: Tuning profile ID
          required: true
          schema:
            type: string
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "204":
          description: Tuning profile deleted
        "400":
          description: Malformed request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Missing or invalid authentication
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Tuning profile not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Persistence error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/uploads/confirm:
    post:
      tags:
        - uploads
      summary: Confirm uploads are complete and start processing.
      description: Snapshot uploaded bytes and atomically persist job IDs with recovery manifests.
      operationId: confirm_upload
      parameters:
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfirmUploadRequest'
        required: true
      responses:
        "202":
          description: Uploads confirmed, processing started
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfirmUploadResponse'
        "400":
          description: Invalid request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Upload batch not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "409":
          description: Confirmation is in progress or uploaded bytes changed; retry
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: An uploaded document is larger than the project's configured limit
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: A project monthly extraction quota is exhausted; Retry-After gives seconds until the next UTC calendar month
          headers:
            Retry-After:
              schema:
                type: integer
                format: int64
                minimum: 0
              description: Seconds until the next UTC calendar month.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Authentication, local upload origin or dispatch is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/uploads/local/{project_id}/{token}:
    put:
      tags:
        - uploads
      summary: Upload bytes using an expiring one-use capability scoped to one project and object.
      description: |-
        Write raw document bytes for the selected project using the capability issued by presign.
        The upload size is bounded by the limit recorded when that capability was issued.
      operationId: put_local_upload
      parameters:
        - name: project_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: token
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
        required: true
      responses:
        "204":
          description: Document uploaded
        "400":
          description: 'The request is malformed: a path parameter, a query parameter or the body did not parse'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Capability missing, used or expired
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: Document too large
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security: []
  /v1/uploads/presign:
    post:
      tags:
        - uploads
      summary: Generate presigned upload URLs.
      description: |-
        Stages a durable project-scoped batch of up to 100 document metadata
        entries without carrying document bytes or enqueueing extraction. The cap
        bounds the pending-job fan-out created when the batch is confirmed.
        <!-- public batch-limit contract -->
      operationId: presign_upload
      parameters:
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PresignUploadRequest'
        required: true
      responses:
        "200":
          description: Presigned upload URLs generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PresignUploadResponse'
        "400":
          description: Invalid request; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "403":
          description: The API key is read-only and cannot use this method
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Selected project not found or unavailable to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "413":
          description: The request body is larger than this deployment accepts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "415":
          description: 'The request body was not sent with Content-Type: application/json'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Authentication, local upload origin or dispatch is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
  /v1/usage:
    get:
      tags:
        - usage
      summary: Aggregate extraction usage for a date range.
      description: |-
        Returns usage for the authenticated project between inclusive start and exclusive end dates,
        defaulting to the current month when dates are omitted.
      operationId: get_usage
      parameters:
        - name: start
          in: query
          description: Start date (ISO 8601, e.g. "2026-03-01"). Defaults to first day of current month.
          required: false
          schema:
            type: string
            format: date
        - name: end
          in: query
          description: Exclusive UTC end date (ISO 8601, e.g. "2026-04-01"). Defaults to first day of next month.
          required: false
          schema:
            type: string
            format: date
        - name: X-Project-ID
          in: header
          description: Optional selected project UUID. Defaults to the authenticated or local workspace project. Human membership and existing endpoint permissions are required; project API keys cannot select another project.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Usage statistics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageResponse'
        "400":
          description: Invalid date format; X-Project-ID must be a UUID
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "404":
          description: Selected project not found or unavailable to the caller
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "422":
          description: Request fails validation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "429":
          description: Rate limit exceeded for this caller. The body is the shared error envelope with code `rate_limited`; `Retry-After` and the `x-ratelimit-*` headers carry the wait.
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until this request may be retried.
            x-ratelimit-after:
              schema:
                type: integer
              description: Seconds until the rate-limit bucket admits another request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Maximum requests allowed in the current rate-limit burst.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests remaining in the current rate-limit burst.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "503":
          description: Session authentication is temporarily unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearer_auth: []
components:
  schemas:
    AnnotationKind:
      oneOf:
        - type: object
          description: Bold (strong) text formatting.
          required:
            - annotation_type
          properties:
            annotation_type:
              type: string
              enum:
                - bold
        - type: object
          description: Italic (emphasis) text formatting.
          required:
            - annotation_type
          properties:
            annotation_type:
              type: string
              enum:
                - italic
        - type: object
          description: Underlined text.
          required:
            - annotation_type
          properties:
            annotation_type:
              type: string
              enum:
                - underline
        - type: object
          description: Strikethrough text.
          required:
            - annotation_type
          properties:
            annotation_type:
              type: string
              enum:
                - strikethrough
        - type: object
          description: Inline code span.
          required:
            - annotation_type
          properties:
            annotation_type:
              type: string
              enum:
                - code
        - type: object
          description: Subscript text.
          required:
            - annotation_type
          properties:
            annotation_type:
              type: string
              enum:
                - subscript
        - type: object
          description: Superscript text.
          required:
            - annotation_type
          properties:
            annotation_type:
              type: string
              enum:
                - superscript
        - type: object
          description: Hyperlink annotation.
          required:
            - url
            - annotation_type
          properties:
            annotation_type:
              type: string
              enum:
                - link
            title:
              type:
                - string
                - "null"
              description: Optional link title attribute.
            url:
              type: string
              description: Hyperlink target URL.
        - type: object
          description: Highlighted text (PDF highlights, HTML `<mark>`).
          required:
            - annotation_type
          properties:
            annotation_type:
              type: string
              enum:
                - highlight
        - type: object
          description: Text color (CSS-compatible value, e.g. "#ff0000", "red").
          required:
            - value
            - annotation_type
          properties:
            annotation_type:
              type: string
              enum:
                - color
            value:
              type: string
              description: CSS-compatible color value (e.g. `"#ff0000"`, `"red"`).
        - type: object
          description: Font size with units (e.g. "12pt", "1.2em", "16px").
          required:
            - value
            - annotation_type
          properties:
            annotation_type:
              type: string
              enum:
                - font_size
            value:
              type: string
              description: Font size including unit (e.g. `"12pt"`, `"1.2em"`, `"16px"`).
        - type: object
          description: Extensible annotation for format-specific styling.
          required:
            - name
            - annotation_type
          properties:
            annotation_type:
              type: string
              enum:
                - custom
            name:
              type: string
              description: Name of the custom annotation kind.
            value:
              type:
                - string
                - "null"
              description: Optional value or parameter for the annotation.
      description: Types of inline text annotations.
    ApiKeyResponse:
      type: object
      description: |-
        API key details for list operations. Never includes the full key, only
        the prefix, for security.
      required:
        - id
        - key_prefix
        - scope
        - created_at
      properties:
        created_at:
          type: string
          format: date-time
          description: ISO 8601 creation timestamp.
        expires_at:
          type:
            - string
            - "null"
          description: |-
            Optional expiration date in ISO 8601 format. Both tiers reject the key
            after this timestamp.
        id:
          type: string
          format: uuid
          description: Unique API key identifier.
        key_prefix:
          type: string
          description: Key prefix for identification (e.g. `"kz_abcdefgh"`).
        last_used_at:
          type:
            - string
            - "null"
          description: Optional last-used timestamp in ISO 8601 format.
        name:
          type:
            - string
            - "null"
          description: User-provided name for the key, when set.
        revoked_at:
          type:
            - string
            - "null"
          description: Optional revocation timestamp in ISO 8601 format.
        scope:
          $ref: '#/components/schemas/ApiKeyScope'
          description: 'What the key may do: `read` or `write`.'
    ApiKeyScope:
      type: string
      description: |-
        What an API key may do.

        A `Read` key is refused on every request whose HTTP method is not safe.
        The safe methods are GET, HEAD, OPTIONS and TRACE. A `Write` key has full
        access. The rule is the method, not the route, so a read that is shaped as a
        POST needs a `Write` key. The value is stored in `api_keys.scope` and
        carried on the wire in lowercase.
      enum:
        - read
        - write
    ArchiveEntry:
      type: object
      description: |-
        A single file extracted from an archive.

        When archives (ZIP, TAR, 7Z, GZIP) are extracted with recursive extraction
        enabled, each processable file produces its own full `ExtractedDocument`.
      required:
        - path
        - mime_type
        - result
      properties:
        mime_type:
          type: string
          description: Detected MIME type of the file.
        path:
          type: string
          description: Archive-relative file path (e.g. "folder/document.pdf").
        result:
          $ref: '#/components/schemas/ExtractedDocument'
          description: Full extraction result for this file.
    ArchiveMetadata:
      type: object
      description: |-
        Archive (ZIP/TAR/7Z) metadata.

        Extracted from compressed archive files containing file lists and size information.
      required:
        - format
        - file_count
        - file_list
        - total_size
        - format_type
      properties:
        compressed_size:
          type:
            - integer
            - "null"
          format: int64
          description: Compressed size in bytes (if available)
          minimum: 0
        file_count:
          type: integer
          format: int32
          description: Total number of files in the archive
          minimum: 0
        file_list:
          type: array
          items:
            type: string
          description: List of file paths within the archive
        format:
          type: string
          description: Archive format ("ZIP", "TAR", "7Z", etc.)
        format_type:
          type: string
          enum:
            - archive
        total_size:
          type: integer
          format: int64
          description: Total uncompressed size in bytes
          minimum: 0
    Attributes:
      type: object
      description: |-
        Element attributes in Djot.

        Represents the attributes attached to elements using {.class #id key="value"} syntax.
      properties:
        classes:
          type: array
          items:
            type: string
          description: CSS classes (.class1 .class2)
        id:
          type:
            - string
            - "null"
          description: Element ID (#identifier)
        key_values:
          type: array
          items:
            $ref: '#/components/schemas/KeyValueAttribute'
          description: Key-value pairs (key="value")
    AudioMetadata:
      type: object
      description: |-
        Audio/video file metadata.

        Populated from container tags (ID3v2, MP4 atoms, Vorbis comments, etc.) and
        PCM decode properties. Available when the `transcription-types` feature is enabled.
      required:
        - format_type
      properties:
        bitrate:
          type:
            - integer
            - "null"
          format: int32
          description: Audio bitrate in kbps from the source file tags/properties.
          minimum: 0
        channels:
          type:
            - integer
            - "null"
          format: int32
          description: Number of audio channels (1 = mono, 2 = stereo).
          minimum: 0
        codec:
          type:
            - string
            - "null"
          description: Audio codec (e.g. "mp3", "aac", "opus", "flac").
        container:
          type:
            - string
            - "null"
          description: Container format (e.g. "mpeg", "mp4", "ogg", "wav").
        duration_ms:
          type:
            - integer
            - "null"
          format: int64
          description: Duration in milliseconds derived from the decoded audio stream.
          minimum: 0
        format_type:
          type: string
          enum:
            - audio
        sample_rate_hz:
          type:
            - integer
            - "null"
          format: int32
          description: Sample rate in Hz after decode (always 16000 when resampled for Whisper).
          minimum: 0
    AuditEntry:
      type: object
      description: A single customer-facing audit log entry.
      required:
        - id
        - actor
        - action
        - resource_type
        - metadata
        - created_at
      properties:
        action:
          type: string
          description: Action performed (e.g. `"job.submit"`, `"api_key.revoke"`).
        actor:
          type: string
          description: Actor that performed the action (e.g. an API key prefix or user id).
        created_at:
          type: string
          format: date-time
          description: ISO 8601 event timestamp.
        id:
          type: string
          format: uuid
          description: Unique event identifier.
        metadata:
          type: object
          description: Additional structured metadata describing the event.
          additionalProperties: {}
          propertyNames:
            type: string
        resource_id:
          type:
            - string
            - "null"
          description: Identifier of the affected resource, when applicable.
        resource_type:
          type: string
          description: Type of resource the action applied to (e.g. `"job"`, `"api_key"`).
    AuthConfigResponse:
      type: object
      description: '`GET /auth/config` response: which auth methods this instance accepts.'
      required:
        - methods
        - oidc
      properties:
        identity:
          $ref: '#/components/schemas/LocalIdentity'
          description: Local operator identity when authentication is disabled.
        methods:
          type: array
          items:
            type: string
          description: Accepted authentication methods (`"api_key"`, optionally `"oidc"`).
        mode:
          $ref: '#/components/schemas/AuthMode'
          description: Explicit instance authentication policy; older servers require authentication.
        oidc:
          $ref: '#/components/schemas/OidcInfo'
          description: |-
            OIDC-specific detail, used by the frontend to decide whether to render
            a "Sign in with Google" button.
        saml:
          oneOf:
            - $ref: '#/components/schemas/SamlInfo'
              description: |-
                SAML discovery endpoint. Provider details remain project-scoped and are
                disclosed only after a verified email-domain lookup.
            - type: "null"
    AuthMode:
      type: string
      description: Authentication policy advertised by the instance.
      enum:
        - required
        - disabled
    AutoTuneBudget:
      type: object
      description: Budget limits for an auto-tune run.
      properties:
        max_billable_pages:
          type:
            - integer
            - "null"
          format: int64
          description: Maximum number of billable pages to process across all trials.
          minimum: 0
        max_trials:
          type:
            - integer
            - "null"
          format: int32
          description: Maximum number of trials to run.
          minimum: 0
        max_wall_clock_secs:
          type:
            - integer
            - "null"
          format: int64
          description: Maximum wall-clock time, in seconds, before the run is cut short.
          minimum: 0
    AutoTuneCapabilitiesResponse:
      type: object
      description: |-
        Auto-tune capabilities (OCR backends and tunable knobs) reported for a deployment. Powers a
        deployment-aware wizard rather than hardcoding backend/knob names on the frontend.
      required:
        - max_trials
        - max_wall_clock_secs
        - ocr_backends
        - default_ocr_ladder
        - tunable_knobs
      properties:
        default_ocr_ladder:
          type: array
          items:
            type: string
          description: Backend names composing the default OCR search ladder, in search order.
        max_trials:
          type: integer
          format: int32
          description: Maximum number of trials accepted for one run by this deployment.
          minimum: 0
        max_wall_clock_secs:
          type: integer
          format: int64
          description: Maximum wall-clock budget accepted for one run, in seconds.
          minimum: 0
        ocr_backends:
          type: array
          items:
            $ref: '#/components/schemas/OcrBackendCapabilityInfo'
          description: OCR backends available to this deployment.
        structured_ground_truth_available:
          type: boolean
          description: Whether the deployment can score structured ground truth with its configured language model.
        tunable_knobs:
          type: array
          items:
            $ref: '#/components/schemas/TunableKnobInfo'
          description: Knobs that auto-tune can search over.
    AutoTuneDocumentInput:
      type: object
      description: One document submitted to an auto-tune run, paired with its ground truth.
      required:
        - filename
        - ground_truth_kind
        - ground_truth
      properties:
        content_ref:
          type:
            - string
            - "null"
          description: |-
            Reserved, not yet supported: a request that sets this field is rejected with 400 on every
            tier (no backend resolves it yet). Upload the document bytes as a multipart file part instead.
            Intended future use: referencing previously stored bytes by a per-project storage key without
            re-uploading them.
          readOnly: true
        filename:
          type: string
          description: Original filename of the document (used for format detection).
        ground_truth:
          type: string
          description: |-
            The ground truth payload. For `markdown`/`plaintext`, this is the
            raw expected text. For `structured`, this is a JSON string encoding
            `{"schema": ..., "expected": ...}` (both the JSON schema and the
            expected output are required).
        ground_truth_kind:
          $ref: '#/components/schemas/GroundTruthKind'
          description: Which kind of ground truth is supplied.
    AutoTuneHealth:
      type: object
      description: Auto-tune readiness summary for the health response.
      required:
        - enabled
      properties:
        enabled:
          type: boolean
          description: |-
            Whether the in-process auto-tune runtime is wired. Mirrors
            `crate::auto_tune::auto_tune_runtime_enabled` at boot time — `false` means every
            `/v1/auto-tune` and `/v1/tuning-profiles` request 404s with `auto_tune.disabled`.
    AutoTuneJobStatus:
      type: object
      description: Current status and progress of an auto-tune job.
      required:
        - auto_tune_job_id
        - status
        - trials_completed
        - trials_total
      properties:
        auto_tune_job_id:
          type: string
          description: Unique ID of the auto-tune job (ULID).
        best_score:
          type:
            - number
            - "null"
          format: double
          description: Best primary score observed so far, if any trial has completed.
        budget_spent:
          type:
            - integer
            - "null"
          format: int64
          description: |-
            Billable pages metered across this run's terminal trials on Enterprise, including completed,
            partially successful, failed and cancelled trials. In-flight trials are excluded.
            Zero before any terminal trial is metered; null on Pro, where auto-tune trial-page spending is unavailable.
          minimum: 0
        error:
          type:
            - string
            - "null"
          description: Why the run failed. Set only when `status` is `failed`, and null otherwise.
        status:
          type: string
          description: Current job status (e.g. `queued`, `running`, `completed`, `failed`).
        trials_completed:
          type: integer
          format: int32
          description: Number of trials completed so far.
          minimum: 0
        trials_total:
          type: integer
          format: int32
          description: Total number of trials planned for this run.
          minimum: 0
    AutoTuneJobSummary:
      type: object
      description: Lightweight projection of an auto-tune job (for listings).
      required:
        - auto_tune_job_id
        - status
        - trials_completed
        - trials_total
        - created_at
      properties:
        auto_tune_job_id:
          type: string
          description: Unique ID of the auto-tune job (ULID).
        best_score:
          type:
            - number
            - "null"
          format: double
          description: Best primary score observed, if any trial has completed.
        created_at:
          type: string
          format: date-time
          description: ISO 8601 creation timestamp.
        status:
          type: string
          description: Current job status (`running`, `completed`, or `failed`).
        trials_completed:
          type: integer
          format: int32
          description: Number of trials completed so far.
          minimum: 0
        trials_total:
          type: integer
          format: int32
          description: Total number of trials planned for this run.
          minimum: 0
    AutoTuneMultipartForm:
      type: object
      description: |-
        Multipart (`multipart/form-data`) variant of the submit-auto-tune request body.

        Send a text part named `request` carrying a JSON-encoded
        `CreateAutoTuneJobRequest`, plus one binary part per document whose
        part name (or filename) matches a `documents[].filename` entry in the
        request.
      required:
        - request
        - file
      properties:
        file:
          type: array
          items:
            type: integer
            format: binary
            minimum: 0
          description: A document file. Repeat with one binary part per file.
          contentMediaType: application/octet-stream
        request:
          $ref: '#/components/schemas/CreateAutoTuneJobRequest'
          description: The JSON-encoded `request` part — a `CreateAutoTuneJobRequest`.
    AutoTuneResult:
      type: object
      description: Final result of a completed auto-tune run.
      required:
        - auto_tune_job_id
        - profile
        - leaderboard
      properties:
        auto_tune_job_id:
          type: string
          description: Unique ID of the auto-tune job (ULID).
        held_out_confidence:
          type:
            - number
            - "null"
          format: double
          description: Gain from routing over the global configuration on held-out documents.
        leaderboard:
          type: array
          items:
            $ref: '#/components/schemas/LeaderboardEntry'
          description: Ranked leaderboard of trialed configurations.
        profile:
          type: object
          description: |-
            The winning tuning profile artifact. The concrete `TuningProfile`
            shape is defined in `autotune-core`; here it is opaque JSON.
        routing_outcome:
          $ref: '#/components/schemas/RoutingOutcome'
          description: |-
            Whether routing was requested, kept routes, or fell back to the global configuration.
            Absent on results recorded before this field was introduced.
        search_space_total:
          type:
            - integer
            - "null"
          format: int64
          description: |-
            Combinations the search space held. Compare it with the leaderboard
            length to see how much of the space the trial budget reached: a run
            that explored a fraction of it otherwise reads like one that explored
            all of it. Absent on results recorded before this was reported.
          minimum: 0
    AutoTuneSearchConfig:
      type: object
      description: Search configuration for an auto-tune run.
      required:
        - search_space
      properties:
        max_trials:
          type:
            - integer
            - "null"
          format: int32
          description: Maximum number of trials to run during the search.
          minimum: 0
        search_params:
          $ref: '#/components/schemas/AutoTuneSearchParams'
          description: |-
            Structured, wizard-friendly alternative to `search_space`. Ignored when `search_space` is
            also set.
        search_space:
          type: object
          description: |-
            Which knobs/axes to tune, and their candidate values. The concrete
            `SearchSpace` shape is defined in `autotune-core`; here it is
            opaque JSON so this crate has no dependency on that crate.

            Power-user escape hatch. Takes precedence over `search_params` when both are set.
        tolerance:
          type:
            - number
            - "null"
          format: double
          description: Tolerance band used when ranking near-tied configurations.
          maximum: 1
          minimum: 0
    AutoTuneSearchParams:
      type: object
      description: |-
        Wizard-friendly, structured alternative to `AutoTuneSearchConfig.search_space`: which OCR
        backends to sweep and per-knob candidate values, without a client having to construct a full
        `autotune_core::search::SearchSpace`.
      required:
        - candidates
      properties:
        candidates:
          type: object
          description: |-
            Per-knob candidate-value overrides, keyed by the knob's wire name (see
            `TunableKnobInfo.knob` / `autotune_core::search::Knob` for the full set of names). Each
            knob's values are opaque JSON so this crate has no dependency on `autotune-core`.
          additionalProperties:
            type: array
            items: {}
          propertyNames:
            type: string
        ocr_backends:
          type:
            - array
            - "null"
          items:
            type: string
          description: |-
            OCR backends to sweep, in dominance order. `None` falls back to the deployment's default
            OCR ladder (see `AutoTuneCapabilitiesResponse.default_ocr_ladder`).
    AutoTuneWebhookEvent:
      type: object
      description: |-
        Auto-Tune terminal body. Retrieve the full result through
        `GET /v1/auto-tune/{id}/result`; webhook retries carry this same small body.
      required:
        - event
        - event_id
        - project_id
        - auto_tune_job_id
        - status
        - timestamp
        - trials_completed
        - trials_total
      properties:
        auto_tune_job_id:
          type: string
        best_score:
          type:
            - number
            - "null"
          format: double
        error:
          $ref: '#/components/schemas/WebhookEventError'
        event:
          $ref: '#/components/schemas/AutoTuneWebhookEventType'
        event_id:
          type: string
          format: uuid
        metadata:
          type: object
          additionalProperties:
            type: string
          propertyNames:
            type: string
        project_id:
          type: string
          format: uuid
        status:
          $ref: '#/components/schemas/AutoTuneWebhookStatus'
        timestamp:
          type: string
          format: date-time
        trials_completed:
          type: integer
          format: int32
          minimum: 0
        trials_total:
          type: integer
          format: int32
          minimum: 0
    AutoTuneWebhookEventType:
      type: string
      description: Terminal event names for Auto-Tune runs, separate from extraction-job events.
      enum:
        - auto_tune.completed
        - auto_tune.failed
    AutoTuneWebhookStatus:
      type: string
      enum:
        - completed
        - failed
    BeginOAuthResponse:
      type: object
      description: OAuth connection response.
      required:
        - authorize_url
      properties:
        authorize_url:
          type: string
          description: Provider authorization URL to send the user to.
    BibtexMetadata:
      type: object
      description: BibTeX bibliography metadata.
      required:
        - entry_count
        - format_type
      properties:
        authors:
          type: array
          items:
            type: string
          description: Author names collected across all bibliography entries.
        citation_keys:
          type: array
          items:
            type: string
          description: BibTeX citation keys (e.g. `"knuth1984"`) for all entries.
        entry_count:
          type: integer
          description: Number of entries in the bibliography.
          minimum: 0
        entry_types:
          type:
            - object
            - "null"
          description: Count of entries grouped by BibTeX entry type (e.g. `"article"` → 5).
          additionalProperties:
            type: integer
            minimum: 0
          propertyNames:
            type: string
        format_type:
          type: string
          enum:
            - bibtex
        year_range:
          $ref: '#/components/schemas/YearRange'
          description: Earliest and latest publication years found in the bibliography.
    BlockType:
      type: string
      description: Types of block-level elements in Djot.
      enum:
        - paragraph
        - heading
        - blockquote
        - code_block
        - list_item
        - ordered_list
        - bullet_list
        - task_list
        - definition_list
        - definition_term
        - definition_description
        - div
        - section
        - thematic_break
        - raw_block
        - math_display
    BoundingBox:
      type: object
      description: Bounding box coordinates for element positioning.
      required:
        - x0
        - y0
        - x1
        - y1
      properties:
        x0:
          type: number
          format: double
          description: Left x-coordinate
        x1:
          type: number
          format: double
          description: Right x-coordinate
        y0:
          type: number
          format: double
          description: Bottom y-coordinate
        y1:
          type: number
          format: double
          description: Top y-coordinate
    CellChange:
      type: object
      description: |-
        A single changed cell within a table.

        Defined here (rather than only in `crate::diff`) so `RevisionDelta` can
        reference it unconditionally, without requiring the `diff` Cargo feature.
        `crate::diff` re-exports this type verbatim.
      required:
        - row
        - col
        - from
        - to
      properties:
        col:
          type: integer
          description: Zero-based column index.
          minimum: 0
        from:
          type: string
          description: Value before the change.
        row:
          type: integer
          description: Zero-based row index.
          minimum: 0
        to:
          type: string
          description: Value after the change.
    Chunk:
      type: object
      description: |-
        A text chunk with optional embedding and metadata.

        Chunks are created when chunking is enabled in `ExtractionConfig`. Each chunk
        contains the text content, optional embedding vector (if embedding generation
        is configured), and metadata about its position in the document.
      required:
        - content
        - metadata
      properties:
        chunk_type:
          $ref: '#/components/schemas/ChunkType'
          description: |-
            Semantic structural classification of this chunk.

            Assigned by the heuristic classifier based on content patterns and
            heading context. Defaults to `ChunkType::Unknown` when no rule matches.
        content:
          type: string
          description: The text content of this chunk.
        embedding:
          type:
            - array
            - "null"
          items:
            type: number
            format: float
          description: |-
            Optional embedding vector for this chunk.

            Only populated when `EmbeddingConfig` is provided in chunking configuration.
            The dimensionality depends on the chosen embedding model.
        late_interaction:
          $ref: '#/components/schemas/MultiVectorEmbedding'
          description: |-
            Optional ColBERT-style multi-vector (late-interaction) embedding for this chunk.

            Only populated when late-interaction embedding generation is configured for
            chunking. `None` otherwise, including on builds without the `late-interaction`
            feature.

            Uses the crate-root `MultiVectorEmbedding` alias for the same reason
            `sparse_embedding` uses `SparseEmbedding` — see that field's docs.
        metadata:
          $ref: '#/components/schemas/ChunkMetadata'
          description: Metadata about this chunk's position and properties.
        sparse_embedding:
          $ref: '#/components/schemas/SparseEmbedding'
          description: |-
            Optional sparse (SPLADE) learned embedding for this chunk.

            Only populated when sparse-embedding generation is configured for chunking.
            `None` otherwise, including on builds without the `sparse-embeddings` feature.

            Uses the crate-root `SparseEmbedding` alias rather than
            `crate::sparse_embeddings::SparseEmbedding` directly: the `sparse_embeddings`
            module itself only compiles under `sparse-embeddings`/`sparse-embedding-presets`,
            while the crate-root alias is always defined (a field-compatible stub on builds
            without either feature), so this field — and `Chunk` itself — compiles on every
            feature combination, including the crate's default features.
    ChunkMetadata:
      type: object
      description: Metadata about a chunk's position in the original document.
      required:
        - byte_start
        - byte_end
        - chunk_index
        - total_chunks
      properties:
        byte_end:
          type: integer
          description: Byte offset where this chunk ends in the original text (UTF-8 valid boundary).
          minimum: 0
        byte_start:
          type: integer
          description: Byte offset where this chunk starts in the original text (UTF-8 valid boundary).
          minimum: 0
        chunk_index:
          type: integer
          description: Zero-based index of this chunk in the document.
          minimum: 0
        classifications:
          type: array
          items:
            $ref: '#/components/schemas/ClassificationLabel'
          description: |-
            Multi-label classification result for this chunk.

            Populated by the chunk-classification post-processor when
            `ExtractionConfig.chunk_classification`
            is set. A chunk may match zero, one, or many of the configured label
            definitions. Empty when chunk classification was not configured.
        first_page:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            First page number this chunk spans (1-indexed).

            Only populated when page tracking is enabled in extraction configuration.
          minimum: 0
        heading_context:
          $ref: '#/components/schemas/HeadingContext'
          description: |-
            Heading context when using Markdown chunker.

            Contains the heading hierarchy this chunk falls under.
            Only populated when `ChunkerType::Markdown` is used.
        heading_path:
          type: array
          items:
            type: string
          description: |-
            Flattened heading trail from document root to this chunk's section.

            Each element is a heading's text, outermost first. Derived from
            `heading_context` when present; empty otherwise.
            Provides a binding-friendly, RAG-shaped breadcrumb without requiring
            callers to walk the nested `HeadingContext` structure.
        image_indices:
          type: array
          items:
            type: integer
            format: int32
            minimum: 0
          description: |-
            Indices into `ExtractedDocument.images` for images on pages covered by this chunk.

            Contains zero-based indices into the top-level `images` collection for every
            image whose `page_number` falls within `[first_page, last_page]`.
            Empty when image extraction is disabled or the chunk spans no pages with images.
        last_page:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Last page number this chunk spans (1-indexed, equal to first_page for single-page chunks).

            Only populated when page tracking is enabled in extraction configuration.
          minimum: 0
        node_ids:
          type: array
          items:
            type: string
          description: |-
            Ids of the `DocumentNode`s
            this chunk was derived from.

            Joins a chunk back to the structured document tree via
            `DocumentNode.id`.
            Populated from exact node provenance when available, with a textual
            containment fallback for rendered chunks that do not retain byte offsets.
        page_spans:
          type: array
          items:
            $ref: '#/components/schemas/PageSpan'
          description: |-
            Per-page bounding-box spans this chunk covers, for viewer highlighting (#1295).

            One entry per page the chunk overlaps, in page order — the first and last entries'
            `page` fields equal `first_page`/`last_page`.
            Populated whenever page-boundary provenance is available (the same condition under
            which `first_page`/`last_page` are populated); each entry's `bbox` is additionally
            populated when the document's structured node tree (`ExtractedDocument.document`) is
            available, as the union of that page's body-layer node bounding boxes found within this
            chunk. Empty when page-boundary provenance is unavailable (mirrors `first_page`/
            `last_page` being `None`).
        token_count:
          type:
            - integer
            - "null"
          description: |-
            Number of tokens in this chunk (if available).

            This is calculated by the embedding model's tokenizer if embeddings are enabled.
          minimum: 0
        total_chunks:
          type: integer
          description: Total number of chunks in the document.
          minimum: 0
    ChunkType:
      type: string
      description: |-
        Semantic structural classification of a text chunk.

        Assigned by the heuristic classifier in `chunking::classifier`.
        Defaults to `Unknown` when no rule matches.
        Designed to be extended in future versions without breaking changes.
      enum:
        - heading
        - party_list
        - definitions
        - operative_clause
        - signature_block
        - schedule
        - table_like
        - formula
        - code_block
        - function
        - class
        - module
        - image
        - org_chart
        - diagram
        - unknown
    ChunkingConfig:
      type: object
      description: Text chunking configuration.
      properties:
        chunker_type:
          type:
            - string
            - "null"
          description: |-
            Chunker strategy: `text`, `markdown`, `yaml`, or `semantic`. Default
            `text`. Any other value is refused with a 400.
        embedding:
          $ref: '#/components/schemas/EmbeddingConfig'
          description: Embedding generation configuration
        max_characters:
          type:
            - integer
            - "null"
          format: int32
          description: Maximum chunk size in characters. Defaults to 1000 when omitted.
          minimum: 0
        overlap:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Overlap between adjacent chunks in characters. Defaults to 200 when omitted,
            reduced to fit when `max_characters` is smaller than that.
          minimum: 0
        preset:
          type:
            - string
            - "null"
          description: Preset name (overrides other settings)
        trim:
          type:
            - boolean
            - "null"
          description: Trim whitespace from chunk boundaries. Defaults to true when omitted.
      additionalProperties: false
    CitationMetadata:
      type: object
      description: Citation file metadata (RIS, PubMed, EndNote).
      required:
        - citation_count
        - format_type
      properties:
        authors:
          type: array
          items:
            type: string
          description: Author names collected across all citation records.
        citation_count:
          type: integer
          description: Total number of citation records in the file.
          minimum: 0
        dois:
          type: array
          items:
            type: string
          description: DOI identifiers found in the citation records.
        format:
          type:
            - string
            - "null"
          description: Detected citation file format (e.g. `"ris"`, `"pubmed"`, `"endnote"`).
        format_type:
          type: string
          enum:
            - citation
        keywords:
          type: array
          items:
            type: string
          description: Keywords collected from all citation records.
        year_range:
          $ref: '#/components/schemas/YearRange'
          description: Earliest and latest publication years found in the file.
    ClassificationLabel:
      type: object
      description: A single label + confidence pair.
      required:
        - label
      properties:
        confidence:
          type:
            - number
            - "null"
          format: float
          description: |-
            Backend-reported confidence in `[0.0, 1.0]`. `None` when the backend (e.g. an LLM
            prompt without explicit confidence schema) did not report one.
        label:
          type: string
          description: Label name as configured in `PageClassificationConfig::labels`.
    CodeChunkInfo:
      type: object
      description: |-
        A single structurally-meaningful code chunk produced by tree-sitter parsing.

        Purpose-built payload owned by xberg — deliberately does not expose the upstream
        `tree_sitter_language_pack` types, so binding generators never need to resolve an
        external crate's types across FFI/language boundaries.
      required:
        - text
        - context_path
        - node_types
        - byte_start
        - byte_end
      properties:
        byte_end:
          type: integer
          description: Exclusive end byte offset of this chunk in the original source.
          minimum: 0
        byte_start:
          type: integer
          description: Inclusive start byte offset of this chunk in the original source.
          minimum: 0
        context_path:
          type: array
          items:
            type: string
          description: Hierarchical path of enclosing structural items (e.g. `["MyClass", "my_method"]`).
        node_types:
          type: array
          items:
            type: string
          description: |-
            Tree-sitter node kinds that appear at the top level of this chunk (e.g.
            `"function_definition"`, `"class_definition"`).
        text:
          type: string
          description: The raw source text of this chunk.
    CodeDataAttribute:
      type: object
      description: |-
        An XML-style attribute attached to an `Element` node.

        Populated only for `CodeDataNodeKind::Element`; always empty for `KeyValue` and
        `Sequence` nodes.
      required:
        - name
        - value
        - byte_start
        - byte_end
      properties:
        byte_end:
          type: integer
          description: Exclusive end byte offset of the `name="value"` attribute token.
          minimum: 0
        byte_start:
          type: integer
          description: Inclusive start byte offset of the `name="value"` attribute token.
          minimum: 0
        name:
          type: string
          description: Attribute name (e.g. `"class"`, `"href"`).
        value:
          type: string
          description: Attribute value as a raw string (quotes stripped).
    CodeDataNode:
      type: object
      description: |-
        A node in the hierarchical data tree produced by data-format extraction.

        Purpose-built payload owned by xberg — mirrors
        `tree_sitter_language_pack::DataNode` but flattens its `Span` down to plain byte
        offsets, so binding generators never need to resolve an external crate's types
        across FFI/language boundaries.
      required:
        - kind
        - byte_start
        - byte_end
      properties:
        attributes:
          type: array
          items:
            $ref: '#/components/schemas/CodeDataAttribute'
          description: |-
            Attributes on element-shape nodes (XML `STag` attributes). Empty for all
            other kinds.
        byte_end:
          type: integer
          description: Exclusive end byte offset of this node in the original source.
          minimum: 0
        byte_start:
          type: integer
          description: Inclusive start byte offset of this node in the original source.
          minimum: 0
        children:
          type: array
          items:
            $ref: '#/components/schemas/CodeDataNode'
          description: Children for nested containers and XML element bodies.
        key:
          type:
            - string
            - "null"
          description: |-
            Key, attribute name, tag name, or positional index (`"0"`, `"1"`, …).
            `None` at the document root.
        kind:
          $ref: '#/components/schemas/CodeDataNodeKind'
          description: Whether this node is a key/value pair, XML element, or sequence item.
        value:
          type:
            - string
            - "null"
          description: |-
            Leaf scalar value, if any. `None` for containers (objects, arrays, XML
            elements with child elements).
    CodeDataNodeKind:
      type: string
      description: |-
        Discriminates the shape of a `CodeDataNode`.

        Purpose-built mirror of `tree_sitter_language_pack::DataNodeKind` — kept as an
        xberg-owned type so binding generators never need to resolve the upstream crate's
        types across FFI/language boundaries.
      enum:
        - key_value
        - element
        - sequence
    CodeMetadata:
      type: object
      description: |-
        Code-format metadata: the structural chunks produced by tree-sitter parsing.

        Wrapped by `FormatMetadata.Code`. Kept as a named struct (rather than an inline
        enum-variant body) so serde can tag it under internal tagging and utoipa can emit a
        referenceable `CodeMetadata` component in the OpenAPI schema.
      required:
        - chunks
        - format_type
      properties:
        chunks:
          type: array
          items:
            $ref: '#/components/schemas/CodeChunkInfo'
          description: Structural code chunks (function/class/module boundaries).
        data:
          $ref: '#/components/schemas/CodeDataNode'
          description: |-
            Hierarchical key/value data tree extracted from data-format source
            (JSON, YAML, TOML, XML, CSV, etc.), when data extraction was enabled.
        format_type:
          type: string
          enum:
            - code
    CollectionChunkingConfig:
      type: object
      description: |-
        Per-collection text chunking configuration.

        Mirrors the tunable knobs of xberg's chunker. Every field is optional;
        omitted fields fall back to the ingest pipeline's defaults. The API layer
        converts this into xberg's native `ChunkingConfig` before persisting it
        to the collection, so the ingest pipeline consumes the canonical type.
      properties:
        chunker_type:
          type:
            - string
            - "null"
          description: 'Chunker strategy: `text`, `markdown`, `yaml`, or `semantic`. Default `text`.'
          pattern: ^(text|markdown|yaml|semantic)$
        max_characters:
          type:
            - integer
            - "null"
          format: int32
          description: Maximum characters per chunk (must be greater than `overlap`). Default 1000.
          minimum: 100
        overlap:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Overlap between adjacent chunks in characters (must be less than
            `max_characters`). Default 200.
          minimum: 0
        topic_threshold:
          type:
            - number
            - "null"
          format: float
          description: |-
            Cosine-similarity threshold (0.0–1.0) for the `semantic` chunker's topic
            boundary detection. Ignored by other chunker types.
          maximum: 1
          minimum: 0
        trim:
          type:
            - boolean
            - "null"
          description: Trim whitespace from chunk boundaries. Default true.
      additionalProperties: false
    CollectionResponse:
      type: object
      description: Response for collection retrieval / listing
      required:
        - id
        - name
        - embedding_dim
        - distance_metric
        - index_method
        - index_status
      properties:
        chunking_config:
          $ref: '#/components/schemas/CollectionChunkingConfig'
          description: Per-collection text chunking configuration
        distance_metric:
          type: string
          description: Distance metric
        embedding_dim:
          type: integer
          format: int32
          description: Embedding dimension
          minimum: 0
        embedding_source:
          type:
            - string
            - "null"
          description: Embedding source for this collection
        id:
          type: string
          format: uuid
          description: Collection unique identifier
        index_error:
          type: string
          description: Safe failure summary when `index_status` is `failed`
        index_method:
          type: string
          description: Vector index method
        index_status:
          type: string
          description: Durable state of the collection's currently served vector index.
          enum:
            - pending
            - building
            - ready
            - failed
        name:
          type: string
          description: Collection name
        quota_max_documents:
          type:
            - integer
            - "null"
          format: int64
          description: Maximum documents quota
          minimum: 0
        rerank_default:
          $ref: '#/components/schemas/RerankerSpec'
          description: Default reranker spec for retrieve operations on this collection
        stats:
          $ref: '#/components/schemas/CollectionStatsResponse'
          description: |-
            Collection statistics. Populated on the single-collection detail view
            (`GET /v1/rag/collections/{name}`); omitted from the list view, which is
            intentionally lightweight and does not run per-collection counts.
    CollectionStatsResponse:
      type: object
      description: Collection statistics response
      required:
        - document_count
        - chunk_count
      properties:
        chunk_count:
          type: integer
          format: int64
          description: Total chunks across all documents
          minimum: 0
        document_count:
          type: integer
          format: int64
          description: Total documents in collection
          minimum: 0
        last_ingested_at:
          type:
            - string
            - "null"
          description: Last ingestion timestamp (RFC3339)
    ConfirmUploadRequest:
      type: object
      description: Request body for confirming uploads.
      required:
        - batch_id
      properties:
        batch_id:
          type: string
          format: uuid
          description: Batch ID from the presign response.
    ConfirmUploadResponse:
      type: object
      description: Response from the confirm endpoint.
      required:
        - job_ids
        - status
      properties:
        job_ids:
          type: array
          items:
            type: string
          description: Job IDs that are now queued for processing
        status:
          type: string
          description: Status of the jobs
    ContentFilterConfig:
      type: object
      description: Content filtering configuration (headers, footers, watermarks).
      properties:
        include_footers:
          type:
            - boolean
            - "null"
          description: Include running footers
        include_headers:
          type:
            - boolean
            - "null"
          description: Include running headers
        include_watermarks:
          type:
            - boolean
            - "null"
          description: Include watermarks
        strip_repeating_text:
          type:
            - boolean
            - "null"
          description: Strip cross-page repeating text. Defaults to true when omitted.
      additionalProperties: false
    ContentLayer:
      type: string
      description: |-
        Content layer classification for document nodes.

        Replaces separate body/furniture arrays with per-node granularity.
      enum:
        - body
        - header
        - footer
        - footnote
    ContributorRole:
      type: object
      description: JATS contributor with role.
      required:
        - name
      properties:
        name:
          type: string
          description: Contributor display name.
        role:
          type:
            - string
            - "null"
          description: Contributor role (e.g. `"author"`, `"editor"`).
    CrawlChildJob:
      type: object
      description: One extraction job spawned by a crawl job.
      required:
        - id
        - filename
        - status
      properties:
        body_bytes:
          type:
            - integer
            - "null"
          format: int64
          description: Source response-body size.
        filename:
          type: string
          description: Original filename.
        id:
          type: string
          format: uuid
          description: Child job identifier.
        processing_time_ms:
          type:
            - integer
            - "null"
          format: int64
          description: Processing duration in milliseconds.
        result:
          $ref: '#/components/schemas/ExtractedDocument'
          description: Embedded extraction result, when available.
        source_url:
          type:
            - string
            - "null"
          description: URL that produced the document.
        status:
          type: string
          description: Current child-job status.
    CrawlEventV1:
      oneOf:
        - type: object
          description: A page was successfully crawled and indexed.
          required:
            - crawl_job_id
            - ts
            - url
            - status_code
            - depth
            - kind
          properties:
            crawl_job_id:
              type: string
              format: uuid
              description: Job ID this event belongs to
            depth:
              type: integer
              description: Crawl depth when this page was discovered
              minimum: 0
            kind:
              type: string
              enum:
                - page
            status_code:
              type: integer
              format: int32
              description: HTTP status code
              minimum: 0
            ts:
              type: string
              format: date-time
              description: Event timestamp (UTC)
            url:
              type: string
              description: URL that was crawled
        - type: object
          description: An error occurred while crawling a URL.
          required:
            - crawl_job_id
            - ts
            - url
            - error
            - kind
          properties:
            crawl_job_id:
              type: string
              format: uuid
              description: Job ID this event belongs to
            error:
              type: string
              description: Error description
            kind:
              type: string
              enum:
                - error
            terminal:
              type: boolean
              description: Whether this error is the job's terminal event.
            ts:
              type: string
              format: date-time
              description: Event timestamp (UTC)
            url:
              type: string
              description: URL that failed
        - type: object
          description: A new URL was discovered during crawling.
          required:
            - crawl_job_id
            - ts
            - url
            - depth
            - kind
          properties:
            crawl_job_id:
              type: string
              format: uuid
              description: Job ID this event belongs to
            depth:
              type: integer
              description: Depth at which this URL was discovered
              minimum: 0
            kind:
              type: string
              enum:
                - discovered
            ts:
              type: string
              format: date-time
              description: Event timestamp (UTC)
            url:
              type: string
              description: URL that was discovered
        - type: object
          description: Crawl job completed.
          required:
            - crawl_job_id
            - ts
            - pages_crawled
            - kind
          properties:
            crawl_job_id:
              type: string
              format: uuid
              description: Job ID this event belongs to
            kind:
              type: string
              enum:
                - complete
            pages_crawled:
              type: integer
              description: Total pages successfully crawled
              minimum: 0
            ts:
              type: string
              format: date-time
              description: Event timestamp (UTC)
      description: |-
        A crawl event published to the SSE stream.

        Tagged union with event kind as the discriminator. Each variant carries
        `crawl_job_id` and `ts` (timestamp) for client-side ordering and deduplication.

        Internally tagged on `kind` (`#[serde(tag = "kind")]`): each variant
        serializes as a flat JSON object with a `kind` field alongside its other
        fields, e.g. `{"kind":"page","crawl_job_id":"...","ts":"...",...}`. utoipa
        derives the matching `oneOf` schema straight from that `#[serde(tag = ..)]`
        attribute, so no separate `#[schema(...)]` tagging is needed. An explicit
        `discriminator` is not applicable here: utoipa only supports OpenAPI
        discriminators for `#[serde(untagged)]` enums with single-reference-field
        variants, not internally tagged enums like this one.
    CrawlJobResponse:
      type: object
      description: Metadata and progress for one crawl job.
      required:
        - id
        - status
        - seed_urls
        - output_mode
        - created_at
        - progress
      properties:
        created_at:
          type: string
          format: date-time
          description: ISO 8601 creation timestamp.
        id:
          type: string
          format: uuid
          description: Crawl job identifier.
        jobs:
          type: array
          items:
            $ref: '#/components/schemas/CrawlChildJob'
          description: Child extraction jobs.
        output_mode:
          type: string
          description: Requested output mode.
        pages:
          type: array
          items:
            $ref: '#/components/schemas/CrawledPage'
          description: Retained raw pages.
        progress:
          $ref: '#/components/schemas/CrawlProgress'
          description: Current progress counters.
        seed_urls:
          type: array
          items:
            type: string
          description: Submitted seed URLs.
        status:
          $ref: '#/components/schemas/CrawlJobStatus'
          description: Current crawl status.
    CrawlJobStatus:
      type: string
      description: Crawl job lifecycle status.
      enum:
        - pending
        - crawling
        - extracting
        - completed
        - partial_success
        - failed
        - cancelled
    CrawlProgress:
      type: object
      description: Progress counters for a crawl job.
      required:
        - pages_crawled
        - documents_discovered
        - documents_extracted
        - documents_failed
      properties:
        documents_discovered:
          type: integer
          format: int32
          description: Documents discovered so far.
        documents_extracted:
          type: integer
          format: int32
          description: Documents extracted successfully.
        documents_failed:
          type: integer
          format: int32
          description: Documents that failed extraction.
        pages_crawled:
          type: integer
          format: int32
          description: Pages fetched so far.
    CrawlWebhookEvent:
      type: object
      description: Crawl-level webhook body. Unlike extraction job events, it never embeds results.
      required:
        - event
        - event_id
        - crawl_job_id
        - timestamp
        - status
        - progress
      properties:
        crawl_job_id:
          type: string
          format: uuid
          description: Crawl job that reached a terminal status.
        error:
          $ref: '#/components/schemas/WebhookEventError'
          description: Error details, present only on `crawl.failed`.
        event:
          $ref: '#/components/schemas/CrawlWebhookEventType'
          description: Terminal crawl event name.
        event_id:
          type: string
          format: uuid
          description: Stable identity shared by retries of this event.
        metadata:
          type:
            - object
            - "null"
          description: Customer metadata, sorted for stable signed payload bytes.
          additionalProperties:
            type: string
          propertyNames:
            type: string
        progress:
          $ref: '#/components/schemas/CrawlProgress'
          description: The same four counters exposed by `CrawlJobResponse`.
        status:
          $ref: '#/components/schemas/CrawlWebhookStatus'
          description: The terminal crawl status, including partial success when applicable.
        timestamp:
          type: string
          format: date-time
          description: RFC 3339 time when the crawl reached a terminal status.
    CrawlWebhookEventType:
      type: string
      description: Terminal event names for crawl-level deliveries. Job event names remain separate.
      enum:
        - crawl.completed
        - crawl.failed
    CrawlWebhookStatus:
      type: string
      description: Terminal crawl status carried separately from the event name.
      enum:
        - completed
        - partial_success
        - failed
    CrawledPage:
      type: object
      description: One raw page retained by a scrape or crawl output job.
      required:
        - url
        - status
        - fetched_at
      properties:
        content_url:
          type:
            - string
            - "null"
          description: Presigned URL for the retained response body.
        error:
          type:
            - string
            - "null"
          description: Fetch error, when the page failed.
        fetched_at:
          type: string
          format: date-time
          description: ISO 8601 fetch timestamp.
        mime_type:
          type:
            - string
            - "null"
          description: Response MIME type.
        size_bytes:
          type:
            - integer
            - "null"
          format: int64
          description: Response body size.
        status:
          type: string
          description: Fetch status.
        url:
          type: string
          description: Source URL.
    CreateApiKeyRequest:
      type: object
      description: Request to create a new API key.
      properties:
        expires_at:
          type:
            - string
            - "null"
          description: |-
            Optional expiration date in ISO 8601 format. Both tiers reject the key
            after this timestamp.
        name:
          type:
            - string
            - "null"
          description: |-
            Descriptive name for the API key. Optional on Pro (unnamed keys are
            allowed); Enterprise requires a non-empty name at the handler level.
        scope:
          $ref: '#/components/schemas/ApiKeyScope'
          description: |-
            What the key may do. `read` keys are refused on POST, PUT, PATCH and
            DELETE. Omitted means `write`.
    CreateApiKeyResponse:
      type: object
      description: |-
        API key details returned only on creation/regeneration. `key` contains
        the full plaintext key and is only ever returned once.
      required:
        - id
        - key
        - key_prefix
        - scope
        - created_at
      properties:
        created_at:
          type: string
          format: date-time
          description: ISO 8601 creation timestamp.
        expires_at:
          type:
            - string
            - "null"
          description: |-
            Optional expiration date in ISO 8601 format. Both tiers reject the key
            after this timestamp.
        id:
          type: string
          format: uuid
          description: Unique API key identifier.
        key:
          type: string
          description: Full API key. Only returned once, on creation/regeneration.
        key_prefix:
          type: string
          description: Key prefix for identification.
        kind:
          type:
            - string
            - "null"
          description: |-
            Key kind (e.g. `"project"`, `"sandbox"`). Pro-only today; Enterprise
            always issues `"project"` keys from this endpoint but does not surface
            the kind on the wire.
        name:
          type:
            - string
            - "null"
          description: User-provided name for the key, when set.
        scope:
          $ref: '#/components/schemas/ApiKeyScope'
          description: 'What the key may do: `read` or `write`.'
    CreateAutoTuneJobRequest:
      type: object
      description: Request to submit a new auto-tune job.
      required:
        - documents
      properties:
        budget:
          $ref: '#/components/schemas/AutoTuneBudget'
          description: Optional budget limits; falls back to defaults when omitted.
        documents:
          type: array
          items:
            $ref: '#/components/schemas/AutoTuneDocumentInput'
          description: Documents and their ground truth for this run. At least one is required.
          minItems: 1
        routing:
          type:
            - boolean
            - "null"
          description: |-
            Whether to tune per-document routing in addition to global
            configuration. `None`/`false` tunes global-only.
        search:
          $ref: '#/components/schemas/AutoTuneSearchConfig'
          description: Optional search configuration; falls back to defaults when omitted.
        webhook:
          $ref: '#/components/schemas/WebhookConfig'
          description: Optional terminal callback, snapshotted when the run is submitted.
    CreateAutoTuneJobResponse:
      type: object
      description: Response from submitting a new auto-tune job.
      required:
        - auto_tune_job_id
        - status
      properties:
        auto_tune_job_id:
          type: string
          description: Unique ID of the newly created auto-tune job (ULID).
        status:
          type: string
          description: Initial job status (e.g. `queued`).
    CreateCollectionRequest:
      type: object
      description: Request to create a new RAG collection.
      required:
        - name
        - embedding_dim
      properties:
        chunking_config:
          $ref: '#/components/schemas/CollectionChunkingConfig'
          description: |-
            Per-collection text chunking configuration.

            Omit any field to inherit the ingest pipeline's built-in default
            (max 1000 characters, 200-character overlap, text chunker).
        distance_metric:
          type: string
          description: Distance metric for similarity search ("cosine", "l2", "inner_product")
          pattern: ^(cosine|l2|inner_product)$
        embedding_dim:
          type: integer
          format: int32
          description: Embedding dimension (e.g., 384 for mxbai-embed-large-v1)
          maximum: 2000
          minimum: 1
        embedding_source:
          type:
            - string
            - "null"
          description: |-
            Embedding source for the collection.

            Omit to use the deployment-wide default (usually "managed_local").
            When provided, overrides the deployment default.
        index_method:
          type:
            - string
            - "null"
          description: |-
            Vector index method ("diskann" or "hnsw").

            Omit to inherit the deployment-wide default
            (`XBERG_VECTORSTORE_DEFAULT_INDEX_METHOD` env var, Helm value
            `vectorstore.defaultIndexMethod`). The API layer is responsible for
            filling the default before persisting the collection.
          pattern: ^(diskann|hnsw)$
        label_classifier_threshold:
          type:
            - number
            - "null"
          format: float
          description: |-
            Not available. Sending this field is refused with
            `rag.label_classification.unsupported`, for the same reason as
            `label_vocabulary`.
        label_vocabulary:
          type:
            - array
            - "null"
          items:
            type: string
          description: |-
            Not available. Sending this field is refused with
            `rag.label_classification.unsupported`: no build ships an ingest-time
            classifier, so a stored vocabulary would never label a document. Supply
            labels per document via `enrichment_overrides.labels` instead.
        name:
          type: string
          description: Collection name (unique per project)
          maxLength: 255
          minLength: 1
          pattern: ^[A-Za-z0-9_-]+$
        quota_max_documents:
          type:
            - integer
            - "null"
          format: int64
          description: Maximum documents quota for this collection
          minimum: 0
        rerank_default:
          $ref: '#/components/schemas/RerankerSpec'
          description: |-
            Default reranker spec for retrieve operations on this collection.

            When present, this reranker is used as the default unless the
            retrieve request overrides it. Can be a Preset (ONNX cross-encoder)
            or Llm (platform-managed LLM).
        rerank_timeout_ms:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Milliseconds a rerank may take before the primary order is returned
            instead. Omit to take the deployment default.

            The budget is per collection because a reranker's cost is a property of
            the model and the hardware, not of one request: several shipped presets
            do not finish inside the default on a CPU-only deployment. A retrieve
            that exceeds it reports `rerank.status = "fell_back"` with
            `reason = "timeout"`, so raising it is a decision the caller can make on
            evidence rather than a number they cannot see.
          maximum: 30000
          minimum: 1
      additionalProperties: false
    CreateCollectionResponse:
      type: object
      description: Response for collection creation
      required:
        - id
        - name
        - embedding_dim
        - distance_metric
        - index_method
      properties:
        chunking_config:
          $ref: '#/components/schemas/CollectionChunkingConfig'
          description: Per-collection text chunking configuration
        distance_metric:
          type: string
          description: Distance metric
        embedding_dim:
          type: integer
          format: int32
          description: Embedding dimension
          minimum: 0
        embedding_source:
          type:
            - string
            - "null"
          description: Embedding source for this collection
        id:
          type: string
          format: uuid
          description: Collection unique identifier
        index_method:
          type: string
          description: Vector index method
        name:
          type: string
          description: Collection name
        quota_max_documents:
          type:
            - integer
            - "null"
          format: int64
          description: Maximum documents quota
          minimum: 0
        rerank_default:
          $ref: '#/components/schemas/RerankerSpec'
          description: Default reranker spec for retrieve operations on this collection
    CreateDocumentAsyncResponse:
      type: object
      description: Response for asynchronous document creation
      required:
        - job_id
        - document_id
        - status
        - poll
      properties:
        document_id:
          type: string
          format: uuid
          description: The pending document ID (for reference)
        job_id:
          type: string
          description: Job identifier for polling (ULID)
        poll:
          type: string
          description: Polling endpoint
        status:
          type: string
          description: Lifecycle status
    CreateDocumentChunkInput:
      type: object
      description: Caller-supplied chunk for bring-your-own-chunks ingest
      required:
        - content
      properties:
        content:
          type: string
          description: Chunk content (text)
        embedding:
          type:
            - array
            - "null"
          items:
            type: number
            format: float
          description: |-
            Caller-supplied embedding vector. When present on every chunk in the
            request, triggers synchronous write (200). When absent on any chunk,
            the entire request goes async and the worker embeds all chunks.
        external_id:
          type:
            - string
            - "null"
          description: External identifier for this chunk (optional)
        metadata:
          type: object
          description: Metadata attached to this chunk
          additionalProperties: {}
          propertyNames:
            type: string
        ordinal:
          type:
            - integer
            - "null"
          format: int32
          description: Position in the document (0-indexed)
          minimum: 0
      additionalProperties: false
    CreateDocumentRequest:
      type: object
      description: |-
        OpenAPI schema for `CreateDocumentRequest`.

        This DTO keeps the published request schema as the flat JSON object accepted
        by the handler while runtime deserialization still uses `CreateDocumentRequest`.
      required:
        - kind
      properties:
        chunks:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/CreateDocumentChunkInput'
          description: 'Pre-split chunks for `kind: "chunks"`'
        enrichment_overrides:
          $ref: '#/components/schemas/EnrichmentOverridesRequest'
          description: Pre-computed enrichments to skip the server's pass
        external_id:
          type:
            - string
            - "null"
          description: Caller-supplied external identifier for the document
        kind:
          type: string
          description: 'Source variant: `"text"` or `"chunks"`'
        metadata:
          type:
            - object
            - "null"
          description: Caller-supplied metadata stored alongside the document
          additionalProperties: {}
          propertyNames:
            type: string
        mime:
          type:
            - string
            - "null"
          description: MIME type of the source document for audit purposes
        source_object_key:
          type:
            - string
            - "null"
          description: GCS/S3 object key for the source document
        text:
          type:
            - string
            - "null"
          description: 'Raw text content for `kind: "text"`'
        title:
          type:
            - string
            - "null"
          description: Human-readable document title
        webhook:
          $ref: '#/components/schemas/WebhookConfig'
          description: Optional webhook for ingest completion/failure delivery
    CreateDocumentResponse:
      type: object
      description: Response for synchronous document creation (BYO-vector fast path)
      required:
        - document_id
        - collection
        - chunk_count
        - embedding_dim
        - ingested_at
      properties:
        chunk_count:
          type: integer
          format: int32
          description: Number of chunks created
        collection:
          type: string
          description: Collection name
        document_id:
          type: string
          format: uuid
          description: Unique document identifier
        embedding_dim:
          type: integer
          format: int32
          description: Embedding dimension (from collection spec)
        external_id:
          type:
            - string
            - "null"
          description: Caller-supplied external ID (if provided)
        ingested_at:
          type: string
          format: date-time
          description: Ingestion timestamp (RFC3339)
    CreateDocumentSource:
      oneOf:
        - $ref: '#/components/schemas/CreateDocumentSourceText'
        - $ref: '#/components/schemas/CreateDocumentSourceChunks'
      description: Document source variant for ingest
      discriminator:
        propertyName: kind
        mapping:
          chunks: '#/components/schemas/CreateDocumentSourceChunks'
          text: '#/components/schemas/CreateDocumentSourceText'
    CreateDocumentSourceChunks:
      type: object
      description: Pre-chunked document — chunks may or may not have embeddings
      required:
        - chunks
        - kind
      properties:
        chunks:
          type: array
          items:
            $ref: '#/components/schemas/CreateDocumentChunkInput'
          description: List of pre-split chunks
        enrichment_overrides:
          $ref: '#/components/schemas/EnrichmentOverridesRequest'
          description: Pre-computed enrichments to skip server's pass
        kind:
          type: string
          enum:
            - chunks
    CreateDocumentSourceText:
      type: object
      description: Full-text document — server runs chunking + enrichment + embedding (async)
      required:
        - text
        - kind
      properties:
        enrichment_overrides:
          $ref: '#/components/schemas/EnrichmentOverridesRequest'
          description: Pre-computed enrichments (keywords, entities, labels) to skip server's pass
        kind:
          type: string
          enum:
            - text
        text:
          type: string
          description: Raw text content
    CreateIntegrationRequest:
      type: object
      description: |-
        Request to create an integration.

        Pro's own submission shape — see `api_types::CreateIntegrationRequest`'s
        module doc for the unreconciled drift versus Enterprise's `provider` +
        `client_id` + `client_secret` shape; adopting the shared request type is
        deferred pending a product decision, so this stays a Pro-local struct.
      required:
        - name
        - kind
        - auth_type
        - credentials
      properties:
        auth_type:
          type: string
        config:
          type: object
          description: |-
            Additional configuration. Dropbox accepts an optional absolute `path`; SharePoint requires `site_id`.
            Object stores require `bucket`; S3 also requires `region`; all accept an optional `root`.
        credentials:
          type: object
          description: |-
            BYO credentials. Dropbox and Microsoft Graph OAuth accept `client_id` and `client_secret`.
            Object-store fields are documented on the shared request schema.
        kind:
          type: string
        name:
          type: string
          maxLength: 100
          minLength: 1
    CreateProjectRequest:
      type: object
      description: Create a Pro project with a name, a required URL-safe slug and optional quota overrides.
      required:
        - name
        - slug
      properties:
        max_file_size_mb:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Maximum upload file size (MB) override. Pro-only; Enterprise applies a
            service-wide default instead.
        max_monthly_jobs:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Monthly job quota override. Pro-only; Enterprise applies a
            service-wide default instead.
        max_monthly_pages:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Monthly page quota override. Pro-only; Enterprise applies a
            service-wide default instead.
        name:
          type: string
          description: Project name.
        slug:
          type: string
          description: Required URL-safe project slug.
    CreateSavedPresetRequest:
      type: object
      description: Request to create a saved preset.
      required:
        - name
        - schema
        - preferred_call_mode
      properties:
        context_template:
          type:
            - string
            - "null"
          description: Optional context template.
        description:
          type:
            - string
            - "null"
          description: Optional description of the preset.
        emit_citations:
          type: boolean
          description: |-
            Whether to emit citations in results.

            Stored and returned verbatim, but **not applied at extraction time** —
            see `preferred_call_mode`. Results produced from a saved preset carry no
            citations regardless of this value. Defaults to `true` when omitted.
        name:
          type: string
          description: |-
            Preset name (must be unique per project). Trimmed before storage; a
            name that is entirely whitespace is rejected once trimmed.
          maxLength: 255
          minLength: 1
          pattern: \S
        preferred_call_mode:
          type: string
          description: |-
            Preferred orchestration path (`text_only`, `vision_only`, or
            `text_plus_vision`).

            Validated on write, stored, and returned verbatim, but
            **not applied at extraction time**. Call-mode selection belongs to the
            vision pipeline, which a job reaches only through the curated preset
            registry (`options.preset`); a saved preset never routes there. On Xberg
            Pro a saved preset contributes its `schema`, `system_prompt` and
            `context_template`, and nothing else.
          pattern: ^(text_only|vision_only|text_plus_vision)$
        schema:
          type: object
          description: JSON Schema (Draft 2020-12) for the extraction result.
        system_prompt:
          type:
            - string
            - "null"
          description: Optional custom system prompt override.
    CreateSavedPresetResponse:
      type: object
      description: Response from creating a saved preset.
      required:
        - success
        - id
        - name
        - message
      properties:
        id:
          type: string
          format: uuid
          description: The newly created preset's id.
        message:
          type: string
          description: Human-readable confirmation message.
        name:
          type: string
          description: The preset's name (same value as the request).
        success:
          type: boolean
          description: Always `true` on success (the endpoint returns an error status otherwise).
    CsvMetadata:
      type: object
      description: CSV/TSV file metadata.
      required:
        - row_count
        - column_count
        - has_header
        - format_type
      properties:
        column_count:
          type: integer
          format: int32
          description: Number of columns detected.
          minimum: 0
        column_types:
          type:
            - array
            - "null"
          items:
            type: string
          description: Inferred data type for each column (e.g. `"string"`, `"integer"`, `"float"`).
        delimiter:
          type:
            - string
            - "null"
          description: Field delimiter character (e.g. `","` or `"\t"`).
        format_type:
          type: string
          enum:
            - csv
        has_header:
          type: boolean
          description: Whether the first row was treated as a header.
        row_count:
          type: integer
          format: int32
          description: Total number of data rows (excluding the header row if present).
          minimum: 0
    DbfFieldInfo:
      type: object
      description: dBASE field information.
      required:
        - name
        - field_type
      properties:
        field_type:
          type: string
          description: dBASE field type character (e.g. `"C"` for character, `"N"` for numeric).
        name:
          type: string
          description: Field (column) name.
    DbfMetadata:
      type: object
      description: dBASE (DBF) file metadata.
      required:
        - record_count
        - field_count
        - format_type
      properties:
        field_count:
          type: integer
          description: Number of field (column) definitions.
          minimum: 0
        fields:
          type: array
          items:
            $ref: '#/components/schemas/DbfFieldInfo'
          description: Descriptor for each field in the table schema.
        format_type:
          type: string
          enum:
            - dbf
        record_count:
          type: integer
          description: Total number of data records in the DBF file.
          minimum: 0
    DeleteDocumentsRequest:
      type: object
      description: OpenAPI schema for `DeleteDocumentsRequest`.
      properties:
        filter:
          type: object
          description: Vectorstore filter expression.
        ids:
          type:
            - array
            - "null"
          items:
            type: string
            format: uuid
          description: List of document UUIDs to delete.
    DeleteDocumentsResponse:
      type: object
      description: Response for deletion operation
      required:
        - deleted_count
      properties:
        deleted_count:
          type: integer
          format: int64
          description: Number of documents deleted
          minimum: 0
    DiffLine:
      oneOf:
        - type: object
          description: Unchanged context line.
          required:
            - text
            - kind
          properties:
            kind:
              type: string
              enum:
                - context
            text:
              type: string
              description: Unchanged context line.
        - type: object
          description: Line added in the "after" version.
          required:
            - text
            - kind
          properties:
            kind:
              type: string
              enum:
                - added
            text:
              type: string
              description: Line added in the "after" version.
        - type: object
          description: Line removed from the "before" version.
          required:
            - text
            - kind
          properties:
            kind:
              type: string
              enum:
                - removed
            text:
              type: string
              description: Line removed from the "before" version.
      description: |-
        A single line in a unified-diff hunk.

        Defined here (rather than only in `crate::diff`) so `RevisionDelta` can
        reference it unconditionally, without requiring the `diff` Cargo feature.
        `crate::diff` re-exports this type verbatim.
    DjotAttributeGroup:
      type: object
      required:
        - identifier
        - attributes
      properties:
        attributes:
          type: object
          description: |-
            Element attributes in Djot.

            Represents the attributes attached to elements using {.class #id key="value"} syntax.
          properties:
            classes:
              type: array
              items:
                type: string
              description: CSS classes (.class1 .class2)
            id:
              type:
                - string
                - "null"
              description: Element ID (#identifier)
            key_values:
              type: array
              items:
                $ref: '#/components/schemas/KeyValueAttribute'
              description: Key-value pairs (key="value")
        identifier:
          type: string
    DjotContent:
      type: object
      description: |-
        Comprehensive Djot document structure with semantic preservation.

        This type captures the full richness of Djot markup, including:
        - Block-level structures (headings, lists, blockquotes, code blocks, etc.)
        - Inline formatting (emphasis, strong, highlight, subscript, superscript, etc.)
        - Attributes (classes, IDs, key-value pairs)
        - Links, images, footnotes
        - Math expressions (inline and display)
        - Tables with full structure

        Available when the `djot` feature is enabled.
      required:
        - plain_text
        - blocks
        - metadata
        - tables
        - images
        - links
        - footnotes
      properties:
        attributes:
          type: array
          items:
            $ref: '#/components/schemas/DjotAttributeGroup'
          description: Attributes mapped by element identifier (if present)
        blocks:
          type: array
          items:
            $ref: '#/components/schemas/FormattedBlock'
          description: Structured block-level content
        footnotes:
          type: array
          items:
            $ref: '#/components/schemas/Footnote'
          description: Footnote definitions
        images:
          type: array
          items:
            $ref: '#/components/schemas/DjotImage'
          description: Extracted images with metadata
        links:
          type: array
          items:
            $ref: '#/components/schemas/DjotLink'
          description: Extracted links with URLs
        metadata:
          $ref: '#/components/schemas/Metadata'
          description: Metadata from YAML frontmatter
        plain_text:
          type: string
          description: Plain text representation for backwards compatibility
        tables:
          type: array
          items:
            $ref: '#/components/schemas/Table'
          description: Extracted tables as structured data
    DjotImage:
      type: object
      description: Image element in Djot.
      required:
        - src
        - alt
      properties:
        alt:
          type: string
          description: Alternative text
        attributes:
          $ref: '#/components/schemas/Attributes'
          description: Element attributes
        src:
          type: string
          description: Image source URL or path
        title:
          type:
            - string
            - "null"
          description: Optional title
    DjotLink:
      type: object
      description: Link element in Djot.
      required:
        - url
        - text
      properties:
        attributes:
          $ref: '#/components/schemas/Attributes'
          description: Element attributes
        text:
          type: string
          description: Link text content
        title:
          type:
            - string
            - "null"
          description: Optional title
        url:
          type: string
          description: Link URL
    DocumentCounts:
      type: object
      description: |-
        Cheap structural counts for an extracted document.

        Populated on every `ExtractedDocument` returned by `extract` /
        `extract_batch`, regardless of whether the heavy `pages` / `images`
        collections are materialized. A caller that only needs "how many pages /
        tables / images did this document have?" (reporting, cost estimation,
        progress, quotas) can read these without enabling per-page or per-image
        extraction.

        The page count comes from the parse (the extractor already walks the page
        tree); it does not require opting into per-page content. `pages` is `0` for
        inputs that are not page-addressable (e.g. plain text).
      required:
        - pages
        - tables
        - images
      properties:
        images:
          type: integer
          description: Images detected in the document.
          minimum: 0
        pages:
          type: integer
          description: Total pages in the source document (`0` when not page-addressable).
          minimum: 0
        tables:
          type: integer
          description: Tables detected in the document.
          minimum: 0
    DocumentInput:
      type: object
      description: |-
        A single document to extract.

        A document is sourced **either** inline (`data`, base64-encoded) **or** by
        reference to a connected integration (`integration`). Exactly one of the
        two must be present; supplying both or neither is a validation error.

        A single-document extraction is simply a `documents` vector of length one
        on `ExtractRequest`; both tiers also accept a batch of up to
        `MAX_DOCUMENTS_PER_REQUEST` entries (10 on both Pro and Enterprise).
      required:
        - filename
        - mime_type
      properties:
        config:
          $ref: '#/components/schemas/FileExtractionConfig'
          description: |-
            Per-file extraction config override.

            Precedence, highest to lowest: this override > the request's
            batch-level `options.extraction_config` > a curated/saved preset > the
            project default. The merge is shallow: a top-level field this override
            sets replaces the batch-level value outright, and a field it leaves
            unset falls through to whatever the batch level (already resolved
            against any preset and project default) carries for it. Omitting
            `config` entirely means "use the batch default" for every field. On a
            multipart request the equivalent is a `config-<filename>` JSON text
            part, mirroring the existing `document_id-<filename>` convention.
        data:
          type:
            - string
            - "null"
          description: Base64-encoded document bytes. Mutually exclusive with `integration`.
        document_id:
          type:
            - string
            - "null"
          description: |-
            Optional client-supplied document identifier. When provided, this
            extraction is recorded as a version of that logical document and the
            response carries `document_id` + `version_sequence` on the matching
            `DocumentSubmission`. Enterprise-only lineage feature; Pro leaves
            this unset. Server mints a new identifier when absent. Typed as a string
            to span Pro (ULID) and Enterprise (UUID) identifier forms; the concrete
            format is validated per service.
        filename:
          type: string
          description: Original filename.
          maxLength: 255
          minLength: 1
        integration:
          $ref: '#/components/schemas/IntegrationInput'
          description: |-
            Reference to a document in a connected integration. Mutually exclusive
            with `data`.
        mime_type:
          type: string
          description: |-
            MIME type of the document. For an integration-sourced document this is
            a hint only — the receiving service prefers the connector-reported
            type at fetch time.
      additionalProperties: false
    DocumentNode:
      type: object
      description: |-
        A single node in the document tree.

        Each node has deterministic `id`, typed `content`, optional `parent`/`children`
        for tree structure, and metadata like page number, bounding box, and content layer.
      required:
        - content
      properties:
        annotations:
          type: array
          items:
            $ref: '#/components/schemas/TextAnnotation'
          description: |-
            Inline annotations (formatting, links) on this node's text content.

            Only meaningful for text-carrying nodes; empty for containers.
        attributes:
          type:
            - object
            - "null"
          description: |-
            Format-specific key-value attributes.

            Extensible bag for miscellaneous data without a dedicated typed field: CSS classes,
            LaTeX environment names, Excel cell formulas, slide layout names, etc.
          additionalProperties:
            type: string
          propertyNames:
            type: string
        bbox:
          $ref: '#/components/schemas/BoundingBox'
          description: Bounding box in document coordinates.
        children:
          type: array
          items:
            $ref: '#/components/schemas/NodeIndex'
          description: Child node indices in reading order.
        content:
          $ref: '#/components/schemas/NodeContent'
          description: Node content — tagged enum, type-specific data only.
        content_layer:
          $ref: '#/components/schemas/ContentLayer'
          description: |-
            Content layer classification.

            Always serialised — Kotlin-Android (and any other typed binding) treats
            the field as non-nullable, so omitting it from the JSON wire would
            break consumer deserialisation.  `#[serde(default)]` covers the
            missing-field case on inbound JSON.
        id:
          type: string
          description: |-
            Deterministic identifier (hash of node type + text + page + position).

            Stable and unique within a single extraction response: every internal
            construction path threads the node's position (its index in
            `DocumentStructure::nodes`) into the hash, so identical
            `(node_type, text, page)` tuples at different positions never collide.
            Always serialised — `ChunkMetadata::node_ids` references it to join
            chunks back to the nodes they were derived from.
            `#[serde(default)]` covers the missing-field case on inbound JSON
            (e.g. documents serialised before this field existed).
        page:
          type:
            - integer
            - "null"
          format: int32
          description: Page number where this node starts (1-indexed).
          minimum: 0
        page_end:
          type:
            - integer
            - "null"
          format: int32
          description: Page number where this node ends (for multi-page tables/sections).
          minimum: 0
        parent:
          $ref: '#/components/schemas/NodeIndex'
          description: Parent node index (`None` = root-level node).
    DocumentRelationship:
      type: object
      description: A resolved relationship between two nodes in the document tree.
      required:
        - source
        - target
        - kind
      properties:
        kind:
          $ref: '#/components/schemas/RelationshipKind'
          description: Semantic kind of the relationship.
        source:
          $ref: '#/components/schemas/NodeIndex'
          description: Source node index (the referencing node).
        target:
          $ref: '#/components/schemas/NodeIndex'
          description: Target node index (the referenced node).
    DocumentResponse:
      type: object
      description: Single document's metadata, as reported by a connected integration.
      required:
        - id
        - name
        - mime_type
      properties:
        id:
          type: string
          description: |-
            Source-specific stable document identifier (opaque; format depends on
            the connected integration, e.g. a Google Drive file id).
        mime_type:
          type: string
          description: MIME type of the document.
        modified_at:
          type:
            - string
            - "null"
          description: Last-modified timestamp (RFC 3339), when reported by the source.
        name:
          type: string
          description: Human-readable document name.
        size_bytes:
          type:
            - integer
            - "null"
          format: int64
          description: Size in bytes, when reported by the source.
          minimum: 0
    DocumentRevision:
      type: object
      description: |-
        A single tracked change embedded in a document.

        Populated by per-format extractors that understand change-tracking metadata
        (DOCX `w:ins`/`w:del`/`w:rPrChange`, ODT `text:change-*`, …). Every
        extractor defaults to `ExtractedDocument.revisions = None` until a
        format-specific implementation is added.
      required:
        - revision_id
        - kind
        - delta
      properties:
        anchor:
          oneOf:
            - $ref: '#/components/schemas/RevisionAnchor'
              description: |-
                Best-effort document location for this revision.

                Resolution is format-dependent and may be `None` when the location
                cannot be determined (e.g. changes inside table cells before
                table-cell anchor support is added).
            - type: "null"
        author:
          type:
            - string
            - "null"
          description: Display name of the author who made this change, when available.
        delta:
          $ref: '#/components/schemas/RevisionDelta'
          description: The content changes that make up this revision.
        kind:
          $ref: '#/components/schemas/RevisionKind'
          description: Semantic kind of this revision.
        revision_id:
          type: string
          description: |-
            Format-specific revision identifier.

            For DOCX this is the `w:id` attribute value on the change element
            (e.g. `"42"`). When the attribute is absent a synthetic fallback is
            generated (`"docx-ins-0"`, `"docx-del-3"`, …).
        timestamp:
          type:
            - string
            - "null"
          description: |-
            ISO-8601 timestamp of the change, when available.

            Stored as a plain string so this type remains FFI-friendly and
            unconditionally available without the `chrono` optional dep.
            DOCX populates this from the `w:date` attribute (e.g.
            `"2024-03-15T10:30:00Z"`).
    DocumentStructure:
      type: object
      description: |-
        Top-level structured document representation.

        A flat array of nodes with index-based parent/child references forming a tree.
        Root-level nodes have `parent: None`. Use `body_roots()` and `furniture_roots()`
        to iterate over top-level content by layer.

        # Validation

        Call `validate()` after construction to verify all node indices are in bounds
        and parent-child relationships are bidirectionally consistent.
      required:
        - nodes
      properties:
        node_types:
          type: array
          items:
            type: string
          description: |-
            Sorted, deduplicated list of node type names present in this document.

            Each value is the snake_case `node_type` tag of the corresponding
            `NodeContent` variant (e.g. `"paragraph"`, `"heading"`, `"table"`, …).

            Computed from `nodes` via node-type derivation.
            Empty until that method is called (internal construction paths call it
            at the end of derivation).
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/DocumentNode'
          description: All nodes in document/reading order.
        relationships:
          type: array
          items:
            $ref: '#/components/schemas/DocumentRelationship'
          description: |-
            Resolved relationships between nodes (footnote refs, citations, anchor links, etc.).

            Populated during derivation from the internal document representation.
            Empty when no relationships are detected.
        source_format:
          type:
            - string
            - "null"
          description: |-
            Origin format identifier (e.g. "docx", "pptx", "html", "pdf").

            Allows renderers to apply format-aware heuristics when converting
            the document tree to output formats.
    DocumentSubmission:
      type: object
      description: Per-document submission detail included in `ExtractResponse.documents`.
      required:
        - job_id
      properties:
        document_id:
          type:
            - string
            - "null"
          description: |-
            Document lineage identifier, when the submission set `document_id`
            or one was minted server-side. `None` for non-lineage jobs.
        job_id:
          type: string
          format: uuid
          description: Job ID created for this document.
        version_sequence:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Version sequence number within the document's lineage, when lineage
            tracking applies.
    DocumentSummary:
      type: object
      description: Summary of an extracted document.
      required:
        - text
        - strategy
      properties:
        strategy:
          $ref: '#/components/schemas/SummaryStrategy'
          description: Strategy that produced this summary.
        text:
          type: string
          description: Summary text (plain prose).
        token_count:
          type:
            - integer
            - "null"
          format: int32
          description: Approximate token count of the summary, when known.
          minimum: 0
    DocxMetadata:
      type: object
      description: |-
        Word document metadata.

        Extracted from DOCX files using shared Office Open XML metadata extraction.
        Integrates with `office_metadata` module for core/app/custom properties.
      required:
        - format_type
      properties:
        app_properties:
          type:
            - object
            - "null"
          description: |-
            Application properties from docProps/app.xml (Word-specific statistics)

            Contains word count, page count, paragraph count, editing time, etc.
            DOCX-specific variant of Office application properties.
        core_properties:
          type:
            - object
            - "null"
          description: |-
            Core properties from docProps/core.xml (Dublin Core metadata)

            Contains title, creator, subject, keywords, dates, etc.
            Shared format across DOCX/PPTX/XLSX documents.
        custom_properties:
          type:
            - object
            - "null"
          description: |-
            Custom properties from docProps/custom.xml (user-defined properties)

            Contains key-value pairs defined by users or applications.
            Values can be strings, numbers, booleans, or dates.
          additionalProperties: {}
          propertyNames:
            type: string
        format_type:
          type: string
          enum:
            - docx
    Element:
      type: object
      description: |-
        Semantic element extracted from document.

        Represents a logical unit of content with semantic classification,
        unique identifier, and metadata for tracking origin and position.
      required:
        - element_type
        - text
        - metadata
      properties:
        element_id:
          type: string
          description: |-
            Deterministic element identifier. Empty only when deserializing legacy payloads
            that predate this field's wire representation.
        element_type:
          $ref: '#/components/schemas/ElementType'
          description: Semantic type of this element
        metadata:
          $ref: '#/components/schemas/ElementMetadata'
          description: Metadata about the element
        text:
          type: string
          description: Text content of the element
    ElementMetadata:
      type: object
      description: Metadata for a semantic element.
      required:
        - additional
      properties:
        additional:
          type: object
          description: Additional custom metadata
          additionalProperties:
            type: string
          propertyNames:
            type: string
        coordinates:
          oneOf:
            - $ref: '#/components/schemas/BoundingBox'
              description: Bounding box coordinates if available
            - type: "null"
        element_index:
          type:
            - integer
            - "null"
          description: Position index in the element sequence
          minimum: 0
        filename:
          type:
            - string
            - "null"
          description: Source filename or document name
        page_number:
          type:
            - integer
            - "null"
          format: int32
          description: Page number (1-indexed)
          minimum: 0
    ElementType:
      type: string
      description: |-
        Semantic element type classification.

        Categorizes text content into semantic units for downstream processing.
        Supports the element types commonly found in Unstructured documents.
      enum:
        - title
        - narrative_text
        - heading
        - list_item
        - table
        - image
        - page_break
        - code_block
        - formula
        - block_quote
        - footer
        - header
    EmailMetadata:
      type: object
      description: |-
        Email metadata extracted from .eml and .msg files.

        Includes sender/recipient information, message ID, and attachment list.
      required:
        - to_emails
        - cc_emails
        - bcc_emails
        - attachments
        - format_type
      properties:
        attachments:
          type: array
          items:
            type: string
          description: List of attachment filenames
        bcc_emails:
          type: array
          items:
            type: string
          description: BCC recipients
        cc_emails:
          type: array
          items:
            type: string
          description: CC recipients
        format_type:
          type: string
          enum:
            - email
        from_email:
          type:
            - string
            - "null"
          description: Sender's email address
        from_name:
          type:
            - string
            - "null"
          description: Sender's display name
        message_id:
          type:
            - string
            - "null"
          description: Message-ID header value
        to_emails:
          type: array
          items:
            type: string
          description: Primary recipients
    EmbeddingConfig:
      type: object
      description: Embedding generation configuration for chunks.
      properties:
        batch_size:
          type:
            - integer
            - "null"
          format: int32
          description: Batch size for embedding generation. Defaults to 32 when omitted.
          minimum: 0
        model:
          type: object
          description: 'Model configuration (flexible JSON: {"type":"preset","name":"balanced"})'
        normalize:
          type:
            - boolean
            - "null"
          description: Normalize embedding vectors. Defaults to true when omitted.
        show_download_progress:
          type:
            - boolean
            - "null"
          description: Show model download progress
      additionalProperties: false
    EnrichCaptioningOptions:
      type: object
      title: EnrichCaptioningOptions
      description: 'Captioning options: caller-supplied images plus the VLM used to caption them.'
      required:
        - images
        - llm
      properties:
        images:
          type: array
          items:
            type: string
          description: |-
            Base64-encoded image bytes, in order. Each entry gets its own parallel
            slot in `EnrichResult::captions`.
        llm:
          $ref: '#/components/schemas/EnrichLlmConfig'
          description: VLM used for captioning.
      additionalProperties: false
    EnrichChunkClassificationDefinition:
      type: object
      title: EnrichChunkClassificationDefinition
      description: |-
        One label definition for chunk classification: a name plus a description of
        when it applies, injected verbatim into the classification prompt.
      required:
        - label
        - description
      properties:
        description:
          type: string
          description: Semantic description of when this label applies.
        label:
          type: string
          description: Label name returned in the matching entry of `chunk_classifications`.
      additionalProperties: false
    EnrichChunkClassificationOptions:
      type: object
      title: EnrichChunkClassificationOptions
      description: |-
        Chunk-classification options. The caller supplies its own chunk texts:
        `/v1/enrich` operates on a single text blob with no chunking pipeline of its
        own, unlike the file-extraction pipeline's `ExtractedDocument::chunks`.
      required:
        - chunks
        - definitions
        - llm
      properties:
        chunks:
          type: array
          items:
            type: string
          description: |-
            Chunk texts to classify, in order. Each entry gets its own parallel slot
            in `EnrichResult::chunk_classifications`.
        definitions:
          type: array
          items:
            $ref: '#/components/schemas/EnrichChunkClassificationDefinition'
          description: |-
            The set of label definitions the classifier may emit. Must contain at
            least one entry.
        llm:
          $ref: '#/components/schemas/EnrichLlmConfig'
          description: LLM used for classification.
      additionalProperties: false
    EnrichClassificationOptions:
      type: object
      title: EnrichClassificationOptions
      description: Whole-text classification options.
      required:
        - labels
        - llm
      properties:
        labels:
          type: array
          items:
            type: string
          description: The set of labels the classifier may emit. Must contain at least one entry.
        llm:
          $ref: '#/components/schemas/EnrichLlmConfig'
          description: LLM used for classification.
        multi_label:
          type: boolean
          description: Allow multiple labels for the text. Single-label mode returns at most one label.
      additionalProperties: false
    EnrichJobStatus:
      oneOf:
        - type: object
          description: Job submitted, awaiting processing.
          required:
            - status
          properties:
            status:
              type: string
              enum:
                - pending
        - type: object
          description: Processing completed successfully.
          required:
            - result
            - status
          properties:
            result:
              $ref: '#/components/schemas/EnrichResult'
              description: Enrichment result.
            status:
              type: string
              enum:
                - completed
        - type: object
          description: Processing failed.
          required:
            - error
            - status
          properties:
            error:
              type: string
              description: Error message.
            status:
              type: string
              enum:
                - failed
      description: |-
        Status response for an enrichment job.

        `Completed` is far larger than the other two variants now that
        `EnrichResult` carries classification, chunk classifications and captions
        (#1568). Boxing it would satisfy the lint but is the wrong trade here: the
        wire shape is fixed by the API, and `Box<EnrichResult>` would churn every
        construction site on both tiers to save a stack copy. Pro's per-job status
        cache (`services/pro/src/jobs/enrich.rs`) does hold these in a collection
        now, but weighs entries by encoded size rather than by count, so the large
        variant costs proportionally more of the cache's budget instead of being
        undercounted as "one entry" the way boxing-for-a-collection would invite.
    EnrichJobSubmitted:
      type: object
      title: EnrichJobSubmitted
      description: Response from submitting an enrichment job.
      required:
        - job_id
      properties:
        job_id:
          type: string
          description: Unique job identifier (ULID).
    EnrichLlmConfig:
      type: object
      title: EnrichLlmConfig
      description: |-
        LLM/VLM provider configuration for an enrichment stage, routed through
        liter-llm. A narrower, enrichment-scoped mirror of
        `LlmConfig`'s wire shape.

        `Debug` is implemented by hand: unlike `LlmConfig`, which prints `base_url`'s
        *presence* through `Option`'s derived `Debug`, this type prints the same
        `"<redacted>"` marker whether `base_url` is set or not. Whether a caller
        routed a request at its own endpoint is, on its own, information about that
        caller's infrastructure — not only the endpoint value itself (#1864 residual
        tracked for the Pro enrichment provider paths in `services/pro/src/jobs/enrich_provider.rs`).
      required:
        - model
      properties:
        api_key:
          type:
            - string
            - "null"
          description: |-
            API key for the provider. When omitted, liter-llm falls back to the
            provider's standard environment variable on this deployment. Treated as
            a secret — never logged.
        base_url:
          type:
            - string
            - "null"
          description: |-
            Override the provider base URL. Setting this never falls back to this
            deployment's own provider credentials — see
            `services/pro/src/jobs/enrich_provider.rs::to_xberg_llm_config`.
        max_retries:
          type:
            - integer
            - "null"
          format: int32
          description: Max retry attempts. Clamped to this deployment's ceiling.
          minimum: 0
        max_tokens:
          type:
            - integer
            - "null"
          format: int64
          description: Maximum tokens to generate.
          minimum: 0
        model:
          type: string
          description: liter-llm routing identifier, e.g. "openai/gpt-4o-mini".
        temperature:
          type:
            - number
            - "null"
          format: double
          description: Sampling temperature.
        timeout_secs:
          type:
            - integer
            - "null"
          format: int64
          description: Request timeout in seconds. Clamped to this deployment's ceiling.
          minimum: 0
      additionalProperties: false
    EnrichRequestOptions:
      type: object
      title: EnrichRequestOptions
      description: |-
        Enrichment processing options.

        Unknown keys are refused: an option this type does not carry is one the
        caller asked for and will not get, and a 202 would report otherwise.
      properties:
        captioning:
          $ref: '#/components/schemas/EnrichCaptioningOptions'
          description: |-
            VLM captioning of caller-supplied images. Enterprise refuses a
            non-`None` value; only Xberg Pro implements it (#1568).
        chunk_classification:
          $ref: '#/components/schemas/EnrichChunkClassificationOptions'
          description: |-
            Multi-label classification of caller-supplied chunks against a described
            label taxonomy. `/v1/enrich` has no chunking pipeline of its own, so the
            caller supplies the chunk texts directly. Enterprise refuses a
            non-`None` value; only Xberg Pro implements it (#1568).
        classification:
          $ref: '#/components/schemas/EnrichClassificationOptions'
          description: |-
            Whole-text zero-shot classification against a caller-supplied label set.
            Enterprise refuses a non-`None` value; only Xberg Pro implements it (#1568).
        entities:
          type: boolean
          description: Extract named entities via NER.
        keywords:
          type: boolean
          description: Extract keywords from the text.
        labels:
          type: array
          items:
            type: string
          description: |-
            Candidate labels for classification.

            Xberg Pro refuses non-empty values with
            `rag.label_classification.unsupported` because it ships no classifier.
      additionalProperties: false
    EnrichResult:
      type: object
      title: EnrichResult
      description: Result of enrichment processing.
      properties:
        captions:
          type: array
          items:
            type: string
          description: |-
            Per-image captions, positionally parallel to the submitted
            `captioning.images`. Empty when captioning was not requested.
        chunk_classifications:
          type: array
          items:
            type: array
            items:
              $ref: '#/components/schemas/ClassificationLabel'
          description: |-
            Per-chunk classification labels, positionally parallel to the submitted
            `chunk_classification.chunks`. Empty when chunk classification was not
            requested.
        classification:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/ClassificationLabel'
          description: |-
            Whole-text classification labels, or `None` when classification was not
            requested. `Some(vec![])` means it ran and matched no label.
        entities:
          type:
            - array
            - "null"
          items:
            type: object
          description: Extracted named entities, or null when entity detection was not requested.
        keywords:
          type: array
          items:
            type: string
          description: Extracted keywords.
        labels:
          description: Labels selected by a classifier, or null when classification was not requested or available.
    EnrichTextRequest:
      type: object
      title: EnrichTextRequest
      description: Request to submit text for enrichment.
      required:
        - text
      properties:
        options:
          $ref: '#/components/schemas/EnrichRequestOptions'
          description: Enrichment options.
        text:
          type: string
          description: Text to enrich (max 1 MB).
    EnrichmentOverridesRequest:
      type: object
      description: |-
        Request shape of `enrichment_overrides`.

        The NATS message type stays lenient so an older publisher's payload still
        decodes, but the request boundary is strict: an unknown key is a typo the
        caller must see, and `entities` and `labels` carry typed shapes so a
        malformed value fails at parse rather than reaching the pipeline as an
        opaque blob that no later step can reject.
      properties:
        entities:
          type:
            - array
            - "null"
          items:
            type: object
            additionalProperties: {}
            propertyNames:
              type: string
          description: Pre-computed entities, as a list of objects.
        keywords:
          type:
            - array
            - "null"
          items:
            type: string
          description: Pre-computed keywords.
        labels:
          type:
            - array
            - "null"
          items:
            type: string
          description: Pre-computed labels, as a list of strings.
      additionalProperties: false
    Entity:
      type: object
      description: A single named entity detected in the extracted text.
      required:
        - category
        - text
        - start
        - end
      properties:
        category:
          $ref: '#/components/schemas/EntityCategory'
          description: Canonical category the entity belongs to (PERSON, ORG, LOCATION, etc.).
        confidence:
          type:
            - number
            - "null"
          format: float
          description: |-
            Backend-reported confidence in `[0.0, 1.0]`. `None` when the backend does not
            expose confidence scores.
        end:
          type: integer
          format: int32
          description: Byte-offset span in `ExtractedDocument::content` where the mention ends (exclusive).
          minimum: 0
        start:
          type: integer
          format: int32
          description: Byte-offset span in `ExtractedDocument::content` where the mention starts.
          minimum: 0
        text:
          type: string
          description: Raw mention text exactly as it appeared in the source.
    EntityCategory:
      oneOf:
        - type: string
          description: A person's name.
          enum:
            - person
        - type: string
          description: A company, institution, or organisation name.
          enum:
            - organization
        - type: string
          description: A geographic location (city, country, address).
          enum:
            - location
        - type: string
          description: A calendar date.
          enum:
            - date
        - type: string
          description: A time of day or duration.
          enum:
            - time
        - type: string
          description: A monetary amount with optional currency.
          enum:
            - money
        - type: string
          description: A percentage value.
          enum:
            - percent
        - type: string
          description: An email address.
          enum:
            - email
        - type: string
          description: A phone number.
          enum:
            - phone
        - type: string
          description: A URL or URI.
          enum:
            - url
        - type: object
          description: A caller-supplied custom category label.
          required:
            - custom
          properties:
            custom:
              type: string
              description: A caller-supplied custom category label.
      description: |-
        Standard entity categories produced by built-in NER backends.

        The `Custom(String)` variant lets caller-supplied categories (e.g. LLM
        schemas) flow through without losing fidelity to the consumer.
    EpubMetadata:
      type: object
      description: EPUB metadata (Dublin Core extensions).
      required:
        - format_type
      properties:
        cover_image:
          type:
            - string
            - "null"
          description: Path or identifier of the cover image within the EPUB container.
        coverage:
          type:
            - string
            - "null"
          description: Dublin Core `coverage` field (geographic or temporal scope).
        dc_format:
          type:
            - string
            - "null"
          description: Dublin Core `format` field (media type of the resource).
        dc_type:
          type:
            - string
            - "null"
          description: Dublin Core `type` field (nature or genre of the resource).
        format_type:
          type: string
          enum:
            - epub
        relation:
          type:
            - string
            - "null"
          description: Dublin Core `relation` field (related resource identifier).
        source:
          type:
            - string
            - "null"
          description: Dublin Core `source` field (origin resource identifier).
    ErrorBody:
      type: object
      description: Error body containing code, message, and optional details.
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: Stable string error code (e.g., "rag.vector.dim_mismatch", "not_found")
          example: rag.vector.dim_mismatch
        details:
          type: object
          description: Optional detailed error information (context-specific)
        message:
          type: string
          description: Human-readable error message
          example: Caller-supplied embedding does not match collection dimension
    ErrorMetadata:
      type: object
      description: Error metadata (for batch operations).
      required:
        - error_type
        - message
      properties:
        error_type:
          type: string
          description: Machine-readable error type identifier (e.g. "UnsupportedFormat").
        message:
          type: string
          description: Human-readable error description.
    ErrorResponse:
      type: object
      description: |-
        Canonical error response body.

        Wraps the nested error envelope required by all API error paths.
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/ErrorBody'
          description: The nested error body with code, message, and optional details.
    ExcelMetadata:
      type: object
      description: |-
        Excel/spreadsheet format metadata.

        Identifies the document as a spreadsheet source via the `FormatMetadata::Excel`
        discriminant. Sheet count and sheet names are stored inside this struct.
      required:
        - format_type
      properties:
        format_type:
          type: string
          enum:
            - excel
        sheet_count:
          type:
            - integer
            - "null"
          format: int32
          description: Number of sheets in the workbook.
          minimum: 0
        sheet_names:
          type:
            - array
            - "null"
          items:
            type: string
          description: Names of all sheets in the workbook.
    ExtractMultipartForm:
      type: object
      description: |-
        Multipart (`multipart/form-data`) variant of the extract request body.

        Send one or more files as binary parts whose field name is the filename
        (for example a part named `invoice.pdf`). Two reserved text parts carry
        JSON: `options` (an `ExtractionOptions`) and `webhook` (a
        `WebhookConfig`). Pro does not accept caller-supplied document lineage IDs
        in multipart or JSON requests. To attach a per-file extraction config
        override, add a text part named `config-<filename>` whose value is a
        JSON-encoded `FileExtractionConfig`.

        Documentation-only: parsing does not go through axum's derived
        `FromRequest`; this shape mirrors Enterprise's own multipart form.
      required:
        - file
      properties:
        file:
          type: array
          items:
            type: integer
            format: binary
            minimum: 0
          description: |-
            A document file. Repeat with one binary part per file; the part's field
            name (or filename) becomes the document filename and its `Content-Type`
            the document MIME type.
          contentMediaType: application/octet-stream
        options:
          $ref: '#/components/schemas/ExtractionOptions'
          description: Optional `options` part — a JSON-encoded `ExtractionOptions`.
        webhook:
          $ref: '#/components/schemas/WebhookConfig'
          description: |-
            Optional `webhook` part — a JSON-encoded `WebhookConfig` for async
            result delivery.
    ExtractRequest:
      allOf:
        - type: object
          description: |-
            Request body for `POST /v1/extract`.

            Carries a document batch, a set of seed URLs to crawl, or both. `options`
            and `webhook` apply to every document and URL in the request (ADR-0066,
            amended 2026-09-05).
          properties:
            crawl_config:
              type: object
              description: |-
                Crawl configuration applied to every entry in `urls`, overridable
                per-URL. Kept as opaque JSON — see `UrlInput.crawl_config`. Sending
                it without any `urls` is a validation error rather than a no-op.
            documents:
              type: array
              items:
                $ref: '#/components/schemas/DocumentInput'
              description: Documents to extract inline or via an integration reference.
            options:
              $ref: '#/components/schemas/ExtractionOptions'
              description: Extraction options shared by every document/URL in this request.
            project_id:
              type:
                - string
                - "null"
              format: uuid
              description: |-
                Project the job is written into. Absent writes into the project the
                credential carries.

                A session token freezes its project at login, so a user who belongs to
                several projects needs a way to name the one the dashboard shows. The
                Pro service authorizes any other project against the projects the
                caller can see, exactly as the job reads do.

                The Enterprise API service cannot make that check — its credential
                names no user — so it rejects a value that names any project other
                than the one the credential carries.
            urls:
              type: array
              items:
                $ref: '#/components/schemas/UrlInput'
              description: |-
                URLs to crawl and extract documents from. Seed URLs that share one
                effective configuration become one crawl job.
            webhook:
              $ref: '#/components/schemas/WebhookConfig'
              description: Webhook configuration for async result delivery.
          additionalProperties: false
        - anyOf:
            - type: object
              properties:
                crawl_config:
                  type: "null"
            - type: object
              required:
                - urls
              properties:
                urls:
                  type: array
                  items:
                    $ref: '#/components/schemas/UrlInput'
                  minItems: 1
    ExtractResponse:
      type: object
      description: |-
        Response body for `POST /v1/extract` (HTTP 202 Accepted).

        `job_ids` and `documents` carry one entry per directly submitted document,
        and `crawl_job_ids` one per group of seed URLs sharing a configuration.
        Each list is omitted from the wire entirely when empty, so a request that
        submitted only documents carries no `crawl_job_ids` and vice versa.
      required:
        - status
      properties:
        crawl_job_ids:
          type: array
          items:
            type: string
          description: |-
            Crawl job IDs, one per group of seed URLs sharing a configuration.
            Each is the `crawl_job_id` of the crawl-event stream.
        documents:
          type: array
          items:
            $ref: '#/components/schemas/DocumentSubmission'
          description: |-
            Detailed per-document submissions, including lineage when present.
            Populated by both tiers; `document_id`/`version_sequence` stay `None`
            on Pro, which has no lineage feature.
        job_ids:
          type: array
          items:
            type: string
          description: Job IDs created for directly submitted documents.
        status:
          type: string
          description: Overall submission status (e.g. `"pending"`, `"processing"`).
    ExtractedDocument:
      type: object
      description: |-
        Document extracted by the core extraction pipeline.

        `extract` and `extract_batch` return an `ExtractionResult` envelope whose
        `results` field contains these per-document payloads.
      required:
        - content
        - mime_type
        - metadata
        - tables
      properties:
        annotations:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/PdfAnnotation'
          description: |-
            PDF annotations extracted from the document.

            When annotation extraction is enabled via `PdfConfig::extract_annotations`,
            this field contains text notes, highlights, links, stamps, and other
            annotations found in PDF documents.
        children:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/ArchiveEntry'
          description: |-
            Nested extraction results from archive contents.

            When extracting archives, each processable file inside produces its own
            full extraction result. Set to `None` for non-archive formats.
            Use `max_archive_depth` in config to control recursion depth.
        chunks:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/Chunk'
          description: |-
            Text chunks when chunking is enabled.

            When chunking configuration is provided, the content is split into
            overlapping chunks for efficient processing. Each chunk contains the text,
            optional embeddings (if enabled), and metadata about its position.
        code_intelligence:
          description: |-
            Code intelligence results from tree-sitter analysis.

            Populated when extracting source code files with the `tree-sitter` feature.
            Contains metrics, structural analysis, imports/exports, comments,
            docstrings, symbols, diagnostics, and optionally chunked code segments.

            Stored as an opaque JSON value so that all language bindings (Go, Java,
            C#, …) can deserialize it as a raw JSON object rather than a typed struct.
            The underlying type is `tree_sitter_language_pack::ProcessResult`.
        content:
          type: string
          description: Plain-text representation of the extracted document content.
        counts:
          $ref: '#/components/schemas/DocumentCounts'
          description: |-
            Cheap structural counts (pages, tables, images).

            Always populated by the extraction pipeline, even when the `pages` /
            `images` collections are `None`. See `DocumentCounts`.
        detected_language_confidences:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/LanguageConfidence'
          description: |-
            Structured per-language detection results: confidence, document share, script,
            and reliability, alongside the ISO-code-only `detected_languages` (#261).

            One entry per language in `detected_languages`, in the same order. `None` under
            the same conditions as `detected_languages`: detection disabled, empty input
            text, or no language met the configured `min_confidence`.
        detected_languages:
          type:
            - array
            - "null"
          items:
            type: string
          description: ISO 639-1 language codes detected in the document content.
        djot_content:
          $ref: '#/components/schemas/DjotContent'
          description: |-
            Rich Djot content structure (when extracting Djot documents).

            When extracting Djot documents with structured extraction enabled,
            this field contains the full semantic structure including:
            - Block-level elements with nesting
            - Inline formatting with attributes
            - Links, images, footnotes
            - Math expressions
            - Complete attribute information

            The `content` field still contains plain text for backward compatibility.

            Always `None` for non-Djot documents.
        document:
          $ref: '#/components/schemas/DocumentStructure'
          description: |-
            Structured document tree (when document structure extraction is enabled).

            When `include_document_structure` is true in `ExtractionConfig`, this field
            contains the full hierarchical representation of the document including:
            - Heading-driven section nesting
            - Table grids with cell-level metadata
            - Content layer classification (body, header, footer, footnote)
            - Inline text annotations (formatting, links)
            - Bounding boxes and page numbers

            Independent of `result_format` — can be combined with Unified or ElementBased.
        elements:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/Element'
          description: |-
            Semantic elements when element-based result format is enabled.

            When result_format is set to ElementBased, this field contains semantic
            elements with type classification, unique identifiers, and metadata for
            Unstructured-compatible element-based processing.
        entities:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/Entity'
          description: |-
            Named entities detected in `content` by the NER post-processor.

            `None` when no NER backend is configured. Populated by the `xberg-gliner`
            ONNX backend or the LLM-driven backend (see `crates/xberg/src/text/ner/`).
        extracted_keywords:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/Keyword'
          description: |-
            Extracted keywords when keyword extraction is enabled.

            When keyword extraction (RAKE or YAKE) is configured, this field contains
            the extracted keywords with scores, algorithm info, and position data.
            Previously stored in `metadata.additional["keywords"]`.
        extraction_confidence:
          $ref: '#/components/schemas/ExtractionConfidence'
          description: |-
            Confidence score computed by the heuristics pipeline.

            Populated when the `heuristics` feature is enabled and confidence
            scoring has been performed.  Combines text-coverage, OCR aggregate
            confidence, and schema-compliance into a single `[0, 1]` value.

            `None` when confidence scoring is not configured or the feature is
            absent.
        extraction_method:
          $ref: '#/components/schemas/ExtractionMethod'
          description: |-
            Extraction strategy used to produce the returned text.

            Populated when the extractor can reliably distinguish native text extraction,
            OCR-only extraction, or mixed native/OCR output.
        form_fields:
          type: array
          items:
            $ref: '#/components/schemas/PdfFormField'
          description: |-
            Form fields extracted from a PDF's AcroForm or XFA structure.

            Populated by the PDF extractor when `PdfConfig::extract_form_fields` is
            enabled (default) and the document is a fillable form. Empty otherwise.
        formulas:
          type: array
          items:
            $ref: '#/components/schemas/Formula'
          description: |-
            Mathematical formulas recognized in the document.

            Populated from every source that produces formulas: layout-guided OCR
            (with geometry), VLM OCR (text only), and markup extraction (DOCX,
            PPTX, ODT, EPUB, HTML, JATS, LaTeX, Markdown, and related formats,
            without geometry). Empty when the document contains no formulas.
        images:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/ExtractedImage'
          description: |-
            Extracted images from the document.

            When image extraction is enabled via `ImageExtractionConfig`, this field
            contains all images found in the document with their raw data and metadata.
            Each image may optionally contain a nested `ocr_result` if OCR was performed.
        llm_usage:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/LlmUsage'
          description: |-
            LLM token usage and cost data for all LLM calls made during this extraction.

            Contains one entry per LLM call. Multiple entries are produced when
            VLM OCR, structured extraction, or LLM embeddings run during
            the same extraction.

            `None` when no LLM was used.
        metadata:
          $ref: '#/components/schemas/Metadata'
          description: Document-level metadata (author, title, dates, format-specific fields).
        mime_type:
          type: string
          description: MIME type of the source document (e.g. `"application/pdf"`).
        ocr_elements:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/OcrElement'
          description: |-
            OCR elements with full spatial and confidence metadata.

            When OCR is performed with element extraction enabled, this field contains
            the structured representation of detected text including:
            - Bounding geometry (rectangles or quadrilaterals)
            - Confidence scores (detection and recognition)
            - Rotation information
            - Hierarchical relationships (Tesseract only)

            This field preserves all metadata that would otherwise be lost when
            converting to plain text or markdown output formats.

            Only populated when `OcrElementConfig.include_elements` is true.
        ocr_page_failures:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/OcrPageFailure'
          description: |-
            Pages whose OCR failed while extraction continued, in the order the failures were reported.

            A record with `recovered` set means the document still has content for that page
            from native text or from embedded images. Xberg-produced results use `None` when
            no page failed.
        page_classifications:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/PageClassification'
          description: |-
            Per-page classifications produced by the page-classification post-processor.

            `None` when classification is not configured.
        pages:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/PageContent'
          description: |-
            Per-page content when page extraction is enabled.

            When page extraction is configured, the document is split into per-page content
            with tables and images mapped to their respective pages.
        processing_warnings:
          type: array
          items:
            $ref: '#/components/schemas/ProcessingWarning'
          description: |-
            Non-fatal warnings collected during processing pipeline stages.

            Captures errors from optional pipeline features (embedding, chunking,
            language detection, output formatting) that don't prevent extraction
            but may indicate degraded or incomplete results. These warnings are
            independent of `quality_score`, which assesses only retained text.
            Previously stored as individual keys in `metadata.additional`.
        quality_score:
          type:
            - number
            - "null"
          format: double
          description: |-
            Text cleanliness/readability score from quality analysis.

            A value between 0.0 and 1.0 describing the quality of the text that was
            retained. This is not a completeness or recall score: clean text can score
            highly even when an extractor omitted or rejected other content. Inspect
            `processing_warnings` separately for known degraded or partial extraction.

            When the text came from OCR and the result carries enough recognized words to
            judge, this score is additionally capped by the mean OCR recognition confidence.
            Text that looks clean but that OCR itself had little confidence in therefore
            cannot score high. A native, non-OCR extraction is not capped.
            Previously stored in `metadata.additional["quality_score"]`.
        redaction_report:
          $ref: '#/components/schemas/RedactionReport'
          description: |-
            Audit report of redactions applied by the redaction post-processor.

            The redaction processor rewrites `content`, `formatted_content`, every
            chunk's text, and the textual fields of `entities` / `summary` / `translation` /
            `page_classifications` in place. This report describes what was found and how it
            was replaced. `None` when redaction is not configured.
        revisions:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/DocumentRevision'
          description: |-
            Tracked changes embedded in the source document.

            Populated by per-format extractors that understand change-tracking
            metadata (DOCX `w:ins`/`w:del`/`w:rPrChange`, ODT `text:change-*`,
            …). Every extractor defaults to `None` until its format-specific
            implementation is added. Extractors that do populate this field follow
            the "accepted-changes" convention: inserted text is present in
            `content`, deleted text is absent — the revision list is the separate
            audit trail.
        structured_output:
          description: |-
            Structured extraction output from LLM-based JSON schema extraction.

            When `structured_extraction` is configured in `ExtractionConfig`, the
            extracted document content is sent to a VLM with the provided JSON schema.
            The response is parsed and stored here as a JSON value matching the schema.
        summary:
          $ref: '#/components/schemas/DocumentSummary'
          description: |-
            Summary of `content` produced by the summarisation post-processor.

            `None` when summarisation is not configured. Populated by the TextRank
            extractive backend (deterministic, no external service) or by the
            liter-llm-driven abstractive backend.
        tables:
          type: array
          items:
            $ref: '#/components/schemas/Table'
          description: Tables extracted from the document, each with structured cell data.
        translation:
          $ref: '#/components/schemas/Translation'
          description: |-
            Translation of `content` produced by the translation post-processor.

            `None` when translation is not configured.
        uris:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/ExtractedUri'
          description: |-
            URIs/links discovered during document extraction.

            Contains hyperlinks, image references, citations, email addresses, and
            other URI-like references found in the document. Always extracted when
            present in the source document.
    ExtractedImage:
      type: object
      description: |-
        Extracted image from a document.

        Contains raw image data, metadata, and optional nested OCR results.
        Raw bytes allow cross-language compatibility - users can convert to
        PIL.Image (Python), Sharp (Node.js), or other formats as needed.
      required:
        - data
        - format
        - image_index
      properties:
        bits_per_component:
          type:
            - integer
            - "null"
          format: int32
          description: Bits per color component (e.g., 8, 16)
          minimum: 0
        bounding_box:
          $ref: '#/components/schemas/BoundingBox'
          description: |-
            Bounding box of the image on the page (PDF coordinates: x0=left, y0=bottom, x1=right, y1=top).
            Only populated for PDF-extracted images when position data is available from the PDF extractor.
        caption:
          type:
            - string
            - "null"
          description: |-
            VLM-generated caption describing the image, when captioning is configured.

            Populated by the captioning post-processor
            (`crates/xberg/src/plugins/processor/builtin/captioning.rs`), which routes
            each image through `crate::llm::region_extractor::extract_region_with_vlm` in
            caption mode. `None` when captioning is disabled or the VLM declined to caption.
        cluster_id:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Identifier shared across images that form a single logical figure
            (e.g. all raster tiles of one technical drawing). `None` for singletons.
          minimum: 0
        colorspace:
          type:
            - string
            - "null"
          description: Colorspace information (e.g., "RGB", "CMYK", "Gray")
        data:
          type: array
          items:
            type: integer
            format: binary
            minimum: 0
          description: |-
            Raw image data (PNG, JPEG, WebP, etc. bytes).
            Uses `bytes::Bytes` for cheap cloning of large buffers.
        data_base64:
          type:
            - string
            - "null"
          description: |-
            Base64-encoded copy of `data`; populated when `ImageExtractionConfig::include_data_base64`
            is `true`. Omitted from JSON by default; use instead of `data` in JSON-only clients.
        description:
          type:
            - string
            - "null"
          description: Optional description of the image
        format:
          type: string
          description: |-
            Image format (e.g., "jpeg", "png", "webp")
            Uses Cow<'static, str> to avoid allocation for static literals.
        height:
          type:
            - integer
            - "null"
          format: int32
          description: Image height in pixels
          minimum: 0
        image_index:
          type: integer
          format: int32
          description: Zero-indexed position of this image in the document/page
          minimum: 0
        image_kind:
          $ref: '#/components/schemas/ImageKind'
          description: |-
            Heuristic classification of what this image likely depicts.
            `None` if classification was disabled or inconclusive.
        is_mask:
          type: boolean
          description: Whether this image is a mask image
        kind_confidence:
          type:
            - number
            - "null"
          format: float
          description: Confidence score for `image_kind`, in the range 0.0 to 1.0.
        ocr_result:
          $ref: '#/components/schemas/ExtractedDocument'
          description: |-
            Nested OCR extraction result (if image was OCRed)

            When OCR is performed on this image, the result is embedded here
            rather than in a separate collection, making the relationship explicit.
        page_number:
          type:
            - integer
            - "null"
          format: int32
          description: Page/slide number where image was found (1-indexed)
          minimum: 0
        qr_codes:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/QrCode'
          description: |-
            QR codes decoded from this image, when QR detection is enabled.

            Populated by the QR post-processor (`crates/xberg/src/extractors/qr.rs`) via
            the pure-Rust `rqrr` decoder. `None` when QR detection is disabled; an empty
            `Some(vec![])` when detection ran but found nothing.
        source_path:
          type:
            - string
            - "null"
          description: |-
            Original source path of the image within the document archive (e.g., "media/image1.png" in DOCX).
            Used for rendering image references when the binary data is not extracted.
        width:
          type:
            - integer
            - "null"
          format: int32
          description: Image width in pixels
          minimum: 0
    ExtractedUri:
      type: object
      description: |-
        A URI extracted from a document.

        Represents any link, reference, or resource pointer found during extraction.
        The `kind` field classifies the URI semantically, while `label` carries
        optional human-readable display text.
      required:
        - url
        - kind
      properties:
        kind:
          $ref: '#/components/schemas/UriKind'
          description: Semantic classification of the URI.
        label:
          type:
            - string
            - "null"
          description: Optional display text / label for the link.
        page:
          type:
            - integer
            - "null"
          format: int32
          description: Optional page number where the URI was found (1-indexed).
          minimum: 0
        url:
          type: string
          description: The URL or path string.
    ExtractionConfidence:
      type: object
      description: |-
        Combined confidence on `[0, 1]`.

        When OCR did not run, the `ocr_aggregate` weight folds into `text_coverage`
        so the weighted sum still totals 1.0.
      required:
        - text_coverage
        - schema_compliance
        - combined
      properties:
        combined:
          type: number
          format: float
          description: Weighted blend in `[0, 1]`.  The value compared against the fallback threshold.
        ocr_aggregate:
          type:
            - number
            - "null"
          format: float
          description: |-
            OCR recognition confidence, word-count-weighted across every recognized word, when OCR
            ran; `None` when it did not.
        schema_compliance:
          $ref: '#/components/schemas/SchemaCompliance'
          description: Whether the merged output validates against the preset schema.
        text_coverage:
          type: number
          format: float
          description: Fraction of pages with a usable text layer.
    ExtractionJobResponse:
      type: object
      description: Metadata for one extraction job returned by `GET /v1/jobs/{id}`.
      required:
        - id
        - filename
        - mime_type
        - status
        - created_at
      properties:
        cached:
          type:
            - boolean
            - "null"
          description: Whether the result came from the server-side cache, when known.
        child_job_ids:
          type: array
          items:
            type: string
          description: Child extraction jobs created by document splitting.
        completed_at:
          type:
            - string
            - "null"
          description: ISO 8601 completion timestamp, when available.
        created_at:
          type: string
          format: date-time
          description: ISO 8601 creation timestamp.
        error:
          type:
            - string
            - "null"
          description: Failure reason for a failed job.
        filename:
          type: string
          description: Original filename.
        id:
          type: string
          format: uuid
          description: Unique job identifier.
        images_extracted:
          type:
            - integer
            - "null"
          format: int32
          description: Images extracted, when recorded by the tier's persistence layer.
        mime_type:
          type: string
          description: MIME type of the submitted document.
        pages_extracted:
          type:
            - integer
            - "null"
          format: int32
          description: Pages extracted, when recorded by the tier's persistence layer.
        parent_crawl_job_id:
          type:
            - string
            - "null"
          format: uuid
          description: The crawl job that spawned this job as a child, when any.
        processing_time_ms:
          type:
            - integer
            - "null"
          format: int64
          description: Server-side processing duration in milliseconds.
        result:
          $ref: '#/components/schemas/ExtractedDocument'
          description: |-
            Extraction result embedded by Enterprise for compatibility. Clients may
            use `GET /v1/jobs/{id}/result` on either tier instead.
        source_url:
          type:
            - string
            - "null"
          description: |-
            The crawled page URL this document came from, when the job is a
            crawl child (#1961). Absent for a direct upload.
        status:
          $ref: '#/components/schemas/JobStatus'
          description: Current job status.
        tables_extracted:
          type:
            - integer
            - "null"
          format: int32
          description: Tables extracted, when recorded by the tier's persistence layer.
        tuning_profile_id:
          type:
            - string
            - "null"
          description: Applied tuning profile id, when this Pro job used one.
        tuning_profile_route:
          type:
            - string
            - "null"
          description: 'Matched Pro tuning profile route: `global` or `route:<zero-based-index>`.'
        webhook_delivery:
          $ref: '#/components/schemas/WebhookDelivery'
          description: Inline webhook delivery state, when this job configured one.
    ExtractionMethod:
      type: string
      description: How the extracted text was produced.
      enum:
        - native
        - ocr
        - mixed
    ExtractionOptions:
      type: object
      description: Extraction options shared across every document in a request.
      properties:
        context:
          type:
            - object
            - "null"
          description: |-
            Free-form context merged into the preset's context template before
            the system prompt is sent to the model. Ignored when `preset` and
            `saved_preset_id` are both unset.
          additionalProperties:
            type: string
          propertyNames:
            type: string
        extraction_config:
          type: object
          description: |-
            Extraction configuration as opaque JSON. Kept untyped here: the
            concrete schema (`services/api::rest::types::ExtractionConfig` on
            Enterprise, a raw `serde_json::Value` on Pro) is service-local and out
            of scope for this convergence pass.
            Both tiers reject unknown extraction-config fields and reject an
            effective config with `disable_ocr: true` alongside an `ocr` block.
        preset:
          type:
            - string
            - "null"
          description: |-
            Curated preset identifier. Mutually exclusive with `saved_preset_id`
            and with an inline schema in `extraction_config`.
        saved_preset_id:
          type:
            - string
            - "null"
          description: |-
            Saved preset ID (project-scoped, user-created). Mutually exclusive
            with `preset`.

            Both tiers apply it. The Enterprise extraction handler (`extract` in
            `services/api/src/rest/extract.rs`) fetches the saved preset through
            the project-scoped connection after the project is authorized and
            applies its schema and system prompt before any job exists, the same
            way Pro injects it, so both tiers dispatch the same config. An unknown
            id is a validation error.
        split_documents:
          type:
            - boolean
            - "null"
          description: |-
            When true and a document is a PDF, enable multi-document boundary
            detection and fan-out orchestration. Enterprise-only. Xberg Pro has no
            splitter and returns 400 when `split_documents` is true.
        tuning_profile_id:
          type:
            - string
            - "null"
          description: |-
            Apply a promoted Auto-Tune profile to Pro document extraction, by its id
            from `GET /v1/tuning-profiles`. Enterprise and Pro requests containing
            crawl URLs reject this option instead of silently ignoring it.

            The profile supplies each document's starting configuration, routed by
            that document's own features, so a profile learned with routing on
            applies its routes rather than only its global config. A per-document
            `config` override still wins, as it does over every other source.

            Mutually exclusive with `preset`, `saved_preset_id` and a request-level
            `extraction_config`: each of those is another answer to the same
            question, and silently picking one would make the winner invisible.
      additionalProperties: false
    FictionBookMetadata:
      type: object
      description: FictionBook (FB2) metadata.
      required:
        - format_type
      properties:
        annotation:
          type:
            - string
            - "null"
          description: Short annotation / summary from the FB2 `<annotation>` element.
        format_type:
          type: string
          enum:
            - fiction_book
        genres:
          type: array
          items:
            type: string
          description: Genre tags as declared in the FB2 `<genre>` elements.
        sequences:
          type: array
          items:
            type: string
          description: Book series (sequence) names, if any.
    FileExtractionConfig:
      type: object
      description: |-
        Per-file extraction config override for batch/presign requests.

        All fields are optional — only provided fields override the batch-level config.
        Batch-only fields (`use_cache`, `cache_namespace`, `cache_ttl_secs`,
        `max_concurrent_extractions`, `security_limits`, `max_archive_depth`,
        `max_embedded_file_bytes`) are not per-file overrides.

        Precedence, highest to lowest: this per-file config > the request's
        batch-level `options.extraction_config` > a curated/saved preset > the
        project default. The preset and project default are already folded into
        the batch-level config by the time a per-file override is applied, and the
        merge is shallow over top-level fields, so an override here always wins the
        field it sets and an unset field falls through to whatever the batch level
        carries.
      properties:
        chunking:
          $ref: '#/components/schemas/ChunkingConfig'
        content_filter:
          $ref: '#/components/schemas/ContentFilterConfig'
        disable_ocr:
          type:
            - boolean
            - "null"
          description: |-
            `true` conflicts with an `ocr` block, including one inherited from
            the batch configuration.
        enable_quality_processing:
          type:
            - boolean
            - "null"
        extraction_timeout_secs:
          type:
            - integer
            - "null"
          format: int64
          description: |-
            Per-file extraction timeout in seconds (default: 600, min: 1, max: 600).

            The engine clamps a client-supplied value lower-only against its own
            default (`crates/engine/src/config_convert.rs`'s `wire_to_xberg`, which
            `wire_to_xberg_file` delegates to for a per-file override): a value
            above 600 is silently reduced to 600, never rejected. `maximum` here
            documents that existing ceiling for the SDK rather than enforcing a
            second one — this field is otherwise unvalidated at the API boundary.
          example: 600
          maximum: 600
          minimum: 1
        force_ocr:
          type:
            - boolean
            - "null"
        force_ocr_pages:
          type:
            - array
            - "null"
          items:
            type: integer
            format: int32
            minimum: 0
        html_options:
          type: object
        images:
          $ref: '#/components/schemas/ImageExtractionConfig'
        include_document_structure:
          type:
            - boolean
            - "null"
        keywords:
          $ref: '#/components/schemas/KeywordConfig'
        language_detection:
          $ref: '#/components/schemas/LanguageDetectionConfig'
        layout:
          $ref: '#/components/schemas/LayoutDetectionConfig'
        ocr:
          $ref: '#/components/schemas/OcrConfig'
        ocr_strategy:
          $ref: '#/components/schemas/OcrStrategy'
          description: |-
            Which pages go to OCR when neither `force_ocr` nor `force_ocr_pages`
            applies. Omitting it keeps xberg's default, `auto`.
        output_format:
          type:
            - string
            - "null"
          description: |-
            Rendered output format: `plain` or `text`, `markdown` or `md`, `djot`,
            `html`, `json`, or `doctags`. Any other value is refused with a 400.
        pages:
          $ref: '#/components/schemas/PageConfig'
        pdf_options:
          $ref: '#/components/schemas/PdfConfig'
        postprocessor:
          $ref: '#/components/schemas/PostProcessorConfig'
        result_format:
          type:
            - string
            - "null"
        structured_extraction:
          $ref: '#/components/schemas/StructuredExtractionConfig'
        token_reduction:
          $ref: '#/components/schemas/TokenReductionConfig'
      additionalProperties: false
    Footnote:
      type: object
      description: Footnote in Djot.
      required:
        - label
        - content
      properties:
        content:
          type: array
          items:
            $ref: '#/components/schemas/FormattedBlock'
          description: Footnote content blocks
        label:
          type: string
          description: Footnote label
    FormFieldType:
      type: string
      description: |-
        Kind of a PDF form field.

        Mirrors `xberg_native_pdf`'s widget field taxonomy without leaking the upstream
        type across the binding surface.
      enum:
        - text
        - checkbox
        - radio
        - choice
        - signature
        - button
        - unknown
    FormatMetadata:
      oneOf:
        - $ref: '#/components/schemas/PdfMetadata'
        - $ref: '#/components/schemas/DocxMetadata'
        - $ref: '#/components/schemas/ExcelMetadata'
        - $ref: '#/components/schemas/EmailMetadata'
        - $ref: '#/components/schemas/PptxMetadata'
        - $ref: '#/components/schemas/ArchiveMetadata'
        - $ref: '#/components/schemas/ImageMetadata'
        - $ref: '#/components/schemas/XmlMetadata'
        - $ref: '#/components/schemas/TextMetadata'
        - $ref: '#/components/schemas/HtmlMetadata'
        - $ref: '#/components/schemas/OcrMetadata'
        - $ref: '#/components/schemas/CsvMetadata'
        - $ref: '#/components/schemas/BibtexMetadata'
        - $ref: '#/components/schemas/CitationMetadata'
        - $ref: '#/components/schemas/FictionBookMetadata'
        - $ref: '#/components/schemas/DbfMetadata'
        - $ref: '#/components/schemas/EpubMetadata'
        - $ref: '#/components/schemas/JatsMetadata'
        - $ref: '#/components/schemas/PstMetadata'
        - $ref: '#/components/schemas/AudioMetadata'
        - $ref: '#/components/schemas/CodeMetadata'
      description: Format-specific metadata (discriminated union). Only one format type can exist per extraction result.
      discriminator:
        propertyName: format_type
        mapping:
          archive: '#/components/schemas/ArchiveMetadata'
          audio: '#/components/schemas/AudioMetadata'
          bibtex: '#/components/schemas/BibtexMetadata'
          citation: '#/components/schemas/CitationMetadata'
          code: '#/components/schemas/CodeMetadata'
          csv: '#/components/schemas/CsvMetadata'
          dbf: '#/components/schemas/DbfMetadata'
          docx: '#/components/schemas/DocxMetadata'
          email: '#/components/schemas/EmailMetadata'
          epub: '#/components/schemas/EpubMetadata'
          excel: '#/components/schemas/ExcelMetadata'
          fiction_book: '#/components/schemas/FictionBookMetadata'
          html: '#/components/schemas/HtmlMetadata'
          image: '#/components/schemas/ImageMetadata'
          jats: '#/components/schemas/JatsMetadata'
          ocr: '#/components/schemas/OcrMetadata'
          pdf: '#/components/schemas/PdfMetadata'
          pptx: '#/components/schemas/PptxMetadata'
          pst: '#/components/schemas/PstMetadata'
          text: '#/components/schemas/TextMetadata'
          xml: '#/components/schemas/XmlMetadata'
    FormattedBlock:
      type: object
      description: |-
        Block-level element in a Djot document.

        Represents structural elements like headings, paragraphs, lists, code blocks, etc.
      required:
        - block_type
        - inline_content
      properties:
        attributes:
          $ref: '#/components/schemas/Attributes'
          description: Element attributes (classes, IDs, key-value pairs)
        block_type:
          $ref: '#/components/schemas/BlockType'
          description: Type of block element
        children:
          type: array
          items:
            $ref: '#/components/schemas/FormattedBlock'
          description: Nested blocks for containers (blockquotes, list items, divs)
        code:
          type:
            - string
            - "null"
          description: Raw code content for code blocks
        inline_content:
          type: array
          items:
            $ref: '#/components/schemas/InlineElement'
          description: Inline content within the block
        language:
          type:
            - string
            - "null"
          description: Language identifier for code blocks
        level:
          type:
            - integer
            - "null"
          description: Heading level (1-6) for headings, or nesting level for lists
          minimum: 0
    Formula:
      type: object
      description: |-
        A mathematical formula extracted from a document.

        Three kinds of sources populate this type. Layout-guided OCR detects
        formula regions and recognizes them; those formulas carry a `bbox` and a
        `page`. VLM OCR recognizes formulas in transcribed text without layout, so
        its formulas carry no geometry. Markup extraction (DOCX, PPTX, ODT, EPUB,
        HTML, JATS, LaTeX, Markdown, and related formats) converts embedded math
        to LaTeX, also without geometry.
      required:
        - latex
      properties:
        bbox:
          $ref: '#/components/schemas/BoundingBox'
          description: |-
            Bounding box of the formula region on its page. `None` for markup sources.

            PDF OCR sources report PDF point coordinates with the origin at the
            bottom-left of the page, comparable to native PDF geometry. Image
            sources, and PDF pages whose geometry is unavailable, report pixels of
            the image the OCR backend saw. The C FFI reports an absent bbox as a
            null pointer.
        latex:
          type: string
          description: |-
            LaTeX source of the formula, without surrounding `$$` delimiters.

            Markup converters and formula OCR produce real LaTeX. The native PDF
            layout path stores the plain text of a detected formula region, which
            keeps the original Unicode math characters instead of LaTeX commands.
            To render the formula in Markdown or other formats, wrap it in `$$..$$`.
        page:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            1-indexed page number the formula appears on. `None` when the source
            format has no page concept. The C FFI reports an absent page as `0`.
          minimum: 0
    GetJobResponse:
      oneOf:
        - allOf:
            - $ref: '#/components/schemas/ExtractionJobResponse'
              description: An extraction job.
            - type: object
              required:
                - job_type
              properties:
                job_type:
                  type: string
                  enum:
                    - extraction
          description: An extraction job.
        - allOf:
            - $ref: '#/components/schemas/CrawlJobResponse'
              description: A crawl job.
            - type: object
              required:
                - job_type
              properties:
                job_type:
                  type: string
                  enum:
                    - crawl
          description: A crawl job.
      description: |-
        Canonical response for `GET /v1/jobs/{id}` on both product tiers.

        `job_type` is the stable wire discriminator. Keeping selection explicit
        lets clients accept future fields without making the variants ambiguous.
    GridCell:
      type: object
      description: Individual grid cell with position and span metadata.
      required:
        - content
        - row
        - col
      properties:
        bbox:
          $ref: '#/components/schemas/BoundingBox'
          description: Bounding box for this cell (if available).
        col:
          type: integer
          format: int32
          description: Zero-indexed column position.
          minimum: 0
        col_span:
          type: integer
          format: int32
          description: Number of columns this cell spans.
          minimum: 0
        content:
          type: string
          description: Cell text content.
        heading_level:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Outline level (1-6) of the heading style this cell's text carries, when it has one.

            A DOCX banner row -- row 0, one cell spanning the grid, styled `Heading1`..`Heading6` --
            is what Word's navigation pane and a `TOC` field treat as the document's outline, but as
            a table cell it reached consumers as anonymous text (GH#1587). `content` is deliberately
            left as the bare cell text: prefixing it with `#` would put a markdown heading inside a
            table cell, which is invalid where it lands and changes text every existing consumer
            already reads. This field is the signal instead, so a caller can decide for itself
            whether a `heading 2` in a banner row is a section title or a column label.
          minimum: 0
        is_header:
          type: boolean
          description: Whether this is a header cell.
        row:
          type: integer
          format: int32
          description: Zero-indexed row position.
          minimum: 0
        row_span:
          type: integer
          format: int32
          description: Number of rows this cell spans.
          minimum: 0
        style_name:
          type:
            - string
            - "null"
          description: |-
            Human-readable name of the paragraph style applied to this cell's text (`heading 2`).

            Carries the style even when it resolves to no outline level, so a caller can key on a
            named style this crate does not map to a heading. See `GridCell.heading_level`.
    GroundTruthKind:
      type: string
      description: Kind of ground truth supplied for a document in an auto-tune run.
      enum:
        - markdown
        - plaintext
        - structured
    HeaderMetadata:
      type: object
      description: Header/heading element metadata.
      required:
        - level
        - text
        - depth
        - html_offset
      properties:
        depth:
          type: integer
          format: int32
          description: Document tree depth at the header element
          minimum: 0
        html_offset:
          type: integer
          format: int32
          description: Byte offset in original HTML document
          minimum: 0
        id:
          type:
            - string
            - "null"
          description: HTML id attribute if present
        level:
          type: integer
          format: int32
          description: 'Header level: 1 (h1) through 6 (h6)'
          minimum: 0
        text:
          type: string
          description: Normalized text content of the header
    HeadingContext:
      type: object
      description: |-
        Heading context for a chunk within a Markdown document.

        Contains the heading hierarchy from document root to this chunk's section.
      required:
        - headings
      properties:
        headings:
          type: array
          items:
            $ref: '#/components/schemas/HeadingLevel'
          description: |-
            The heading hierarchy from document root to this chunk's section.
            Index 0 is the outermost (h1), last element is the most specific.
    HeadingLevel:
      type: object
      description: A single heading in the hierarchy.
      required:
        - level
        - text
      properties:
        level:
          type: integer
          format: int32
          description: Heading depth (1 = h1, 2 = h2, etc.)
          minimum: 0
        text:
          type: string
          description: The text content of the heading.
    HealthResponse:
      type: object
      description: Liveness + tier probe. The frontend reads `tier` for runtime feature gating.
      required:
        - status
        - tier
        - license
        - rag
        - auto_tune
      properties:
        auto_tune:
          $ref: '#/components/schemas/AutoTuneHealth'
        license:
          $ref: '#/components/schemas/LicenseHealth'
        rag:
          $ref: '#/components/schemas/RagHealth'
        status:
          type: string
        tier:
          type: string
    HierarchicalBlock:
      type: object
      required:
        - text
        - font_size
        - level
      properties:
        bbox:
          $ref: '#/components/schemas/HierarchicalBoundingBox'
          description: |-
            Bounding box information for the block

            Contains left, top, right, and bottom coordinates in PDF units.
        font_size:
          type: number
          format: float
          description: The font size of the text in this block
        level:
          type: string
          description: |-
            The hierarchy level of this block (H1-H6 or Body)

            Levels correspond to HTML heading tags:
            - "h1": Top-level heading
            - "h2": Secondary heading
            - "h3": Tertiary heading
            - "h4": Quaternary heading
            - "h5": Quinary heading
            - "h6": Senary heading
            - "body": Body text (no heading level)
        text:
          type: string
          description: The text content of this block
    HierarchicalBoundingBox:
      type: object
      required:
        - left
        - top
        - right
        - bottom
      properties:
        bottom:
          type: number
        left:
          type: number
        right:
          type: number
        top:
          type: number
    HierarchyConfig:
      type: object
      description: Heading hierarchy detection configuration.
      properties:
        enabled:
          type:
            - boolean
            - "null"
          description: Enable hierarchy detection. Defaults to true when omitted.
        include_bbox:
          type:
            - boolean
            - "null"
          description: Include bounding boxes. Defaults to true when omitted.
        k_clusters:
          type:
            - integer
            - "null"
          format: int32
          description: Number of font-size clusters for heading levels. Defaults to 3 when omitted.
          minimum: 0
        ocr_coverage_threshold:
          type:
            - number
            - "null"
          format: float
          description: Unsupported compatibility field. Non-null values are not supported by the extraction engine; omit or send null.
      additionalProperties: false
    HtmlMetadata:
      type: object
      description: |-
        HTML metadata extracted from HTML documents.

        Includes document-level metadata, Open Graph data, Twitter Card metadata,
        and extracted structural elements (headers, links, images, structured data).
      required:
        - format_type
      properties:
        author:
          type:
            - string
            - "null"
          description: Document author from `<meta name="author">` tag
        base_href:
          type:
            - string
            - "null"
          description: Base URL from `<base href="">` tag for resolving relative URLs
        canonical_url:
          type:
            - string
            - "null"
          description: Canonical URL from `<link rel="canonical">` tag
        description:
          type:
            - string
            - "null"
          description: Document description from `<meta name="description">` tag
        format_type:
          type: string
          enum:
            - html
        headers:
          type: array
          items:
            $ref: '#/components/schemas/HeaderMetadata'
          description: Extracted header elements with hierarchy
        images:
          type: array
          items:
            $ref: '#/components/schemas/ImageMetadataType'
          description: Extracted images with source and dimensions
        keywords:
          type: array
          items:
            type: string
          description: Document keywords from `<meta name="keywords">` tag, split on commas
        language:
          type:
            - string
            - "null"
          description: Document language from `lang` attribute
        links:
          type: array
          items:
            $ref: '#/components/schemas/LinkMetadata'
          description: Extracted hyperlinks with type classification
        meta_tags:
          type: object
          description: |-
            Additional meta tags not covered by specific fields
            Keys are meta name/property attributes, values are content
          additionalProperties:
            type: string
          propertyNames:
            type: string
        open_graph:
          type: object
          description: |-
            Open Graph metadata (og:* properties) for social media
            Keys like "title", "description", "image", "url", etc.
          additionalProperties:
            type: string
          propertyNames:
            type: string
        structured_data:
          type: array
          items:
            $ref: '#/components/schemas/StructuredData'
          description: Extracted structured data blocks
        text_direction:
          $ref: '#/components/schemas/TextDirection'
          description: Document text direction from `dir` attribute
        title:
          type:
            - string
            - "null"
          description: Document title from `<title>` tag
        twitter_card:
          type: object
          description: |-
            Twitter Card metadata (twitter:* properties)
            Keys like "card", "site", "creator", "title", "description", "image", etc.
          additionalProperties:
            type: string
          propertyNames:
            type: string
    ImageDimensions:
      type: object
      required:
        - width
        - height
      properties:
        height:
          type: integer
        width:
          type: integer
    ImageDpi:
      type: object
      required:
        - horizontal
        - vertical
      properties:
        horizontal:
          type: number
        vertical:
          type: number
    ImageExtractionConfig:
      type: object
      description: Image extraction and processing configuration.
      properties:
        auto_adjust_dpi:
          type:
            - boolean
            - "null"
          description: Auto-adjust DPI based on content. Defaults to true when omitted.
        extract_images:
          type:
            - boolean
            - "null"
          description: Extract images from documents. Defaults to true when omitted.
        inject_placeholders:
          type:
            - boolean
            - "null"
          description: Inject image reference placeholders in markdown output. Defaults to true when omitted.
        max_dpi:
          type:
            - integer
            - "null"
          format: int32
          description: Maximum DPI threshold. Defaults to 600 when omitted.
        max_image_dimension:
          type:
            - integer
            - "null"
          format: int32
          description: Maximum image dimension (width or height). Defaults to 4096 when omitted.
        min_dpi:
          type:
            - integer
            - "null"
          format: int32
          description: Minimum DPI threshold. Defaults to 72 when omitted.
        target_dpi:
          type:
            - integer
            - "null"
          format: int32
          description: Target DPI for image normalization. Defaults to 300 when omitted.
      additionalProperties: false
    ImageKind:
      type: string
      description: Heuristic classification of what an image likely depicts.
      enum:
        - photograph
        - diagram
        - chart
        - drawing
        - text_block
        - decoration
        - logo
        - icon
        - tile_fragment
        - mask
        - page_raster
        - unknown
    ImageMetadata:
      type: object
      description: |-
        Image metadata extracted from image files.

        Includes dimensions, format, and EXIF data.
      required:
        - width
        - height
        - format
        - exif
        - format_type
      properties:
        exif:
          type: object
          description: EXIF metadata tags
          additionalProperties:
            type: string
          propertyNames:
            type: string
        format:
          type: string
          description: Image format (e.g., "PNG", "JPEG", "TIFF")
        format_type:
          type: string
          enum:
            - image
        height:
          type: integer
          format: int32
          description: Image height in pixels
          minimum: 0
        width:
          type: integer
          format: int32
          description: Image width in pixels
          minimum: 0
    ImageMetadataType:
      type: object
      description: Image element metadata.
      required:
        - src
        - image_type
        - attributes
      properties:
        alt:
          type:
            - string
            - "null"
          description: Alternative text from alt attribute
        attributes:
          type: array
          items:
            $ref: '#/components/schemas/KeyValueAttribute'
          description: Additional attributes as key-value pairs.
        dimensions:
          oneOf:
            - $ref: '#/components/schemas/ImageDimensions'
              description: Image dimensions if available.
            - type: "null"
        image_type:
          $ref: '#/components/schemas/ImageType'
          description: Image type classification
        src:
          type: string
          description: Image source (URL, data URI, or SVG content)
        title:
          type:
            - string
            - "null"
          description: Title attribute
    ImagePreprocessingConfig:
      type: object
      description: |-
        Image preprocessing configuration for OCR.

        These settings control how images are preprocessed before OCR to improve
        text recognition quality. Different preprocessing strategies work better
        for different document types.
      properties:
        auto_rotate:
          type: boolean
          description: Auto-detect and correct image rotation.
          default: false
        binarization_method:
          type: string
          description: |-
            Binarization method: "none" (alias "off"), "otsu", "sauvola", or "adaptive".
            `deskew` must be `false` when this is `none` or `off`.
          default: otsu
        contrast_enhance:
          type: boolean
          description: |-
            Enhance contrast for better text visibility. With `none` or `off` binarization, this
            applies background normalization and sharpening while preserving grayscale pixels.
          default: false
        denoise:
          type: boolean
          description: Remove noise from the image.
          default: false
        deskew:
          type: boolean
          description: Correct skew (tilted images). Must be `false` with `none` or `off` binarization.
          default: true
        invert_colors:
          type: boolean
          description: Invert colors (white text on black → black on white).
          default: false
        normalize_shaded_rows:
          type: boolean
          description: |-
            Normalize shaded table rows (e.g. a subtotal row on a light or dark fill) before
            binarization, so each shaded band is stretched to its own dark-text-on-white
            polarity instead of being lost to a single whole-page threshold (GH#1785).

            This is a per-band step, not a replacement for `binarization_method`: no single
            whole-page method recovers every fill color, and the per-band step itself can
            regress a row style it does not fully model (e.g. a mid-grey fill with white
            text), so it defaults to `false` rather than being enabled unconditionally.

            Has no effect when `binarization_method` is `"none"` or `"off"` **and** `contrast_enhance`
            is `true`: that path re-reads the original image to apply background normalization and
            sharpening, discarding the per-band result. A `WARN` is emitted when the option is
            requested in that combination (GH#1837).
          default: false
        target_dpi:
          type: integer
          format: int32
          description: |-
            Target DPI for the image (300 is standard, 600 for small text).

            For a PDF page, this resamples the already-rendered raster; it does not make the page
            re-render at a higher native resolution. A value above what
            `image::preprocessing::calculate_target_dpi`'s memory/dimension clamp allows is
            silently capped (GH#1786: 400 and 600 both clamped to the same ~372 on a Letter page
            and produced byte-identical output), so upscaling interpolated pixels this way adds no
            detail. To change the actual PDF render resolution, set `images.target_dpi` on
            `ExtractionConfig` instead
            (`image::dpi::effective_pdf_render_dpi`).
          default: 300
      additionalProperties: false
    ImagePreprocessingMetadata:
      type: object
      required:
        - original_dimensions
        - original_dpi
        - target_dpi
        - scale_factor
        - auto_adjusted
        - final_dpi
        - resample_method
        - dimension_clamped
        - skipped_resize
      properties:
        auto_adjusted:
          type: boolean
          description: Whether DPI was auto-adjusted based on content
        calculated_dpi:
          type:
            - integer
            - "null"
          format: int32
          description: Calculated optimal DPI (if auto_adjust_dpi enabled)
        dimension_clamped:
          type: boolean
          description: Whether dimensions were clamped to max_image_dimension
        final_dpi:
          type: integer
          format: int32
          description: Final DPI after processing
        new_dimensions:
          oneOf:
            - $ref: '#/components/schemas/PixelDimensions'
              description: New dimensions after resizing (if resized).
            - type: "null"
        original_dimensions:
          $ref: '#/components/schemas/PixelDimensions'
          description: Original image dimensions in pixels.
        original_dpi:
          $ref: '#/components/schemas/ImageDpi'
          description: Original image resolution.
        resample_method:
          type: string
          description: Resampling algorithm used ("LANCZOS3", "CATMULLROM", etc.)
        resize_error:
          type:
            - string
            - "null"
          description: Error message if resize failed
        scale_factor:
          type: number
          format: double
          description: Scaling factor applied to the image
        skipped_resize:
          type: boolean
          description: Whether resize was skipped (dimensions already optimal)
        target_dpi:
          type: integer
          format: int32
          description: Target DPI from configuration
    ImageType:
      type: string
      description: Image type classification.
      enum:
        - data-uri
        - inline-svg
        - external
        - relative
    IncludeMask:
      type: object
      description: Mask of optional response fields.
      properties:
        content:
          type: boolean
          description: Include chunk content text. Default true.
        document:
          type: boolean
          description: Include parent document summary. Default false.
        score_breakdown:
          type: boolean
          description: Include primary-stage + rerank score breakdown. Default false.
      additionalProperties: false
    InlineElement:
      type: object
      description: |-
        Inline element within a block.

        Represents text with formatting, links, images, etc.
      required:
        - element_type
        - content
      properties:
        attributes:
          $ref: '#/components/schemas/Attributes'
          description: Element attributes
        content:
          type: string
          description: Text content
        element_type:
          $ref: '#/components/schemas/InlineType'
          description: Type of inline element
        metadata:
          type:
            - object
            - "null"
          description: Additional metadata (e.g., href for links, src/alt for images)
          additionalProperties:
            type: string
          propertyNames:
            type: string
    InlineType:
      type: string
      description: Types of inline elements in Djot.
      enum:
        - text
        - strong
        - emphasis
        - highlight
        - subscript
        - superscript
        - insert
        - delete
        - code
        - link
        - image
        - span
        - math
        - raw_inline
        - footnote_ref
        - symbol
    IntegrationInput:
      type: object
      description: |-
        Reference to a document living in a connected integration (e.g. Google Drive).

        No bytes are carried; the receiving service resolves them at fetch time
        through the integration's source connector, scoped to the caller's project.
      required:
        - integration_id
        - resource_id
      properties:
        integration_id:
          type: string
          format: uuid
          description: The `integrations.id` (hyphenated UUID) the document is fetched through.
        resource_id:
          type: string
          format: uuid
          description: Connector-specific document identifier (e.g. a Google Drive file id).
      additionalProperties: false
    IntegrationResponse:
      type: object
      description: |-
        Integration details for get/list/create responses. Never includes
        secrets or credentials, only integration metadata.
      required:
        - id
        - name
        - kind
        - auth_type
        - provider
      properties:
        auth_type:
          type: string
          description: Authentication type.
        id:
          type: string
          format: uuid
          description: Unique integration identifier.
        kind:
          type: string
          description: Integration kind.
        name:
          type: string
          description: User-friendly integration name.
        project_id:
          type:
            - string
            - "null"
          description: |-
            Owning project ID. Pro-only; Enterprise omits it (already scoped by
            the request path).
        provider:
          type: string
          description: Provider name.
    IntegrationSyncResponse:
      type: object
      required:
        - enabled
        - interval_seconds
      properties:
        enabled:
          type: boolean
        interval_seconds:
          type: integer
          format: int32
    JatsMetadata:
      type: object
      description: JATS (Journal Article Tag Suite) metadata.
      required:
        - format_type
      properties:
        contributor_roles:
          type: array
          items:
            $ref: '#/components/schemas/ContributorRole'
          description: Authors and contributors with their stated roles.
        copyright:
          type:
            - string
            - "null"
          description: Copyright statement from the article's `<permissions>` element.
        format_type:
          type: string
          enum:
            - jats
        history_dates:
          type: object
          description: Publication history dates keyed by event type (e.g. `"received"`, `"accepted"`).
          additionalProperties:
            type: string
          propertyNames:
            type: string
        license:
          type:
            - string
            - "null"
          description: Open-access license URI from the article's `<license>` element.
    JobResult:
      type: object
      description: Result envelope returned by GET /v1/jobs/{id}/result. The results array contains the extracted documents produced by this Pro job, in discovery order. The endpoint returns 409 until the job reaches completed or partial_success.
      required:
        - job_id
        - status
        - cached
      properties:
        cached:
          type: boolean
          description: |-
            This job's recorded extraction-cache hit, independent of child jobs and billing.
            False means no recorded hit; a pending job has not necessarily attempted extraction.
        completed_at:
          type:
            - string
            - "null"
          description: ISO 8601 completion timestamp, when available.
        errors:
          type: array
          items:
            $ref: '#/components/schemas/JobResultError'
          description: Non-fatal per-document errors.
        job_id:
          type: string
          format: uuid
          description: The job this result belongs to.
        results:
          type: array
          items:
            $ref: '#/components/schemas/JobResultDocument'
          description: |-
            Extracted documents, present once the job reaches a terminal
            successful state (`completed` or `partial_success`).

            Opaque JSON on purpose: each document is `xberg::types::ExtractedDocument`
            plus the keys the structured pipeline merges in (`structured_output`,
            `structured_output_flat`). Passing the stored document through untouched
            is what keeps those keys on the wire; a typed round trip through the
            engine's document type dropped every key that type does not declare.
        status:
          $ref: '#/components/schemas/JobStatus'
          description: Job status at the time the result was read.
        webhook_delivery:
          $ref: '#/components/schemas/WebhookDelivery'
          description: Inline webhook delivery state, when this job configured one.
    JobResultCounts:
      type: object
      description: Structural counts for a `JobResultDocument`, always present.
      required:
        - pages
        - tables
        - images
      properties:
        images:
          type: integer
          format: int32
          description: Number of images extracted.
          minimum: 0
        pages:
          type: integer
          format: int32
          description: Number of pages. `0` for formats that are not page-addressable.
          minimum: 0
        tables:
          type: integer
          format: int32
          description: Number of tables extracted.
          minimum: 0
    JobResultDocument:
      type: object
      description: |-
        One extracted document inside `JobResult.results`.

        A **projection** of the engine's `xberg::types::ExtractedDocument`, not a
        separate wire type: the service serializes the engine type itself, and this
        schema names the subset of its fields the API surface documents. Additional
        properties are permitted and do occur — per-page content, OCR elements,
        document structure, archive children and extracted URIs are all carried by
        the payload without appearing here, because registering the engine type
        would pull its entire nested schema graph into the spec.
      required:
        - content
        - mime_type
        - metadata
        - counts
        - tables
      properties:
        chunks:
          type:
            - array
            - "null"
          items: {}
          description: |-
            Text chunks, present only when chunking was requested. Opaque here; the
            engine type is `Chunk`.
        content:
          type: string
          description: |-
            Plain-text (or Markdown, per the requested output format) rendering of
            the document.
        counts:
          $ref: '#/components/schemas/JobResultCounts'
          description: |-
            Structural counts, always present: `{"pages": n, "tables": n,
            "images": n}`. `pages` is `0` for formats that are not page-addressable.
        detected_languages:
          type:
            - array
            - "null"
          items:
            type: string
          description: |-
            ISO language codes detected in the content, when detection ran and
            something met its confidence floor.
        extraction_method:
          type:
            - string
            - "null"
          description: |-
            How the text was produced: `native`, `ocr`, or `mixed`. Absent when the
            extractor cannot distinguish them — a client must not read absence as
            "no OCR".
        metadata:
          type: object
          description: |-
            Document-level metadata (title, authors, dates, page structure,
            format-specific fields). Opaque here; the engine type is `Metadata`.
          additionalProperties: {}
          propertyNames:
            type: string
        mime_type:
          type: string
          description: MIME type the engine resolved for the source document.
        processing_warnings:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/ProcessingWarning'
          description: |-
            Non-fatal warnings from the extraction pipeline — the signal that an
            otherwise successful job produced degraded output. Absent when the
            document raised none.
        quality_score:
          type:
            - number
            - "null"
          format: double
          description: |-
            Cleanliness/readability score in `[0.0, 1.0]` for the text that was
            retained. Not a completeness score: read `processing_warnings` to learn
            what was dropped.
        structured_output:
          type: object
          description: |-
            LLM-extracted JSON matching the requested schema, present only when the
            job supplied one (`options.preset`, an explicit
            `structured_extraction.schema`, or `options.saved_preset_id`, which
            both tiers apply).
        tables:
          type: array
          items: {}
          description: |-
            Tables extracted from the document. Opaque here; the engine type is
            `Table`.
      additionalProperties: true
    JobResultError:
      type: object
      description: |-
        A non-fatal error attached to one document within a `JobResult`.

        Used when a job processed multiple documents (e.g. via `split_documents`)
        and some, but not all, failed. Fields mirror `xberg`'s per-item error shape
        so Pro's stored errors translate without loss; Enterprise leaves the fields
        it does not populate unset.
      required:
        - error_type
        - message
      properties:
        code:
          type:
            - integer
            - "null"
          format: int64
          description: Numeric error code, when the producing engine assigns one.
        error_type:
          type: string
          description: Machine-readable error kind (e.g. `"unsupported_mime_type"`).
        index:
          type:
            - integer
            - "null"
          format: int64
          description: |-
            Zero-based index of the input document this error applies to, used to
            correlate with `results` in a multi-document job. Unset when the error
            is not attributable to a specific document.
        message:
          type: string
          description: Human-readable error message.
        source:
          type:
            - string
            - "null"
          description: Origin of the error (e.g. the failing stage or component).
    JobStatus:
      type: string
      description: Lifecycle status of a Pro extraction job, serialized as snake_case.
      enum:
        - pending
        - processing
        - completed
        - partial_success
        - failed
        - cancelled
    JobSummary:
      type: object
      description: Summary of a job, as returned by `GET /v1/jobs` (list).
      required:
        - job_type
        - id
        - filename
        - mime_type
        - status
        - created_at
      properties:
        completed_at:
          type:
            - string
            - "null"
          description: |-
            ISO 8601 completion timestamp, present once the job reaches a
            terminal state.
        created_at:
          type: string
          format: date-time
          description: ISO 8601 creation timestamp.
        filename:
          type: string
          description: Original filename for an extraction, or the primary seed URL for a crawl.
        id:
          type: string
          format: uuid
          description: Unique job identifier.
        job_type:
          $ref: '#/components/schemas/JobSummaryType'
          description: Kind of detail view represented by this row.
        mime_type:
          type: string
          description: MIME type of the submitted document, or `crawl` for a crawl parent.
        parent_crawl_job_id:
          type:
            - string
            - "null"
          format: uuid
          description: The crawl job that spawned this job as a child, when any.
        source_url:
          type:
            - string
            - "null"
          description: |-
            The crawled page URL this document came from, when the job is a
            crawl child (#1961). Absent for a direct upload.
        status:
          $ref: '#/components/schemas/JobStatus'
          description: Current job status.
        webhook_delivery:
          $ref: '#/components/schemas/WebhookDelivery'
          description: Inline webhook delivery state, when this job configured one.
    JobSummaryType:
      type: string
      description: Kind of job represented by a `JobSummary`.
      enum:
        - extraction
        - crawl
    KeyValueAttribute:
      type: object
      required:
        - key
        - value
      properties:
        key:
          type: string
        value:
          type: string
    Keyword:
      type: object
      description: Extracted keyword with metadata.
      required:
        - text
        - score
        - algorithm
      properties:
        algorithm:
          $ref: '#/components/schemas/KeywordAlgorithm'
          description: Algorithm that extracted this keyword.
        positions:
          type:
            - array
            - "null"
          items:
            type: integer
            minimum: 0
          description: Optional positions where keyword appears in text (character offsets).
        score:
          type: number
          format: float
          description: Relevance score (higher is better, algorithm-specific range).
        text:
          type: string
          description: The keyword text.
    KeywordAlgorithm:
      type: string
      description: Keyword algorithm selection.
      enum:
        - yake
        - rake
    KeywordConfig:
      type: object
      description: Keyword extraction configuration.
      properties:
        algorithm:
          $ref: '#/components/schemas/KeywordAlgorithm'
          description: Algorithm to use for extraction.
        language:
          type:
            - string
            - "null"
          description: |-
            Language code for stopword filtering (e.g., "en", "de", "fr").

            If None, no stopword filtering is applied.
        max_keywords:
          type: integer
          description: 'Maximum number of keywords to extract (default: 10).'
          minimum: 0
        min_score:
          type: number
          format: float
          description: |-
            Minimum score threshold (0.0-1.0, default: 0.0).

            Keywords with scores below this threshold are filtered out.
            Note: Score ranges differ between algorithms.
        ngram_range:
          $ref: '#/components/schemas/NgramRange'
          description: |-
            N-gram range for keyword extraction (min, max).

            (1, 1) = unigrams only
            (1, 2) = unigrams and bigrams
            (1, 3) = unigrams, bigrams, and trigrams (default)
        rake_params:
          $ref: '#/components/schemas/RakeParams'
          description: RAKE-specific tuning parameters.
        yake_params:
          $ref: '#/components/schemas/YakeParams'
          description: YAKE-specific tuning parameters.
      additionalProperties: false
    LanguageConfidence:
      type: object
      description: |-
        Structured per-language detection result: confidence, document share, and script —
        the information the ISO-code-only `detected_languages` list cannot convey (#261).

        Populated by the language-detection processor alongside `detected_languages`, with one
        entry per language, in the same order as `detected_languages`.
      required:
        - language
        - confidence
        - proportion
        - script
        - reliable
      properties:
        confidence:
          type: number
          format: double
          description: |-
            Confidence for this language, in `[0.0, 1.0]`.

            In single-language mode this is whatlang's `Info::confidence()` for the whole
            document. In multi-language mode this is the average whatlang confidence across
            the document's 200-character chunks that were classified as this language.
        language:
          type: string
          description: ISO 639-3 language code, matching the corresponding entry in `detected_languages`.
        proportion:
          type: number
          format: double
          description: |-
            Share of the document's analyzed content classified as this language, in `[0.0, 1.0]`.

            In single-language mode this is always `1.0`. In multi-language mode this is the
            fraction of 200-character chunks classified as this language (chunks that did not
            meet `min_confidence` for any language are excluded from the count but still count
            toward the denominator).
        reliable:
          type: boolean
          description: |-
            Whether this detection is considered reliable.

            In single-language mode this is whatlang's own `Info::is_reliable()` (confidence
            above whatlang's internal 0.9 threshold). In multi-language mode this is the
            chunk-averaged `confidence` above that same 0.9 threshold, since whatlang's
            `is_reliable()` only applies to a single detection.
        script:
          type: string
          description: Writing system whatlang detected for this language (e.g. `"Latin"`, `"Cyrillic"`).
    LanguageDetectionConfig:
      type: object
      description: Language detection configuration.
      properties:
        detect_multiple:
          type:
            - boolean
            - "null"
          description: Detect multiple languages in document
        enabled:
          type:
            - boolean
            - "null"
          description: Enable language detection. Defaults to true when omitted.
        min_confidence:
          type:
            - number
            - "null"
          format: double
          description: Minimum confidence threshold (0.0-1.0). Defaults to 0.8 when omitted.
      additionalProperties: false
    LatencyBreakdown:
      type: object
      description: Latency breakdown (milliseconds) for a single retrieve call.
      required:
        - retrieve_ms
        - total_ms
      properties:
        rerank_ms:
          type:
            - integer
            - "null"
          format: int64
          description: |-
            Reranker call (when active). `None` when rerank was off or
            fell back to primary order.
          minimum: 0
        retrieve_ms:
          type: integer
          format: int64
          description: Primary-stage retrieve (vector / fts / hybrid).
          minimum: 0
        total_ms:
          type: integer
          format: int64
          description: End-to-end including response serialization.
          minimum: 0
    LayoutDetectionConfig:
      type: object
      description: Document layout detection configuration.
      properties:
        apply_heuristics:
          type:
            - boolean
            - "null"
          description: Apply postprocessing heuristics. Defaults to true when omitted.
        confidence_threshold:
          type:
            - number
            - "null"
          format: float
          description: Confidence threshold override
        preset:
          type:
            - string
            - "null"
          description: |-
            Layout presets are unavailable. xberg's `LayoutDetectionConfig` has no
            preset concept, and the wire config has no field to carry one, so a
            value here reached nothing. Omit this field; setting it returns HTTP 400.
            Set `confidence_threshold` and `apply_heuristics` directly instead.
        table_model:
          type:
            - string
            - "null"
          description: |-
            Table structure model: "tatr", "slanet_wired", "slanet_wireless",
            "slanet_plus", "slanet_auto" or "disabled". Engine default when omitted.
        table_overlap_preference:
          type:
            - string
            - "null"
          description: |-
            Which region wins when a table and another block overlap. Engine
            default when omitted.
      additionalProperties: false
    LayoutRegion:
      type: object
      description: |-
        A detected layout region on a page.

        When layout detection is enabled, each page may have layout regions
        identifying different content types (text, pictures, tables, etc.)
        with confidence scores and spatial positions.
      required:
        - class_name
        - confidence
        - bounding_box
        - area_fraction
      properties:
        area_fraction:
          type: number
          format: double
          description: Fraction of the page area covered by this region (0.0 to 1.0).
        bounding_box:
          $ref: '#/components/schemas/BoundingBox'
          description: Bounding box in document coordinate space.
        class_name:
          type: string
          description: Layout class name (e.g. "picture", "table", "text", "section_header").
        confidence:
          type: number
          format: double
          description: Confidence score from the layout detection model (0.0 to 1.0).
    LeaderboardEntry:
      type: object
      description: One entry in an auto-tune run's leaderboard.
      required:
        - config
        - primary_score
        - variance
        - breakdown
        - tied_with_winner
      properties:
        breakdown:
          type: object
          description: |-
            Per-dimension score breakdown (e.g. per-document or per-metric
            scores). The concrete shape is defined in `autotune-core`; here it
            is opaque JSON.
        config:
          type: object
          description: |-
            The extraction configuration trialed for this entry. The concrete
            shape is defined in `autotune-core`; here it is opaque JSON.
        primary_score:
          type: number
          format: double
          description: Primary aggregate score for this configuration.
        tied_with_winner:
          type: boolean
          description: |-
            Whether this entry is statistically tied with the winning entry
            within the configured tolerance band.
        variance:
          type: number
          format: double
          description: |-
            Variance of the score across documents/trials, surfaced so callers
            can judge score stability rather than trusting a single number.
    LicenseHealth:
      type: object
      description: |-
        Coarse license state for the unauthenticated health probe.

        Deliberately minimal: `/healthz` is reachable by anyone who can reach the
        port, unauthenticated, so it reports enough for a liveness probe to act on
        (is the license in a state that keeps this instance serving) and nothing
        an operator would consider an inventory — no expiry timestamp, no
        `features[]`. See `GET /v1/license` for the full, admin-only detail.
      required:
        - state
      properties:
        state:
          type: string
          description: '"licensed", "grace", "expired", "invalid", or "unlicensed"'
    LicenseInfoResponse:
      type: object
      description: |-
        Full license detail for `GET /v1/license` — admin-only (ADR-0087 §5). Unlike
        `LicenseHealth`, this is behind authentication, so it can afford to name the licensee,
        license id, plan, expiry, grace window, and days remaining.
      required:
        - state
      properties:
        days_remaining:
          type:
            - integer
            - "null"
          format: int64
          description: |-
            Days remaining before this state moves to the next harsher one — see
            `LicenseState.days_remaining`. Absent for `expired`, `invalid`, and `unlicensed`.
        expires_at:
          type:
            - string
            - "null"
        grace_days:
          type:
            - integer
            - "null"
          format: int32
          minimum: 0
        license_id:
          type:
            - string
            - "null"
          description: Present for every state with claims to report (`licensed`, `grace`, `expired`).
        licensee:
          type:
            - string
            - "null"
        plan:
          type:
            - string
            - "null"
          description: Display and support only — this build does not gate anything on `plan`.
        state:
          type: string
          description: '"licensed", "grace", "expired", "invalid", or "unlicensed"'
    LinkMetadata:
      type: object
      description: Link element metadata.
      required:
        - href
        - text
        - link_type
        - rel
        - attributes
      properties:
        attributes:
          type: array
          items:
            $ref: '#/components/schemas/KeyValueAttribute'
          description: Additional attributes as key-value pairs.
        href:
          type: string
          description: The href URL value
        link_type:
          $ref: '#/components/schemas/LinkType'
          description: Link type classification
        rel:
          type: array
          items:
            type: string
          description: Rel attribute values
        text:
          type: string
          description: Link text content (normalized)
        title:
          type:
            - string
            - "null"
          description: Optional title attribute
    LinkType:
      type: string
      description: Link type classification.
      enum:
        - anchor
        - internal
        - external
        - email
        - phone
        - other
    ListApiKeysResponse:
      type: object
      description: Response for the paginated API key list endpoint.
      required:
        - api_keys
        - total
        - limit
        - offset
      properties:
        api_keys:
          type: array
          items:
            $ref: '#/components/schemas/ApiKeyResponse'
          description: API keys in the current page.
        limit:
          type: integer
          format: int64
          description: Page size used.
        offset:
          type: integer
          format: int64
          description: Offset used for pagination.
        total:
          type: integer
          format: int64
          description: Total number of API keys for the project.
    ListAuditEntriesResponse:
      type: object
      description: Response for the customer-facing audit read endpoint (`GET /v1/audit`).
      required:
        - entries
        - total
        - limit
        - offset
      properties:
        entries:
          type: array
          items:
            $ref: '#/components/schemas/AuditEntry'
          description: Audit entries in the current page, most recent first.
        limit:
          type: integer
          format: int64
          description: Page size used.
        offset:
          type: integer
          format: int64
          description: Offset used for pagination.
        total:
          type: integer
          format: int64
          description: Total number of audit entries matching the query.
    ListAutoTuneJobsResponse:
      type: object
      description: Response from listing auto-tune jobs.
      required:
        - jobs
        - total
      properties:
        jobs:
          type: array
          items:
            $ref: '#/components/schemas/AutoTuneJobSummary'
          description: Auto-tune jobs for the current project (newest first).
        total:
          type: integer
          format: int64
          description: Total count of auto-tune jobs in the project.
    ListCollectionsResponse:
      type: object
      description: Response for listing collections (paginated)
      required:
        - collections
        - total
        - limit
        - offset
      properties:
        collections:
          type: array
          items:
            $ref: '#/components/schemas/CollectionResponse'
          description: List of collections
        limit:
          type: integer
          format: int32
          description: Maximum results per page
        offset:
          type: integer
          format: int32
          description: Number of results skipped
        total:
          type: integer
          format: int32
          description: Total number of collections for this project
    ListDocumentsResponse:
      type: object
      description: |-
        Response body for the integration document list endpoint.

        See the module-level doc for why this intentionally omits
        `total`/`limit`/`offset`.
      required:
        - documents
      properties:
        documents:
          type: array
          items:
            $ref: '#/components/schemas/DocumentResponse'
          description: Documents matching the filter, in source-reported order.
    ListIntegrationsResponse:
      type: object
      description: Response for the paginated integration list endpoint.
      required:
        - integrations
        - total
        - limit
        - offset
      properties:
        integrations:
          type: array
          items:
            $ref: '#/components/schemas/IntegrationResponse'
          description: Integrations in the current page.
        limit:
          type: integer
          format: int64
          description: Page size used.
        offset:
          type: integer
          format: int64
          description: Offset used for pagination.
        total:
          type: integer
          format: int64
          description: Total number of integrations for the project.
    ListJobsResponse:
      type: object
      description: Response for `GET /v1/jobs` (paginated job list).
      required:
        - jobs
        - total
        - limit
        - offset
      properties:
        jobs:
          type: array
          items:
            $ref: '#/components/schemas/JobSummary'
          description: Jobs in the current page, most recent first.
        limit:
          type: integer
          format: int64
          description: Page size used.
        offset:
          type: integer
          format: int64
          description: Offset used for pagination.
        total:
          type: integer
          format: int64
          description: Total number of jobs for the project.
    ListManagedEmbeddingPresetsResponse:
      type: object
      description: Bounded list of managed embedding presets shipped by this server build.
      required:
        - presets
      properties:
        presets:
          type: array
          items:
            $ref: '#/components/schemas/ManagedEmbeddingPreset'
          description: Available presets, in the canonical backend order.
          maxItems: 32
    ListProjectsResponse:
      type: object
      description: Response for the paginated project list endpoint.
      required:
        - projects
        - total
        - limit
        - offset
      properties:
        limit:
          type: integer
          format: int64
          description: Page size used.
        offset:
          type: integer
          format: int64
          description: Offset used for pagination.
        projects:
          type: array
          items:
            $ref: '#/components/schemas/ProjectResponse'
          description: Projects in the current page.
        total:
          type: integer
          format: int64
          description: Total number of projects for the caller.
    ListSavedPresetsResponse:
      type: object
      description: Response from listing saved presets.
      required:
        - presets
        - total
        - page
        - limit
      properties:
        limit:
          type: integer
          format: int64
          description: Page size used.
        page:
          type: integer
          format: int64
          description: Current page (0-indexed).
        presets:
          type: array
          items:
            $ref: '#/components/schemas/SavedPresetSummary'
          description: List of saved presets for the current project.
        total:
          type: integer
          format: int64
          description: Total count of presets in the project.
    ListTuningProfilesResponse:
      type: object
      description: Response from listing tuning profiles.
      required:
        - profiles
        - total
      properties:
        profiles:
          type: array
          items:
            $ref: '#/components/schemas/TuningProfileSummary'
          description: Tuning profiles for the current project.
        total:
          type: integer
          format: int64
          description: Total count of tuning profiles in the project.
    LlmConfig:
      type: object
      description: LLM provider configuration routed through liter-llm.
      required:
        - model
      properties:
        api_key:
          type: string
          description: |-
            API key for the provider. Mutually exclusive with `use_managed = true`.
            Treated as a secret — never logged. Omit to leave it unset; explicit
            `null` is not a distinct value from omission and is rejected.
        base_url:
          type:
            - string
            - "null"
          description: Override the provider base URL.
        max_retries:
          type:
            - integer
            - "null"
          format: int32
          description: Max retry attempts.
          minimum: 0
        max_tokens:
          type:
            - integer
            - "null"
          format: int64
          description: Maximum tokens to generate.
          minimum: 0
        model:
          type: string
          description: liter-llm routing identifier, e.g. "openai/gpt-4o-mini".
          pattern: ^[^/]+/[A-Za-z0-9._:/-]+$
        temperature:
          type:
            - number
            - "null"
          format: double
          description: Sampling temperature.
        timeout_secs:
          type:
            - integer
            - "null"
          format: int64
          description: Request timeout in seconds.
          minimum: 0
        use_managed:
          type: boolean
          description: |-
            Managed provider keys are not available. The worker holds no key to
            substitute, so a request that sets this is rejected with a 400.
      additionalProperties: false
    LlmUsage:
      type: object
      description: |-
        Token usage and cost data for a single LLM call made during extraction.

        Populated when VLM OCR, structured extraction, or LLM-based embeddings
        are used. Multiple entries may be present when multiple LLM calls occur
        within one extraction (e.g. VLM OCR + structured extraction).
      required:
        - model
        - source
      properties:
        estimated_cost:
          type:
            - number
            - "null"
          format: double
          description: Estimated cost in USD based on the provider's published pricing.
        finish_reason:
          type:
            - string
            - "null"
          description: Why the model stopped generating (e.g. "stop", "length", "content_filter").
        input_tokens:
          type:
            - integer
            - "null"
          format: int64
          description: Number of input/prompt tokens consumed.
          minimum: 0
        model:
          type: string
          description: The LLM model identifier (e.g. "openai/gpt-4o", "anthropic/claude-sonnet-4-20250514").
        output_tokens:
          type:
            - integer
            - "null"
          format: int64
          description: Number of output/completion tokens generated.
          minimum: 0
        source:
          type: string
          description: |-
            The pipeline stage that triggered this LLM call
            (e.g. "vlm_ocr", "structured_extraction", "embeddings").
        total_tokens:
          type:
            - integer
            - "null"
          format: int64
          description: Total tokens (input + output).
          minimum: 0
    LocalIdentity:
      type: object
      description: Persisted local operator exposed only when authentication is disabled.
      required:
        - user_id
        - name
        - default_project_id
        - project_name
        - role
      properties:
        default_project_id:
          type: string
          format: uuid
          description: Persisted default workspace identifier.
        email:
          type:
            - string
            - "null"
          description: No external identity provider supplies an email in this mode.
        name:
          type: string
          description: Display name of the local operator.
        project_name:
          type: string
          description: Default workspace display name.
        role:
          type: string
          description: Workspace role of the local operator.
        user_id:
          type: string
          description: Local audit and project ownership principal.
    LoginRequest:
      type: object
      description: Login request with an OIDC ID token (e.g. from Google/Firebase sign-in).
      required:
        - id_token
      properties:
        id_token:
          type: string
          description: ID token from the configured OIDC identity provider.
          example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
    LoginResponse:
      type: object
      description: |-
        Successful login response. Shape matches Xberg Enterprise backend's
        `LoginResponse` (`services/backend/src/handlers/auth.rs`) so the shared
        frontend auth action and generated TypeScript client work unchanged
        against either product.
      required:
        - token
        - user
        - pending_invitations
      properties:
        pending_invitations:
          type: integer
          format: int32
          description: Always `0` for Pro — there is no invitation system to defer against.
          example: 0
        project:
          $ref: '#/components/schemas/ProjectInfo'
          description: |-
            The user's active project. Always present for Pro (unlike Enterprise,
            Pro has no pending-invitation state — every login has an active project
            by the time this response is built, auto-provisioning one if needed).
        token:
          type: string
          description: Session JWT (valid for `jwt_expiration_secs`, default 7 days).
          example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
        user:
          $ref: '#/components/schemas/UserInfo'
          description: Authenticated user information.
    ManagedEmbeddingPreset:
      type: object
      description: One server-managed embedding preset available to RAG collections.
      required:
        - name
        - embedding_source
        - embedding_dim
        - description
        - is_default
      properties:
        description:
          type: string
          description: Human-readable intended use.
        embedding_dim:
          type: integer
          format: int32
          description: Vector width emitted by the preset.
          minimum: 0
        embedding_source:
          type: string
          description: Canonical value accepted by collection create and migration requests.
        is_default:
          type: boolean
          description: Whether this is the default managed preset for omitted sources.
        name:
          type: string
          description: Stable preset name.
    MarkdownCodeBlock:
      type: object
      required:
        - language
        - code
      properties:
        code:
          type: string
        language:
          type: string
    MarkdownLink:
      type: object
      required:
        - text
        - url
      properties:
        text:
          type: string
        url:
          type: string
    Metadata:
      type: object
      description: |-
        Extraction result metadata.

        Contains common fields applicable to all formats, format-specific metadata
        via a discriminated union, and additional custom fields from postprocessors.
      properties:
        abstract_text:
          type:
            - string
            - "null"
          description: Abstract or summary text (from frontmatter).
        additional:
          type: object
          description: |-
            Additional custom fields from postprocessors.

            Serialized as a nested `"additional"` object (not flattened at root level).
            Uses `Cow<'static, str>` keys so static string keys avoid allocation.
          additionalProperties: {}
          propertyNames:
            type: string
        authors:
          type:
            - array
            - "null"
          items:
            type: string
          description: Primary author(s) - always Vec for consistency
        category:
          type:
            - string
            - "null"
          description: Document category (from frontmatter or classification).
        created_at:
          type:
            - string
            - "null"
          description: Creation timestamp (ISO 8601 format)
        created_by:
          type:
            - string
            - "null"
          description: User who created the document
        document_version:
          type:
            - string
            - "null"
          description: Document version string (from frontmatter).
        error:
          $ref: '#/components/schemas/ErrorMetadata'
          description: Error metadata (for batch operations)
        extraction_duration_ms:
          type:
            - integer
            - "null"
          format: int64
          description: |-
            Extraction duration in milliseconds (for benchmarking).

            This field is populated by batch extraction to provide per-file timing
            information. It's `None` for single-file extraction (which uses external timing).
          minimum: 0
        format:
          $ref: '#/components/schemas/FormatMetadata'
          description: |-
            Format-specific metadata (discriminated union)

            Contains detailed metadata specific to the document format.
            Serialized as a nested `"format"` object with a `format_type` discriminator field.
        image_preprocessing:
          $ref: '#/components/schemas/ImagePreprocessingMetadata'
          description: Image preprocessing metadata (when OCR preprocessing was applied)
        json_schema:
          description: JSON schema (for structured data extraction)
        keywords:
          type:
            - array
            - "null"
          items:
            type: string
          description: Keywords/tags - always Vec for consistency
        language:
          type:
            - string
            - "null"
          description: Primary language (ISO 639 code)
        modified_at:
          type:
            - string
            - "null"
          description: Last modification timestamp (ISO 8601 format)
        modified_by:
          type:
            - string
            - "null"
          description: User who last modified the document
        ocr_used:
          type: boolean
          description: |-
            Whether OCR was used during extraction.

            Set to `true` whenever the extraction pipeline ran an OCR backend
            (Tesseract, PaddleOCR, VLM, etc.) and used that output as the primary
            or fallback text. `false` means native text extraction was used exclusively.
        output_format:
          type:
            - string
            - "null"
          description: |-
            Output format identifier (e.g., "markdown", "html", "text").

            Set by the output format pipeline stage when format conversion is applied.
            Previously stored in `metadata.additional["output_format"]`.
        pages:
          $ref: '#/components/schemas/PageStructure'
          description: Page/slide/sheet structure with boundaries
        subject:
          type:
            - string
            - "null"
          description: Document subject or description
        tags:
          type:
            - array
            - "null"
          items:
            type: string
          description: Document tags (from frontmatter).
        title:
          type:
            - string
            - "null"
          description: Document title
    MigrateEmbeddingsRequest:
      type: object
      description: Request to migrate a collection's embedding source.
      required:
        - target_embedding_source
        - target_embedding_version
      properties:
        target_embedding_source:
          type: string
          description: |-
            Target embedding source, for example `managed_local`,
            `managed_local:quality` or `provider:<name>`.

            The legacy `preset:<name>` spelling is still accepted. It is not what
            comes back: see `MigrateEmbeddingsResponse.to_source`.
          minLength: 1
        target_embedding_version:
          type: integer
          format: int32
          description: Target embedding version (must be > collection's current embedding_version).
          maximum: 2147483647
          minimum: 1
        webhook:
          $ref: '#/components/schemas/WebhookConfig'
          description: Optional webhook for migration completion/failure delivery.
      additionalProperties: false
    MigrateEmbeddingsResponse:
      type: object
      description: Response to a migrate embeddings request.
      required:
        - job_id
        - collection_id
        - from_source
        - to_source
        - from_version
        - to_version
        - status
        - poll
      properties:
        collection_id:
          type: string
          format: uuid
          description: Collection being migrated.
        from_source:
          type: string
          description: |-
            Current embedding source, as stored, which is the spelling the create
            request used rather than a canonical rendering.
        from_version:
          type: integer
          format: int32
          description: Current embedding version.
          minimum: 0
        job_id:
          type: string
          description: Job ID for polling progress (ULID).
        poll:
          type: string
          description: 'Poll endpoint: `/v1/rag/collections/{name}/migrate-embeddings/{job_id}`.'
        status:
          $ref: '#/components/schemas/MigrateStatus'
          description: Current migration status.
        to_source:
          type: string
          description: |-
            Target embedding source, in its canonical spelling.

            This is the parsed rendering, not the string the request carried, so a
            request sending the legacy `preset:balanced` gets `managed_local` back.
            The same value reaches the migration message and, on completion,
            `vector_collections.embedding_source`, which is what keeps that column
            on one spelling. Match on it by parsing rather than by string equality.
        to_version:
          type: integer
          format: int32
          description: Target embedding version.
          minimum: 0
    MigrateProgress:
      type: object
      description: Detailed progress of an ongoing or completed migration.
      required:
        - documents_total
        - documents_dual_written
        - current_phase
      properties:
        current_phase:
          type: string
          description: Current phase of migration.
        documents_dual_written:
          type: integer
          format: int64
          description: Documents successfully dual-written to the new version.
          minimum: 0
        documents_total:
          type: integer
          format: int64
          description: Total documents to migrate.
          minimum: 0
    MigrateStatus:
      type: string
      description: Current status of a migration job.
      enum:
        - pending
        - running
        - completed
        - failed
    MigrateStatusResponse:
      type: object
      description: Status response for migration progress polling.
      required:
        - job_id
        - status
      properties:
        error:
          type:
            - string
            - "null"
          description: Error message (only when failed).
        error_code:
          type:
            - string
            - "null"
          description: Machine-readable cause when the failed job has a known classification.
        job_id:
          type: string
          description: Job ID (ULID).
        progress:
          $ref: '#/components/schemas/MigrateProgress'
          description: Progress details (only available when running or completed).
        status:
          $ref: '#/components/schemas/MigrateStatus'
          description: Current status.
    MultiVectorEmbedding:
      type: object
      description: |
        A ColBERT multi-vector embedding: one row per attention-live token.

        `data` is a flat, row-major buffer of length `num_tokens * dim` — row `i`
        (the embedding for token `i`) occupies `data[i*dim .. (i+1)*dim]`. Flat
        storage keeps the type FFI-friendly across binding boundaries; use
        `MultiVectorEmbedding.rows` internally to iterate per-token slices.
      required:
        - num_tokens
        - dim
        - data
      properties:
        data:
          type: array
          items:
            type: number
            format: float
          description: Flat row-major buffer, length `num_tokens * dim`.
        dim:
          type: integer
          format: int32
          description: Dimensionality of each per-token vector.
          minimum: 0
        num_tokens:
          type: integer
          format: int32
          description: |-
            Number of attention-live token rows (padding rows are dropped, not
            zeroed — see token normalization).
          minimum: 0
    NgramRange:
      type: object
      required:
        - min
        - max
      properties:
        max:
          type: integer
          minimum: 1
        min:
          type: integer
          minimum: 1
    NodeContent:
      oneOf:
        - type: object
          description: Document title.
          required:
            - text
            - node_type
          properties:
            node_type:
              type: string
              enum:
                - title
            text:
              type: string
              description: The title text content.
        - type: object
          description: Section heading with level (1-6).
          required:
            - level
            - text
            - node_type
          properties:
            level:
              type: integer
              format: int32
              description: Heading depth (1 = h1, 2 = h2, …, 6 = h6).
              minimum: 0
            node_type:
              type: string
              enum:
                - heading
            text:
              type: string
              description: The heading text content.
        - type: object
          description: Body text paragraph.
          required:
            - text
            - node_type
          properties:
            node_type:
              type: string
              enum:
                - paragraph
            text:
              type: string
              description: The paragraph text content.
        - type: object
          description: List container — children are `ListItem` nodes.
          required:
            - ordered
            - node_type
          properties:
            node_type:
              type: string
              enum:
                - list
            ordered:
              type: boolean
              description: '`true` for ordered (numbered) lists; `false` for unordered (bullet) lists.'
        - type: object
          description: Individual list item.
          required:
            - text
            - node_type
          properties:
            node_type:
              type: string
              enum:
                - list_item
            text:
              type: string
              description: The list item text content.
        - type: object
          description: Table with structured cell grid.
          required:
            - grid
            - node_type
          properties:
            grid:
              $ref: '#/components/schemas/TableGrid'
              description: Structured grid of table cells.
            node_type:
              type: string
              enum:
                - table
        - type: object
          description: Image reference.
          required:
            - node_type
          properties:
            description:
              type:
                - string
                - "null"
              description: Optional alt text or caption describing the image.
            image_index:
              type:
                - integer
                - "null"
              format: int32
              description: Index into the parent `ExtractedDocument::images` list.
              minimum: 0
            node_type:
              type: string
              enum:
                - image
            src:
              type:
                - string
                - "null"
              description: Source URL or path of the image (from `<img src="...">` or `![](src)`).
        - type: object
          description: Code block.
          required:
            - text
            - node_type
          properties:
            language:
              type:
                - string
                - "null"
              description: Programming language identifier (e.g. `"rust"`, `"python"`).
            node_type:
              type: string
              enum:
                - code
            text:
              type: string
              description: The source code text content.
        - type: object
          description: Block quote — container, children carry the quoted content.
          required:
            - node_type
          properties:
            node_type:
              type: string
              enum:
                - quote
        - type: object
          description: Mathematical formula / equation.
          required:
            - text
            - node_type
          properties:
            node_type:
              type: string
              enum:
                - formula
            text:
              type: string
              description: The formula source text (LaTeX or plain mathematical notation).
        - type: object
          description: Footnote reference content.
          required:
            - text
            - node_type
          properties:
            node_type:
              type: string
              enum:
                - footnote
            text:
              type: string
              description: The footnote body text.
        - type: object
          description: |-
            Reviewer/editor comment content (e.g. DOCX comments).

            Distinct from `NodeContent.Footnote` (xberg-io/xberg#300): comments and
            footnotes both reach the internal document via a marker/definition pair, but
            a consumer needs to tell a reviewer comment apart from an authored footnote.
          required:
            - text
            - node_type
          properties:
            node_type:
              type: string
              enum:
                - comment
            text:
              type: string
              description: The comment body text.
        - type: object
          description: |-
            Logical grouping container (section, key-value area).

            `heading_level` + `heading_text` capture the section heading directly
            rather than relying on a first-child positional convention.
          required:
            - node_type
          properties:
            heading_level:
              type:
                - integer
                - "null"
              format: int32
              description: Heading level of the section heading that opened this group (1-6).
              minimum: 0
            heading_text:
              type:
                - string
                - "null"
              description: Text of the section heading that opened this group.
            label:
              type:
                - string
                - "null"
              description: Optional display label for the group (e.g. section name).
            node_type:
              type: string
              enum:
                - group
        - type: object
          description: Page break marker.
          required:
            - node_type
          properties:
            node_type:
              type: string
              enum:
                - page_break
        - type: object
          description: Presentation slide container — children are the slide's content nodes.
          required:
            - number
            - node_type
          properties:
            node_type:
              type: string
              enum:
                - slide
            number:
              type: integer
              format: int32
              description: 1-indexed slide number.
              minimum: 0
            title:
              type:
                - string
                - "null"
              description: Slide title text, if present.
        - type: object
          description: Definition list container — children are `DefinitionItem` nodes.
          required:
            - node_type
          properties:
            node_type:
              type: string
              enum:
                - definition_list
        - type: object
          description: Individual definition list entry with term and definition.
          required:
            - term
            - definition
            - node_type
          properties:
            definition:
              type: string
              description: The definition or description of the term.
            node_type:
              type: string
              enum:
                - definition_item
            term:
              type: string
              description: The term being defined.
        - type: object
          description: Citation or bibliographic reference.
          required:
            - key
            - text
            - node_type
          properties:
            key:
              type: string
              description: Citation key (e.g. BibTeX key or reference ID).
            node_type:
              type: string
              enum:
                - citation
            text:
              type: string
              description: Formatted citation text as it appears in the document.
        - type: object
          description: |-
            Admonition / callout container (note, warning, tip, etc.).

            Children carry the admonition body content.
          required:
            - kind
            - node_type
          properties:
            kind:
              type: string
              description: Kind of admonition (e.g. "note", "warning", "tip", "danger").
            node_type:
              type: string
              enum:
                - admonition
            title:
              type:
                - string
                - "null"
              description: Optional explicit title overriding the default kind label.
        - type: object
          description: |-
            Raw block preserved verbatim from the source format.

            Used for content that cannot be mapped to a semantic node type
            (e.g. JSX in MDX, raw LaTeX in markdown, embedded HTML).
          required:
            - format
            - content
            - node_type
          properties:
            content:
              type: string
              description: Verbatim source content in the specified format.
            format:
              type: string
              description: Source format identifier (e.g. "html", "latex", "jsx").
            node_type:
              type: string
              enum:
                - raw_block
        - type: object
          description: Structured metadata block (email headers, YAML frontmatter, etc.).
          required:
            - entries
            - node_type
          properties:
            entries:
              type: array
              items:
                $ref: '#/components/schemas/KeyValueAttribute'
              description: Key-value pairs extracted from the metadata block.
            node_type:
              type: string
              enum:
                - metadata_block
      description: |-
        Tagged enum for node content. Each variant carries only type-specific data.

        Uses `#[serde(tag = "node_type")]` to avoid "type" keyword collision in
        Go/Java/TypeScript bindings.
    NodeIndex:
      type: integer
      format: int32
      description: |-
        Newtype for node indices into the `DocumentStructure::nodes` array.

        Uses `u32` for cross-platform consistency (WASM is 32-bit) and to avoid
        confusion with page numbers or other `usize` values.
      minimum: 0
    OcrBackendCapabilityInfo:
      type: object
      description: One OCR backend available to this deployment, for capability discovery.
      required:
        - name
        - cost
        - in_default_ladder
      properties:
        cost:
          $ref: '#/components/schemas/OcrBackendCostKind'
          description: Cost classification of running this backend.
        in_default_ladder:
          type: boolean
          description: Whether this backend is included in the deployment's default OCR search ladder.
        name:
          type: string
          description: Registered backend name (e.g. `tesseract`, `paddle-ocr`).
        supported_languages:
          type: array
          items:
            type: string
          description: Known language identifiers advertised by this backend. The list can be non-exhaustive.
        supported_languages_exhaustive:
          type: boolean
          description: Whether `supported_languages` contains every identifier accepted by the backend.
    OcrBackendCostKind:
      type: string
      description: Kind classification for the cost of running an OCR backend.
      enum:
        - local
        - llm_metered
    OcrBoundingGeometry:
      oneOf:
        - type: object
          description: Axis-aligned bounding box (typical for Tesseract output).
          required:
            - left
            - top
            - width
            - height
            - type
          properties:
            height:
              type: integer
              format: int32
              description: Height in pixels
              minimum: 0
            left:
              type: integer
              format: int32
              description: Left x-coordinate in pixels
              minimum: 0
            top:
              type: integer
              format: int32
              description: Top y-coordinate in pixels
              minimum: 0
            type:
              type: string
              enum:
                - rectangle
            width:
              type: integer
              format: int32
              description: Width in pixels
              minimum: 0
        - type: object
          description: |-
            4-point quadrilateral for rotated/skewed text (PaddleOCR).

            Points are in clockwise order starting from top-left:
            `[top_left, top_right, bottom_right, bottom_left]`
          required:
            - points
            - type
          properties:
            points:
              type: array
              items:
                $ref: '#/components/schemas/OcrPoint'
              description: Exactly four corner points in clockwise order.
            type:
              type: string
              enum:
                - quadrilateral
      description: |-
        Bounding geometry for an OCR element.

        Supports both axis-aligned rectangles (from Tesseract) and 4-point quadrilaterals
        (from PaddleOCR and rotated text detection).
    OcrConfidence:
      type: object
      description: |-
        Confidence scores for an OCR element.

        Separates detection confidence (how confident that text exists at this location)
        from recognition confidence (how confident about the actual text content).
      required:
        - recognition
      properties:
        detection:
          type:
            - number
            - "null"
          format: double
          description: |-
            Detection confidence: how confident the OCR engine is that text exists here.

            PaddleOCR provides this as `box_score`, Tesseract doesn't have a direct equivalent.
            Range: 0.0 to 1.0 (or None if not available).
        recognition:
          type: number
          format: double
          description: |-
            Recognition confidence: how confident about the text content.

            Range: 0.0 to 1.0.
    OcrConfig:
      type: object
      description: OCR backend and language configuration.
      properties:
        auto_rotate:
          type:
            - boolean
            - "null"
          description: Automatic page rotation detection
        backend:
          type:
            - string
            - "null"
          description: |-
            OCR backend name. Must be one this deployment has compiled in and
            registered; the accepted set is deployment-dependent and is rejected
            with a 400 when not recognized.
        backend_options:
          type: object
          description: |-
            Per-call options for the named OCR backend, as a JSON object.

            The accepted keys depend on the backend. The candle backends accept a
            `device` (`auto`, `cpu`, `cuda`, `metal`) plus their own keys:
            `candle-deepseek-ocr` takes `model_id` (default
            `deepseek-ai/DeepSeek-OCR`), an optional `model_path` that takes
            precedence when set, `version`, `hf_revision` (required for a
            non-default `model_id`), `cache_dir` and `dtype`;
            `candle-paddleocr-vl` takes `task`, `model_path`, `model_id`,
            `hf_revision` and `cache_dir`; `candle-glm-ocr` takes `task`,
            `layout_mode`, `enable_chart_understanding` and `cache_dir`;
            `candle-trocr` takes `variant`, `hf_revision` and `cache_dir`. An option
            the named backend does not accept is rejected with a 400.
        element_config:
          $ref: '#/components/schemas/OcrElementConfig'
          description: Structured OCR element extraction
        language:
          type:
            - string
            - "null"
          description: Language code (e.g., "eng", "deu", "fra")
        output_format:
          type:
            - string
            - "null"
          description: OCR output format override
        paddle_ocr_settings:
          type: object
          description: PaddleOCR model and inference configuration.
        pipeline:
          $ref: '#/components/schemas/OcrPipelineConfig'
          description: Multi-backend OCR pipeline with quality-based fallback
        psm:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Tesseract page-segmentation mode (1..=13).

            Present because this service's own auto-tune sweeps it (`Knob::Psm`) and
            returns it inside a winning profile. Without the field the DTO dropped it
            at parse, so a profile this API produced could not be replayed through
            this API. When this synthesizes a Tesseract config, the engine carries
            the top-level language into that config.
        quality_thresholds:
          $ref: '#/components/schemas/OcrQualityThresholds'
          description: Quality thresholds for OCR fallback decisions
        tesseract_config:
          $ref: '#/components/schemas/TesseractConfig'
          description: Tesseract-specific configuration
        vlm_config:
          type: object
          description: VLM (Vision Language Model) configuration
        vlm_fallback:
          $ref: '#/components/schemas/VlmFallbackPolicy'
          description: |-
            Request VLM fallback using the deployment's model.
            An explicit OCR pipeline takes precedence, so it ignores this fallback policy.
        vlm_prompt:
          type:
            - string
            - "null"
          description: Custom Jinja2 prompt template for VLM OCR
      additionalProperties: false
    OcrElement:
      type: object
      description: |-
        A unified OCR element representing detected text with full metadata.

        This is the primary type for structured OCR output, preserving all information
        from both Tesseract and PaddleOCR backends.
      required:
        - text
        - geometry
        - confidence
      properties:
        backend_metadata:
          type: object
          description: Backend-specific metadata that doesn't fit the unified schema.
          additionalProperties: {}
          propertyNames:
            type: string
        confidence:
          $ref: '#/components/schemas/OcrConfidence'
          description: Confidence scores for detection and recognition.
        geometry:
          $ref: '#/components/schemas/OcrBoundingGeometry'
          description: Bounding geometry (rectangle or quadrilateral).
        level:
          $ref: '#/components/schemas/OcrElementLevel'
          description: Hierarchical level (word, line, block, page).
        page_number:
          type: integer
          format: int32
          description: Page number (1-indexed).
          minimum: 0
        parent_id:
          type:
            - string
            - "null"
          description: |-
            Parent element ID for hierarchical relationships.

            When hierarchy output is enabled, this resolves to another emitted element's
            `backend_metadata["element_id"]` value.
        rotation:
          $ref: '#/components/schemas/OcrRotation'
          description: Rotation information (if detected).
        text:
          type: string
          description: The recognized text content.
    OcrElementConfig:
      type: object
      description: Structured OCR element extraction configuration.
      properties:
        build_hierarchy:
          type: boolean
          description: Build parent-child relationships
        include_elements:
          type: boolean
          description: Include OCR elements in result
        min_confidence:
          type:
            - number
            - "null"
          format: double
          description: Minimum recognition confidence (0.0-1.0)
        min_level:
          type:
            - string
            - "null"
          description: 'Minimum hierarchical level: "word", "line", "block", "page"'
      additionalProperties: false
    OcrElementLevel:
      type: string
      description: |-
        Hierarchical level of an OCR element.

        Maps to Tesseract's page segmentation hierarchy and provides
        equivalent semantics for PaddleOCR.
      enum:
        - word
        - line
        - block
        - page
    OcrMetadata:
      type: object
      description: |-
        OCR processing metadata.

        Captures information about OCR processing configuration and results.
      required:
        - language
        - psm
        - output_format
        - table_count
        - format_type
      properties:
        format_type:
          type: string
          enum:
            - ocr
        language:
          type: string
          description: OCR language code(s) used
        output_format:
          type: string
          description: Output format (e.g., "text", "hocr")
        psm:
          type: integer
          format: int32
          description: Tesseract Page Segmentation Mode (PSM)
        table_cols:
          type:
            - integer
            - "null"
          format: int32
          description: Number of columns in the detected table (if a single table was found).
          minimum: 0
        table_count:
          type: integer
          format: int32
          description: Number of tables detected
          minimum: 0
        table_rows:
          type:
            - integer
            - "null"
          format: int32
          description: Number of rows in the detected table (if a single table was found).
          minimum: 0
    OcrPageFailure:
      type: object
      description: |-
        A page whose OCR failed while extraction of the document continued.

        The warning in `ExtractedDocument::processing_warnings` stays available. One warning
        can cover several pages: when an automatic OCR run of selected pages fails as a whole,
        it adds one warning and one record for each selected page. A failure of the OCR fallback
        for the whole document adds a warning and no record. Repeated failures on one page make
        one record.
      required:
        - page
        - error
        - recovered
      properties:
        error:
          type: string
          description: OCR backend error text.
        page:
          type: integer
          format: int32
          description: One-based page number in the source document.
          minimum: 0
        recovered:
          type: boolean
          description: Whether content for this page was retained or recovered through another route.
    OcrPipelineConfig:
      type: object
      description: Multi-backend OCR pipeline with quality-based fallback.
      properties:
        quality_thresholds:
          $ref: '#/components/schemas/OcrQualityThresholds'
          description: Quality evaluation thresholds for fallback decisions
        stages:
          type: array
          items:
            $ref: '#/components/schemas/OcrPipelineStage'
          description: Ordered list of backends to try (sorted by priority descending)
      additionalProperties: false
    OcrPipelineStage:
      type: object
      description: Single backend stage in the OCR pipeline.
      required:
        - backend
      properties:
        backend:
          type: string
          description: |-
            Backend name for this pipeline stage. Must be one this deployment has
            compiled in and registered; the accepted set is deployment-dependent
            and is rejected with a 400 when not recognized.
        backend_options:
          type: object
          description: |-
            Per-call options for this stage's backend, as a JSON object.

            The top-level `ocr.backend_options` has no effect once a pipeline is
            set, so a stage carries its own.
        language:
          type:
            - string
            - "null"
          description: Language override for this stage
        priority:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Priority (higher = tried first). Omit to take the default of 100.

            Optional so that an omitted priority stays omitted. As a plain `u32` it
            deserialized to zero, serialized as `"priority": 0` into the job
            message, and the mapping layer could no longer tell a caller who asked
            for the lowest priority from one who never mentioned it.
          minimum: 0
        tesseract_config:
          $ref: '#/components/schemas/TesseractConfig'
          description: Tesseract config override for this stage
        vlm_config:
          type: object
          description: VLM config override for this stage
      additionalProperties: false
    OcrPoint:
      type: object
      required:
        - x
        - y
      properties:
        x:
          type: integer
        y:
          type: integer
    OcrQualityThresholds:
      type: object
      description: Quality thresholds for OCR fallback decisions.
      properties:
        alnum_ws_ratio_threshold:
          type:
            - number
            - "null"
          format: double
        critical_fragmented_word_ratio:
          type:
            - number
            - "null"
          format: double
        max_fragmented_word_ratio:
          type:
            - number
            - "null"
          format: double
        min_alnum_ratio:
          type:
            - number
            - "null"
          format: double
        min_avg_word_length:
          type:
            - number
            - "null"
          format: double
        min_consecutive_repeat_ratio:
          type:
            - number
            - "null"
          format: double
        min_garbage_chars:
          type:
            - integer
            - "null"
          format: int64
          minimum: 0
        min_meaningful_word_len:
          type:
            - integer
            - "null"
          format: int64
          minimum: 0
        min_meaningful_words:
          type:
            - integer
            - "null"
          format: int64
          minimum: 0
        min_non_whitespace_per_page:
          type:
            - number
            - "null"
          format: double
        min_total_non_whitespace:
          type:
            - integer
            - "null"
          format: int64
          minimum: 0
        min_words_for_avg_length_check:
          type:
            - integer
            - "null"
          format: int64
          minimum: 0
        min_words_for_repeat_check:
          type:
            - integer
            - "null"
          format: int64
          minimum: 0
        non_text_min_chars:
          type:
            - integer
            - "null"
          format: int64
          minimum: 0
        pipeline_min_quality:
          type:
            - number
            - "null"
          format: double
        substantive_min_chars:
          type:
            - integer
            - "null"
          format: int64
          minimum: 0
      additionalProperties: false
    OcrRotation:
      type: object
      description: Rotation information for an OCR element.
      required:
        - angle_degrees
      properties:
        angle_degrees:
          type: number
          format: double
          description: Rotation angle in degrees (0, 90, 180, 270 for PaddleOCR).
        confidence:
          type:
            - number
            - "null"
          format: double
          description: Confidence score for the rotation detection.
    OcrStrategy:
      oneOf:
        - type: object
          description: OCR only where the native text layer fails a quality check (default).
          required:
            - mode
          properties:
            mode:
              type: string
              enum:
                - auto
        - type: object
          description: Also OCR every page that looks like a scan at or above `min_confidence`.
          required:
            - min_confidence
            - mode
          properties:
            min_confidence:
              type: number
              format: double
            mode:
              type: string
              enum:
                - scanned_pages
      description: |-
        Which pages of a PDF go to OCR when neither `force_ocr` nor
        `force_ocr_pages` applies.

        Mirrors xberg's `OcrStrategy`, tag and variant names included, so a caller
        who reads the library documentation can write the same object here.

        `auto` OCRs a page only when its native text layer fails a quality check,
        and a scanner's invisible text layer passes that check. `scanned_pages`
        additionally OCRs every page that grades at or above `min_confidence` on
        scan detection, which is the knob between "OCR everything" and "OCR
        nothing": a lower threshold sends more pages to recognition, a higher one
        sends fewer.

        `min_confidence` is in `[0.0, 1.0]` and anything else is a 400. xberg's own
        default is 0.70, and it is what an omitted `ocr_strategy` keeps.

        Rejects an unrecognised key on either variant, unlike the wire type
        it mirrors (`messages::jobs::OcrStrategy`): this DTO sits on the live
        request path, which ADR-0082 keeps strict, so
        `{"mode":"auto","min_confidence":0.3}` is a 400 naming the field
        rather than a threshold parsed and silently dropped. The wire type
        stays lenient on purpose — it is reachable from the worker's tolerant
        decode paths, and ADR-0082's amendment measured that adding
        `deny_unknown_fields` to a tagged enum reachable from there can
        permanently strand a stored job instead of merely dropping a field.

        `Deserialize` is hand-written rather than derived because serde's
        `deny_unknown_fields` only rejects an extra field beside a fieldless
        variant when that variant declares its (empty) field set with braces
        (`Auto {}`), and a struct variant cannot carry `#[default]`. So the
        public enum keeps `Auto` a plain unit variant — every call site
        matches it that way — and deserializes through a private shadow enum
        that uses the brace form purely to get the rejection. xberg's own
        `OcrStrategy` uses the identical split for the identical reason.
    OidcInfo:
      type: object
      description: OIDC login configuration, as visible to an unauthenticated caller.
      required:
        - enabled
      properties:
        enabled:
          type: boolean
          description: Whether OIDC login is enabled at all.
        issuer:
          type:
            - string
            - "null"
          description: The configured OIDC issuer URL, or `None`.
        provider:
          type:
            - string
            - "null"
          description: |-
            Provider label derived from the configured issuer (`"google"`,
            `"github"`, `"firebase"`, `"oidc"`), or `None` when disabled or running
            in mock mode without a configured issuer.
    PageBoundary:
      type: object
      description: |-
        Byte offset boundary for a page.

        Tracks where a specific page's content starts and ends in the main content string,
        enabling mapping from byte positions to page numbers. Offsets are guaranteed to be
        at valid UTF-8 character boundaries when using standard String methods (push_str, push, etc.).
      required:
        - byte_start
        - byte_end
        - page_number
      properties:
        byte_end:
          type: integer
          description: Byte offset where this page ends in the content string (UTF-8 valid boundary, exclusive)
          minimum: 0
        byte_start:
          type: integer
          description: Byte offset where this page starts in the content string (UTF-8 valid boundary, inclusive)
          minimum: 0
        page_number:
          type: integer
          format: int32
          description: Page number (1-indexed)
          minimum: 0
    PageClassification:
      type: object
      description: Classification result for a single page.
      required:
        - page_number
        - labels
      properties:
        labels:
          type: array
          items:
            $ref: '#/components/schemas/ClassificationLabel'
          description: |-
            Labels assigned to the page. Single-label classification yields exactly one
            entry; multi-label classification yields any subset of the configured label set.
        page_number:
          type: integer
          format: int32
          description: 1-indexed page number this classification belongs to.
          minimum: 0
    PageConfig:
      type: object
      description: Page extraction configuration.
      properties:
        extract_pages:
          type:
            - boolean
            - "null"
          description: |-
            Extract pages as separate array in result. Omit to keep the library default.

            Optional so that an omitted key stays omitted on the wire. A plain `bool`
            serialized `false` into the job message, and the conversion wrote that
            over the library default as if the caller had asked for it.
        insert_page_markers:
          type:
            - boolean
            - "null"
          description: Insert page markers in content text. Omit to keep the library default.
        keep_native_content:
          type: boolean
          description: Retain a PDF page's native text when OCR replaces its content. Omit to keep the library default.
        marker_format:
          type:
            - string
            - "null"
          description: Page marker format template (e.g., "\n\n<!-- PAGE {page_num} -->\n\n")
      additionalProperties: false
    PageContent:
      type: object
      description: |-
        Content for a single page/slide.

        When page extraction is enabled, documents are split into per-page content
        with associated tables and images mapped to each page.

        # Performance

        Uses Arc-wrapped tables and images for memory efficiency:
        - `Vec<Arc<Table>>` enables zero-copy sharing of table data
        - `Vec<Arc<ExtractedImage>>` enables zero-copy sharing of image data
        - Maintains exact JSON compatibility via custom Serialize/Deserialize

        This reduces memory overhead for documents with shared tables/images
        by avoiding redundant copies during serialization.
      required:
        - page_number
        - content
      properties:
        content:
          type: string
          description: Text content for this page
        hierarchy:
          $ref: '#/components/schemas/PageHierarchy'
          description: |-
            Hierarchy information for the page (when hierarchy extraction is enabled)

            Contains text hierarchy levels (H1-H6) extracted from the page content.
        image_indices:
          type: array
          items:
            type: integer
            format: int32
            minimum: 0
          description: |-
            Indices into `ExtractedDocument.images` for images found on this page.

            Each value is a zero-based index into the top-level `images` collection.
            Only populated when `extract_images = true` in the extraction config.
        image_preprocessing:
          $ref: '#/components/schemas/ImagePreprocessingMetadata'
          description: OCR image preprocessing applied to this page's raster.
        is_blank:
          type:
            - boolean
            - "null"
          description: |-
            Whether this page is blank (no meaningful text content)

            Determined during extraction based on text content analysis.
            A page is blank if it has fewer than 3 non-whitespace characters
            and contains no tables or images.
        layout_regions:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/LayoutRegion'
          description: |-
            Layout detection regions for this page (when layout detection is enabled).

            Contains detected layout regions with class, confidence, bounding box,
            and area fraction. Only populated when layout detection is configured.
        native_content:
          type:
            - string
            - "null"
          description: |-
            Text layer of this page, kept when OCR output replaced it in `content`.

            Set only when `PageConfig::keep_native_content` is on, an OCR route replaced this page's `content`, and the
            text layer is not blank and differs from the OCR text. `None` on every other page. PDF only.
        ocr_confidence:
          $ref: '#/components/schemas/PageOcrConfidence'
          description: Aggregate OCR confidence for this page. `None` when the page was not OCR'd.
        page_number:
          type: integer
          format: int32
          description: Page number (1-indexed)
          minimum: 0
        section_name:
          type:
            - string
            - "null"
          description: |-
            Section name this slide belongs to (PPTX only).

            PowerPoint sections group slides into logical chapters (`<p:sectionLst>` in
            `ppt/presentation.xml`). Only populated when the source is a PPTX file and
            the slide belongs to a named section.
        sheet_name:
          type:
            - string
            - "null"
          description: |-
            Sheet name for this page (XLSX/ODS only).

            Each spreadsheet sheet maps to one `PageContent` entry. This field carries the
            sheet's display name as it appears in the workbook. `None` for all non-spreadsheet
            formats and for sheets with an empty name.
        speaker_notes:
          type:
            - string
            - "null"
          description: |-
            Speaker notes for this slide (PPTX only).

            Contains the text from the slide's notes pane (`ppt/notesSlides/notesSlide{N}.xml`).
            Only populated when the source is a PPTX file and notes are present.
        tables:
          type: array
          items:
            $ref: '#/components/schemas/Table'
          description: |-
            Tables found on this page (uses Arc for memory efficiency)

            Serializes as `Vec<Table>` for JSON compatibility while maintaining
            Arc semantics in-memory for zero-copy sharing.
    PageDimensions:
      type: object
      required:
        - width
        - height
      properties:
        height:
          type: number
        width:
          type: number
    PageHierarchy:
      type: object
      description: |-
        Page hierarchy structure containing heading levels and block information.

        Used when PDF text hierarchy extraction is enabled. Contains hierarchical
        blocks with heading levels (H1-H6) for semantic document structure.
      required:
        - block_count
      properties:
        block_count:
          type: integer
          format: int32
          description: Number of hierarchy blocks on this page
          minimum: 0
        blocks:
          type: array
          items:
            $ref: '#/components/schemas/HierarchicalBlock'
          description: Hierarchical blocks with heading levels
    PageInfo:
      type: object
      required:
        - number
      properties:
        dimensions:
          $ref: '#/components/schemas/PageDimensions'
          description: Dimensions in points (PDF) or pixels (images).
        has_vector_graphics:
          type: boolean
          description: |-
            Whether this page contains non-trivial vector graphics (paths, shapes, curves)

            Indicates the presence of vector-drawn content such as charts, diagrams,
            or geometric shapes (e.g., from Adobe InDesign, LaTeX TikZ). These are
            invisible to `ExtractedDocument.images` since they are not embedded as raster
            XObjects. Set to `true` when path count exceeds a heuristic threshold,
            signaling that downstream consumers may want to rasterize the page to
            capture this content.

            Only populated for PDFs; `None` for other document types.
        hidden:
          type:
            - boolean
            - "null"
          description: Whether this page is hidden (e.g., in presentations)
        image_count:
          type:
            - integer
            - "null"
          format: int32
          description: Number of images on this page
          minimum: 0
        is_blank:
          type:
            - boolean
            - "null"
          description: |-
            Whether this page is blank (no meaningful text, no images, no tables)

            A page is considered blank if it has fewer than 3 non-whitespace characters
            and contains no tables or images. This is useful for filtering out empty pages
            in scanned documents or PDFs with blank separator pages.
        number:
          type: integer
          format: int32
          description: Page number (1-indexed)
          minimum: 0
        table_count:
          type:
            - integer
            - "null"
          format: int32
          description: Number of tables on this page
          minimum: 0
        title:
          type:
            - string
            - "null"
          description: Page title (usually for presentations)
    PageOcrConfidence:
      type: object
      description: |-
        Aggregate OCR legibility score for a page, reported by the backend that produced its text.

        This is distinct from `OcrConfidence`, which scores a single detected element (a word or
        line) using detection/recognition confidence from the OCR engine itself. `PageOcrConfidence`
        is a page-level summary computed after noise filtering, intended for triage of which pages
        are worth a closer look, not for comparing OCR engines against each other.
      required:
        - word_count
        - backend
      properties:
        backend:
          type: string
          description: Name of the OCR backend that produced the page text.
        score:
          type:
            - number
            - "null"
          format: double
          description: |-
            Aggregate legibility score in `0.0..=1.0`, or `None` when the backend that
            produced this page does not report a calibrated legibility scale.

            Backends differ in what their confidence numbers mean (see `OcrConfidence`'s
            per-backend normalization), and not every backend maps onto a 0.0-1.0 legibility
            scale at all. When a backend has no such calibrated scale, this is `None` rather
            than a misleading number, and scores must never be compared across backends.
        word_count:
          type: integer
          format: int32
          description: |-
            Number of words the score was averaged over, AFTER noise filtering.

            A small `word_count` means the average is based on little evidence, so a high
            `score` next to a small `word_count` is not representative of the whole page.
          minimum: 0
    PageSpan:
      type: object
      description: |-
        A single page covered by a chunk, with an optional bounding box on that page.

        See `ChunkMetadata.page_spans` (#1295) for population semantics.
      required:
        - page
      properties:
        bbox:
          $ref: '#/components/schemas/BoundingBox'
          description: Bounding box on this page, if known.
        page:
          type: integer
          format: int32
          description: Page number (1-indexed).
          minimum: 0
    PageStructure:
      type: object
      description: |-
        Unified page structure for documents.

        Supports different page types (PDF pages, PPTX slides, Excel sheets)
        with character offset boundaries for chunk-to-page mapping.
      required:
        - total_count
        - unit_type
      properties:
        boundaries:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/PageBoundary'
          description: |-
            Character offset boundaries for each page

            Maps character ranges in the extracted content to page numbers.
            Used for chunk page range calculation.
        pages:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/PageInfo'
          description: Detailed per-page metadata (optional, only when needed)
        total_count:
          type: integer
          format: int32
          description: Total number of pages/slides/sheets
          minimum: 0
        unit_type:
          $ref: '#/components/schemas/PageUnitType'
          description: Type of paginated unit
    PageUnitType:
      type: string
      description: |-
        Type of paginated unit in a document.

        Distinguishes between different types of "pages" (PDF pages, presentation slides, spreadsheet sheets).
      enum:
        - page
        - slide
        - sheet
    PdfAnnotation:
      type: object
      description: A PDF annotation extracted from a document page.
      required:
        - annotation_type
        - page_number
      properties:
        annotation_type:
          $ref: '#/components/schemas/PdfAnnotationType'
          description: The type of annotation.
        author:
          type:
            - string
            - "null"
          description: Author/creator of the annotation (PDF `/T` entry).
        bounding_box:
          $ref: '#/components/schemas/BoundingBox'
          description: Bounding box of the annotation on the page.
        color:
          type:
            - string
            - "null"
          description: |-
            Annotation colour (PDF `/C` entry), normalised to a CSS-compatible
            `#rrggbb` hex string. Gray and CMYK colour spaces are converted to RGB.
        content:
          type:
            - string
            - "null"
          description: Text content of the annotation (e.g., comment text, link URL).
        marked_text:
          type:
            - string
            - "null"
          description: |-
            The document text covered by `quad_points`, recovered from the
            page content underneath the marked-up region. Populated for
            Highlight, Underline, StrikeOut, and Squiggly annotations when the
            underlying text could be recovered.
        modified:
          type:
            - string
            - "null"
          description: |-
            Last modification date of the annotation (PDF `/M` entry), as a raw
            PDF date string (e.g. `"D:20240115120000Z"`).
        page_number:
          type: integer
          format: int32
          description: Page number where the annotation appears (1-indexed).
          minimum: 0
        quad_points:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/BoundingBox'
          description: |-
            Per-line bounding boxes derived from the annotation's `/QuadPoints`
            entry. Present for text markup annotations (Highlight, Underline,
            StrikeOut, Squiggly), one box per marked line/run of text.
        subject:
          type:
            - string
            - "null"
          description: Subject of the annotation (PDF `/Subj` entry).
    PdfAnnotationType:
      type: string
      description: Type of PDF annotation.
      enum:
        - text
        - highlight
        - link
        - stamp
        - underline
        - strike_out
        - squiggly
        - ink
        - square
        - circle
        - polygon
        - poly_line
        - line
        - caret
        - file_attachment
        - sound
        - movie
        - other
    PdfConfig:
      type: object
      description: PDF-specific extraction options.
      properties:
        bottom_margin_fraction:
          type:
            - number
            - "null"
          format: float
          description: Bottom margin fraction to skip (0.0-1.0)
        extract_annotations:
          type:
            - boolean
            - "null"
          description: Extract PDF annotations
        extract_images:
          type:
            - boolean
            - "null"
          description: Extract images from PDF
        extract_metadata:
          type:
            - boolean
            - "null"
          description: Extract PDF metadata. Defaults to true when omitted.
        extract_tables:
          type:
            - boolean
            - "null"
          description: Extract tables from PDF pages. Defaults to true when omitted.
        hierarchy:
          $ref: '#/components/schemas/HierarchyConfig'
          description: Heading hierarchy detection
        passwords:
          type:
            - array
            - "null"
          items:
            type: string
          description: PDF passwords to try
        top_margin_fraction:
          type:
            - number
            - "null"
          format: float
          description: Top margin fraction to skip (0.0-1.0)
      additionalProperties: false
    PdfFormField:
      type: object
      description: |
        A form field extracted from a PDF's AcroForm or XFA structure.

        Populated by the PDF extractor when `PdfConfig::extract_form_fields` is
        enabled and the document is a fillable form. Supports both AcroForm (standard)
        and XFA (XML Forms Architecture) layers. When both are present, AcroForm fields
        take priority (canonical fallback per PDF spec), and XFA-only fields are appended.
        The collection is empty for non-form PDFs and for non-PDF formats.
      required:
        - name
        - full_name
        - field_type
      properties:
        bbox:
          $ref: '#/components/schemas/BoundingBox'
          description: Widget bounding box on its page, if known.
        default_value:
          type:
            - string
            - "null"
          description: Default field value, if any.
        field_type:
          $ref: '#/components/schemas/FormFieldType'
          description: Classified field type.
        flags:
          type: integer
          format: int32
          description: Raw field-flags bitmask (read-only, required, multiline, …).
          minimum: 0
        full_name:
          type: string
          description: Fully-qualified field name (dotted path from the form root).
        max_length:
          type:
            - integer
            - "null"
          format: int32
          description: Maximum input length for text fields, if specified.
          minimum: 0
        name:
          type: string
          description: Partial field name (the leaf name within the field hierarchy).
        page:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            1-indexed page the field's widget appears on. Currently always `None` for
            AcroForm fields; page assignment is a deferred enhancement requiring spatial
            analysis of widget annotations per page.
          minimum: 0
        tooltip:
          type:
            - string
            - "null"
          description: Tooltip / alternate field description, if present.
        value:
          type:
            - string
            - "null"
          description: Current field value, if any.
    PdfMetadata:
      type: object
      description: |-
        PDF-specific metadata.

        Contains metadata fields specific to PDF documents that are not in the common
        `Metadata` structure. Common fields like title, authors, keywords, and dates
        are at the `Metadata` level.
      required:
        - format_type
      properties:
        fabricated_text_pages:
          type:
            - array
            - "null"
          items:
            type: integer
            format: int32
            minimum: 0
          description: |-
            Pages whose text was dominated by fabricated character mappings (1-indexed):
            `MappingProvenance::Fallback`, a font whose glyph-to-Unicode mapping resolved to
            a value the extractor chose rather than read from the file (issue #1254). This is
            a fact about how the text was derived, independent of `scanned_pages`'s raster-based
            scan detection, and independent of whether the resulting text happens to look
            structurally like prose (issue #1667: a broken mapping that lands on ordinary
            letters and punctuation passes every character-shape check but is still fabricated).

            `None` when `OcrQualityThresholds::enable_provenance_ocr_routing` is `false`; empty
            when no page qualifies.
        format_type:
          type: string
          enum:
            - pdf
        height:
          type:
            - integer
            - "null"
          format: int64
          description: First page height in points (1/72 inch)
        implausible_text_pages:
          type:
            - array
            - "null"
          items:
            type: integer
            format: int32
            minimum: 0
          description: |-
            Pages whose native text layer reads as no real detectable language (1-indexed): a
            `/ToUnicode` CMap (or other mapping tier) that resolves every glyph to *a* character,
            consistently the WRONG one (e.g. a ROT-shifted mapping), so the page is structurally
            indistinguishable from real prose to `fabricated_text_pages`'s provenance check and to
            every character-shape heuristic (issue #1696; issue #1667's `quality_score: 1.0` with
            no warning on such a page is the same underlying gap). This is a content-plausibility
            fact, independent of `fabricated_text_pages` and of `scanned_pages`.

            `None` when `OcrQualityThresholds::enable_plausibility_ocr_routing` is `false`; empty
            when no page qualifies.
        is_encrypted:
          type:
            - boolean
            - "null"
          description: Whether the PDF is encrypted/password-protected
        layout_gate_reasons:
          type:
            - array
            - "null"
          items:
            type: string
          description: |-
            Why the `auto` layout gate selected or skipped each page.

            Index `i` is page `i + 1`. Snake_case values such as `multi_column`,
            `table_grid`, or `plain_text` (the skip reason). `None` unless layout
            detection ran with `LayoutStrategy::Auto`.
        layout_gated_pages:
          type:
            - array
            - "null"
          items:
            type: integer
            format: int32
            minimum: 0
          description: |-
            Pages the `auto` layout strategy skipped (1-indexed).

            `None` unless layout detection ran with `LayoutStrategy::Auto`; empty
            when the gate selected every page.
        page_count:
          type:
            - integer
            - "null"
          format: int32
          description: Total number of pages in the PDF document
          minimum: 0
        pdf_version:
          type:
            - string
            - "null"
          description: PDF version (e.g., "1.7", "2.0")
        producer:
          type:
            - string
            - "null"
          description: PDF producer (application that created the PDF)
        scanned_confidence:
          type:
            - number
            - "null"
          format: float
          description: |-
            How strongly the document's most scan-like page resembles a scan, in `[0.0, 1.0]`.

            `None` when the document could not be inspected. A full-page raster with no
            visible text scores at least `0.85`; a born-digital slide with a full-bleed
            background image scores `0.50`.
        scanned_pages:
          type:
            - array
            - "null"
          items:
            type: integer
            format: int32
            minimum: 0
          description: |-
            Pages that look like scans (1-indexed), using the default confidence threshold.

            `None` when the document could not be inspected; empty when no page qualifies.
        width:
          type:
            - integer
            - "null"
          format: int64
          description: First page width in points (1/72 inch)
    PiiCategory:
      oneOf:
        - type: string
          description: Email address (e.g. `user@example.com`).
          enum:
            - email
        - type: string
          description: Phone number in any common format.
          enum:
            - phone
        - type: string
          description: US Social Security Number.
          enum:
            - ssn
        - type: string
          description: Payment card number (Visa, Mastercard, Amex, etc.).
          enum:
            - credit_card
        - type: string
          description: Postal / ZIP code.
          enum:
            - postal_code
        - type: string
          description: IPv4 or IPv6 address.
          enum:
            - ip_address
        - type: string
          description: International Bank Account Number.
          enum:
            - iban
        - type: string
          description: SWIFT / BIC bank identifier code.
          enum:
            - swift_bic
        - type: string
          description: Date of birth.
          enum:
            - date_of_birth
        - type: string
          description: Person name, surfaced by the optional NER backend.
          enum:
            - person
        - type: string
          description: Organization name, surfaced by the optional NER backend.
          enum:
            - organization
        - type: string
          description: Location, surfaced by the optional NER backend.
          enum:
            - location
        - type: object
          description: |-
            Caller-supplied custom category (e.g. internal employee IDs).

            Surfaced by the redaction engine when a hit comes from
            custom term rules
            or custom pattern rules.
            The string is the label passed alongside the term/pattern. Use those
            fields rather than constructing `Custom` directly via the
            `categories` filter — the pattern engine cannot detect arbitrary text
            from a category name alone.
          required:
            - custom
          properties:
            custom:
              type: string
              description: |-
                Caller-supplied custom category (e.g. internal employee IDs).

                Surfaced by the redaction engine when a hit comes from
                custom term rules
                or custom pattern rules.
                The string is the label passed alongside the term/pattern. Use those
                fields rather than constructing `Custom` directly via the
                `categories` filter — the pattern engine cannot detect arbitrary text
                from a category name alone.
      description: PII categories the pattern engine recognises.
    PixelDimensions:
      type: object
      required:
        - width
        - height
      properties:
        height:
          type: integer
        width:
          type: integer
    PostProcessorConfig:
      type: object
      description: Post-processing pipeline configuration.
      properties:
        disabled_processors:
          type:
            - array
            - "null"
          items:
            type: string
          description: Blacklist of processors to disable (None = none)
        enabled:
          type:
            - boolean
            - "null"
          description: Enable post-processors. Defaults to true when omitted.
        enabled_processors:
          type:
            - array
            - "null"
          items:
            type: string
          description: Whitelist of processors to enable (None = all)
      additionalProperties: false
    PptxMetadata:
      type: object
      description: |-
        PowerPoint presentation metadata.

        Extracted from PPTX files containing slide counts and presentation details.
      required:
        - slide_count
        - slide_names
        - format_type
      properties:
        format_type:
          type: string
          enum:
            - pptx
        image_count:
          type:
            - integer
            - "null"
          format: int32
          description: Number of embedded images
          minimum: 0
        slide_count:
          type: integer
          format: int32
          description: Total number of slides in the presentation
          minimum: 0
        slide_names:
          type: array
          items:
            type: string
          description: Names of slides (if available)
        table_count:
          type:
            - integer
            - "null"
          format: int32
          description: Number of tables
          minimum: 0
    PresetCallMode:
      type: string
      description: Preferred call mode for a preset.
      enum:
        - text_only
        - vision_only
        - text_plus_vision
    PresetCategory:
      type: string
      description: High-level category used to group presets in the registry UI.
      enum:
        - finance
        - identity
        - legal
        - logistics
        - medical
        - hr
        - other
    PresetDetail:
      type: object
      description: Full preset detail, returned by `GET /v1/presets/{id}`.
      required:
        - id
        - version
        - schema_name
        - description
        - category
        - schema
        - system_prompt
        - preferred_call_mode
        - emit_citations
        - fingerprint
      properties:
        category:
          $ref: '#/components/schemas/PresetCategory'
        context_template:
          type:
            - string
            - "null"
        description:
          type: string
        emit_citations:
          type: boolean
        fingerprint:
          type: string
        id:
          type: string
          description: Stable, URL-safe preset identifier (lowercase snake_case slug, not a UUID).
        preferred_call_mode:
          $ref: '#/components/schemas/PresetCallMode'
        sample:
          $ref: '#/components/schemas/PresetSampleRef'
        schema:
          type: object
          description: JSON Schema (Draft 2020-12) describing the structured output shape.
        schema_name:
          type: string
        system_prompt:
          type: string
        tags:
          type: array
          items:
            type: string
        version:
          type: string
    PresetSampleRef:
      type: object
      description: Public pointer to bundled sample data exposed via `GET /v1/presets/{id}/sample`.
      required:
        - input_url
        - output_url
      properties:
        input_url:
          type: string
          description: URL path (relative to the API root) to fetch the sample input bytes.
        output_url:
          type: string
          description: URL path (relative to the API root) to fetch the reference output JSON.
    PresetSummary:
      type: object
      description: Lightweight projection of a preset, returned by `GET /v1/presets`.
      required:
        - id
        - version
        - schema_name
        - description
        - category
        - preferred_call_mode
        - emit_citations
        - fingerprint
      properties:
        category:
          $ref: '#/components/schemas/PresetCategory'
        description:
          type: string
        emit_citations:
          type: boolean
        fingerprint:
          type: string
          description: Stable sha256 fingerprint used by clients to detect updates.
        id:
          type: string
          description: Stable, URL-safe preset identifier (lowercase snake_case slug, not a UUID).
        preferred_call_mode:
          $ref: '#/components/schemas/PresetCallMode'
        schema_name:
          type: string
        tags:
          type: array
          items:
            type: string
        version:
          type: string
    PresignDocumentInput:
      type: object
      description: Document metadata for presigned upload (no bytes).
      required:
        - filename
        - mime_type
      properties:
        config:
          $ref: '#/components/schemas/FileExtractionConfig'
          description: |-
            Per-file extraction config override.
            Merged with batch-level `config` at confirm time. None = use batch default.
        filename:
          type: string
          description: Original filename
        mime_type:
          type: string
          description: MIME type of the document
      additionalProperties: false
    PresignUploadRequest:
      type: object
      description: Request body for presigning upload URLs.
      required:
        - documents
      properties:
        config:
          type: object
          description: Batch-level extraction configuration (applied to all documents).
        context:
          type:
            - object
            - "null"
          description: |-
            Free-form context merged into a curated or saved preset's context
            template, resolved the same way as `/v1/extract`'s `options.context`
            (`api_types::ExtractionOptions::context`). Ignored when neither
            `preset` nor `saved_preset_id` is set.
          additionalProperties:
            type: string
          propertyNames:
            type: string
        documents:
          type: array
          items:
            $ref: '#/components/schemas/PresignDocumentInput'
          description: Document metadata (no file data)
        preset:
          type:
            - string
            - "null"
          description: |-
            Curated preset identifier, resolved the same way as `/v1/extract`'s
            `options.preset`. Mutually exclusive with `saved_preset_id` and with
            an inline schema in `config`.
        saved_preset_id:
          type:
            - string
            - "null"
          description: |-
            Saved preset ID (project-scoped, user-created), resolved the same way
            as `/v1/extract`'s `options.saved_preset_id`. Mutually exclusive with
            `preset`.
        webhook:
          $ref: '#/components/schemas/WebhookConfig'
          description: Webhook configuration for async result delivery
      additionalProperties: false
    PresignUploadResponse:
      type: object
      description: Response from the presign endpoint.
      required:
        - batch_id
        - uploads
      properties:
        batch_id:
          type: string
          format: uuid
          description: Batch ID to pass to the confirm endpoint.
        uploads:
          type: array
          items:
            $ref: '#/components/schemas/PresignedUploadInfo'
          description: Per-document upload URLs
    PresignedUploadInfo:
      type: object
      description: Info about a single presigned upload.
      required:
        - job_id
        - upload_url
        - object_key
        - method
        - expires_in_secs
      properties:
        expires_in_secs:
          type: integer
          format: int64
          description: Seconds until the URL expires
          minimum: 0
        job_id:
          type: string
          format: uuid
          description: Job ID for this document
        method:
          type: string
          description: HTTP method to use (PUT)
        object_key:
          type: string
          description: Storage object key
        upload_url:
          type: string
          description: Presigned URL to upload the document to
    PrimaryScore:
      oneOf:
        - type: object
          description: |-
            Vector similarity score, where a higher value is the better match.

            The collection's distance metric decides the scale: cosine gives the
            cosine similarity in `[-1, 1]`, inner product gives the unbounded inner
            product, and L2 gives `1 / (1 + distance)` in `(0, 1]`. Scores are
            comparable within a collection, not across collections.

            Every score in one response rises with relevance, but the scales differ,
            so a vector score and a full-text score are not comparable with each
            other even inside one breakdown.
          required:
            - score
            - kind
          properties:
            kind:
              type: string
              enum:
                - vector
            score:
              type: number
              format: float
              description: Score value.
        - type: object
          description: Full-text rank score.
          required:
            - score
            - kind
          properties:
            kind:
              type: string
              enum:
                - full_text
            score:
              type: number
              format: float
              description: Score value.
        - type: object
          description: RRF-fused vector + full-text.
          required:
            - rrf
            - kind
          properties:
            full_text:
              type:
                - number
                - "null"
              format: float
              description: |-
                Full-text component. `null` when the chunk matched only the
                vector leg.
            kind:
              type: string
              enum:
                - hybrid
            rrf:
              type: number
              format: float
              description: Reciprocal-rank-fusion combined score.
            vector:
              type:
                - number
                - "null"
              format: float
              description: |-
                Vector component. `null` when the chunk matched only the
                full-text leg.
      description: JSON-friendly mirror of vectorstore's internal `PrimaryScore`.
    ProIntegrationListResponse:
      allOf:
        - $ref: '#/components/schemas/ListIntegrationsResponse'
          description: Existing paginated integration fields.
        - type: object
          required:
            - oauth_configured
          properties:
            oauth_configured:
              type: boolean
              description: Whether the integration hub is configured to establish OAuth connections.
      description: Pro integration page and the instance's OAuth connection capability.
    ProReadinessChecks:
      type: object
      description: Per-dependency readiness results.
      required:
        - database
        - maintenance_database
        - storage
      properties:
        database:
          type: string
          description: '`ok` or `unhealthy`: a connection was checked out of the pool.'
        maintenance_database:
          type: string
          description: '`ok` or `unhealthy`: the maintenance login acquired a connection.'
        storage:
          type: string
          description: |-
            `ok` or `unhealthy`: the object store answered a metadata request. A
            refusal counts as an answer; the check proves reachability, not
            credentials.
        worker:
          type:
            - string
            - "null"
          description: |-
            `ok` or `not_ready`: the embedded vector-index worker answered its own
            readiness endpoint with success. A worker that answers not ready, or not
            at all, is `not_ready`. Absent when the process runs without a worker.
    ProcessingWarning:
      type: object
      description: |-
        A non-fatal warning from a processing pipeline stage.

        Captures errors from optional features that don't prevent extraction
        but may indicate degraded or incomplete results. Inspect these independently
        from `ExtractedDocument::quality_score`, which assesses retained text only.
      required:
        - source
        - message
      properties:
        message:
          type: string
          description: Human-readable description of what went wrong.
        source:
          type: string
          description: |-
            The pipeline stage or feature that produced this warning
            (e.g., "embedding", "chunking", "language_detection", "output_format").
    ProjectInfo:
      type: object
      description: Project information in the login response.
      required:
        - id
        - name
        - role
      properties:
        id:
          type: string
          format: uuid
          description: Project UUID.
          example: 550e8400-e29b-41d4-a716-446655440000
        name:
          type: string
          description: Project name.
          example: Default
        role:
          type: string
          description: |-
            User role for this session (`OWNER` or `MEMBER`). See
            `compute_role` for how this is derived.
          example: OWNER
    ProjectResponse:
      type: object
      description: |-
        Project details, as returned by create/get/list.

        Mirrors the Enterprise backend's `ProjectResponse` exactly. Pro's current
        HTTP DTOs (`ProjectSummary`, `CreateProjectResponse`) expose a subset of
        these fields, but Pro's `persistence::ProjectSummary` model already tracks
        every one of them (`owner_user_id`, `api_key_count`, `webhook_count`,
        `total_pages_extracted`) — adopting this full shape is a handler-level
        wiring change, not a schema gap, so no field here is `Option`.
      required:
        - id
        - name
        - slug
        - status
        - owner_user_id
        - created_at
        - updated_at
        - api_key_count
        - webhook_count
        - total_pages_extracted
      properties:
        api_key_count:
          type: integer
          format: int32
          description: Number of active (non-revoked) API keys.
        created_at:
          type: string
          format: date-time
          description: ISO 8601 creation timestamp.
        id:
          type: string
          format: uuid
          description: Unique project identifier.
        name:
          type: string
          description: Human-readable project name.
        owner_user_id:
          type: string
          description: User ID of the project owner (from the identity provider).
        slug:
          type: string
          description: URL-safe identifier.
        status:
          type: string
          description: Project status (e.g. `"ACTIVE"`, `"SUSPENDED"`, `"INACTIVE"`).
        total_pages_extracted:
          type: integer
          format: int64
          description: Total billable pages extracted (excluding cancelled jobs).
        updated_at:
          type: string
          format: date-time
          description: ISO 8601 last update timestamp.
        webhook_count:
          type: integer
          format: int32
          description: Number of active (non-deleted) webhooks.
    PromoteProfileRequest:
      type: object
      description: |-
        Request to promote a completed auto-tune result to a named, reusable
        tuning profile.
      required:
        - name
      properties:
        allow_unscored:
          type: boolean
          description: |-
            Promote the run even when no trial in it scored any document.

            Such a run's winning configuration extracted nothing, so the profile it produces is
            unproven and promotion refuses it by default. Set this when you know why the run
            scored nothing and you want its configuration anyway.
        name:
          type: string
          description: Name to give the promoted tuning profile.
          minLength: 1
    PropertyChange:
      type: object
      description: |-
        A single run-level or style-level property change.

        Used for revisions that change formatting rather than text content. `from`
        and `to` store normalized property values when the source format exposes
        them; either side may be absent when the format only records one side of the
        change.
      required:
        - name
      properties:
        from:
          type:
            - string
            - "null"
          description: Value before the change, when available.
        name:
          type: string
          description: Property name, such as `"bold"`, `"italic"`, `"font_size"`, or `"font_color"`.
        to:
          type:
            - string
            - "null"
          description: Value after the change, when available.
    PstMetadata:
      type: object
      description: Outlook PST archive metadata.
      required:
        - message_count
        - format_type
      properties:
        format_type:
          type: string
          enum:
            - pst
        message_count:
          type: integer
          description: Total number of email messages found in the PST archive.
          minimum: 0
    QrBoundingBox:
      type: object
      description: Pixel-space bounding box of a QR code inside its source image.
      required:
        - x
        - y
        - width
        - height
      properties:
        height:
          type: integer
          format: int32
          description: Height of the bounding box in pixels.
          minimum: 0
        width:
          type: integer
          format: int32
          description: Width of the bounding box in pixels.
          minimum: 0
        x:
          type: integer
          format: int32
          description: Horizontal pixel offset of the bounding box top-left corner.
          minimum: 0
        y:
          type: integer
          format: int32
          description: Vertical pixel offset of the bounding box top-left corner.
          minimum: 0
    QrCode:
      type: object
      description: One QR code decoded from an extracted image.
      required:
        - payload
      properties:
        bbox:
          $ref: '#/components/schemas/QrBoundingBox'
          description: |-
            Bounding box of the QR code inside the source image, in pixel coordinates
            (`x`, `y` of the top-left corner; `width`, `height` of the rectangle).
            `None` if the decoder did not report a bounding box.
        confidence:
          type:
            - number
            - "null"
          format: float
          description: |-
            Detector-reported confidence in `[0.0, 1.0]`. `None` when the decoder
            does not expose confidence (the default `rqrr` backend always reports
            `Some` because successful decode implies high confidence).
        payload:
          type: string
          description: Decoded payload (text, URL, vCard string, …).
    RagConfigResponse:
      type: object
      description: |-
        A project's RAG configuration, as returned by `GET .../rag-config` and
        `PUT .../rag-config`.

        A plain, sqlx-free mirror of `common::ProjectRagConfig` — `api-types` must
        not depend on `sqlx`/`tokio-postgres`, so the shape is duplicated here
        rather than shared.
      required:
        - enabled
      properties:
        default_collection:
          type:
            - string
            - "null"
          description: Default vector collection for RAG operations.
        enabled:
          type: boolean
          description: Whether RAG is enabled for this project.
    RagHealth:
      type: object
      description: RAG readiness summary for the health response.
      required:
        - enabled
      properties:
        enabled:
          type: boolean
          description: |-
            Whether the in-process RAG runtime is wired. Mirrors
            `crate::rag::rag_runtime_enabled` at boot time — `false` means every
            `/v1/rag/*` request 404s with `rag.disabled` regardless of any
            individual project's own RAG config.
    RagJobKind:
      type: string
      description: Operation type for RAG async jobs
      enum:
        - ingest
        - reindex
        - migrate
    RagJobPendingReason:
      type: string
      description: Why a pending job has not started
      enum:
        - embedding_model_download
    RagJobProgress:
      type: object
      description: Progress details for long-running operations (migrate only)
      required:
        - documents_total
        - documents_processed
        - current_phase
      properties:
        current_phase:
          type: string
          description: 'Current processing phase (for migrate: "dual_write"|"index_build"|"swap"|"cleanup")'
        documents_processed:
          type: integer
          format: int64
          description: Documents successfully processed
          minimum: 0
        documents_total:
          type: integer
          format: int64
          description: Total documents to process
          minimum: 0
    RagJobState:
      type: string
      description: Job lifecycle state
      enum:
        - pending
        - running
        - completed
        - failed
    RagJobStatusResponse:
      type: object
      description: |-
        Unified RAG async job status response covering ingest, reindex, and migrate operations.

        The response shape varies depending on the operation type (`kind`):
        - **ingest**: status field reflects document ingestion progress (pending, indexed, failed)
        - **reindex**: status field reflects re-chunking and re-embedding progress
        - **migrate**: status and progress fields reflect embedding migration state
      required:
        - job_id
        - kind
        - state
        - created_at
      properties:
        collection_id:
          type:
            - string
            - "null"
          format: uuid
          description: Associated collection ID (for migrate only; absent for ingest/reindex)
        created_at:
          type: string
          format: date-time
          description: Timestamp when job was submitted (RFC3339)
        document_id:
          type:
            - string
            - "null"
          format: uuid
          description: Associated document ID (for ingest/reindex only; absent for migrate)
        error:
          type:
            - string
            - "null"
          description: Error message if state = failed
        error_code:
          type:
            - string
            - "null"
          description: Machine-readable cause when the failed job has a known classification.
        job_id:
          type: string
          description: Job identifier (ULID) for this async operation
        kind:
          $ref: '#/components/schemas/RagJobKind'
          description: 'Operation type: "ingest", "reindex", or "migrate"'
        pending_reason:
          $ref: '#/components/schemas/RagJobPendingReason'
          description: |-
            Why the job has not started yet. Absent unless state = pending, and
            absent when the job is simply queued.
        progress:
          $ref: '#/components/schemas/RagJobProgress'
          description: Progress details (only for migrate operations; null for ingest/reindex)
        state:
          $ref: '#/components/schemas/RagJobState'
          description: Current job state
    RakeParams:
      type: object
      description: RAKE-specific parameters.
      required:
        - min_word_length
        - max_words_per_phrase
      properties:
        max_words_per_phrase:
          type: integer
          description: 'Maximum words in a keyword phrase (default: 3).'
          minimum: 0
        min_word_length:
          type: integer
          description: 'Minimum word length to consider (default: 1).'
          minimum: 0
      additionalProperties: false
    ReadinessResponse:
      type: object
      description: 'Readiness report: the process can serve traffic only when both checks pass.'
      required:
        - status
        - checks
      properties:
        checks:
          $ref: '#/components/schemas/ProReadinessChecks'
        status:
          type: string
          description: '`ready` or `not_ready`'
    RedactionFinding:
      type: object
      description: 'One redaction event: which span was rewritten, why, and with what.'
      required:
        - start
        - end
        - category
        - strategy
        - replacement_token
      properties:
        category:
          $ref: '#/components/schemas/PiiCategory'
          description: PII category that fired this redaction.
        end:
          type: integer
          format: int32
          description: Byte-offset end (exclusive) in the original `ExtractedDocument::content`.
          minimum: 0
        replacement_token:
          type: string
          description: |-
            String that replaced the original mention. Always present; for `Drop` the
            replacement is the empty string.
        start:
          type: integer
          format: int32
          description: Byte-offset start in the original (pre-redaction) `ExtractedDocument::content`.
          minimum: 0
        strategy:
          $ref: '#/components/schemas/RedactionStrategy'
          description: Strategy applied to this finding (mask, hash, token-replace, drop).
    RedactionReport:
      type: object
      description: |-
        Audit report describing what the redaction processor found and how it replaced it.

        The redactor returns this alongside the rewritten content so compliance, replay, and
        audit-log consumers can see exactly what fired. Offsets are relative to the *original*
        pre-redaction `content` and are intended for audit reconstruction only — the original
        bytes are dropped at the end of the pipeline.
      required:
        - findings
        - total_redacted
      properties:
        findings:
          type: array
          items:
            $ref: '#/components/schemas/RedactionFinding'
          description: Individual redaction findings in original-source byte order.
        total_redacted:
          type: integer
          format: int32
          description: Total number of redactions applied across the document.
          minimum: 0
    RedactionStrategy:
      type: string
      description: Strategy applied when a PII match is rewritten.
      enum:
        - mask
        - hash
        - token_replace
        - drop
    ReindexRequest:
      type: object
      description: Request to reindex a document (re-chunk, re-enrich, re-embed)
      properties:
        re_extract:
          type: boolean
          description: If true, re-extract from source object via xberg before re-chunking
        webhook:
          $ref: '#/components/schemas/WebhookConfig'
          description: Optional webhook for reindex completion/failure delivery.
      additionalProperties: false
    RelationshipKind:
      type: string
      description: Semantic kind of a relationship between document elements.
      enum:
        - footnote_reference
        - citation_reference
        - internal_link
        - caption
        - label
        - toc_entry
        - cross_reference
    RerankFallbackReason:
      type: string
      description: Why a requested rerank did not order the results.
      enum:
        - timeout
        - error
    RerankOutcomeReport:
      oneOf:
        - type: object
          description: Neither the caller nor the collection asked for a rerank.
          required:
            - status
          properties:
            status:
              type: string
              enum:
                - not_requested
        - type: object
          description: A rerank ran and the results carry its order.
          required:
            - kind
            - status
          properties:
            kind:
              $ref: '#/components/schemas/RerankerKind'
              description: The reranker that produced the order.
            status:
              type: string
              enum:
                - applied
        - type: object
          description: A rerank was asked for and the results carry the primary order instead.
          required:
            - kind
            - reason
            - status
          properties:
            kind:
              $ref: '#/components/schemas/RerankerKind'
              description: The reranker that was asked for.
            reason:
              $ref: '#/components/schemas/RerankFallbackReason'
              description: Why its order is not the one returned.
            status:
              type: string
              enum:
                - fell_back
      description: |-
        What happened to the rerank stage of a retrieve.

        One field with three exclusive states, because the field this replaces
        reported the same absence for "no rerank was asked for" and for "a rerank
        was asked for and did not serve the request". A caller could tell those
        apart only by asking for `include.score_breakdown` and reading a per-chunk
        string, so a request that was not served returned success and looked
        ordinary.
    RerankerKind:
      type: string
      description: |-
        Wire-shape mirror of the internal `RerankerKind` — the response shape stays
        stable even when we add variants internally.
      enum:
        - preset
        - llm
    RerankerSpec:
      oneOf:
        - type: object
          description: Local ONNX cross-encoder preset.
          required:
            - name
            - type
          properties:
            name:
              type: string
              description: |-
                Model name, for example `bge-reranker-v2-m3`, or one of the
                aliases `fast`, `balanced`, `quality`, `multilingual`. An alias
                can point at a different model between releases; name the model
                to pin it. The RAG concepts guide lists the shipped models.
            type:
              type: string
              enum:
                - preset
        - type: object
          description: Platform-managed LLM reranker (provider + API key from Helm env).
          required:
            - type
          properties:
            model:
              type:
                - string
                - "null"
              description: |-
                Optional model override (e.g. `"gemini-1.5-pro"`). When
                `None`, the platform default is used.
            type:
              type: string
              enum:
                - llm
      description: |-
        Caller-specified reranker. The Llm variant carries an optional
        model override that picks from the platform-supported set.
    RetrieveMode:
      type: string
      description: Retrieve mode discriminant.
      enum:
        - vector
        - full_text
        - hybrid
    RetrieveRequest:
      type: object
      description: Retrieve request body.
      properties:
        candidate_multiplier:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            1..=20, default 4. Ignored when rerank is inactive. The store
            pulls `top_k * candidate_multiplier` candidates from the
            primary stage so the reranker has more to choose from.
          maximum: 20
          minimum: 1
        filter:
          type: object
          description: Filter expression (deserialized by the vectorstore Filter IR).
        group_by_document:
          type: boolean
          description: Collapse to one chunk per document.
        include:
          $ref: '#/components/schemas/IncludeMask'
          description: Optional response field mask.
        mode:
          $ref: '#/components/schemas/RetrieveMode'
          description: Retrieve mode. Defaults to `hybrid`.
        query:
          type:
            - string
            - "null"
          description: |-
            Text query. Required for full_text and hybrid modes; optional
            for vector mode when `query_vector` is supplied.
          maxLength: 8192
          minLength: 1
          pattern: .*\S.*
        query_vector:
          type:
            - array
            - "null"
          items:
            type: number
            format: float
          description: |-
            BYO query vector. Length must equal the collection's
            `embedding_dim`. Forbidden when `mode = full_text`.
        rerank:
          oneOf:
            - $ref: '#/components/schemas/RerankerSpec'
              description: |-
                Three-state rerank: absent (collection default) | null
                (disable) | present (override). The OpenAPI schema documents
                this as `Option<RerankerSpec>` — callers control the third
                state by simply omitting the field.
            - type: "null"
        top_k:
          type: integer
          format: int32
          description: Number of top results to return. `1..=200`, default 10.
          maximum: 200
          minimum: 1
      additionalProperties: false
    RetrieveResponse:
      type: object
      description: Retrieve response body.
      required:
        - mode
        - rerank
        - latency_breakdown
        - chunks
      properties:
        chunks:
          type: array
          items:
            $ref: '#/components/schemas/RetrievedChunk'
          description: Result chunks (relevance-ordered).
        latency_breakdown:
          $ref: '#/components/schemas/LatencyBreakdown'
          description: Latency breakdown.
        mode:
          $ref: '#/components/schemas/RetrieveMode'
          description: Mode used.
        rerank:
          $ref: '#/components/schemas/RerankOutcomeReport'
          description: What happened to the rerank stage. Always present.
        vector_candidates:
          $ref: '#/components/schemas/VectorCandidateReport'
          description: HNSW vector-leg completeness before fusion or reranking; absent for other retrieval paths.
    RetrievedChunk:
      type: object
      description: Single retrieved chunk in the response.
      required:
        - id
        - document_id
        - ordinal
        - score
        - chunk_metadata
      properties:
        chunk_metadata:
          type: object
          description: Chunk-level metadata.
          additionalProperties: {}
          propertyNames:
            type: string
        content:
          type:
            - string
            - "null"
          description: Chunk content. Present iff `include.content`.
        document:
          $ref: '#/components/schemas/RetrievedDocumentSummary'
          description: Parent document summary. Present iff `include.document`.
        document_id:
          type: string
          format: uuid
          description: Parent document ID.
        external_id:
          type:
            - string
            - "null"
          description: Caller-supplied external_id, if any.
        id:
          type: string
          format: uuid
          description: Chunk ID.
        ordinal:
          type: integer
          format: int32
          description: Position within document.
          minimum: 0
        score:
          type: number
          format: float
          description: Effective score after rerank (or primary score when rerank off).
        score_breakdown:
          $ref: '#/components/schemas/ScoreBreakdown'
          description: Score detail. Present iff `include.score_breakdown`.
    RetrievedDocumentSummary:
      type: object
      description: |-
        Wire shape of `RetrievedChunk.document` — the parent document summary
        returned when `include.document` is set.

        Mirrors `vectorstore::DocumentSummary`'s wire shape for documentation
        purposes only, without adding a dependency on `crates/vectorstore` (see
        the module-level doc on the `api-types`/`vectorstore` boundary above).
        `RetrievedChunk.document` stays `serde_json::Value` at rest; this type
        is used solely via `#[schema(value_type = ...)]` to give it a shape.
      required:
        - id
        - keywords
        - labels
        - entities
        - metadata
        - ingested_at
      properties:
        entities:
          type: array
          items: {}
          description: |-
            Named entities. Opaque item shape here; the engine type is
            enrichment-pipeline output.
        external_id:
          type: string
          description: Caller-supplied external identifier, if any.
        id:
          type: string
          format: uuid
          description: Document ID.
        ingested_at:
          type: string
          format: date-time
          description: Timestamp when the document was ingested.
        keywords:
          type: array
          items:
            type: string
          description: Extracted keywords.
        labels:
          type: array
          items: {}
          description: |-
            Labels matched from the collection's label vocabulary. Opaque item
            shape here; the engine type is per-collection configuration.
        metadata:
          type: object
          description: Document-level metadata.
          additionalProperties: {}
          propertyNames:
            type: string
        mime:
          type: string
          description: MIME type.
        title:
          type: string
          description: Document title.
    RevisionAnchor:
      oneOf:
        - type: object
          description: Body paragraph, identified by its zero-based index in the document flow.
          required:
            - index
            - type
          properties:
            index:
              type: integer
              description: Zero-based index of the paragraph in document order.
              minimum: 0
            type:
              type: string
              enum:
                - paragraph
        - type: object
          description: Cell inside a table.
          required:
            - row
            - col
            - table_index
            - type
          properties:
            col:
              type: integer
              description: Zero-based column index within the table.
              minimum: 0
            row:
              type: integer
              description: Zero-based row index within the table.
              minimum: 0
            table_index:
              type: integer
              description: Zero-based index of the table in document order.
              minimum: 0
            type:
              type: string
              enum:
                - table_cell
        - type: object
          description: Page, identified by its zero-based index.
          required:
            - index
            - type
          properties:
            index:
              type: integer
              description: Zero-based page index.
              minimum: 0
            type:
              type: string
              enum:
                - page
        - type: object
          description: Presentation slide, identified by its zero-based index.
          required:
            - index
            - type
          properties:
            index:
              type: integer
              description: Zero-based slide index.
              minimum: 0
            type:
              type: string
              enum:
                - slide
        - type: object
          description: Spreadsheet cell or range, identified by sheet index and optional name.
          required:
            - index
            - type
          properties:
            index:
              type: integer
              description: Zero-based sheet index.
              minimum: 0
            name:
              type:
                - string
                - "null"
              description: Sheet display name when available.
            type:
              type: string
              enum:
                - sheet
      description: Best-effort document location for a revision.
    RevisionDelta:
      type: object
      description: |-
        The content changes that make up a single revision.

        For insertions and deletions the `content` field carries the added/removed
        lines as `DiffLine::Added` / `DiffLine::Removed` entries. For format
        changes, `property_changes` carries normalized before/after formatting
        values when the source document exposes them.
      required:
        - content
        - table_changes
      properties:
        content:
          type: array
          items:
            $ref: '#/components/schemas/DiffLine'
          description: Line-level content changes for this revision.
        property_changes:
          type: array
          items:
            $ref: '#/components/schemas/PropertyChange'
          description: Formatting or metadata property changes for this revision.
        table_changes:
          type: array
          items:
            $ref: '#/components/schemas/CellChange'
          description: Cell-level table changes for this revision.
    RevisionKind:
      type: string
      description: Semantic classification of a tracked change.
      enum:
        - insertion
        - deletion
        - format_change
        - comment
    RoutingFallbackReason:
      type: string
      description: Why a routing-enabled run produced a global-only profile.
      enum:
        - no_valid_route
        - insufficient_holdout
        - confidence_below_threshold
    RoutingOutcome:
      oneOf:
        - type: object
          description: Per-document routing was disabled for the run.
          required:
            - status
          properties:
            status:
              type: string
              enum:
                - not_requested
        - type: object
          description: Learned routes improved on the global configuration and were kept.
          required:
            - status
          properties:
            status:
              type: string
              enum:
                - routes_kept
        - type: object
          description: The run requested routing but retained only the global configuration.
          required:
            - reason
            - status
          properties:
            reason:
              $ref: '#/components/schemas/RoutingFallbackReason'
              description: Why the learned routes were not retained.
            status:
              type: string
              enum:
                - global_only
      description: Outcome of per-document routing for an auto-tune run.
    SamlInfo:
      type: object
      description: SAML login configuration visible before authentication.
      required:
        - enabled
        - label
        - login_url
      properties:
        enabled:
          type: boolean
        label:
          type: string
        login_url:
          type: string
    SavedPresetDetail:
      type: object
      description: Response when retrieving a saved preset.
      required:
        - id
        - name
        - schema
        - preferred_call_mode
        - emit_citations
        - created_at
        - updated_at
      properties:
        context_template:
          type:
            - string
            - "null"
          description: Optional context template.
        created_at:
          type: string
          format: date-time
          description: ISO 8601 creation timestamp.
        description:
          type:
            - string
            - "null"
          description: Optional description.
        emit_citations:
          type: boolean
          description: |-
            Whether to emit citations in results.

            Stored and returned verbatim, but **not applied at extraction time** —
            see `preferred_call_mode`. Results produced from a saved preset carry no
            citations regardless of this value.
        id:
          type: string
          format: uuid
          description: Unique preset ID.
        name:
          type: string
          description: Preset name.
        preferred_call_mode:
          type: string
          description: |-
            Preferred orchestration path (`text_only`, `vision_only`, or
            `text_plus_vision`).

            Validated on write, stored, and returned verbatim, but
            **not applied at extraction time**. Call-mode selection belongs to the
            vision pipeline, which a job reaches only through the curated preset
            registry (`options.preset`); a saved preset never routes there. On Xberg
            Pro a saved preset contributes its `schema`, `system_prompt` and
            `context_template`, and nothing else.
        schema:
          type: object
          description: JSON Schema for the extraction result.
        system_prompt:
          type:
            - string
            - "null"
          description: Optional custom system prompt.
        updated_at:
          type: string
          format: date-time
          description: ISO 8601 last update timestamp.
    SavedPresetSummary:
      type: object
      description: Lightweight projection of a saved preset (for listings).
      required:
        - id
        - name
        - preferred_call_mode
        - created_at
      properties:
        created_at:
          type: string
          format: date-time
          description: ISO 8601 creation timestamp.
        description:
          type:
            - string
            - "null"
          description: Optional description.
        id:
          type: string
          format: uuid
          description: Unique preset ID.
        name:
          type: string
          description: Preset name.
        preferred_call_mode:
          type: string
          description: |-
            Preferred orchestration path (`text_only`, `vision_only`, or
            `text_plus_vision`).

            Validated on write, stored, and returned verbatim, but
            **not applied at extraction time**. Call-mode selection belongs to the
            vision pipeline, which a job reaches only through the curated preset
            registry (`options.preset`); a saved preset never routes there. On Xberg
            Pro a saved preset contributes its `schema`, `system_prompt` and
            `context_template`, and nothing else.
    SchemaCompliance:
      type: string
      description: |-
        Schema-validation outcome surfaced as one of three buckets.

        Fold into the combined confidence score without leaking internal validation
        error types.
      enum:
        - all_valid
        - partial_valid
        - all_invalid
    ScoreBreakdown:
      type: object
      description: Score detail for a single returned chunk.
      required:
        - primary
        - rank_primary
        - rank_final
      properties:
        primary:
          $ref: '#/components/schemas/PrimaryScore'
          description: Primary-stage score discriminant + value(s).
        rank_final:
          type: integer
          format: int32
          description: |-
            Rank position after reranking (1-indexed). Equal to
            `rank_primary` when rerank was off or fell back.
          minimum: 0
        rank_primary:
          type: integer
          format: int32
          description: Rank position in the primary-stage result set (1-indexed).
          minimum: 0
        rerank:
          type:
            - number
            - "null"
          format: float
          description: Reranker score when rerank was applied. `None` otherwise.
        rerank_fallback:
          type:
            - string
            - "null"
          description: |-
            Reason rerank was skipped or fell back, if applicable.
            Values: `"timeout"`.
    SetIntegrationSyncRequest:
      type: object
      required:
        - enabled
      properties:
        enabled:
          type: boolean
    SetRagConfigRequest:
      type: object
      description: Request to set a project's RAG configuration (`PUT .../rag-config`).
      required:
        - enabled
      properties:
        default_collection:
          type:
            - string
            - "null"
          description: Default vector collection for RAG operations.
        enabled:
          type: boolean
          description: Whether RAG should be enabled for this project.
    SparseEmbedding:
      type: object
      description: |
        A sparse learned embedding: vocabulary term indices and their weights.

        `indices` are ascending vocabulary token ids; `values[i]` is the weight for
        `indices[i]`. The two arrays always have equal length. Only strictly-positive
        terms are retained, so the representation is genuinely sparse.
      required:
        - indices
        - values
      properties:
        indices:
          type: array
          items:
            type: integer
            format: int32
            minimum: 0
          description: Vocabulary token ids with non-zero weight, ascending.
        values:
          type: array
          items:
            type: number
            format: float
          description: Weights parallel to `SparseEmbedding.indices`.
    StructuredData:
      type: object
      description: Structured data (Schema.org, microdata, RDFa) block.
      required:
        - data_type
        - raw_json
      properties:
        data_type:
          $ref: '#/components/schemas/StructuredDataType'
          description: Type of structured data
        raw_json:
          type: string
          description: Raw JSON string representation
        schema_type:
          type:
            - string
            - "null"
          description: Schema type if detectable (e.g., "Article", "Event", "Product")
    StructuredDataType:
      type: string
      description: Structured data type classification.
      enum:
        - json-ld
        - microdata
        - rdfa
    StructuredExtractionConfig:
      type: object
      description: |-
        LLM-driven structured extraction.

        When set, the worker passes the extracted document text and the supplied
        JSON schema to an LLM provider (routed through liter-llm) and returns the
        validated JSON on `ExtractionResult::structured_output`.
      required:
        - llm
      properties:
        llm:
          $ref: '#/components/schemas/LlmConfig'
          description: LLM provider + model configuration.
        prompt:
          type:
            - string
            - "null"
          description: Custom Jinja2 prompt template. When omitted, the xberg default is used.
        schema:
          type: object
          description: |-
            JSON Schema (draft-07) the structured output must conform to. Optional
            only when `options.preset` or `options.saved_preset_id` supplies it.
        schema_description:
          type:
            - string
            - "null"
          description: Optional human-readable schema description supplied to the LLM.
        schema_name:
          type: string
          description: Schema name passed to the LLM's structured-output mode. Defaults to "extraction".
        strict:
          type: boolean
          description: Strict mode — output must match the schema exactly (provider-dependent).
      additionalProperties: false
    SummaryStrategy:
      type: string
      description: Summarisation strategy.
      enum:
        - extractive
        - abstractive
    Table:
      type: object
      description: |-
        Extracted table structure.

        Represents a table detected and extracted from a document (PDF, image, etc.).
        Tables are converted to both structured cell data and Markdown format.
      required:
        - cells
        - markdown
        - page_number
      properties:
        bounding_box:
          $ref: '#/components/schemas/BoundingBox'
          description: |-
            Bounding box of the table's position. Only populated when position data is
            available from the producing extractor.

            The coordinate space depends on how the table was produced, and callers must
            know which route produced a given `Table` before interpreting this field:

            - Tables extracted from a PDF's native content, and tables recognized on a
              scanned PDF page that went through xberg's OCR pipeline (`--force-ocr` /
              `--ocr-scanned-pages`), are in **PDF points with a bottom-left origin**
              (x0=left, y0=bottom, x1=right, y1=top; y increases upward). For the OCR
              case, the pipeline rescales the backend's raw pixel output into this space
              before it reaches `Table::bounding_box` — see
              `rescale_ocr_bboxes_to_page_points` in `extractors::pdf::ocr`.
            - Tables detected by OCR on a standalone image with no backing PDF page (for
              example extracting a bare PNG/JPEG/TIFF) are in **raster pixel coordinates
              with a top-left origin** (x0=left, y0=top, x1=right, y1=bottom; y increases
              downward) — the same convention the OCR backend (Tesseract, PaddleOCR,
              candle-based backends) or the layout detector reported them in. There is no
              PDF page geometry to rescale into for this case, so the raw pixel box is
              passed through unchanged.
        cell_styles:
          type: array
          items:
            $ref: '#/components/schemas/TableCellStyle'
          description: |-
            Paragraph styles carried by individual cells, for the cells that have one.

            Sparse and flat on purpose. A DOCX banner row -- row 0, one cell spanning the grid,
            styled `Heading1`..`Heading6` -- is what Word's navigation pane and a `TOC` field treat
            as the document outline, but as a table cell it reached consumers as anonymous text
            (GH#1587). `cells` keeps the bare text: prefixing it with `#` would put a markdown
            heading inside a table cell, which is invalid where it lands and would change text every
            existing consumer already reads. This list is the signal instead.

            Entries are only emitted for cells that actually carry a style, so an ordinary table
            serialises exactly as it did before. Indices are into `cells`.
        cells:
          type: array
          items:
            type: array
            items:
              type: string
          description: Table cells as a 2D vector (rows × columns)
        columns:
          type:
            - array
            - "null"
          items:
            type: string
          description: |-
            Header cells for this fragment, i.e. the first row of `cells`.

            Populated even when this fragment's own header row was merged away or
            physically lives in a sibling fragment (see `table_id`), so a single
            fragment is interpretable in isolation. `None` when no header row
            could be determined.
        markdown:
          type: string
          description: Markdown representation of the table
        page_number:
          type: integer
          format: int32
          description: Page number where the table was found (1-indexed)
          minimum: 0
        table_id:
          type:
            - string
            - "null"
          description: |-
            Stable identifier shared by every `tables[]` entry that represents a
            fragment of the same physical table.

            Assigned deterministically by the extraction pipeline (e.g. a
            sequential `"table-N"` in document order); never derived from
            randomness or wall-clock time, so the same input document always
            produces the same ids. Consumers can use it to reconcile the markdown
            blocks in `content` / `pages[].content` / `chunks[].content` with the
            structured entries in `tables[]`. `None` when the extractor did not
            assign one.

            Today, same-page fragments of one physical table are already merged
            into a single `tables[]` entry before ids are assigned (see PDF table
            stitching), so in practice `table_id` is unique per entry rather than
            shared across several. A table split across a page boundary is
            intentionally *not* linked — its per-page pieces get separate ids.
            Sharing one id across page-boundary fragments is a known possible
            future extension, not implemented yet.
    TableCellStyle:
      type: object
      description: |-
        The paragraph style a single table cell's text carries, located by grid position.

        Flat rather than a nested `Vec<Vec<Option<..>>>`: the nested shape marshals badly across the
        FFI bindings, and the data is sparse anyway. See `Table.cell_styles`.
      required:
        - row
        - col
      properties:
        col:
          type: integer
          format: int32
          description: Zero-indexed column of the cell this style belongs to.
          minimum: 0
        heading_level:
          type:
            - integer
            - "null"
          format: int32
          description: Outline level 1-6 when the style resolves to a heading, otherwise `None`.
          minimum: 0
        row:
          type: integer
          format: int32
          description: Zero-indexed row of the cell this style belongs to.
          minimum: 0
        style_name:
          type:
            - string
            - "null"
          description: Human-readable style name, e.g. `heading 2`.
    TableGrid:
      type: object
      description: |-
        Structured table grid with cell-level metadata.

        Stores row/column dimensions and a flat list of cells with position info.
      required:
        - rows
        - cols
        - cells
      properties:
        cells:
          type: array
          items:
            $ref: '#/components/schemas/GridCell'
          description: All cells in row-major order.
        cols:
          type: integer
          format: int32
          description: Number of columns in the table.
          minimum: 0
        rows:
          type: integer
          format: int32
          description: Number of rows in the table.
          minimum: 0
    TesseractConfig:
      type: object
      description: |-
        Tesseract OCR configuration.

        Provides fine-grained control over Tesseract OCR engine parameters.
        Most users can use the defaults, but these settings allow optimization
        for specific document types (invoices, handwriting, etc.).

        **This is the public-facing counterpart of `ocr::types::TesseractConfig`
        (the internal, engine-facing representation with `u8`/`String` fields instead of
        `i32`/`Vec<String>`).** They are two independent struct definitions bridged only by
        an explicit `From<&TesseractConfig> for crate::ocr::types::TesseractConfig` impl in
        `ocr/types.rs` — that conversion carries field *values* across, but each struct keeps
        its own `Default` impl, and the conversion does nothing to keep those two defaults in
        sync. Several production call sites (`extractors::image::apply_default_tesseract_psm`,
        `configured_region_ocr`, `sparse_image_ocr_fallback_config`) construct *this* struct's
        default and convert it, bypassing the internal struct's `Default` entirely — so if the
        two defaults disagree, standalone image OCR silently uses this struct's value while
        PDF-embedded OCR (which can reach the internal `Default` directly when no
        `tesseract_config` is set) uses the other. When changing a default here, also update
        `ocr::types::TesseractConfig::default`, and vice versa.
      properties:
        classify_use_pre_adapted_templates:
          type: boolean
          description: Use pre-adapted templates for character classification
          default: true
        enable_table_detection:
          type: boolean
          description: Enable automatic table detection and reconstruction
          default: true
        language:
          type: array
          items:
            type: string
          description: |-
            Language code(s) for OCR recognition. For Tesseract, languages are joined with "+".

            A list is the canonical form and the only form accepted by the binding
            object APIs (Python, Node, PHP, WASM, etc.): `["eng", "deu"]`. When
            deserializing from a config file, JSON body, or the REST/MCP API, a
            single string is also accepted, either as one code ("eng") or
            "+"-joined ("eng+deu").
          default:
            - eng
        language_model_ngram_on:
          type: boolean
          description: |-
            Enable N-gram language model.

            Kept on by default (see default settings and
            `ocr::types::TesseractConfig::language_model_ngram_on` for the rationale);
            keep this field's default in sync with the internal struct's.
          default: true
        min_confidence:
          type: number
          format: double
          description: |-
            Minimum confidence threshold (0.0-100.0).

            Words with confidence below this threshold may be rejected or flagged.
          default: 0
        oem:
          type: integer
          format: int32
          description: |-
            OCR Engine Mode (0-3).

            - 0: Legacy engine only
            - 1: Neural nets (LSTM) only (usually best)
            - 2: Legacy + LSTM
            - 3: Default (based on what's available)
          default: 3
        output_format:
          type: string
          description: Output format ("text" or "markdown")
          default: markdown
        preprocessing:
          $ref: '#/components/schemas/ImagePreprocessingConfig'
          description: |-
            Image preprocessing configuration.

            Controls how images are preprocessed before OCR. Can significantly
            improve quality for scanned documents or low-quality images.
        psm:
          type:
            - integer
            - "null"
          format: int32
          description: |-
            Page Segmentation Mode (1-13).

            PSM 0 is rejected: Tesseract's `PSM_OSD_ONLY` performs orientation and script
            detection with no character recognition, so it cannot satisfy a text-extraction
            request and previously yielded an empty document (GH#1586).

            `None` (the default) means the caller made no explicit choice: the extraction
            pipeline applies its own context-appropriate PSM (whole-image PSM 11, vertical-
            language PSM 5, layout-region PSM 6, or the sparse-text retry's PSM 3) exactly as
            it would with no `TesseractConfig` at all — see issue #1573. Setting any other
            field on this struct no longer changes that behaviour.

            A rendered PDF page (`force_ocr` / `force_ocr_pages` / scanned-page OCR) that is one
            full-page scan gets the same whole-image PSM as standalone image OCR of that raster
            (11, or 5 for a vertical language), so the scan needs no explicit `psm` (GH#1786). A
            page that is not a scan, such as forced OCR of a vector page, keeps the engine's
            automatic-layout PSM (3).

            Common explicit values:
            - 3: Fully automatic page segmentation (native engine default)
            - 6: Assume a single uniform block of text (WASM engine default — avoids
              layout-analysis hang)
            - 11: Sparse text with no particular order
          default: null
        table_column_threshold:
          type: integer
          format: int32
          description: Column threshold for table detection (pixels)
          default: 50
        table_min_confidence:
          type: number
          format: double
          description: Minimum confidence threshold for table detection (0.0-1.0)
          default: 0
        table_row_threshold_ratio:
          type: number
          format: double
          description: Row threshold ratio for table detection (0.0-1.0)
          default: 0.5
        tessedit_char_blacklist:
          type: string
          description: Blacklist of forbidden characters (empty = none forbidden)
          default: ""
        tessedit_char_whitelist:
          type: string
          description: Whitelist of allowed characters (empty = all allowed)
          default: ""
        tessedit_dont_blkrej_good_wds:
          type: boolean
          description: Don't reject good words during block-level processing
          default: true
        tessedit_dont_rowrej_good_wds:
          type: boolean
          description: Don't reject good words during row-level processing
          default: true
        tessedit_enable_dict_correction:
          type: boolean
          description: Enable dictionary correction
          default: true
        tessedit_use_primary_params_model:
          type: boolean
          description: Use primary language params model
          default: true
        textord_space_size_is_variable:
          type: boolean
          description: Variable-width space detection
          default: true
        thresholding_method:
          type: integer
          format: int32
          description: |-
            Tesseract image-binarization method (0-2): 0 = Otsu (default), 1 = LeptonicaOtsu,
            2 = Sauvola. Sent to Tesseract's integer `thresholding_method` engine variable.

            GH#1784: used to be a `bool` sent as `"true"`/`"false"`, silently ignored by
            Tesseract's integer parser. A config for the old field still deserializes: `false` ->
            `0` (Otsu, the prior no-op), `true` -> `1` (LeptonicaOtsu, the old "adaptive" doc).
          default: 0
        use_cache:
          type: boolean
          description: Enable OCR result caching
          default: true
      additionalProperties: false
    TextAnnotation:
      type: object
      description: |-
        Inline text annotation — byte-range based formatting and links.

        Annotations reference byte offsets into the node's text content,
        enabling precise identification of formatted regions.
      required:
        - start
        - end
        - kind
      properties:
        end:
          type: integer
          format: int32
          description: End byte offset in the node's text content (exclusive).
          minimum: 0
        kind:
          $ref: '#/components/schemas/AnnotationKind'
          description: Annotation type.
        start:
          type: integer
          format: int32
          description: Start byte offset in the node's text content (inclusive).
          minimum: 0
    TextDirection:
      type: string
      description: Text direction enumeration for HTML documents.
      enum:
        - ltr
        - rtl
        - auto
    TextMetadata:
      type: object
      description: |-
        Text/Markdown metadata.

        Extracted from plain text and Markdown files. Includes word counts and,
        for Markdown, structural elements like headers and links.
      required:
        - line_count
        - word_count
        - character_count
        - format_type
      properties:
        character_count:
          type: integer
          format: int32
          description: Number of characters
          minimum: 0
        code_blocks:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/MarkdownCodeBlock'
          description: Code blocks (for Markdown files).
        format_type:
          type: string
          enum:
            - text
        headers:
          type:
            - array
            - "null"
          items:
            type: string
          description: Markdown headers (headings text only, for Markdown files)
        line_count:
          type: integer
          format: int32
          description: Number of lines in the document
          minimum: 0
        links:
          type:
            - array
            - "null"
          items:
            $ref: '#/components/schemas/MarkdownLink'
          description: Markdown links (for Markdown files).
        word_count:
          type: integer
          format: int32
          description: Number of words
          minimum: 0
    TokenReductionConfig:
      type: object
      description: Token reduction configuration for LLM consumption.
      properties:
        mode:
          type:
            - string
            - "null"
          description: |-
            Reduction mode: "off", "light", "moderate", "aggressive", "maximum".
            Defaults to "off" when omitted.
        preserve_important_words:
          type:
            - boolean
            - "null"
          description: Preserve capitalized/technical terms. Defaults to true when omitted.
      additionalProperties: false
    Translation:
      type: object
      description: |-
        Translation of the extracted content.

        Holds the translated rendition of `ExtractedDocument::content` and (when
        `preserve_markup` was requested) the translated `formatted_content`. Chunks
        are translated in place inside `ExtractedDocument::chunks[*].content` rather
        than duplicated here.
      required:
        - target_lang
        - content
      properties:
        content:
          type: string
          description: Translated plain-text body. Matches the shape of `ExtractedDocument::content`.
        formatted_content:
          type:
            - string
            - "null"
          description: |-
            Translated markup body (Markdown / HTML / etc.) when `preserve_markup` was
            enabled on the config. `None` otherwise.
        source_lang:
          type:
            - string
            - "null"
          description: BCP-47 source language. `None` when the translation backend was asked to detect.
        target_lang:
          type: string
          description: BCP-47 language tag the translation was produced into (e.g. `"de"`, `"fr-CA"`).
    TunableKnobInfo:
      type: object
      description: One knob that auto-tune can search over, for capability discovery.
      required:
        - knob
        - description
      properties:
        candidates:
          type: array
          items: {}
          description: Candidate values offered by this deployment, preserving their JSON types and search order.
        description:
          type: string
          description: Human-readable description of what this knob controls.
        knob:
          type: string
          description: Wire name of the knob, matching `autotune_core::search::Knob`'s serialized name.
        unavailable_reason:
          type:
            - string
            - "null"
          description: Why this knob has no usable candidate values, when it is unavailable.
    TuningProfileDetail:
      type: object
      description: Full detail of a promoted tuning profile.
      required:
        - id
        - name
        - profile
        - scores
        - created_at
      properties:
        created_at:
          type: string
          format: date-time
          description: ISO 8601 creation timestamp.
        id:
          type: string
          description: Unique tuning profile ID (ULID).
        name:
          type: string
          description: Tuning profile name.
        profile:
          type: object
          description: |-
            The tuning profile artifact. The concrete `TuningProfile` shape is
            defined in `autotune-core`; here it is opaque JSON.
        scores:
          type: object
          description: |-
            Scores recorded for this profile at promotion time. The concrete
            shape is defined in `autotune-core`; here it is opaque JSON.
    TuningProfileSummary:
      type: object
      description: Lightweight projection of a tuning profile (for listings).
      required:
        - id
        - name
        - created_at
      properties:
        created_at:
          type: string
          format: date-time
          description: ISO 8601 creation timestamp.
        id:
          type: string
          description: Unique tuning profile ID (ULID).
        name:
          type: string
          description: Tuning profile name.
    UpdateSavedPresetRequest:
      type: object
      description: Request to update a saved preset.
      properties:
        context_template:
          type:
            - string
            - "null"
          description: Optional context template.
        description:
          type:
            - string
            - "null"
          description: Optional description of the preset.
        emit_citations:
          type: boolean
          description: |-
            Whether to emit citations in results.

            Stored and returned verbatim, but **not applied at extraction time** —
            see `preferred_call_mode`. Results produced from a saved preset carry no
            citations regardless of this value.
        name:
          type: string
          description: |-
            Preset name. Trimmed before storage; a name that is entirely
            whitespace is rejected once trimmed.
          maxLength: 255
          minLength: 1
          pattern: \S
        preferred_call_mode:
          type: string
          description: |-
            Preferred orchestration path (`text_only`, `vision_only`, or
            `text_plus_vision`).

            Validated on write, stored, and returned verbatim, but
            **not applied at extraction time**. Call-mode selection belongs to the
            vision pipeline, which a job reaches only through the curated preset
            registry (`options.preset`); a saved preset never routes there. On Xberg
            Pro a saved preset contributes its `schema`, `system_prompt` and
            `context_template`, and nothing else.
          pattern: ^(text_only|vision_only|text_plus_vision)$
        schema:
          type: object
          description: JSON Schema (Draft 2020-12) for the extraction result.
        system_prompt:
          type:
            - string
            - "null"
          description: Optional custom system prompt override.
      additionalProperties: false
    UpdateSavedPresetResponse:
      type: object
      description: Response from updating a saved preset.
      required:
        - success
        - message
      properties:
        message:
          type: string
          description: Human-readable confirmation message.
        success:
          type: boolean
          description: Always `true` on success (the endpoint returns an error status otherwise).
    UriKind:
      type: string
      description: Semantic classification of an extracted URI.
      enum:
        - hyperlink
        - image
        - anchor
        - citation
        - reference
        - email
    UrlInput:
      type: object
      description: A seed URL for crawl-based extraction.
      required:
        - url
      properties:
        crawl_config:
          type: object
          description: |-
            Per-URL crawl configuration override. Its non-null keys are merged
            over the request-level `crawl_config`, one level deep, and the result
            is the configuration this URL is crawled under. Kept as opaque JSON:
            the concrete crawl configuration schema is deployment-specific.
        url:
          type: string
          description: URL to crawl and extract documents from.
      additionalProperties: false
    UsageByMimeType:
      type: object
      description: Usage statistics broken down by MIME type.
      required:
        - documents
        - pages
        - failed
      properties:
        documents:
          type: integer
          format: int32
          description: Number of documents of this MIME type processed
        failed:
          type: integer
          format: int32
          description: Number of failed extractions for this MIME type
        pages:
          type: integer
          format: int64
          description: Total pages extracted from documents of this MIME type
    UsageResponse:
      type: object
      description: Current period usage and quota information.
      required:
        - period_start
        - period_end
        - total_pages
        - total_documents
        - total_failed
        - by_mime_type
      properties:
        by_mime_type:
          type: object
          description: Usage breakdown by MIME type
          additionalProperties:
            $ref: '#/components/schemas/UsageByMimeType'
          propertyNames:
            type: string
        period_end:
          type: string
          description: |-
            Inclusive final UTC date of a nonempty requested reporting window (ISO 8601).
            Zero-width requested windows retain the start date, so equal response dates
            can describe either one included UTC date or an empty requested window.
        period_start:
          type: string
          description: Start of the reporting period (ISO 8601 date)
        quota_limit:
          type:
            - integer
            - "null"
          format: int64
          description: Monthly page quota limit (null = unlimited)
        quota_remaining:
          type:
            - integer
            - "null"
          format: int64
          description: Remaining pages in monthly quota (null = unlimited)
        total_documents:
          type: integer
          format: int32
          description: Total documents processed in the period
        total_failed:
          type: integer
          format: int32
          description: Total failed extractions in the period
        total_pages:
          type: integer
          format: int64
          description: Total pages extracted in the period
    UserInfo:
      type: object
      description: User information in the login response.
      required:
        - user_id
      properties:
        default_project_id:
          type:
            - string
            - "null"
          format: uuid
          description: The user's active project id.
          example: 550e8400-e29b-41d4-a716-446655440000
        email:
          type:
            - string
            - "null"
          description: User email address.
          example: user@example.com
        name:
          type:
            - string
            - "null"
          description: User display name.
          example: Jane Doe
        user_id:
          type: string
          description: User id from the identity provider (OIDC `sub`).
          example: user-id-abc123
    VectorCandidateReport:
      type: object
      description: Candidate availability evidence, not approximate nearest-neighbour accuracy or scan-limit cause.
      required:
        - budget
        - returned
        - unit
        - status
      properties:
        budget:
          type: integer
          format: int32
          description: Candidate budget, including any pre-rerank inflation.
          minimum: 0
        returned:
          type: integer
          format: int32
          description: Units the vector leg returned, before fusion or reranking.
          minimum: 0
        status:
          $ref: '#/components/schemas/VectorCandidateStatus'
          description: Evidence established in the retrieval snapshot.
        unit:
          $ref: '#/components/schemas/VectorCandidateUnit'
          description: Unit counted by the candidate budget.
    VectorCandidateStatus:
      type: string
      description: Candidate-set completeness; does not identify why an approximate scan stopped.
      enum:
        - budget_filled
        - eligible_set_exhausted
        - eligible_matches_omitted
        - unknown
    VectorCandidateUnit:
      type: string
      description: Candidate budget unit.
      enum:
        - chunks
        - documents
    VlmFallbackPolicy:
      oneOf:
        - type: object
          description: No VLM fallback (default). Uses single-backend mode only.
          required:
            - mode
          properties:
            mode:
              type: string
              enum:
                - disabled
        - type: object
          description: Try classical OCR first, then fall back to VLM below this quality threshold.
          required:
            - quality_threshold
            - mode
          properties:
            mode:
              type: string
              enum:
                - on_low_quality
            quality_threshold:
              type: number
              format: double
        - type: object
          description: Use VLM instead of classical OCR on pages selected for OCR.
          required:
            - mode
          properties:
            mode:
              type: string
              enum:
                - always
      description: |-
        VLM fallback policy for OCR operations.

        Determines whether and when to use the deployment's VLM model for OCR.
        Enterprise accepts active policies only when the operator configures a model;
        an explicit OCR pipeline takes precedence and ignores this policy.
    WebhookConfig:
      type: object
      description: Webhook configuration for async delivery of job results.
      required:
        - url
      properties:
        metadata:
          type:
            - object
            - "null"
          description: |-
            Optional key-value metadata included in the webhook payload. At most
            100 entries, keys 1-255 characters, values up to 1024 characters, and
            the whole map at most 8192 bytes JSON-encoded. Both tiers refuse a
            larger map with a 400.
          additionalProperties:
            type: string
          propertyNames:
            type: string
        secret:
          type:
            - string
            - "null"
          description: Optional HMAC-SHA256 secret for signing the webhook payload.
        url:
          type: string
          format: uri
          description: URL to deliver results to.
          minLength: 1
      additionalProperties: false
    WebhookDelivery:
      type: object
      description: Delivery diagnostics for a job's inline webhook.
      required:
        - status
        - attempt_count
      properties:
        attempt_count:
          type: integer
          format: int32
          description: Number of delivery attempts made so far.
          minimum: 0
        last_error:
          type:
            - string
            - "null"
          description: Bounded, sanitized diagnostic from the most recent failed attempt.
          maxLength: 2048
        status:
          $ref: '#/components/schemas/WebhookDeliveryStatus'
          description: Current delivery state.
    WebhookDeliveryStatus:
      type: string
      description: Current outcome of an inline webhook configured for a job.
      enum:
        - pending
        - delivered
        - failed
    WebhookEvent:
      type: object
      description: |-
        Webhook event payload delivered to customer endpoints when a job reaches
        a terminal status.

        This is the exact JSON body sent via HTTP POST to the webhook URL. When a
        secret is configured the request carries an `X-Webhook-Signature` header
        with the value `t=<unix seconds>,v1=<hex HMAC-SHA256>` over
        `"<t>.<body>"`; reject a delivery whose `t` is older than five minutes.

        `results` is present only on `job.completed`, and only when the extraction
        result fits in the body (100 KiB); otherwise fetch it from the job endpoint.
        `error` is present only on `job.failed`.
      required:
        - event
        - event_id
        - job_id
        - timestamp
      properties:
        error:
          $ref: '#/components/schemas/WebhookEventError'
          description: Error details, present only on `job.failed`.
        event:
          $ref: '#/components/schemas/WebhookEventType'
          description: Which terminal status the job reached.
        event_id:
          type: string
          format: uuid
          description: |-
            Stable identity of this event. Every delivery attempt of one event
            carries the same id, so a receiver deduplicates on it.
        job_id:
          type: string
          description: |-
            The job that reached the terminal status. UUID for an extraction job,
            ULID for a RAG ingest/reindex/migrate job.
        metadata:
          type:
            - object
            - "null"
          description: |-
            Metadata from the webhook configuration, echoed back for correlation.
            Absent when the configuration carried none. Sorted, so the signed body
            is byte-identical on every delivery attempt.
          additionalProperties:
            type: string
          propertyNames:
            type: string
        results:
          type: object
          description: |-
            The extraction result, present only on `job.completed` and only when it
            fits in the body. The shape is the `result` of `GET /v1/jobs/{id}`.
        timestamp:
          type: string
          format: date-time
          description: RFC 3339 time at which the job reached the terminal status.
    WebhookEventError:
      type: object
      description: Error information included in webhook failure events.
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: Error code naming the failure cause (for example `EXTRACTION_ERROR`).
        message:
          type: string
          description: Human-readable error message.
    WebhookEventType:
      type: string
      description: |-
        The terminal job events a webhook can carry. The serialized names appear
        in the body's `event` field and in subscription filters where supported.
      enum:
        - job.completed
        - job.failed
        - job.cancelled
    XmlMetadata:
      type: object
      description: |-
        XML metadata extracted during XML parsing.

        Provides statistics about XML document structure.
      required:
        - element_count
        - unique_elements
        - format_type
      properties:
        element_count:
          type: integer
          format: int32
          description: Total number of XML elements processed
          minimum: 0
        format_type:
          type: string
          enum:
            - xml
        unique_elements:
          type: array
          items:
            type: string
          description: List of unique element tag names (sorted)
    YakeParams:
      type: object
      description: YAKE-specific parameters.
      required:
        - window_size
      properties:
        window_size:
          type: integer
          description: |-
            Window size for co-occurrence analysis (default: 2).

            Controls the context window for computing co-occurrence statistics.
          minimum: 0
      additionalProperties: false
    YearRange:
      type: object
      description: Year range for bibliographic metadata.
      properties:
        max:
          type:
            - integer
            - "null"
          format: int32
          description: Latest (maximum) year in the range.
          minimum: 0
        min:
          type:
            - integer
            - "null"
          format: int32
          description: Earliest (minimum) year in the range.
          minimum: 0
        years:
          type: array
          items:
            type: integer
            format: int32
            minimum: 0
          description: All individual years present in the collection.
  securitySchemes:
    bearer_auth:
      type: http
      scheme: bearer
security:
  - bearer_auth: []
tags:
  - name: extract
    description: Document extraction endpoints
  - name: jobs
    description: Job status and result endpoints
  - name: audit
    description: Customer-facing audit log endpoint
  - name: auth
    description: OIDC/Google human login endpoints
  - name: projects
    description: Project management endpoints
  - name: integrations
    description: Project integration endpoints
  - name: presets
    description: Public curated preset registry
  - name: saved-presets
    description: User-created extraction schema preset CRUD
  - name: health
    description: Health check endpoint
  - name: license
    description: License status and detail endpoints
  - name: usage
    description: Usage and quota reporting
  - name: uploads
    description: Presigned direct-to-storage upload endpoints
  - name: enrich
    description: Text enrichment endpoints
  - name: rag
    description: RAG ingest, document lifecycle, and collection migration endpoints
  - name: auto-tune
    description: Auto-tune job submission, polling, and tuning-profile lifecycle endpoints
