openapi: 3.1.0
info:
  title: Code To UML Rendering API
  version: 1.0.0
  summary: Kroki-compatible diagram rendering API.
  description: >
    API documentation for the Code To UML Gateway. It exposes Kroki-compatible
    render routes used by GitLab, Markdown, VS Code, Playground, and HTTP
    clients.
  contact:
    name: Code To UML
  license:
    name: MIT
servers:
  - url: http://localhost:8000
    description: Local development gateway
  - url: https://kroki-render-vsf.duckdns.org
    description: Production gateway

tags:
  - name: Rendering
    description: Diagram rendering commands.
  - name: Renderer Catalog
    description: Renderer and theme query endpoints.
  - name: Option Validation
    description: Universal render option validation.
  - name: Operations
    description: Health and metrics endpoints.
  - name: Playground
    description: Browser user interface endpoint.

security:
  - {}

paths:
  /:
    get:
      tags: [Renderer Catalog]
      summary: Get API index
      description: Returns a JSON index for the running rendering service.
      operationId: getApiIndex
      responses:
        "200":
          description: Service index.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "502":
          $ref: "#/components/responses/BadGateway"
        "504":
          $ref: "#/components/responses/GatewayTimeout"
    post:
      tags: [Rendering]
      summary: Render with Kroki JSON envelope
      description: >
        Kroki-compatible JSON render endpoint. Use this when the client wants to
        send the renderer, output format, source, and options in one JSON body.
        The Gateway authenticates and rate-limits this route, then proxies it
        without using the Gateway result cache.
      operationId: renderKrokiEnvelope
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KrokiEnvelopeRenderRequest"
      responses:
        "200":
          $ref: "#/components/responses/RenderedDiagram"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "502":
          $ref: "#/components/responses/BadGateway"
        "504":
          $ref: "#/components/responses/GatewayTimeout"

  /playground:
    get:
      tags: [Playground]
      summary: Open web playground
      description: Returns the self-contained HTML playground page.
      operationId: getPlayground
      responses:
        "200":
          description: HTML playground page.
          headers:
            Content-Security-Policy:
              schema:
                type: string
            Cache-Control:
              schema:
                type: string
                example: no-store
            X-Content-Type-Options:
              schema:
                type: string
                example: nosniff
            Referrer-Policy:
              schema:
                type: string
                example: no-referrer
          content:
            text/html:
              schema:
                type: string
        "502":
          $ref: "#/components/responses/BadGateway"
        "504":
          $ref: "#/components/responses/GatewayTimeout"

  /healthz:
    get:
      tags: [Operations]
      summary: Check Gateway and core health
      description: Returns healthy only when the Gateway can reach the core health endpoint.
      operationId: getHealthz
      responses:
        "200":
          $ref: "#/components/responses/GatewayHealthOk"
        "503":
          $ref: "#/components/responses/GatewayHealthUnavailable"

  /health:
    get:
      tags: [Operations]
      summary: Get detailed core health
      description: >
        Proxies the core health response, including the Kroki and renderer
        component versions. This response is more detailed than /healthz.
      operationId: getHealth
      responses:
        "200":
          $ref: "#/components/responses/CoreHealthOk"
        "502":
          $ref: "#/components/responses/BadGateway"
        "504":
          $ref: "#/components/responses/GatewayTimeout"

  /v1/health:
    get:
      tags: [Operations]
      summary: Get versioned detailed core health
      description: Versioned alias of /health.
      operationId: getV1Health
      responses:
        "200":
          $ref: "#/components/responses/CoreHealthOk"
        "502":
          $ref: "#/components/responses/BadGateway"
        "504":
          $ref: "#/components/responses/GatewayTimeout"

  /metrics:
    get:
      tags: [Operations]
      summary: Get combined Prometheus metrics
      description: >
        Returns core metrics followed by Gateway cache, upstream, in-flight,
        and process metrics.
      operationId: getMetrics
      responses:
        "200":
          description: Prometheus text exposition.
          content:
            text/plain:
              schema:
                type: string
        "502":
          $ref: "#/components/responses/BadGateway"
        "504":
          $ref: "#/components/responses/GatewayTimeout"

  /api/diagram-types:
    get:
      tags: [Renderer Catalog]
      summary: List registered renderers and capabilities
      description: >
        Returns the runtime renderer catalog. This endpoint is authoritative
        because it is built from renderer routes registered in the running server.
      operationId: listDiagramTypes
      responses:
        "200":
          description: Renderer catalog.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DiagramTypesResponse"
        "502":
          $ref: "#/components/responses/BadGateway"
        "504":
          $ref: "#/components/responses/GatewayTimeout"

  /api/themes:
    get:
      tags: [Renderer Catalog]
      summary: List available universal themes
      operationId: listThemes
      responses:
        "200":
          description: Theme catalog.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ThemesResponse"
        "502":
          $ref: "#/components/responses/BadGateway"
        "504":
          $ref: "#/components/responses/GatewayTimeout"

  /api/validate-options:
    post:
      tags: [Option Validation]
      summary: Validate universal render options
      description: Validates an option set for a diagram type without rendering it.
      operationId: validateOptions
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ValidateOptionsRequest"
            examples:
              mermaidDark:
                value:
                  diagramType: mermaid
                  options:
                    theme: dark
                    scale: 2
      responses:
        "200":
          description: Validation result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidateOptionsResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "502":
          $ref: "#/components/responses/BadGateway"
        "504":
          $ref: "#/components/responses/GatewayTimeout"

  /api/render:
    post:
      tags: [Rendering]
      summary: Render with universal JSON API
      description: >
        Renders a diagram using explicit universal options. This route is strict:
        unknown or invalid options return HTTP 400 instead of being treated like
        ignorable inline comments. The Gateway authenticates and rate-limits
        this route, then proxies it without using the Gateway result cache.
      operationId: renderWithUniversalApi
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UniversalRenderRequest"
            examples:
              vegaliteSvg:
                value:
                  diagramType: vegalite
                  format: svg
                  source: '{"mark":"bar","data":{"values":[{"a":"A","b":1}]}}'
                  options:
                    theme: corporate
                    scale: 2
      responses:
        "200":
          $ref: "#/components/responses/RenderedDiagram"
        "204":
          description: Render request produced no diagram body.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/UnsupportedRenderer"
        "429":
          $ref: "#/components/responses/RateLimited"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "502":
          $ref: "#/components/responses/BadGateway"
        "504":
          $ref: "#/components/responses/GatewayTimeout"

  /{engine}/{format}:
    post:
      tags: [Rendering]
      summary: Render source with Kroki-compatible POST route
      description: >
        Renders plain source for a selected engine and output format. Universal
        options may be supplied by inline directive, query string, or
        kroki-diagram-options-* headers. Dynamic option headers are documented
        by naming convention because OpenAPI cannot enumerate arbitrary header
        names.
      operationId: renderByEngineAndFormat
      security:
        - bearerAuth: []
      parameters:
        - $ref: "#/components/parameters/Engine"
        - $ref: "#/components/parameters/Format"
        - $ref: "#/components/parameters/Background"
        - $ref: "#/components/parameters/Accept"
        - $ref: "#/components/parameters/RequestId"
      requestBody:
        required: true
        content:
          text/plain:
            schema:
              type: string
              description: Diagram source text.
          application/json:
            schema:
              $ref: "#/components/schemas/KrokiPathRenderRequest"
          application/xml:
            schema:
              type: string
              description: Raw XML diagram source, not a JSON-style envelope.
      responses:
        "200":
          $ref: "#/components/responses/RenderedDiagram"
        "204":
          description: Render request produced no diagram body.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/UnsupportedRenderer"
        "429":
          $ref: "#/components/responses/RateLimited"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "502":
          $ref: "#/components/responses/BadGateway"
        "504":
          $ref: "#/components/responses/GatewayTimeout"

  /{engine}/{format}/{encodedSource}:
    get:
      tags: [Rendering]
      summary: Render encoded Kroki source with GET route
      description: >
        Public Kroki-compatible encoded render route used by Markdown, GitLab,
        and other clients. The encoded source is zlib-compressed and URL-safe
        base64 encoded. When the optional pydia renderer is enabled, this public
        route can execute submitted Python inside the isolated Pydia sandbox;
        operators must treat network and resource confinement as mandatory.
      operationId: renderEncodedSource
      parameters:
        - $ref: "#/components/parameters/Engine"
        - $ref: "#/components/parameters/Format"
        - name: encodedSource
          in: path
          required: true
          description: URL-safe Kroki encoded diagram source.
          schema:
            type: string
            pattern: "^[A-Za-z0-9_-]+={0,2}$"
        - $ref: "#/components/parameters/Background"
        - $ref: "#/components/parameters/Accept"
        - $ref: "#/components/parameters/RequestId"
      responses:
        "200":
          $ref: "#/components/responses/RenderedDiagram"
        "304":
          description: Client cache is still valid.
          headers:
            ETag:
              schema:
                type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/UnsupportedRenderer"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          $ref: "#/components/responses/BadGateway"
        "504":
          $ref: "#/components/responses/GatewayTimeout"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: shared-rendering-token
      description: Shared rendering token configured with RENDER_API_KEYS.

  parameters:
    Engine:
      name: engine
      in: path
      required: true
      description: Registered renderer type.
      schema:
        $ref: "#/components/schemas/Engine"
    Format:
      name: format
      in: path
      required: true
      description: Requested output format.
      schema:
        $ref: "#/components/schemas/PathOutputFormat"
    Background:
      name: background
      in: query
      required: false
      description: Gateway-level export background override.
      schema:
        type: string
        enum: [white, black, transparent, "#ffffff", "#000000"]
    Accept:
      name: Accept
      in: header
      required: false
      schema:
        type: string
      example: image/svg+xml
    RequestId:
      name: X-Request-Id
      in: header
      required: false
      schema:
        type: string
      description: Optional request correlation id forwarded to the core service.
  responses:
    RenderedDiagram:
      description: Rendered diagram output.
      headers:
        Content-Type:
          schema:
            type: string
        ETag:
          schema:
            type: string
        Cache-Control:
          schema:
            type: string
        X-Cache:
          description: >
            Present only for POST /{engine}/{format} and encoded GET routes.
          schema:
            type: string
            enum: [HIT, MISS, COALESCED]
        X-Diagram-Pipeline:
          schema:
            type: string
        X-Diagram-Renderer:
          schema:
            type: string
        X-Diagram-Adapter:
          schema:
            type: string
        X-Diagram-Warnings:
          schema:
            type: string
      content:
        image/svg+xml:
          schema:
            type: string
        image/png:
          schema:
            type: string
            format: binary
        image/jpeg:
          schema:
            type: string
            format: binary
        application/pdf:
          schema:
            type: string
            format: binary
        text/plain:
          schema:
            type: string
    GatewayHealthOk:
      description: Gateway can reach the core service.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/GatewayHealthResponse"
    GatewayHealthUnavailable:
      description: Gateway cannot reach the core service.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/GatewayHealthResponse"
    CoreHealthOk:
      description: Core service is available and reports component versions.
      content:
        application/health+json:
          schema:
            $ref: "#/components/schemas/CoreHealthResponse"
    BadRequest:
      description: Invalid request.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
        text/plain:
          schema:
            type: string
    Unauthorized:
      description: Missing or invalid rendering API token.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    UnsupportedRenderer:
      description: Renderer or output format is not supported.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
        text/plain:
          schema:
            type: string
    RateLimited:
      description: Rate limit exceeded.
      headers:
        Retry-After:
          schema:
            type: string
        X-RateLimit-Limit:
          schema:
            type: string
        X-RateLimit-Remaining:
          schema:
            type: string
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    PayloadTooLarge:
      description: Request body exceeded the Gateway limit.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    BadGateway:
      description: Core is unavailable or its response exceeded the Gateway limit.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    GatewayTimeout:
      description: Core or renderer did not complete before the Gateway timeout.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  schemas:
    Engine:
      type: string
      description: >
        Engine identifiers accepted by Gateway render paths. Runtime availability
        remains authoritative through GET /api/diagram-types. The pydia engine is
        opt-in and is registered only when its isolated sandbox is enabled.
      enum:
        - plantuml
        - c4plantuml
        - mermaid
        - graphviz
        - dot
        - d2
        - structurizr
        - blockdiag
        - seqdiag
        - actdiag
        - nwdiag
        - packetdiag
        - rackdiag
        - bpmn
        - dbml
        - diagramsnet
        - ditaa
        - erd
        - excalidraw
        - goat
        - nomnoml
        - pikchr
        - svgbob
        - symbolator
        - umlet
        - vega
        - vegalite
        - wavedrom
        - bytefield
        - wireviz
        - tikz
        - pydia

    PathOutputFormat:
      type: string
      enum: [svg, png, pdf, jpeg, txt]
      description: Output formats accepted in Gateway render paths.

    OutputFormat:
      type: string
      enum: [svg, png, pdf, jpeg, base64, txt, utxt]
      description: >
        Output formats available to JSON APIs and renderer metadata. Each
        renderer supports only the formats listed in its runtime profile.

    UniversalOptions:
      type: object
      description: Universal render options validated by OptionSchema.
      additionalProperties: false
      properties:
        direction:
          type: string
          enum: [lr, rl, tb, bt]
        layout:
          type: string
          minLength: 1
          maxLength: 160
          pattern: '^[^\x00-\x1F\x7F]+$'
        rank-separation:
          type: number
          minimum: 0
          maximum: 1000
        node-separation:
          type: number
          minimum: 0
          maximum: 1000
        edge-routing:
          type: string
          enum: [auto, straight, orthogonal, curved]
        orientation:
          type: string
          enum: [portrait, landscape]
        theme:
          type: string
          minLength: 1
          maxLength: 160
          pattern: '^[^\x00-\x1F\x7F]+$'
          examples: [corporate, light, dark]
        font-family:
          type: string
          minLength: 1
          maxLength: 160
          pattern: '^[^\x00-\x1F\x7F]+$'
        font-size:
          type: number
          minimum: 1
          maximum: 256
        font-weight:
          type: string
          enum: [normal, bold, "100", "200", "300", "400", "500", "600", "700", "800", "900"]
        primary-color:
          $ref: "#/components/schemas/Color"
        secondary-color:
          $ref: "#/components/schemas/Color"
        accent-color:
          $ref: "#/components/schemas/Color"
        background:
          $ref: "#/components/schemas/Color"
        text-color:
          $ref: "#/components/schemas/Color"
        line-color:
          $ref: "#/components/schemas/Color"
        border-color:
          $ref: "#/components/schemas/Color"
        border-width:
          type: number
          minimum: 0
          maximum: 100
        border-radius:
          type: number
          minimum: 0
          maximum: 1000
        padding:
          type: number
          minimum: 0
          maximum: 1000
        spacing:
          type: number
          minimum: 0
          maximum: 1000
        scale:
          type: number
          minimum: 0.25
          maximum: 8
        width:
          type: integer
          minimum: 1
          maximum: 32768
        height:
          type: integer
          minimum: 1
          maximum: 32768
        max-width:
          type: integer
          minimum: 1
          maximum: 32768
        max-height:
          type: integer
          minimum: 1
          maximum: 32768
        transparent:
          type: boolean
        format:
          type: string
          enum: [svg, png, jpeg, pdf, base64, txt, utxt]
        quality:
          type: number
          minimum: 0
          maximum: 1
        density:
          type: integer
          minimum: 1
          maximum: 1200

    Color:
      type: string
      pattern: "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"
      examples:
        - "#ffffff"
        - "#0b1220"

    UniversalRenderRequest:
      type: object
      description: >
        Camel-case fields take precedence when both a camel-case field and its
        snake-case alias are present. New clients should use camel case.
      additionalProperties: false
      allOf:
        - anyOf:
            - required: [diagramType]
            - required: [diagram_type]
        - anyOf:
            - required: [source]
            - required: [diagram_source]
      properties:
        diagramType:
          $ref: "#/components/schemas/Engine"
        diagram_type:
          $ref: "#/components/schemas/Engine"
        format:
          $ref: "#/components/schemas/OutputFormat"
          default: svg
        output_format:
          $ref: "#/components/schemas/OutputFormat"
        source:
          type: string
          minLength: 1
        diagram_source:
          type: string
          minLength: 1
        options:
          $ref: "#/components/schemas/UniversalOptions"

    ValidateOptionsRequest:
      type: object
      description: >
        diagramType takes precedence when both diagramType and diagram_type are
        present. New clients should use diagramType.
      additionalProperties: false
      anyOf:
        - required: [diagramType]
        - required: [diagram_type]
      properties:
        diagramType:
          $ref: "#/components/schemas/Engine"
        diagram_type:
          $ref: "#/components/schemas/Engine"
        options:
          $ref: "#/components/schemas/UniversalOptions"

    ValidateOptionsResponse:
      type: object
      required: [valid, renderer, resolvedOptions, warnings]
      additionalProperties: false
      properties:
        valid:
          type: boolean
        renderer:
          type: string
          description: Renderer type used to resolve capabilities.
        resolvedOptions:
          $ref: "#/components/schemas/UniversalOptions"
        warnings:
          type: array
          items:
            type: string

    KrokiPathRenderRequest:
      type: object
      required: [diagram_source]
      additionalProperties: true
      properties:
        diagram_source:
          type: string
          minLength: 1
        diagram_options:
          type: object
          additionalProperties: true
        output_format:
          $ref: "#/components/schemas/OutputFormat"
          description: >
            Optional compatibility field. The format in the request path takes
            precedence.

    KrokiEnvelopeRenderRequest:
      type: object
      required: [diagram_source, diagram_type, output_format]
      additionalProperties: true
      properties:
        diagram_source:
          type: string
          minLength: 1
        diagram_type:
          $ref: "#/components/schemas/Engine"
        output_format:
          $ref: "#/components/schemas/OutputFormat"
        diagram_options:
          type: object
          additionalProperties: true

    DiagramTypesResponse:
      type: object
      required: [renderers, count, pipelineVersion]
      properties:
        renderers:
          type: array
          items:
            $ref: "#/components/schemas/RendererProfile"
        count:
          type: integer
        pipelineVersion:
          type: string

    RendererProfile:
      type: object
      required:
        - type
        - aliases
        - formats
        - supportsGet
        - supportsPost
        - directiveSyntax
        - directiveTransport
        - adapter
        - adapterFamily
        - capabilities
      additionalProperties: false
      properties:
        type:
          $ref: "#/components/schemas/Engine"
        aliases:
          type: array
          items:
            type: string
        formats:
          type: array
          items:
            $ref: "#/components/schemas/OutputFormat"
        supportsGet:
          type: boolean
        supportsPost:
          type: boolean
        directiveSyntax:
          type: string
        directiveTransport:
          type: string
        adapter:
          type: string
        adapterFamily:
          type: string
        capabilities:
          type: object
          additionalProperties:
            type: string
            enum: [native, preprocess, postprocess, unsupported]

    ThemesResponse:
      type: object
      required: [themes]
      additionalProperties: false
      properties:
        themes:
          type: array
          items:
            $ref: "#/components/schemas/Theme"

    Theme:
      type: object
      required: [id, version, displayName]
      additionalProperties: false
      properties:
        id:
          type: string
        version:
          type: integer
        displayName:
          type: string

    GatewayHealthResponse:
      type: object
      required: [status]
      additionalProperties: false
      properties:
        status:
          type: string
          examples: [ok, unavailable]
          enum: [ok, unavailable]

    CoreHealthResponse:
      type: object
      required: [status, version]
      additionalProperties: true
      properties:
        status:
          type: string
          const: pass
        version:
          type: object
          additionalProperties: true

    ErrorResponse:
      type: object
      required: [error]
      additionalProperties: false
      properties:
        error:
          oneOf:
            - type: string
              description: Error generated by the Gateway.
            - $ref: "#/components/schemas/CoreErrorDetail"

    CoreErrorDetail:
      type: object
      required: [code, message]
      additionalProperties: false
      properties:
        code:
          type: integer
          description: HTTP status code.
        message:
          type: string
        diagramCode:
          type: string
        line:
          type: integer
