> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nvisy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Redact a detection

> Applies the pipeline's policies to the detection's stored analysis — with any reviewer `edits` layered on first (suppress a false positive, retag a detection, or add one the analysis missed) — and produces a new redaction: a redacted document plus a review audit recording what was redacted. A detection can be redacted more than once. An edit targeting a detection not in the analysis, or a set that contradicts itself, is rejected (400).



## OpenAPI

````yaml /api-reference/openapi.json post /workspaces/{workspaceId}/detections/{detectionId}/redactions
openapi: 3.1.0
info:
  title: Nvisy API
  summary: Document detection and redaction platform
  description: >-
    Nvisy provides intelligent detection of sensitive content in documents,
    redaction pipelines, and reviewer workflows. This API enables document
    upload, detection analysis, redaction, and collaborative review across your
    workspaces.
  termsOfService: https://nvisy.com/legal/terms-of-service
  contact:
    name: Nvisy Support
    url: https://nvisy.com
    email: hello@nvisy.com
  license:
    name: Apache-2.0
    url: https://www.apache.org/licenses/LICENSE-2.0
  version: 0.1.0
servers: []
security: []
tags:
  - name: Health
    description: Service health checks
  - name: Capabilities
    description: Read-only reference data describing this deployment
  - name: Accounts
    description: Account management and profile operations
  - name: Identities
    description: 'Account sign-in methods: password and linked providers'
  - name: Avatars
    description: Public avatar image serving
  - name: Authentication
    description: Login, signup, and token management
  - name: Workspaces
    description: Workspace creation and management
  - name: Activities
    description: Workspace activity log and export
  - name: Documents
    description: Document upload, download, and management
  - name: Analytics
    description: Aggregate metrics over a workspace's documents
  - name: Members
    description: Workspace member management
  - name: Invites
    description: Workspace invitation handling
  - name: API Tokens
    description: API token management
  - name: Connections
    description: External provider connections
  - name: Connection Syncs
    description: Connection synchronization runs
  - name: Providers
    description: Inference-provider configuration
  - name: Pipelines
    description: Redaction pipeline configuration
  - name: Detections
    description: Detection analysis and redaction
  - name: Redactions
    description: Redactions produced from a detection, with reviewer edits
  - name: Reviews
    description: Document reviews, the review queue, and their discussion
  - name: Policies
    description: Redaction policy configuration
  - name: Webhooks
    description: Webhook configuration
  - name: Notifications
    description: Account notification management
