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

# Upload artifact bytes

> Session-token auth. Camera reservations must be bound to a fresh server-measured event for the same capture kind; their verified bytes are envelope-encrypted before S3. Document reservations must name a manifest document and use its allowlisted PDF/image type. The service verifies the byte size and SHA-256 digest, then scans the raw upload for malware before parsing it. Accepted images are decoded and re-encoded without metadata. Accepted PDFs are rasterized and rebuilt as image-only PDFs. Only this reconstructed derivative is envelope-encrypted and persisted; the raw document is never stored. No filename or public object URL is accepted or persisted.




## OpenAPI

````yaml /api-reference/idv-svc.json put /sessions/{sessionId}/artifacts/{artifactId}/object
openapi: 3.1.0
info:
  title: Minerva IDV API
  version: 1.0.0-draft
  description: >-
    Customer API for identity verification. Create verification sessions for
    profiles, deliver invites, track capture progress, and read review outcomes.
    For the end-to-end flow, see the [IDV Integration
    Guide](/api-reference/idv-integration-guide). Reviewer-safe responses never
    expose storage keys, checksums, wrapped keys, encryption context, raw OCR,
    or raw model output. Decrypted personal data is available only on `GET
    /sessions/{sessionId}/data`, to an authenticated in-scope principal, with
    fail-closed auditing.
servers:
  - url: https://api.gominerva.com/idv/v1
    description: The production API server for Minerva identity verification
security: []
tags:
  - name: sessions
    description: >-
      Verification sessions and invite delivery (application or dashboard
      authentication).
  - name: capture
    description: End-user capture flow (session or invite credentials).
  - name: review
    description: Review decisions and audited structured-data access.
  - name: workflows
    description: Workflow configuration for verification sessions.
  - name: themes
    description: Branding themes and managed logo assets.
