openapi: "3.0.3"
info:
  title: DocUnum
  description: |
    Document merge, validation, conversion, and schema inference service.

    Supports DOCX, XLSX, ODT, ODS, HTML, Markdown, and TXT templates.

    ## Upload formats

    All document endpoints accept three request body formats:

    - **JSON + base64** (`application/json`): template content encoded as standard base64 in the `content` field.
    - **Multipart** (`multipart/form-data`): `template` file field plus `format`, `name`, `data` text fields.
    - **Raw body** (`text/plain`, `text/markdown`, `text/html`): template as raw body; use `?format=`, `?name=`, `?data=` query params.
  version: "1.0.0"

servers:
  - url: /
    description: Current server

tags:
  - name: document
    description: Template operations — peek, validate, merge, convert, schema
  - name: templates
    description: Stored template management — create, list, get, update, delete
  - name: history
    description: Operation history — submission records, job history, audit log
  - name: auth
    description: Authentication
  - name: infra
    description: Health, readiness, capabilities, and spec endpoints

paths:
  /health:
    get:
      tags: [infra]
      summary: Health check
      operationId: getHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusResponse"

  /ready:
    get:
      tags: [infra]
      summary: Readiness check
      operationId: getReady
      responses:
        "200":
          description: Service is ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StatusResponse"

  /v1/capabilities:
    get:
      tags: [infra]
      summary: Service capabilities
      description: Returns enabled formats, expression engines, and feature flags.
      operationId: getCapabilities
      responses:
        "200":
          description: Capabilities
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CapabilitiesResponse"

  /openapi.json:
    get:
      tags: [infra]
      summary: OpenAPI spec (JSON)
      operationId: getOpenAPIJSON
      responses:
        "200":
          description: OpenAPI 3.0 specification in JSON
          content:
            application/json:
              schema:
                type: object

  /openapi.yaml:
    get:
      tags: [infra]
      summary: OpenAPI spec (YAML)
      operationId: getOpenAPIYAML
      responses:
        "200":
          description: OpenAPI 3.0 specification in YAML
          content:
            application/yaml:
              schema:
                type: string

  /docs:
    get:
      tags: [infra]
      summary: Swagger UI
      operationId: getDocs
      responses:
        "200":
          description: Interactive API documentation
          content:
            text/html:
              schema:
                type: string

  /v1/peek:
    post:
      tags: [document]
      summary: Inspect a template
      description: Returns tag inventory, structure, and warnings without merging.
      operationId: peek
      parameters:
        - $ref: "#/components/parameters/formatParam"
        - $ref: "#/components/parameters/nameParam"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TemplateBody"
            example:
              name: invoice.txt
              format: txt
              content: SGVsbG8gWyogbmFtZSAqXSE=
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/TemplateMultipart"
          text/plain:
            schema:
              type: string
              example: "Hello [* name *]!"
          text/markdown:
            schema:
              type: string
          text/html:
            schema:
              type: string
      responses:
        "200":
          description: Peek result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PeekResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"

  /v1/validate:
    post:
      tags: [document]
      summary: Validate a template
      description: Checks template syntax, structure, and optionally data conformance.
      operationId: validate
      parameters:
        - $ref: "#/components/parameters/formatParam"
        - $ref: "#/components/parameters/nameParam"
        - $ref: "#/components/parameters/dataParam"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TemplateWithDataBody"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/TemplateWithDataMultipart"
          text/plain:
            schema:
              type: string
          text/markdown:
            schema:
              type: string
          text/html:
            schema:
              type: string
      responses:
        "200":
          description: Validation result (valid or not — always 200 unless server error)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"

  /v1/merge:
    post:
      tags: [document]
      summary: Merge a template with data
      description: Renders a final document by substituting all tags with data values.
      operationId: merge
      parameters:
        - $ref: "#/components/parameters/formatParam"
        - $ref: "#/components/parameters/nameParam"
        - $ref: "#/components/parameters/dataParam"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TemplateWithDataBody"
            example:
              name: report.md
              format: md
              content: SGVsbG8gWyogbmFtZSAqXSE=
              data:
                name: World
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/TemplateWithDataMultipart"
          text/plain:
            schema:
              type: string
          text/markdown:
            schema:
              type: string
          text/html:
            schema:
              type: string
      responses:
        "200":
          description: Merged document (base64-encoded content)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MergeResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"

  /v1/convert:
    post:
      tags: [document]
      summary: Convert template delimiters
      description: Rewrites template tags from one delimiter scheme to another.
      operationId: convert
      parameters:
        - $ref: "#/components/parameters/formatParam"
        - $ref: "#/components/parameters/nameParam"
        - name: target_open
          in: query
          description: "Target opening delimiter (raw body requests). Example: {{"
          schema:
            type: string
        - name: target_close
          in: query
          description: "Target closing delimiter (raw body requests). Example: }}"
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConvertBody"
            example:
              name: legacy.txt
              format: txt
              content: SGVsbG8gWyogbmFtZSAqXSE=
              target_open: "{{"
              target_close: "}}"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/ConvertMultipart"
          text/plain:
            schema:
              type: string
          text/markdown:
            schema:
              type: string
          text/html:
            schema:
              type: string
      responses:
        "200":
          description: Converted template (base64-encoded content)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConvertResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"

  /v1/schema/infer:
    post:
      tags: [document]
      summary: Infer JSON Schema from a template
      description: Analyses template tags and returns a draft-07 JSON Schema describing the expected data shape.
      operationId: inferSchema
      parameters:
        - $ref: "#/components/parameters/formatParam"
        - $ref: "#/components/parameters/nameParam"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TemplateBody"
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/TemplateMultipart"
          text/plain:
            schema:
              type: string
          text/markdown:
            schema:
              type: string
          text/html:
            schema:
              type: string
      responses:
        "200":
          description: Inferred JSON Schema
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SchemaInferResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"

  /v1/templates:
    post:
      tags: [templates]
      summary: Save a template
      description: |
        Upload and store a template server-side.  Returns a `template_id` that can
        be passed as `{"source":"stored","template_id":"…"}` in any operation request
        instead of resending the full content.

        Requires `templates.storage_dir` to be configured; returns 503 otherwise.
      operationId: createStoredTemplate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoredTemplateCreateBody"
            example:
              name: invoice.docx
              format: docx
              content: <base64>
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/TemplateMultipart"
      responses:
        "201":
          description: Template stored
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoredTemplateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "503":
          $ref: "#/components/responses/StorageDisabled"
    get:
      tags: [templates]
      summary: List stored templates
      description: |
        Returns all stored templates for the authenticated caller, sorted newest first.

        Requires `templates.storage_dir` to be configured; returns 503 otherwise.
      operationId: listStoredTemplates
      parameters:
        - name: format
          in: query
          description: Filter by exact format (e.g. docx, txt).
          schema:
            type: string
            enum: [txt, md, html, docx, xlsx, odt, ods]
        - name: name
          in: query
          description: Filter by name substring (case-insensitive).
          schema:
            type: string
        - $ref: "#/components/parameters/limitParam"
        - $ref: "#/components/parameters/offsetParam"
      responses:
        "200":
          description: Template list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoredTemplateListResponse"
        "503":
          $ref: "#/components/responses/StorageDisabled"

  /v1/templates/{id}:
    get:
      tags: [templates]
      summary: Get stored template metadata
      description: Returns metadata for one stored template (no content).
      operationId: getStoredTemplate
      parameters:
        - $ref: "#/components/parameters/templateIDParam"
      responses:
        "200":
          description: Template metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoredTemplateResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/StorageDisabled"
    put:
      tags: [templates]
      summary: Update a stored template
      description: |
        Replace the content and/or name of a stored template.
        At least one of `name` or `content` must be provided.
      operationId: updateStoredTemplate
      parameters:
        - $ref: "#/components/parameters/templateIDParam"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StoredTemplateUpdateBody"
          multipart/form-data:
            schema:
              type: object
              properties:
                name:
                  type: string
                template:
                  type: string
                  format: binary
      responses:
        "200":
          description: Updated template metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StoredTemplateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/StorageDisabled"
    delete:
      tags: [templates]
      summary: Delete a stored template
      description: Permanently removes a stored template.
      operationId: deleteStoredTemplate
      parameters:
        - $ref: "#/components/parameters/templateIDParam"
      responses:
        "200":
          description: Template deleted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OkResponse"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/StorageDisabled"

  /v1/submissions/templates:
    get:
      tags: [history]
      summary: Template submission history
      description: Returns template submission records for the authenticated login, newest first.
      operationId: listTemplateSubmissions
      parameters:
        - $ref: "#/components/parameters/limitParam"
        - $ref: "#/components/parameters/offsetParam"
      responses:
        "200":
          description: Submission list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TemplateListResponse"

  /v1/jobs:
    get:
      tags: [history]
      summary: Merge job history
      description: Returns merge jobs for the authenticated login, newest first.
      operationId: listJobs
      parameters:
        - $ref: "#/components/parameters/limitParam"
        - $ref: "#/components/parameters/offsetParam"
      responses:
        "200":
          description: Job list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/JobListResponse"

  /v1/audit:
    get:
      tags: [history]
      summary: Audit event log
      description: Returns service-level audit events (not user-scoped), newest first.
      operationId: listAudit
      parameters:
        - $ref: "#/components/parameters/limitParam"
        - $ref: "#/components/parameters/offsetParam"
      responses:
        "200":
          description: Audit events
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuditListResponse"

  /v1/auth/login:
    post:
      tags: [auth]
      summary: Login
      description: Authenticates and sets a session_token cookie.
      operationId: login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginRequest"
      responses:
        "200":
          description: Authenticated
        "401":
          $ref: "#/components/responses/Unauthorized"

  /v1/auth/logout:
    post:
      tags: [auth]
      summary: Logout
      description: Clears the session_token cookie.
      operationId: logout
      responses:
        "200":
          description: Session cleared

  /v1/auth/session:
    get:
      tags: [auth]
      summary: Current session
      description: Returns the current user context, or 401 for unauthenticated requests.
      operationId: getSession
      responses:
        "200":
          description: Current user
        "401":
          $ref: "#/components/responses/Unauthorized"