paths:
  /workspaces/{workspaceId}/detections/{detectionId}/redactions:
    post:
      tags:
        - Detections
      summary: Redact a detection
      description: >-
        Applies the pipeline's policies to the detection's stored analysis —
        with any reviewer `edits` layered on first (suppress a false positive,
        retag a detection, or add one the analysis missed) — and produces a new
        redaction: a redacted document plus a review audit recording what was
        redacted. A detection can be redacted more than once. An edit targeting
        a detection not in the analysis, or a set that contradicts itself, is
        rejected (400).
      parameters:
        - in: path
          name: workspaceId
          description: Workspace identifier.
          required: true
          schema:
            description: Workspace identifier.
            type: string
            format: uuid
          style: simple
        - in: path
          name: detectionId
          description: Opaque identifier of the detection.
          required: true
          schema:
            $ref: '#/components/schemas/DetectionId'
            description: Opaque identifier of the detection.
          style: simple
      requestBody:
        description: >-
          Request payload to redact a detection.


          The reviewer's edits layer over the detection's analysis before
          redaction:

          suppress a false positive, retag a detection, or add one the analysis
          missed.

          Omit `edits` to redact with the policy decisions exactly as detected.
          Each

          redact request produces a new redaction.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RedactWorkspaceDetection'
        required: true
      responses:
        '201':
          description: >-
            Response type for a redaction.


            A redaction is one redact pass over a detection, produced with a
            specific set

            of reviewer edits. It owns the redacted output document
            (downloadable through

            the normal file endpoints) and a review audit recording what was
            redacted and

            why (fetched from the redaction's `review` endpoint).


            Named `WorkspaceRedactionResult` rather than `Redaction` because the
            engine's audit

            schema already carries a `Redaction` (an audit event), and the two
            must not

            collide in the generated `OpenAPI`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceRedactionResult'
        '400':
          description: >-
            The serialized shape of an HTTP error: the inert wire/OpenAPI-schema
            view

            that [`Error`] renders to at the response boundary.


            It carries no builder logic — [`Error`] is the type handlers
            construct and

            thread through `Result`, and it builds an `ErrorResponse` directly
            in its

            `IntoResponse` impl. `context` and `status` are not part of the JSON
            body

            (`context` is logged, `status` sets the HTTP status line).


            [`Error`]: crate::response::Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: >-
            The serialized shape of an HTTP error: the inert wire/OpenAPI-schema
            view

            that [`Error`] renders to at the response boundary.


            It carries no builder logic — [`Error`] is the type handlers
            construct and

            thread through `Result`, and it builds an `ErrorResponse` directly
            in its

            `IntoResponse` impl. `context` and `status` are not part of the JSON
            body

            (`context` is logged, `status` sets the HTTP status line).


            [`Error`]: crate::response::Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: >-
            The serialized shape of an HTTP error: the inert wire/OpenAPI-schema
            view

            that [`Error`] renders to at the response boundary.


            It carries no builder logic — [`Error`] is the type handlers
            construct and

            thread through `Result`, and it builds an `ErrorResponse` directly
            in its

            `IntoResponse` impl. `context` and `status` are not part of the JSON
            body

            (`context` is logged, `status` sets the HTTP status line).


            [`Error`]: crate::response::Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: >-
            The serialized shape of an HTTP error: the inert wire/OpenAPI-schema
            view

            that [`Error`] renders to at the response boundary.


            It carries no builder logic — [`Error`] is the type handlers
            construct and

            thread through `Result`, and it builds an `ErrorResponse` directly
            in its

            `IntoResponse` impl. `context` and `status` are not part of the JSON
            body

            (`context` is logged, `status` sets the HTTP status line).


            [`Error`]: crate::response::Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: >-
            The serialized shape of an HTTP error: the inert wire/OpenAPI-schema
            view

            that [`Error`] renders to at the response boundary.


            It carries no builder logic — [`Error`] is the type handlers
            construct and

            thread through `Result`, and it builds an `ErrorResponse` directly
            in its

            `IntoResponse` impl. `context` and `status` are not part of the JSON
            body

            (`context` is logged, `status` sets the HTTP status line).


            [`Error`]: crate::response::Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '415':
          description: 'Expected request with `Content-Type: application/json`'
          content:
            text/plain:
              schema:
                type: string
        '422':
          description: Failed to deserialize the JSON body into the target type
          content:
            text/plain:
              schema:
                type: string
      security:
        - BearerAuth: []
components:
  schemas:
    DetectionId:
      description: Opaque detection identifier (detection_<uuid>).
      type: string
      pattern: >-
        ^detection_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$
    RedactWorkspaceDetection:
      description: >-
        Request payload to redact a detection.


        The reviewer's edits layer over the detection's analysis before
        redaction:

        suppress a false positive, retag a detection, or add one the analysis
        missed.

        Omit `edits` to redact with the policy decisions exactly as detected.
        Each

        redact request produces a new redaction.
      type: object
      properties:
        edits:
          $ref: '#/components/schemas/EditSet'
          description: >-
            Reviewer edits to apply before redaction, grouped by modality. Omit
            to

            redact with the policy decisions exactly as detected.
    WorkspaceRedactionResult:
      description: >-
        Response type for a redaction.


        A redaction is one redact pass over a detection, produced with a
        specific set

        of reviewer edits. It owns the redacted output document (downloadable
        through

        the normal file endpoints) and a review audit recording what was
        redacted and

        why (fetched from the redaction's `review` endpoint).


        Named `WorkspaceRedactionResult` rather than `Redaction` because the
        engine's audit

        schema already carries a `Redaction` (an audit event), and the two must
        not

        collide in the generated `OpenAPI`.
      type: object
      properties:
        createdAt:
          description: When the redaction was created.
          type: string
          format: date-time
        detectionId:
          description: The detection this redaction was produced from.
          allOf:
            - $ref: '#/components/schemas/DetectionId'
        id:
          description: Opaque identifier of the redaction.
          allOf:
            - $ref: '#/components/schemas/RedactionId'
        outputDocumentId:
          description: >-
            Redacted output document this redaction produced. `None` only if the
            file

            was removed (e.g. by retention).
          type: string
          format: uuid
        requestedBy:
          description: Account that requested the redaction.
          allOf:
            - $ref: '#/components/schemas/AccountRef'
        workspaceHandle:
          description: URL-safe workspace handle. Display-only.
          allOf:
            - $ref: '#/components/schemas/Handle'
        workspaceId:
          description: Unique identifier of the workspace.
          type: string
          format: uuid
      required:
        - id
        - detectionId
        - workspaceId
        - workspaceHandle
        - requestedBy
        - createdAt
    ErrorResponse:
      description: >-
        The serialized shape of an HTTP error: the inert wire/OpenAPI-schema
        view

        that [`Error`] renders to at the response boundary.


        It carries no builder logic — [`Error`] is the type handlers construct
        and

        thread through `Result`, and it builds an `ErrorResponse` directly in
        its

        `IntoResponse` impl. `context` and `status` are not part of the JSON
        body

        (`context` is logged, `status` sets the HTTP status line).


        [`Error`]: crate::response::Error
      type: object
      properties:
        message:
          description: User-friendly error message safe for client display.
          type: string
        name:
          description: The error name/type identifier.
          type: string
      required:
        - name
        - message
    EditSet:
      description: |-
        Reviewer edits for one document, one list per modality.

        Empty by default: an audit nobody has reviewed carries none.
      type: object
      properties:
        audio:
          description: Edits to audio entities.
          type: array
          items:
            $ref: '#/components/schemas/AudioEdit'
        image:
          description: Edits to image entities.
          type: array
          items:
            $ref: '#/components/schemas/ImageEdit'
        tabular:
          description: Edits to tabular entities.
          type: array
          items:
            $ref: '#/components/schemas/TabularEdit'
        text:
          description: Edits to text entities.
          type: array
          items:
            $ref: '#/components/schemas/TextEdit'
    RedactionId:
      description: Opaque redaction identifier (redaction_<uuid>).
      type: string
      pattern: >-
        ^redaction_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$
    AccountRef:
      description: >-
        Public reference to the account behind a resource — whoever created it,

        uploaded it, triggered it, or performed it.


        Reused across resource responses so an account is always presented the
        same

        way: its immutable id (the durable reference) plus a handle and optional

        avatar for display.
      type: object
      properties:
        avatarUrl:
          description: Serve path of the account's avatar, when set.
          type: string
        displayName:
          description: Human-readable display name, when set.
          type: string
        id:
          description: Immutable id of the account.
          type: string
          format: uuid
        username:
          description: Handle of the account (display).
          allOf:
            - $ref: '#/components/schemas/Handle'
      required:
        - id
        - username
    Handle:
      description: >-
        Lowercase, dash-separated identifier used in URLs and as account
        handles.
      type: string
      maxLength: 32
      minLength: 3
      pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$
    AudioEdit:
      description: |-
        One change to an analyzed document.

        `Add` carries no id because the entity does not exist yet; the
        engine mints one when the edit is applied, so a client cannot
        collide with a real detection or shadow an existing entity.
      oneOf:
        - description: A detection recognition missed.
          type: object
          properties:
            op:
              type: string
              const: add
          allOf:
            - $ref: '#/components/schemas/AudioAdd'
          required:
            - op
        - description: A correction to what an existing detection is or covers.
          type: object
          properties:
            op:
              type: string
              const: retag
          allOf:
            - $ref: '#/components/schemas/AudioRetag'
          required:
            - op
        - description: A false positive, to be left alone.
          type: object
          properties:
            op:
              type: string
              const: suppress
          allOf:
            - $ref: '#/components/schemas/Suppress'
          required:
            - op
    ImageEdit:
      description: |-
        One change to an analyzed document.

        `Add` carries no id because the entity does not exist yet; the
        engine mints one when the edit is applied, so a client cannot
        collide with a real detection or shadow an existing entity.
      oneOf:
        - description: A detection recognition missed.
          type: object
          properties:
            op:
              type: string
              const: add
          allOf:
            - $ref: '#/components/schemas/ImageAdd'
          required:
            - op
        - description: A correction to what an existing detection is or covers.
          type: object
          properties:
            op:
              type: string
              const: retag
          allOf:
            - $ref: '#/components/schemas/ImageRetag'
          required:
            - op
        - description: A false positive, to be left alone.
          type: object
          properties:
            op:
              type: string
              const: suppress
          allOf:
            - $ref: '#/components/schemas/Suppress'
          required:
            - op
    TabularEdit:
      description: |-
        One change to an analyzed document.

        `Add` carries no id because the entity does not exist yet; the
        engine mints one when the edit is applied, so a client cannot
        collide with a real detection or shadow an existing entity.
      oneOf:
        - description: A detection recognition missed.
          type: object
          properties:
            op:
              type: string
              const: add
          allOf:
            - $ref: '#/components/schemas/TabularAdd'
          required:
            - op
        - description: A correction to what an existing detection is or covers.
          type: object
          properties:
            op:
              type: string
              const: retag
          allOf:
            - $ref: '#/components/schemas/TabularRetag'
          required:
            - op
        - description: A false positive, to be left alone.
          type: object
          properties:
            op:
              type: string
              const: suppress
          allOf:
            - $ref: '#/components/schemas/Suppress'
          required:
            - op
    TextEdit:
      description: |-
        One change to an analyzed document.

        `Add` carries no id because the entity does not exist yet; the
        engine mints one when the edit is applied, so a client cannot
        collide with a real detection or shadow an existing entity.
      oneOf:
        - description: A detection recognition missed.
          type: object
          properties:
            op:
              type: string
              const: add
          allOf:
            - $ref: '#/components/schemas/TextAdd'
          required:
            - op
        - description: A correction to what an existing detection is or covers.
          type: object
          properties:
            op:
              type: string
              const: retag
          allOf:
            - $ref: '#/components/schemas/TextRetag'
          required:
            - op
        - description: A false positive, to be left alone.
          type: object
          properties:
            op:
              type: string
              const: suppress
          allOf:
            - $ref: '#/components/schemas/Suppress'
          required:
            - op
    AudioAdd:
      description: |-
        A detection recognition missed.

        Recorded on the report with human provenance, so it is never
        mistaken for an automatic hit, then redacted under the policy set
        like any other entity: an added label the policy does not cover
        is left alone.

        Carries no entity id — the entity does not exist yet, and the
        engine mints one when the edit is applied, so a client cannot
        collide with a real detection or shadow an existing entity.
      type: object
      properties:
        actor:
          description: Who made it, when the caller said.
          type: string
        label:
          description: What the reviewer says this is.
          allOf:
            - $ref: '#/components/schemas/LabelRef'
        location:
          description: |-
            Where it sits, in the coordinates of the part it joins.

            Each medium addresses its own way: text a character range
            over the decoded stream, images a bounding box, audio a time
            span, tabular a row and column. A DOCX's
            `word/document.xml` text belongs to the document part
            itself, not to a nested one.

            Text carries either coordinate kind of [`TextCoord`]: a
            [`Decoded`] span for a caller with an offset into the
            decoded stream, or a [`Source`] one for a caller holding
            only raw file bytes — a reviewer selecting rendered text,
            say — which the engine reverse-resolves. Source-only is its
            own kind rather than a decoded span left empty, so such a
            selection is representable as itself.

            The other three media have no such alternative: their
            coordinates are the only way in.

            [`TextCoord`]: elide::modality::text::TextCoord
            [`Decoded`]: elide::modality::text::TextCoord::Decoded
            [`Source`]: elide::modality::text::TextCoord::Source
          allOf:
            - $ref: '#/components/schemas/AudioLocation'
        part:
          description: |-
            The part this belongs to, as a path: `["report.docx"]` for
            the document itself, `["report.docx", "word/media/image1.png"]`
            for media it embeds. `None` means the request's sole
            document, which is the common case and saves a caller
            naming what it already sent.

            Nested media needs this: the report holds an embedded image
            as its own part, and an addition to one has nowhere to go
            without naming it. Text usually does not — a source
            reference already names the part it came from, alongside the
            raw range.

            `None` is an error when the request carried several
            documents, since there is then no sole document to mean.
          type: array
          items:
            type: string
        reason:
          description: The rationale, when one was given.
          type: string
      required:
        - label
        - location
    AudioRetag:
      description: |-
        Correct what an existing detection *is* or *covers*, then redact
        it under the policy set as corrected.

        A reviewer fixing recognition's mistake rather than overriding
        its consequence. Retagging into a label the policy does not cover
        leaves the entity alone, exactly as if it had been detected that
        way — which is why it is auditable rather than a mutable field.
      type: object
      properties:
        actor:
          description: Who made it, when the caller said.
          type: string
        id:
          description: The entity being corrected.
          type: string
          format: uuid
        label:
          $ref: '#/components/schemas/LabelRef'
          description: The corrected label, when recognition got it wrong.
        location:
          $ref: '#/components/schemas/AudioLocation'
          description: |-
            The corrected location, when the span clipped the value or
            ran past it.
        reason:
          description: The rationale, when one was given.
          type: string
      required:
        - id
    Suppress:
      description: |-
        Leave this entity alone: a reviewer calling it a false positive.

        It keeps its place and its provenance; the redaction pass skips
        it. Recorded as a `Manual` event, so *who* decided and *why* is
        auditable rather than the detection silently disappearing.
      type: object
      properties:
        actor:
          description: Who made it, when the caller said.
          type: string
        id:
          description: The entity to leave alone.
          type: string
          format: uuid
        reason:
          description: The rationale, when one was given.
          type: string
      required:
        - id
    ImageAdd:
      description: |-
        A detection recognition missed.

        Recorded on the report with human provenance, so it is never
        mistaken for an automatic hit, then redacted under the policy set
        like any other entity: an added label the policy does not cover
        is left alone.

        Carries no entity id — the entity does not exist yet, and the
        engine mints one when the edit is applied, so a client cannot
        collide with a real detection or shadow an existing entity.
      type: object
      properties:
        actor:
          description: Who made it, when the caller said.
          type: string
        label:
          description: What the reviewer says this is.
          allOf:
            - $ref: '#/components/schemas/LabelRef'
        location:
          description: |-
            Where it sits, in the coordinates of the part it joins.

            Each medium addresses its own way: text a character range
            over the decoded stream, images a bounding box, audio a time
            span, tabular a row and column. A DOCX's
            `word/document.xml` text belongs to the document part
            itself, not to a nested one.

            Text carries either coordinate kind of [`TextCoord`]: a
            [`Decoded`] span for a caller with an offset into the
            decoded stream, or a [`Source`] one for a caller holding
            only raw file bytes — a reviewer selecting rendered text,
            say — which the engine reverse-resolves. Source-only is its
            own kind rather than a decoded span left empty, so such a
            selection is representable as itself.

            The other three media have no such alternative: their
            coordinates are the only way in.

            [`TextCoord`]: elide::modality::text::TextCoord
            [`Decoded`]: elide::modality::text::TextCoord::Decoded
            [`Source`]: elide::modality::text::TextCoord::Source
          allOf:
            - $ref: '#/components/schemas/ImageLocation'
        part:
          description: |-
            The part this belongs to, as a path: `["report.docx"]` for
            the document itself, `["report.docx", "word/media/image1.png"]`
            for media it embeds. `None` means the request's sole
            document, which is the common case and saves a caller
            naming what it already sent.

            Nested media needs this: the report holds an embedded image
            as its own part, and an addition to one has nowhere to go
            without naming it. Text usually does not — a source
            reference already names the part it came from, alongside the
            raw range.

            `None` is an error when the request carried several
            documents, since there is then no sole document to mean.
          type: array
          items:
            type: string
        reason:
          description: The rationale, when one was given.
          type: string
      required:
        - label
        - location
    ImageRetag:
      description: |-
        Correct what an existing detection *is* or *covers*, then redact
        it under the policy set as corrected.

        A reviewer fixing recognition's mistake rather than overriding
        its consequence. Retagging into a label the policy does not cover
        leaves the entity alone, exactly as if it had been detected that
        way — which is why it is auditable rather than a mutable field.
      type: object
      properties:
        actor:
          description: Who made it, when the caller said.
          type: string
        id:
          description: The entity being corrected.
          type: string
          format: uuid
        label:
          $ref: '#/components/schemas/LabelRef'
          description: The corrected label, when recognition got it wrong.
        location:
          $ref: '#/components/schemas/ImageLocation'
          description: |-
            The corrected location, when the span clipped the value or
            ran past it.
        reason:
          description: The rationale, when one was given.
          type: string
      required:
        - id
    TabularAdd:
      description: |-
        A detection recognition missed.

        Recorded on the report with human provenance, so it is never
        mistaken for an automatic hit, then redacted under the policy set
        like any other entity: an added label the policy does not cover
        is left alone.

        Carries no entity id — the entity does not exist yet, and the
        engine mints one when the edit is applied, so a client cannot
        collide with a real detection or shadow an existing entity.
      type: object
      properties:
        actor:
          description: Who made it, when the caller said.
          type: string
        label:
          description: What the reviewer says this is.
          allOf:
            - $ref: '#/components/schemas/LabelRef'
        location:
          description: |-
            Where it sits, in the coordinates of the part it joins.

            Each medium addresses its own way: text a character range
            over the decoded stream, images a bounding box, audio a time
            span, tabular a row and column. A DOCX's
            `word/document.xml` text belongs to the document part
            itself, not to a nested one.

            Text carries either coordinate kind of [`TextCoord`]: a
            [`Decoded`] span for a caller with an offset into the
            decoded stream, or a [`Source`] one for a caller holding
            only raw file bytes — a reviewer selecting rendered text,
            say — which the engine reverse-resolves. Source-only is its
            own kind rather than a decoded span left empty, so such a
            selection is representable as itself.

            The other three media have no such alternative: their
            coordinates are the only way in.

            [`TextCoord`]: elide::modality::text::TextCoord
            [`Decoded`]: elide::modality::text::TextCoord::Decoded
            [`Source`]: elide::modality::text::TextCoord::Source
          allOf:
            - $ref: '#/components/schemas/TabularLocation'
        part:
          description: |-
            The part this belongs to, as a path: `["report.docx"]` for
            the document itself, `["report.docx", "word/media/image1.png"]`
            for media it embeds. `None` means the request's sole
            document, which is the common case and saves a caller
            naming what it already sent.

            Nested media needs this: the report holds an embedded image
            as its own part, and an addition to one has nowhere to go
            without naming it. Text usually does not — a source
            reference already names the part it came from, alongside the
            raw range.

            `None` is an error when the request carried several
            documents, since there is then no sole document to mean.
          type: array
          items:
            type: string
        reason:
          description: The rationale, when one was given.
          type: string
      required:
        - label
        - location
    TabularRetag:
      description: |-
        Correct what an existing detection *is* or *covers*, then redact
        it under the policy set as corrected.

        A reviewer fixing recognition's mistake rather than overriding
        its consequence. Retagging into a label the policy does not cover
        leaves the entity alone, exactly as if it had been detected that
        way — which is why it is auditable rather than a mutable field.
      type: object
      properties:
        actor:
          description: Who made it, when the caller said.
          type: string
        id:
          description: The entity being corrected.
          type: string
          format: uuid
        label:
          $ref: '#/components/schemas/LabelRef'
          description: The corrected label, when recognition got it wrong.
        location:
          $ref: '#/components/schemas/TabularLocation'
          description: |-
            The corrected location, when the span clipped the value or
            ran past it.
        reason:
          description: The rationale, when one was given.
          type: string
      required:
        - id
    TextAdd:
      description: |-
        A detection recognition missed.

        Recorded on the report with human provenance, so it is never
        mistaken for an automatic hit, then redacted under the policy set
        like any other entity: an added label the policy does not cover
        is left alone.

        Carries no entity id — the entity does not exist yet, and the
        engine mints one when the edit is applied, so a client cannot
        collide with a real detection or shadow an existing entity.
      type: object
      properties:
        actor:
          description: Who made it, when the caller said.
          type: string
        label:
          description: What the reviewer says this is.
          allOf:
            - $ref: '#/components/schemas/LabelRef'
        location:
          description: |-
            Where it sits, in the coordinates of the part it joins.

            Each medium addresses its own way: text a character range
            over the decoded stream, images a bounding box, audio a time
            span, tabular a row and column. A DOCX's
            `word/document.xml` text belongs to the document part
            itself, not to a nested one.

            Text carries either coordinate kind of [`TextCoord`]: a
            [`Decoded`] span for a caller with an offset into the
            decoded stream, or a [`Source`] one for a caller holding
            only raw file bytes — a reviewer selecting rendered text,
            say — which the engine reverse-resolves. Source-only is its
            own kind rather than a decoded span left empty, so such a
            selection is representable as itself.

            The other three media have no such alternative: their
            coordinates are the only way in.

            [`TextCoord`]: elide::modality::text::TextCoord
            [`Decoded`]: elide::modality::text::TextCoord::Decoded
            [`Source`]: elide::modality::text::TextCoord::Source
          allOf:
            - $ref: '#/components/schemas/TextLocation'
        part:
          description: |-
            The part this belongs to, as a path: `["report.docx"]` for
            the document itself, `["report.docx", "word/media/image1.png"]`
            for media it embeds. `None` means the request's sole
            document, which is the common case and saves a caller
            naming what it already sent.

            Nested media needs this: the report holds an embedded image
            as its own part, and an addition to one has nowhere to go
            without naming it. Text usually does not — a source
            reference already names the part it came from, alongside the
            raw range.

            `None` is an error when the request carried several
            documents, since there is then no sole document to mean.
          type: array
          items:
            type: string
        reason:
          description: The rationale, when one was given.
          type: string
      required:
        - label
        - location
    TextRetag:
      description: |-
        Correct what an existing detection *is* or *covers*, then redact
        it under the policy set as corrected.

        A reviewer fixing recognition's mistake rather than overriding
        its consequence. Retagging into a label the policy does not cover
        leaves the entity alone, exactly as if it had been detected that
        way — which is why it is auditable rather than a mutable field.
      type: object
      properties:
        actor:
          description: Who made it, when the caller said.
          type: string
        id:
          description: The entity being corrected.
          type: string
          format: uuid
        label:
          $ref: '#/components/schemas/LabelRef'
          description: The corrected label, when recognition got it wrong.
        location:
          $ref: '#/components/schemas/TextLocation'
          description: |-
            The corrected location, when the span clipped the value or
            ran past it.
        reason:
          description: The rationale, when one was given.
          type: string
      required:
        - id
    LabelRef:
      description: |-
        Lightweight reference to a [`Label`], carrying only its id.

        This is what detections and entities hold: cloning is cheap (short
        ids inline into the [`HipStr`]), and the full [`Label`], with its
        localized names and descriptions, is resolved on demand from a
        [`LabelCatalog`].

        [`Label`]: crate::entity::Label
        [`LabelCatalog`]: crate::entity::LabelCatalog
      type: string
    AudioLocation:
      description: |-
        A [`TimeSpan`] within audio content, with an optional speaker label.

        The time span is the coordinate; ordering and overlap consider only it.
        The optional [`speaker_id`] is a diarization label,
        not a coordinate: two utterances from different speakers at the same
        instant still overlap in time, so the speaker is carried for provenance
        but excluded from comparison.

        [`speaker_id`]: Self::speaker_id
      type: object
      properties:
        span:
          description: Time span the location covers, in the stream's timeline.
          allOf:
            - $ref: '#/components/schemas/TimeSpan'
        speaker_id:
          description: Diarization label of the speaker, when a diarizer assigned one.
          type: string
      required:
        - span
    ImageLocation:
      description: |-
        Region within image content.

        An axis-aligned [`BoundingBox`] in pixel coordinates locates the
        region; an optional [`Polygon`] captures a rotated or quadrilateral
        shape when the source produced one (OCR engines that emit 4-point
        polygons), and an optional page number addresses multi-page documents.
      type: object
      properties:
        bounding_box:
          description: Axis-aligned bounding box of the region, in pixel coordinates.
          allOf:
            - $ref: '#/components/schemas/BoundingBox'
        page:
          description: 1-based page number, for multi-page documents like PDFs.
          type: integer
          format: uint32
          minimum: 0
        polygon:
          $ref: '#/components/schemas/Polygon'
          description: |-
            Polygon vertices when the region is rotated or quadrilateral.
            Axis-aligned-only sources leave this unset.
      required:
        - bounding_box
    TabularLocation:
      description: |-
        Cell-addressed location within tabular content.

        Identifies a cell by zero-based [`row_index`] and
        [`column_index`], optionally narrowed to a byte
        range within the cell text for an entity that spans only part of the
        cell. [`sheet_name`] scopes the cell to one sheet of
        a multi-sheet workbook; [`column_name`] is a
        human-readable header carried for provenance.

        Overlap and ordering treat the sheet, cell, and byte range as
        coordinates. The column name is redundant with the column index, so it
        is carried but excluded from comparison.

        [`row_index`]: Self::row_index
        [`column_index`]: Self::column_index
        [`sheet_name`]: Self::sheet_name
        [`column_name`]: Self::column_name
      type: object
      properties:
        column_index:
          description: Zero-based column index of the cell.
          type: integer
          format: uint32
          minimum: 0
        column_name:
          description: Header label of the column, when known.
          type: string
        end_offset:
          description: |-
            Byte offset within the cell text where the entity ends (exclusive).
            Unset means the whole cell.
          type: integer
          format: uint
          minimum: 0
        row_index:
          description: Zero-based row index of the cell.
          type: integer
          format: uint32
          minimum: 0
        sheet_name:
          description: Sheet name within a multi-sheet workbook, when known.
          type: string
        start_offset:
          description: |-
            Byte offset within the cell text where the entity starts. Unset
            means the whole cell.
          type: integer
          format: uint
          minimum: 0
      required:
        - row_index
        - column_index
    TextLocation:
      description: >-
        Where an entity sits in text: a [coordinate](TextCoord) (a decoded byte
        range,

        or a source-only reference) plus an optional page number.


        The coordinate is either a [`Decoded`](TextCoord::Decoded) byte range in
        the

        pipeline's text stream, or a [`Source`](TextCoord::Source)-only
        reference for

        content with no decoded range (a reviewer selecting rendered text). The
        page

        is orthogonal to the coordinate kind, so it sits alongside rather than
        inside.


        Ordering and overlap consider only the coordinate; the page is carried
        for

        codecs that page their text but does not affect comparison.
      type: object
      properties:
        coord:
          description: 'The coordinate: a decoded range, or a source-only reference.'
          allOf:
            - $ref: '#/components/schemas/TextCoord'
        page:
          description: 1-based page number, when known. Orthogonal to the coordinate kind.
          type: integer
          format: uint32
          minimum: 0
      required:
        - coord
    TimeSpan:
      description: |-
        Half-open `[start, end)` stream interval, measured in microseconds.

        The coordinate a time-addressed medium (audio, video) uses to locate a
        region: a transcribed segment, a redacted span. Microsecond precision
        is finer than both word-level speech timings and per-sample audio
        resolution, so a span never loses precision being carried as a
        `TimeSpan`; the endpoints are non-negative offsets by construction.

        Half-open like a byte range: `[start, end)`, so two intervals that
        merely touch (`a.end == b.start`) do not [`overlap`].

        [`from_millis`] and [`as_millis`] bridge the millisecond-based APIs
        that surround it (audio durations, provider timings reported in ms).

        [`overlap`]: Self::overlaps
        [`from_millis`]: Self::from_millis
        [`as_millis`]: Self::start_millis
      type: object
      properties:
        end_us:
          description: |-
            Microseconds from the start of the stream where the interval ends
            (exclusive).
          type: integer
          format: uint64
          minimum: 0
        start_us:
          description: Microseconds from the start of the stream where the interval begins.
          type: integer
          format: uint64
          minimum: 0
      required:
        - start_us
        - end_us
    BoundingBox:
      description: |-
        Axis-aligned rectangle, given by its minimum and maximum corners.

        The location type for the image and document modalities: where a
        detected entity sits within a rendered page. [`min`] is the top-left
        corner and [`max`] the bottom-right under the usual screen convention
        (y grows downward), though the box itself is agnostic to coordinate
        orientation.

        [`min`]: Self::min
        [`max`]: Self::max
      type: object
      properties:
        max:
          description: Maximum corner (bottom-right, conventionally).
          allOf:
            - $ref: '#/components/schemas/Point'
        min:
          description: Minimum corner (top-left, conventionally).
          allOf:
            - $ref: '#/components/schemas/Point'
      required:
        - min
        - max
    Polygon:
      description: |-
        Closed polygon, given by its ordered vertices.

        A richer location than a [`BoundingBox`] for detections whose extent
        is not rectangular: rotated text, a region traced by a vision model, a
        signature. The boundary is implicitly closed, so the last vertex
        connects back to the first.

        [`BoundingBox`]: super::BoundingBox
      type: array
      items:
        $ref: '#/components/schemas/Point'
    TextCoord:
      description: >-
        The coordinate of a [`TextLocation`]: a decoded byte range, or a
        source-only

        reference.


        The distinction is whether a decoded range exists at all. A recognizer
        over

        decoded text produces a [`Decoded`](Self::Decoded) span (with the raw
        source

        it decodes from, when the codec's decoded text differs from the source).
        A

        reviewer marking rendered text has no decoded range, only where the
        selection

        sits in the raw bytes, a [`Source`](Self::Source).
      oneOf:
        - description: >-
            A decoded byte range in the pipeline's text stream, with the raw

            [`SourceRef`]s it came from when the codec's decoded text differs
            from the

            source (empty for plain text / CSV, where decoded == source).
          type: object
          properties:
            kind:
              type: string
              const: decoded
          allOf:
            - $ref: '#/components/schemas/DecodedSpan'
          required:
            - kind
        - description: >-
            Source-only: no decoded range (a reviewer selecting rendered text),
            only

            where it sits in the raw bytes. Non-empty by construction.
          type: object
          properties:
            kind:
              type: string
              const: source
          allOf:
            - $ref: '#/components/schemas/SourceSpan'
          required:
            - kind
    Point:
      description: |-
        Point in a 2-D coordinate space.

        The coordinate basis is left to the consumer: pixel coordinates for a
        raster image, normalized `0.0..=1.0` coordinates for a
        resolution-independent region, or page units for a document. The
        model only requires the two scalars.
      type: object
      properties:
        x:
          description: Horizontal coordinate.
          type: number
          format: double
        'y':
          description: Vertical coordinate.
          type: number
          format: double
      required:
        - x
        - 'y'
    DecodedSpan:
      description: >-
        A decoded byte range and the raw source it decodes from.


        The `range` is a half-open `[start, end)` span in the decoded text
        stream.

        `source` carries the exact raw byte range(s) that span came from for
        codecs

        whose decoded text differs from the source (XML/HTML/DOCX entity
        decoding,

        JSON escapes); it is empty when the source equals the decoded text.
      type: object
      properties:
        range:
          description: Byte range within the (decoded) text content.
          allOf:
            - $ref: '#/components/schemas/Range_of_uint'
        source:
          description: >-
            The exact raw source ranges this decoded span came from. Empty when
            the

            source equals the decoded text.


            Usually one range; a reconciled span that fused several source runs
            (or a

            span crossing an escape) carries several, kept distinct rather than
            merged

            across gaps. Sorted, deduplicated.
          type: array
          items:
            $ref: '#/components/schemas/SourceRef'
      required:
        - range
    SourceSpan:
      description: >-
        A source-only coordinate: raw byte range(s) with no decoded span.


        The counterpart to [`DecodedSpan`] for content the pipeline never
        decoded (a

        reviewer selecting rendered text). A named struct rather than a bare
        `Vec`

        variant so the coordinate serializes as a map, which an internally
        tagged

        enum can inject its `kind` tag into.
      type: object
      properties:
        source:
          description: |-
            The raw byte range(s) the selection sits in. Sorted, deduplicated,
            non-empty by construction.
          type: array
          items:
            $ref: '#/components/schemas/SourceRef'
      required:
        - source
    Range_of_uint:
      type: object
      properties:
        end:
          type: integer
          format: uint
          minimum: 0
        start:
          type: integer
          format: uint
          minimum: 0
      required:
        - start
        - end
    SourceRef:
      description: >-
        A reference back to the original source: a byte range, and, for a
        container

        whose body spans several files, which part that range indexes.


        [`TextLocation`]'s `range` indexes the *decoded* text stream a codec
        hands

        the pipeline (entities resolved, container parts concatenated). A
        `SourceRef`

        is the *exact raw* byte range the decoded range came from, accounting
        for XML

        escapes where `&amp;` (5 raw bytes) decodes to `&` (1). `part` names the

        container file the range is in (`word/header1.xml`) for a multi-file
        body

        like DOCX, and is `None` for a single-file source (XML, HTML). It lets a

        consumer point back at the untouched source bytes.


        [`TextLocation`]: super::TextLocation
      type: object
      properties:
        part:
          description: >-
            The container part the range indexes, for a multi-file body; `None`
            for a

            single-file source.
          type: string
        range:
          description: The raw source byte range.
          allOf:
            - $ref: '#/components/schemas/Range_of_uint'
      required:
        - range

````