> ## 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.

# Create a theme

> Presentation fields are validated as a closed grammar (lowercase-normalized `#rrggbb` colors, font allowlist Inter/system-ui/Roboto/Open Sans/Lato/Montserrat, company_name at most 80 chars; empty string = unset). The FIRST theme created in a scope auto-becomes the default, even while draft (resolution only APPLIES active defaults). `status` defaults to draft; "active" creates-and-activates. Logos never ride on this body; see the binary /logo endpoints.




## OpenAPI

````yaml /api-reference/idv-svc.json post /themes
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:
  /themes:
    post:
      tags:
        - themes
      summary: Create a theme
      description: >
        Presentation fields are validated as a closed grammar
        (lowercase-normalized `#rrggbb` colors, font allowlist
        Inter/system-ui/Roboto/Open Sans/Lato/Montserrat, company_name at most
        80 chars; empty string = unset). The FIRST theme created in a scope
        auto-becomes the default, even while draft (resolution only APPLIES
        active defaults). `status` defaults to draft; "active"
        creates-and-activates. Logos never ride on this body; see the binary
        /logo endpoints.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ThemeUpsertRequest'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ThemeResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
      security:
        - applicationApiKey: []
        - dashboardUserToken: []
components:
  schemas:
    ThemeUpsertRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 120
        company_name:
          type: string
          maxLength: 80
          description: Empty string = unset.
        colors:
          $ref: '#/components/schemas/ThemeColors'
        font_family:
          type: string
          description: Empty string = unset; otherwise the closed allowlist.
          enum:
            - ''
            - Inter
            - system-ui
            - Roboto
            - Open Sans
            - Lato
            - Montserrat
        status:
          type: string
          description: >-
            Create only: draft (default) or active; archived is rejected. PUT
            rejects it.
          enum:
            - draft
            - active
        revision:
          type: integer
          description: 'Replace only: the CAS expectation (or use If-Match).'
    ThemeResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/ThemeObject'
    ThemeColors:
      type: object
      description: >
        Canonical lowercase `#rrggbb` values only (validated + normalized at
        write time). Each color is null/omitted when unset.
      properties:
        primary:
          oneOf:
            - type: string
              pattern: ^#[0-9a-f]{6}$
            - type: 'null'
        primary_foreground:
          oneOf:
            - type: string
              pattern: ^#[0-9a-f]{6}$
            - type: 'null'
        background:
          oneOf:
            - type: string
              pattern: ^#[0-9a-f]{6}$
            - type: 'null'
        foreground:
          oneOf:
            - type: string
              pattern: ^#[0-9a-f]{6}$
            - type: 'null'
        accent:
          oneOf:
            - type: string
              pattern: ^#[0-9a-f]{6}$
            - type: 'null'
    ThemeObject:
      type: object
      description: >
        Workspace-scoped reusable presentation block (replaces the inline
        workflow branding). Non-PII but an INJECTION SURFACE into the consumer
        page and invite email, so every field is a closed grammar; logos are
        managed uploads only (external URLs and SVG are unsupported by design).
      required:
        - theme_id
        - tenant_id
        - workspace_id
        - name
        - status
        - is_default
        - logo
        - logo_square
        - colors
        - font_family
        - created_at
        - updated_at
        - revision
      properties:
        theme_id:
          type: string
          pattern: ^idvt_[0-9a-f]{24}$
        tenant_id:
          type: string
        workspace_id:
          type: string
        name:
          type: string
          minLength: 1
          maxLength: 120
        status:
          $ref: '#/components/schemas/LifecycleStatus'
        is_default:
          type: boolean
          description: >-
            At most one default per tenant+workspace; the FIRST created theme
            auto-defaults (even as a draft).
        company_name:
          description: Optional; null when unset.
          oneOf:
            - type: string
              minLength: 1
              maxLength: 80
            - type: 'null'
        logo:
          oneOf:
            - $ref: '#/components/schemas/ThemeLogo'
            - type: 'null'
        logo_square:
          description: Square (1:1) QR-centre mark; an INDEPENDENT asset slot from `logo`.
          oneOf:
            - $ref: '#/components/schemas/ThemeLogo'
            - type: 'null'
        colors:
          $ref: '#/components/schemas/ThemeColors'
        font_family:
          type: string
          description: '"" = unset; otherwise the closed allowlist.'
          enum:
            - ''
            - Inter
            - system-ui
            - Roboto
            - Open Sans
            - Lato
            - Montserrat
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        revision:
          type: integer
          minimum: 1
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              example: idv_session_not_found
            message:
              type: string
    LifecycleStatus:
      type: string
      description: >
        Shared management lifecycle (workflows AND themes): draft -> active ->
        archived, archived -> active (restore), draft -> archived. Transitions
        only via the idempotent POST /activate and /archive endpoints, never via
        PUT.
      enum:
        - draft
        - active
        - archived
    ThemeLogo:
      type: object
      description: >
        Managed logo asset reference. IMMUTABLE per asset_id: a replacement
        upload mints a new asset and retains prior objects so session copies
        keep resolving. Storage keys never appear on any wire.
      required:
        - asset_id
        - content_type
        - width
        - height
      properties:
        asset_id:
          type: string
          pattern: ^[0-9a-f]{24}$
        content_type:
          type: string
          enum:
            - image/png
          description: Always image/png after the upload re-encode.
        width:
          type: integer
          minimum: 1
        height:
          type: integer
          minimum: 1
  responses:
    BadRequest:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: >-
        Missing or invalid credentials (uniform for token plane, no existence
        oracle)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: Resolved dashboard user lacks the required capability or scope
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    applicationApiKey:
      type: apiKey
      in: header
      name: Authorization
      description: >-
        `Authorization: Api-Key <application_api_key>` (legacy `X-Api-Key` also
        accepted).
    dashboardUserToken:
      type: apiKey
      in: header
      name: x-user-token
      description: >
        Dashboard session token for Minerva dashboard users. Management routes
        accept it alongside an application API key; any in-scope workspace user
        (member or above) can read sessions and record review decisions, while
        workflow and theme writes need a management-capable role. Send it with
        the `x-tenant-id` and `x-workspace-id` headers; a header/access mismatch
        is refused with 403, and when dashboard authentication is disabled (the
        default) any request carrying this header gets a uniform 401.

````