components:
  parameters:
    templateIDParam:
      name: id
      in: path
      required: true
      description: Stored template ID (e.g. tpl_abc123def456).
      schema:
        type: string

    formatParam:
      name: format
      in: query
      description: "Template format for raw body requests. One of: txt, md, html, docx, xlsx, odt, ods. Inferred from Content-Type when omitted."
      schema:
        type: string
        enum: [txt, md, html, docx, xlsx, odt, ods]

    nameParam:
      name: name
      in: query
      description: Output file name (raw body and multipart requests).
      schema:
        type: string

    dataParam:
      name: data
      in: query
      description: URL-encoded JSON data object (raw body requests only).
      schema:
        type: string

    limitParam:
      name: limit
      in: query
      description: Maximum rows to return.
      schema:
        type: integer
        minimum: 0
        default: 100

    offsetParam:
      name: offset
      in: query
      description: Number of rows to skip.
      schema:
        type: integer
        minimum: 0
        default: 0

  schemas:
    Diagnostic:
      type: object
      required: [code, severity, message]
      properties:
        code:
          type: string
          example: LEX_UNCLOSED_TAG
        severity:
          type: string
          enum: [error, warning, info]
        message:
          type: string
        location:
          type: object
          properties:
            line:
              type: integer
            col:
              type: integer
            offset:
              type: integer
            source:
              type: string
        details:
          type: object
          additionalProperties:
            type: string

    TagSummary:
      type: object
      properties:
        kind:
          type: string
          example: field
          description: "Tag kind: field, expr, alias_decl, alias_use, for, if, with, row, include, agg"
        value:
          type: string
          example: customer.name
        line:
          type: integer
        page:
          type: integer
        location:
          $ref: "#/components/schemas/StructuralLocation"

    LoopRef:
      type: object
      description: One level of loop enclosure in a structural location.
      properties:
        variable:
          type: string
          description: Collection or variable name from the for/row tag.
        kind:
          type: string
          enum: [for, row]

    StructuralLocation:
      type: object
      description: Structural position of a tag within the document.
      properties:
        section:
          type: string
          description: "Document section: body, header, footer, footnote, endnote, textbox, or a sheet name for spreadsheets."
          example: body
        table_index:
          type: integer
          description: 0-based table index within the section; -1 when not inside a table.
        row_index:
          type: integer
          description: 0-based row index within the table; -1 when not inside a table.
        col_index:
          type: integer
          description: 0-based column index within the table; -1 when not inside a table.
        loops:
          type: array
          description: Enclosing loop stack, outermost first.
          items:
            $ref: "#/components/schemas/LoopRef"

    TableSummary:
      type: object
      description: One table detected in the template.
      properties:
        section:
          type: string
          example: body
        table_index:
          type: integer
        rows:
          type: integer
          description: Number of template rows in the table.
        cols:
          type: integer
          description: Number of columns (maximum across rows).

    ImageSummary:
      type: object
      description: One embedded image or image placeholder found during peek.
      properties:
        name:
          type: string
          description: Internal asset name within the archive.
        section:
          type: string
          example: body
        tag:
          type: string
          description: DSL field path from the image alt/name (e.g. "logo"). Providing this key in merge data replaces the image.
          example: logo
        declared_type:
          type: string
          description: Declared output type modifier (jpeg, png, webp, gif, svg).
        declared_width:
          type: string
          description: Declared width modifier ("200" or "50%").
        declared_height:
          type: string
          description: Declared height modifier ("150" or "75%").
        stretch:
          type: boolean
        declared_dpi:
          type: integer
        pixel_width:
          type: integer
          description: Actual pixel width of the embedded image asset (best-effort).
        pixel_height:
          type: integer
        file_size:
          type: integer
          format: int64
          description: Embedded asset size in bytes.
        mime_type:
          type: string
          description: Detected MIME type of the embedded image asset.

    StoredTemplateRef:
      type: object
      description: Reference to a previously stored template. Use as the request body instead of uploading content.
      required: [source, template_id]
      properties:
        source:
          type: string
          enum: [stored]
          example: stored
        template_id:
          type: string
          example: tpl_abc123def456
        data:
          type: object
          description: Data to merge (for merge/validate operations).

    StoredTemplateMeta:
      type: object
      properties:
        template_id:
          type: string
          example: tpl_abc123def456
        name:
          type: string
          example: invoice.docx
        format:
          type: string
          example: docx
        size_bytes:
          type: integer
          example: 48210
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    StoredTemplateCreateBody:
      type: object
      required: [format, content]
      properties:
        name:
          type: string
          example: invoice.docx
        format:
          type: string
          enum: [txt, md, html, docx, xlsx, odt, ods]
        content:
          type: string
          format: byte
          description: Template file bytes encoded as standard base64.

    StoredTemplateUpdateBody:
      type: object
      properties:
        name:
          type: string
          description: New name for the template. Omit to keep existing.
        content:
          type: string
          format: byte
          description: Replacement content encoded as standard base64. Omit to keep existing.

    StoredTemplateResponse:
      type: object
      properties:
        ok:
          type: boolean
        template:
          $ref: "#/components/schemas/StoredTemplateMeta"

    StoredTemplateListResponse:
      type: object
      properties:
        ok:
          type: boolean
        templates:
          type: array
          items:
            $ref: "#/components/schemas/StoredTemplateMeta"
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer

    OkResponse:
      type: object
      properties:
        ok:
          type: boolean

    TemplateBody:
      type: object
      description: |
        Supply either inline content (`content` + `format`) or a stored-template reference
        (`template_id`). When `template_id` is provided the server loads the template from
        the template store; `content`, `format`, and `source` are ignored.
      properties:
        source:
          type: string
          enum: [upload, stored]
          default: upload
          description: |
            `upload` — content is delivered in this request (default).
            `stored` — load a previously saved template by `template_id`.
        template_id:
          type: string
          description: |
            ID of a previously stored template. When provided the server loads
            the template from the store; `content` and `format` may be omitted.
          example: tpl_abc123def456
        name:
          type: string
          example: invoice.docx
        format:
          type: string
          enum: [txt, md, html, docx, xlsx, odt, ods]
          example: txt
        content:
          type: string
          format: byte
          description: Template file bytes encoded as standard base64. Required when `source` is `"upload"`.
          example: SGVsbG8gWyogbmFtZSAqXSE=

    TemplateWithDataBody:
      allOf:
        - $ref: "#/components/schemas/TemplateBody"
        - type: object
          properties:
            data:
              type: object
              description: Data object to merge into the template.
              example:
                customer:
                  name: Acme Corp
                items:
                  - sku: A1
                    price: 100

    TemplateMultipart:
      type: object
      required: [template, format]
      properties:
        template:
          type: string
          format: binary
          description: Template file upload.
        format:
          type: string
          enum: [txt, md, html, docx, xlsx, odt, ods]
        name:
          type: string
        data:
          type: string
          description: JSON-encoded data object.

    TemplateWithDataMultipart:
      allOf:
        - $ref: "#/components/schemas/TemplateMultipart"

    ConvertBody:
      allOf:
        - $ref: "#/components/schemas/TemplateBody"
        - type: object
          properties:
            target_open:
              type: string
              description: Target opening delimiter.
              example: "{{"
            target_close:
              type: string
              description: Target closing delimiter.
              example: "}}"

    ConvertMultipart:
      allOf:
        - $ref: "#/components/schemas/TemplateMultipart"
        - type: object
          properties:
            target_open:
              type: string
            target_close:
              type: string

    StatusResponse:
      type: object
      properties:
        ok:
          type: boolean
        status:
          type: string

    CapabilitiesResponse:
      type: object
      properties:
        ok:
          type: boolean
        formats:
          type: array
          items:
            type: string
          example: [txt, md, html, docx, xlsx, odt, ods]
        engines:
          type: array
          items:
            type: string
          example: [native]
        features:
          type: object
          additionalProperties: {}

    PeekResponse:
      type: object
      properties:
        ok:
          type: boolean
        result:
          type: object
          properties:
            format:
              type: string
            tables:
              type: array
              description: One entry per table detected in the template, in reading order per section.
              items:
                $ref: "#/components/schemas/TableSummary"
            images:
              type: array
              description: Embedded images and image placeholders found in the template.
              items:
                $ref: "#/components/schemas/ImageSummary"
            tags:
              type: array
              items:
                $ref: "#/components/schemas/TagSummary"
            controls:
              type: array
              items:
                type: string
            warnings:
              type: array
              items:
                $ref: "#/components/schemas/Diagnostic"

    ValidateResponse:
      type: object
      properties:
        ok:
          type: boolean
        result:
          type: object
          properties:
            valid:
              type: boolean
            errors:
              type: array
              items:
                $ref: "#/components/schemas/Diagnostic"
            warnings:
              type: array
              items:
                $ref: "#/components/schemas/Diagnostic"

    MergeResponse:
      type: object
      properties:
        ok:
          type: boolean
        name:
          type: string
        format:
          type: string
        content:
          type: string
          format: byte
          description: Merged document bytes encoded as standard base64.
        result:
          type: object
          properties:
            stats:
              type: object
              properties:
                fields_resolved:
                  type: integer
                expressions_evaluated:
                  type: integer
                repeat_iterations:
                  type: integer
            warnings:
              type: array
              items:
                $ref: "#/components/schemas/Diagnostic"

    ConvertResponse:
      type: object
      properties:
        ok:
          type: boolean
        name:
          type: string
        format:
          type: string
        content:
          type: string
          format: byte
          description: Converted template bytes encoded as standard base64.
        result:
          type: object
          properties:
            tags_converted:
              type: integer
            warnings:
              type: array
              items:
                $ref: "#/components/schemas/Diagnostic"

    SchemaInferResponse:
      type: object
      properties:
        ok:
          type: boolean
        schema:
          type: object
          description: Inferred JSON Schema (draft-07).
          example:
            "$schema": "http://json-schema.org/draft-07/schema#"
            type: object
            properties:
              name:
                type: string

    TemplateListResponse:
      type: object
      properties:
        ok:
          type: boolean
        templates:
          type: array
          items:
            type: object
        limit:
          type: integer
        offset:
          type: integer

    JobListResponse:
      type: object
      properties:
        ok:
          type: boolean
        jobs:
          type: array
          items:
            type: object
        limit:
          type: integer
        offset:
          type: integer

    AuditListResponse:
      type: object
      properties:
        ok:
          type: boolean
        events:
          type: array
          items:
            type: object
        limit:
          type: integer
        offset:
          type: integer

    ErrorResponse:
      type: object
      properties:
        ok:
          type: boolean
          example: false
        errors:
          type: array
          items:
            $ref: "#/components/schemas/Diagnostic"

    LoginRequest:
      type: object
      required: [username, password]
      properties:
        username:
          type: string
        password:
          type: string
          format: password

  responses:
    BadRequest:
      description: Invalid request body or parameters.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: Authentication required or failed.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFound:
      description: Resource not found.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    UnprocessableEntity:
      description: Template or data could not be processed.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    StorageDisabled:
      description: Template storage is not configured on this server (`templates.storage_dir` is unset).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