paths:
  /sessions/{sessionId}/artifacts/{artifactId}/object:
    put:
      tags:
        - capture
      summary: Upload artifact bytes
      description: >
        Session-token auth. Camera reservations must be bound to a fresh
        server-measured event for the same capture kind; their verified bytes
        are envelope-encrypted before S3. Document reservations must name a
        manifest document and use its allowlisted PDF/image type. The service
        verifies the byte size and SHA-256 digest, then scans the raw upload for
        malware before parsing it. Accepted images are decoded and re-encoded
        without metadata. Accepted PDFs are rasterized and rebuilt as image-only
        PDFs. Only this reconstructed derivative is envelope-encrypted and
        persisted; the raw document is never stored. No filename or public
        object URL is accepted or persisted.
      parameters:
        - $ref: '#/components/parameters/SessionId'
        - $ref: '#/components/parameters/ArtifactId'
      requestBody:
        required: true
        content:
          image/jpeg:
            schema:
              type: string
              format: binary
          image/png:
            schema:
              type: string
              format: binary
          image/webp:
            schema:
              type: string
              format: binary
          application/pdf:
            schema:
              type: string
              format: binary
      responses:
        '200':
          description: Uploaded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          description: >
            The document was rejected by required safety processing, including
            malware detection, an unsupported or malformed source, or a source
            that cannot be safely reconstructed
            (`idv_document_safety_rejected`). No document bytes are persisted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          $ref: '#/components/responses/ServerError'
        '503':
          description: >
            Document safety processing is unavailable, busy, or using stale
            malware signatures (`idv_document_safety_unavailable`). No document
            bytes are persisted. The response includes `Retry-After: 5`.
          headers:
            Retry-After:
              description: Seconds to wait before retrying the upload.
              schema:
                type: integer
                example: 5
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - sessionToken: []
components:
  parameters:
    SessionId:
      name: sessionId
      in: path
      required: true
      schema:
        type: string
    ArtifactId:
      name: artifactId
      in: path
      required: true
      schema:
        type: string
  schemas:
    SessionResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/Session'
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              example: idv_session_not_found
            message:
              type: string
    Session:
      type: object
      description: Reviewer-safe session projection (management planes).
      required:
        - id
        - tenant_id
        - workspace_id
        - status
        - artifacts
        - capture_manifest
        - step_manifest
        - created_at
        - updated_at
        - delete_after
      properties:
        id:
          type: string
        tenant_id:
          type: string
        workspace_id:
          type: string
        creator_application_id:
          type: string
          description: >
            Application principal that created the session. Hosted application
            auth uses the authenticated application id; explicit local/static
            principals use their synthetic id. Never accepted in
            CreateSessionRequest, and omitted for legacy and dashboard-created
            rows. MANAGEMENT planes only.
        profile_id:
          type: string
          minLength: 1
          maxLength: 128
          pattern: ^[A-Za-z0-9._:-]+$
          description: >
            Minerva profile object id the session belongs to (REQUIRED at
            create). MANAGEMENT planes only: the consumer/token plane never sees
            it. TRUST BOUNDARY: the id is verified against the Minerva profiles
            service at create: a missing profile -> 400 `idv_profile_not_found`,
            a profile-service lookup failure -> 503
            `idv_profile_lookup_unavailable`; the id is stored, scoped, and
            indexed, and every profile-scoped query is always tenant+workspace
            bounded, so a wrong id can never leak across tenants.
        status:
          $ref: '#/components/schemas/SessionStatus'
        review_status:
          allOf:
            - $ref: '#/components/schemas/ReviewStatus'
          description: MANAGEMENT planes only.
        flag_categories:
          type: array
          description: >
            Profile-flag categories derived at assessment completion from the
            fraud-flag taxonomy + quality issues + verdicts; empty for clean
            automatic passes. MANAGEMENT planes only.
          items:
            type: string
            enum:
              - idv
              - document
              - questionnaire
        profile_update_enabled:
          type: boolean
          description: >-
            Automatic profile-write opt-in copied from the workflow at create.
            MANAGEMENT planes only.
        flag_settings:
          allOf:
            - $ref: '#/components/schemas/FlagSettings'
          description: >-
            Sparse per-code review overrides copied from the workflow at create.
            MANAGEMENT planes only.
        privacy_contact_email:
          oneOf:
            - type: string
              format: email
            - type: string
              maxLength: 0
          description: >-
            Consumer-facing privacy/deletion contact copied from the workflow at
            create. MANAGEMENT planes only.
        consent_evidence:
          allOf:
            - $ref: '#/components/schemas/ConsentEvidence'
          description: >-
            Explicit consumer consent evidence recorded before capture begins.
            MANAGEMENT planes only.
        review_history:
          type: array
          description: Append-only human-review trail. MANAGEMENT planes only.
          items:
            $ref: '#/components/schemas/ReviewEvent'
        assignee:
          type: string
          description: >-
            Current assignee user id. MANAGEMENT planes only; omitted when
            unassigned.
        submitted_at:
          type: string
          description: First successful submit; empty until then.
        reviewed_at:
          type: string
          description: Most recent human review decision; empty until one lands.
        legal_hold:
          type: boolean
        capture_manifest:
          $ref: '#/components/schemas/CaptureManifest'
        step_manifest:
          $ref: '#/components/schemas/StepManifest'
        workflow_ref:
          allOf:
            - $ref: '#/components/schemas/WorkflowRef'
          description: >
            Present only when the session was created from a workflow object,
            and only on MANAGEMENT-plane responses (never on token-plane
            projections). A create-time snapshot; workflow edits/deletes do not
            change it.
        theme_ref:
          allOf:
            - $ref: '#/components/schemas/ThemeRef'
          description: >
            Present only when a theme resolved at create (workflow theme_id or
            the active workspace default), and only on MANAGEMENT-plane
            responses. A create-time snapshot; theme edits/archives/deletes do
            not change it.
        artifacts:
          type: array
          items:
            $ref: '#/components/schemas/PublicArtifact'
        assessment:
          $ref: '#/components/schemas/AssessmentSummary'
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        delete_after:
          type: string
          format: date-time
          description: >
            Due timestamp of the PII component. Kept under its original name:
            every pre-split reader treats this as the sensitive-field purge
            date. Anchored at create; never empty.
        biometrics_delete_after:
          type: string
          format: date-time
          description: >
            Due timestamp of the biometrics component (liveness captures and the
            cross-session templates derived from them). The key is always
            present but the value is the empty string until the session's
            assessment anchors its retention horizons; read "" as "not yet set",
            not as a parseable timestamp.
        id_images_delete_after:
          type: string
          format: date-time
          description: >
            Due timestamp of the identity-document image component. The key is
            always present but the value is the empty string until the session's
            assessment anchors its retention horizons; read "" as "not yet set",
            not as a parseable timestamp.
        documents_delete_after:
          type: string
          format: date-time
          description: >
            Due timestamp of the supporting-document component. The key is
            always present but the value is the empty string until the session's
            assessment anchors its retention horizons; read "" as "not yet set",
            not as a parseable timestamp.
        biometrics_purged_at:
          type: string
          format: date-time
          description: >
            Set once the biometrics component completed. Distinct from
            sensitive_data_purged_at, which means specifically that the PII
            component completed. The stamp records only that the component's
            retention pass completed at that time; it does not imply the session
            ever held biometric artifacts; a component with no artifact rows is
            stamped on its schedule like any other. Consumers claiming data was
            deleted must consult the artifact rows (tombstoned rows keep
            uploaded_at), not the stamp. MANAGEMENT planes only; omitted (not
            null) until the component completes; absence means "not completed",
            never "held no data".
        id_images_purged_at:
          type: string
          format: date-time
          description: >
            Set once the identity-document image component completed. The stamp
            records only that the component's retention pass completed at that
            time; it does not imply the session ever held ID-image artifacts; a
            component with no artifact rows is stamped on its schedule like any
            other. Consumers claiming data was deleted must consult the artifact
            rows (tombstoned rows keep uploaded_at), not the stamp. MANAGEMENT
            planes only; omitted (not null) until the component completes;
            absence means "not completed", never "held no data".
        documents_purged_at:
          type: string
          format: date-time
          description: >
            Set once the supporting-document component completed. The stamp
            records only that the component's retention pass completed at that
            time; it does not imply the session ever held supporting-document
            artifacts; a component with no artifact rows is stamped on its
            schedule like any other. Consumers claiming data was deleted must
            consult the artifact rows (tombstoned rows keep uploaded_at), not
            the stamp. MANAGEMENT planes only; omitted (not null) until the
            component completes; absence means "not completed", never "held no
            data".
        sensitive_data_purged_at:
          type: string
          format: date-time
          description: >
            Set once the PII component's retention pass completed: encrypted PII
            fields cleared while audit-safe session status, determinations,
            rationales, and fraud flags are preserved. The stamp does not imply
            PII values ever existed, and under independent component schedules
            it does not by itself assert that artifact bytes are gone; the other
            components' artifacts are purged on their own schedules. MANAGEMENT
            planes only; omitted (not null) until the PII component completes;
            absence means "not completed", never "held no data".
    SessionStatus:
      type: string
      description: >
        FLOW axis only since the status split: assessment outcomes live on
        `review_status` (the gate's recommendation stays on the assessment
        block) and `assessed` replaces the legacy conflated
        approved/rejected/requires_review terminal statuses. Terminal flow
        states are assessed, failed, canceled, deleted.
      enum:
        - collecting_artifacts
        - ready_for_assessment
        - queued
        - assessing
        - assessed
        - failed
        - canceled
        - deleted
    ReviewStatus:
      type: string
      description: >
        Human-review axis. Service-derived until a human decides: create ->
        request_sent; successful submit -> pending; assessment terminal ->
        automatic_pass ONLY for a clean pass (gate approved, zero fraud flags,
        zero error/critical quality issues), EVERYTHING else fail-closed to
        requires_review. Human transitions (management planes): requires_review
        and automatic_pass admit escalation|accepted|rejected; escalation admits
        accepted|rejected; accepted/rejected are terminal (further decisions ->
        409 idv_review_terminal; other sources -> 409
        idv_review_invalid_transition).
      enum:
        - request_sent
        - pending
        - requires_review
        - automatic_pass
        - escalation
        - rejected
        - accepted
    FlagSettings:
      type: object
      description: >
        Sparse per-code review overrides. A code ABSENT from the map is ENABLED
        (default-true). Keys are validated against the known configurable
        fraud-flag taxonomy: an unknown key -> 400 idv_flag_code_unknown.
        Deterministic service-enforced policy codes such as id_type_not_allowed
        cannot be disabled and return idv_flag_code_not_configurable.
      additionalProperties:
        type: object
        properties:
          raise_for_review:
            type: boolean
    ConsentEvidence:
      type: object
      description: >-
        Server-recorded evidence of explicit consumer consent before IDV
        capture.
      additionalProperties: false
      required:
        - accepted
        - redacted
        - accepted_at
        - notice_version
        - notice_text
        - notice_digest
        - tenant_name
        - privacy_contact_email
      properties:
        accepted:
          type: boolean
          const: true
        redacted:
          type: boolean
          description: >
            True after erasure removes notice_text and its substitutions.
            notice_digest remains the validated receipt of the original text.
        accepted_at:
          type: string
          format: date-time
        notice_version:
          type: string
          minLength: 1
        notice_text:
          type: string
          description: >
            Exact notice text displayed for this consent version when redacted
            is false; empty after erasure when redacted is true.
        notice_digest:
          type: string
          pattern: ^sha256:[0-9a-f]{64}$
          description: >
            SHA-256 receipt of the current exact notice_text bytes when redacted
            is false. When redacted is true, it remains the receipt of the
            original pre-erasure notice text while notice_text is empty.
        tenant_name:
          type: string
        privacy_contact_email:
          type: string
          description: >
            Privacy contact email when redacted is false; empty after erasure
            when redacted is true.
      oneOf:
        - properties:
            redacted:
              const: false
            notice_text:
              type: string
              minLength: 1
            tenant_name:
              type: string
              minLength: 1
            privacy_contact_email:
              type: string
              minLength: 1
              format: email
        - properties:
            redacted:
              const: true
            notice_text:
              type: string
              maxLength: 0
            tenant_name:
              type: string
              maxLength: 0
            privacy_contact_email:
              type: string
              maxLength: 0
    ReviewEvent:
      type: object
      description: One append-only review or assignment history entry.
      required:
        - status_from
        - status_to
        - actor_kind
        - actor_id
        - note
        - at
      properties:
        status_from:
          type: string
        status_to:
          type: string
        actor_kind:
          type: string
          enum:
            - application
            - dashboard_user
            - service
        actor_id:
          type: string
          description: >
            Application principal id (actor user id when resolved) or the
            dashboard user id the dashboard authentication service attaches
            through the workspace-access account block. Empty when neither
            resolved; the assignment routes refuse a dashboard actor with no
            resolved id rather than writing an entry attributed to nobody.
        note:
          type: string
          maxLength: 1000
        at:
          type: string
          format: date-time
        kind:
          type: string
          description: >
            Empty for a human review decision. A profile-update event sets
            `profile_updates_applied` or `profile_updates_failed`; an assignment
            transition sets `assignment_changed`. Non-decision entries keep
            status_from == status_to.
          enum:
            - ''
            - profile_updates_applied
            - profile_updates_failed
            - assignment_changed
        profile_updates_fields:
          type: array
          description: Profile field names written or attempted on a profile-update event.
          items:
            type: string
        assignee_user_id_from:
          type: string
          description: Previous assignee on assignment_changed; empty means unassigned.
        assignee_user_id_to:
          type: string
          description: New assignee on assignment_changed; empty means unassigned.
    CaptureManifest:
      type: object
      required:
        - workflow
        - required_kinds
      properties:
        workflow:
          $ref: '#/components/schemas/WorkflowLabel'
        required_kinds:
          type: array
          description: >
            Derived from the liveness/id_capture steps: skipped optional capture
            steps contribute nothing, and a declared one-sided document type
            (passport, sides=auto) drops id_back.
          items:
            $ref: '#/components/schemas/CaptureKind'
    StepManifest:
      type: object
      description: The resolved, persisted step manifest (canonical order; config echoed).
      required:
        - steps
      properties:
        steps:
          type: array
          items:
            $ref: '#/components/schemas/FlowStep'
    WorkflowRef:
      type: object
      description: Create-time snapshot of the workflow a session was built from.
      required:
        - workflow_id
        - name
        - revision
      properties:
        workflow_id:
          type: string
        name:
          type: string
        revision:
          type: integer
          minimum: 1
    ThemeRef:
      type: object
      description: Create-time snapshot of the theme resolved for a session.
      required:
        - theme_id
        - name
        - revision
      properties:
        theme_id:
          type: string
        name:
          type: string
        revision:
          type: integer
          minimum: 1
    PublicArtifact:
      type: object
      description: >
        No bucket/key/checksum/encryption metadata. `reserved_at` and
        `uploaded_at` are management-plane only; the capture-token/invite
        projection omits them. Legacy artifacts may not have `uploaded_at`.
      required:
        - artifact_id
        - kind
        - status
      properties:
        artifact_id:
          type: string
        kind:
          $ref: '#/components/schemas/ArtifactKind'
        document_id:
          type: string
          description: Present only for kind `document` - the manifest document fulfilled.
        status:
          type: string
          enum:
            - reserved
            - uploaded
            - superseded
            - deleted
        upload_reference:
          type: string
        content_type:
          type: string
        expires_at:
          type: string
          format: date-time
        delete_after:
          type: string
          format: date-time
        reserved_at:
          type: string
          format: date-time
          description: Upload-slot reservation time; not evidence that capture completed.
        uploaded_at:
          type: string
          format: date-time
          description: Stable first successful object-upload completion time.
    AssessmentSummary:
      type: object
      description: Reviewer-safe assessment (no raw OCR values, no raw model output).
      properties:
        status:
          $ref: '#/components/schemas/SessionStatus'
        model_id:
          type: string
        inference_profile_id:
          type: string
        prompt_version:
          type: string
        face_match:
          $ref: '#/components/schemas/FaceMatch'
        face_match_performed:
          type: boolean
        liveness:
          $ref: '#/components/schemas/Liveness'
        document_authenticity:
          $ref: '#/components/schemas/DocumentAuthenticity'
        document:
          $ref: '#/components/schemas/DocumentInfo'
        fraud_flags:
          type: array
          items:
            $ref: '#/components/schemas/FraudFlag'
        reviewer_summary:
          type: string
        assessed_at:
          type: string
          format: date-time
        holder_apparent_age_range:
          type: string
          description: >
            Coarse apparent-age band of the live person estimated from the
            liveness frontal capture (under_18/18_24/25_34/35_44/45_54/55_64/
            65_74/75_over/unclear). Empty for pre-v8 assessments.
        barcode_check:
          type: string
          description: >
            Outcome of the deterministic id_back PDF417/AAMVA cross-check:
            not_applicable, not_detected, decoded_clean, or decoded_mismatch.
            Empty for pre-v8 assessments and worker-failure receipts. Decoded
            barcode contents are never exposed.
    WorkflowLabel:
      type: string
      description: >
        Derived legacy label on responses. Custom step manifests that match a
        preset's capture shape reuse the preset label; capture-free manifests
        are labeled `custom`.
      enum:
        - liveness_and_id
        - liveness_only
        - id_only
        - custom
    CaptureKind:
      type: string
      enum:
        - liveness_front
        - liveness_left
        - liveness_right
        - id_front
        - id_back
    FlowStep:
      type: object
      required:
        - step_id
        - type
      properties:
        step_id:
          type: string
          pattern: ^[a-z0-9_-]{1,64}$
          description: Unique per session.
        type:
          $ref: '#/components/schemas/StepType'
        required:
          type: boolean
          default: true
          description: false => the consumer may skip the step.
        title:
          type: string
          minLength: 1
          maxLength: 200
          description: Optional display override.
        config:
          $ref: '#/components/schemas/StepConfig'
    ArtifactKind:
      type: string
      description: |
        Camera capture kinds plus workflow-authorized supporting documents.
      enum:
        - liveness_front
        - liveness_left
        - liveness_right
        - id_front
        - id_back
        - document
    FaceMatch:
      type: string
      enum:
        - match
        - mismatch
        - inconclusive
        - not_applicable
    Liveness:
      type: string
      enum:
        - live
        - spoof_suspected
        - inconclusive
        - requires_review
        - not_applicable
    DocumentAuthenticity:
      type: string
      enum:
        - authentic
        - tampered
        - fake
        - inconclusive
        - requires_review
    DocumentInfo:
      type: object
      description: Structured jurisdiction metadata (reviewer-safe, non-PII).
      required:
        - id_type
        - issuing_country
        - issuing_subdivision
      properties:
        id_type:
          $ref: '#/components/schemas/IdType'
        issuing_country:
          type: string
          description: ISO 3166-1 alpha-2, or empty if unknown.
          example: US
        issuing_subdivision:
          type: string
          description: >-
            ISO 3166-2 (e.g. US-CA), only when applicable; empty for
            passports/national IDs.
          example: US-CA
        id_type_confidence:
          type: number
          minimum: 0
          maximum: 1
        jurisdiction_confidence:
          type: number
          minimum: 0
          maximum: 1
    FraudFlag:
      type: object
      required:
        - code
        - category
        - severity
        - confidence
        - explanation
        - rationale
        - gating
        - evidence_codes
        - normalization_reason_codes
      properties:
        code:
          type: string
          description: >
            Bounded taxonomy code. For model-assessed profile/document mismatch:
            the individual-profile field codes profile_name_mismatch (high),
            profile_dob_mismatch (high), profile_geo_mismatch_major (medium) and
            profile_geo_mismatch_minor (low, advisory only, never gates); the
            residual profile_info_mismatch_minor (medium) /
            profile_info_mismatch_major (high) for other identity fields (sex,
            nationality); and, for KYB (organization profiles),
            org_profile_mismatch_minor (medium) / org_profile_mismatch_major
            (high) sourced from the captured business document (category
            "document"). Each carries a FIXED severity.
        category:
          type: string
          enum:
            - image_quality
            - presentation_attack
            - document_tamper
            - identity_mismatch
            - ocr_uncertainty
            - workflow
            - model_uncertainty
            - idv
            - document
            - questionnaire
        severity:
          type: string
          enum:
            - low
            - medium
            - high
            - critical
        confidence:
          type: number
          minimum: 0
          maximum: 1
        explanation:
          type: string
          description: Redacted/derived; not raw model wording.
        rationale:
          type: string
          maxLength: 300
          description: >
            Reviewer-facing explanation of the finding (field + nature, e.g.
            glare region, font anomaly, mismatch character) that never quotes
            full raw document values. REQUIRED on every emitted flag; a
            deterministic per-code fallback fills it when the model omits or
            emits an invalid value.
        gating:
          type: boolean
          description: >
            Whether this flag held the session for review under the session's
            flag_settings. Disabled-code flags are recorded with gating=false
            and do not hold the session.
        evidence_codes:
          type: array
          maxItems: 8
          uniqueItems: true
          items:
            type: string
            enum:
              - laminate_lift_or_reseal
              - cut_clipped_or_punched_edge
              - tape_or_rejoined_document
              - crack_through_security_feature
              - portrait_or_photo_overlay
              - text_or_data_overlay
              - font_or_print_inconsistency
              - security_feature_inconsistency
              - front_back_data_conflict
              - electronic_display_cue
              - biometric_replay_cue
              - synthetic_image_cue
              - face_comparison_conflict
              - liveness_spoof_cue
              - ocr_data_conflict
              - profile_document_comparison
              - profile_questionnaire_comparison
              - workflow_policy_conflict
              - capture_quality_prevents_verification
              - other_affirmative_signal
          description: >
            Allowlisted, PII-free machine codes describing the affirmative
            evidence behind this flag. Empty on historical and deterministic
            flags that have no model evidence provenance.
        normalization_reason_codes:
          type: array
          maxItems: 24
          uniqueItems: true
          items:
            type: string
            enum:
              - assessment_status_invalid
              - face_match_invalid
              - liveness_invalid
              - document_authenticity_invalid
              - face_match_applicability_conflict
              - liveness_applicability_conflict
              - document_authenticity_applicability_conflict
              - document_metadata_invalid
              - reviewer_summary_missing
              - ocr_field_name_missing
              - ocr_confidence_invalid
              - ocr_canonical_fields_repaired
              - fraud_flag_required_field_missing
              - fraud_flag_code_invalid
              - fraud_evidence_scope_invalid
              - fraud_evidence_scope_missing
              - fraud_attachment_evidence_invalid
              - fraud_attachment_evidence_missing
              - fraud_evidence_codes_missing
              - fraud_evidence_code_invalid
              - fraud_severity_invalid
              - fraud_category_invalid
              - fraud_confidence_invalid
              - presentation_output_normalized
              - document_authenticity_evidence_conflict
              - quality_issue_output_normalized
              - holder_appearance_output_normalized
          description: >
            Allowlisted, PII-free deterministic repair reasons. Populated only
            for model_output_schema_violation; empty on all other flags.
    StepType:
      type: string
      enum:
        - liveness
        - id_capture
        - questionnaire
        - document_upload
    StepConfig:
      type: object
      description: >-
        Type-specific step config. Only the fields for the step type are
        allowed.
      properties:
        accepted_id_types:
          type: array
          maxItems: 20
          description: >
            id_capture only. Empty/omitted = any supported built-in type plus
            any custom_id_types configured on the same step.
          items:
            $ref: '#/components/schemas/DeclarableIdType'
        custom_id_types:
          type: array
          maxItems: 20
          description: >
            id_capture only. Workflow-local document categories that can be
            referenced from accepted_id_types and declared by the applicant.
          items:
            $ref: '#/components/schemas/CustomIdType'
        sides:
          $ref: '#/components/schemas/IdCaptureSides'
        questions:
          type: array
          minItems: 1
          maxItems: 50
          description: questionnaire only.
          items:
            $ref: '#/components/schemas/Question'
        documents:
          type: array
          minItems: 1
          maxItems: 5
          description: >-
            document_upload only; the full workflow supports at most five
            documents.
          items:
            $ref: '#/components/schemas/DocumentSpec'
    IdType:
      type: string
      enum:
        - driver_license
        - government_id
        - health_card
        - passport
        - national_id
        - permanent_resident_card
        - residence_permit
        - other
        - unknown
    DeclarableIdType:
      description: >
        Id types a consumer can declare or configure in accepted_id_types.
        Built-ins are listed explicitly; workflow-local custom ids must be
        defined in the id_capture step's custom_id_types. `unknown` is an
        inference outcome, not declarable. `passport` is the one-sided built-in
        type - declaring it drops id_back when sides=auto.
      anyOf:
        - type: string
          enum:
            - driver_license
            - government_id
            - health_card
            - passport
            - national_id
            - permanent_resident_card
            - residence_permit
            - other
        - type: string
          pattern: ^[a-z0-9_-]{1,64}$
          not:
            enum:
              - driver_license
              - government_id
              - health_card
              - passport
              - national_id
              - permanent_resident_card
              - residence_permit
              - other
              - unknown
    CustomIdType:
      type: object
      required:
        - id_type
        - label
        - sides
      properties:
        id_type:
          type: string
          pattern: ^[a-z0-9_-]{1,64}$
          description: >
            Workflow-local id type identifier. Must not match a built-in ID type
            or `unknown`; accepted_id_types can reference it only within the
            same id_capture step.
        label:
          type: string
          minLength: 1
          maxLength: 120
          description: >-
            Human-readable document type label shown to applicants and
            reviewers.
        description:
          type: string
          maxLength: 1000
          description: >
            Optional workflow-admin guidance describing the ID, authenticity
            requirements, and specific details reviewers or the assessment agent
            should inspect for this custom type.
        sides:
          type: string
          enum:
            - one_sided
            - two_sided
          description: Capture side requirement when the custom type is declared.
    IdCaptureSides:
      type: string
      description: >-
        auto adapts to the declared document type; one_sided never demands
        id_back.
      enum:
        - auto
        - two_sided
        - one_sided
      default: auto
    Question:
      type: object
      required:
        - question_id
        - label
        - type
      properties:
        question_id:
          type: string
          pattern: ^[a-z0-9_-]{1,64}$
          description: Unique within the step.
        label:
          type: string
          minLength: 1
          maxLength: 500
        type:
          $ref: '#/components/schemas/QuestionType'
        required:
          type: boolean
          default: true
        required_when:
          allOf:
            - $ref: '#/components/schemas/QuestionRequiredWhen'
          description: >-
            Mutually exclusive with required=true; required defaults to false
            when this field is present.
        visible_when:
          allOf:
            - $ref: '#/components/schemas/QuestionCondition'
          description: >
            Shows this otherwise-optional question only when the earlier,
            always-shown and required boolean question has the specified value.
            Mutually exclusive with required=true. If required_when is also
            present, both fields must contain the same condition.
        options:
          type: array
          maxItems: 50
          description: Required for select/multi_select; forbidden otherwise.
          items:
            $ref: '#/components/schemas/QuestionOption'
    DocumentSpec:
      type: object
      required:
        - document_id
        - label
      properties:
        document_id:
          type: string
          pattern: ^[a-z0-9_-]{1,64}$
          description: Unique within the full workflow.
        label:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type: string
          maxLength: 1000
        accepted_content_types:
          type: array
          minItems: 1
          maxItems: 4
          uniqueItems: true
          description: Defaults to [application/pdf, image/jpeg, image/png] when omitted.
          items:
            type: string
            enum:
              - application/pdf
              - image/jpeg
              - image/png
              - image/webp
        required:
          type: boolean
          default: true
    QuestionType:
      type: string
      enum:
        - text
        - textarea
        - select
        - multi_select
        - date
        - country
        - boolean
    QuestionRequiredWhen:
      type: object
      required:
        - question_id
        - equals
      description: >
        Makes an otherwise-optional question required when an earlier,
        always-shown and unconditionally required boolean question has the
        specified value.
      properties:
        question_id:
          type: string
          pattern: ^[a-z0-9_-]{1,64}$
        equals:
          type: boolean
    QuestionCondition:
      allOf:
        - $ref: '#/components/schemas/QuestionRequiredWhen'
      description: >
        Matches an earlier, always-shown and unconditionally required boolean
        question against the specified value.
    QuestionOption:
      type: object
      required:
        - value
        - label
      properties:
        value:
          type: string
          minLength: 1
          maxLength: 128
        label:
          type: string
          minLength: 1
          maxLength: 200
  responses:
    Unauthorized:
      description: >-
        Missing or invalid credentials (uniform for token plane, no existence
        oracle)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Conflict:
      description: Domain-state conflict (e.g. concurrent update, illegal transition)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ServerError:
      description: Internal error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    sessionToken:
      type: http
      scheme: bearer
      bearerFormat: opaque
      description: >-
        `Authorization: Bearer idvs_<token>`: one-time value from
        create/acquire. Header only; never a query param.

````