openapi: 3.1.0
info:
  title: MasiDyn Native API
  version: "0.masidyn-1.3"
  summary: Local-first, standard chat-completions contract served by the MasiDyn native GGUF router.
  description: >
    MasiDyn owns the process, identity, model registry, storage, routing, and lifecycle.
    Generation and embeddings run through local native binaries; no separate model daemon
    is required. The whole surface is five endpoints. Responses are bound to the running
    process via X-Masidyn-Instance-Id and X-Masidyn-Boot-Id headers.
    Source of truth: agnostic/masidyn_server.py.
  license:
    name: Operator-defined
servers:
  - url: http://127.0.0.1:11434
    description: Canonical local endpoint
  - url: http://127.0.0.1:11435
    description: Optional isolated development port
tags:
  - name: system
  - name: models
  - name: inference
paths:
  /health:
    get:
      tags: [system]
      summary: Liveness and native readiness
      operationId: getHealth
      responses:
        "200":
          description: Server is up.
          content:
            application/json:
              schema:
                type: object
                required: [status, product, native_ready]
                properties:
                  status: { type: string, example: ok }
                  product: { type: string, example: masidyn }
                  native_ready: { type: boolean }
  /v1/identity:
    get:
      tags: [system]
      summary: Bound runtime identity
      operationId: getIdentity
      responses:
        "200":
          description: The exact identity this process is bound to.
          headers:
            X-Masidyn-Instance-Id: { schema: { type: string } }
            X-Masidyn-Boot-Id: { schema: { type: string } }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Identity" }
  /v1/models:
    get:
      tags: [models]
      summary: List installed models
      operationId: listModels
      responses:
        "200":
          description: Standard-shaped model list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  object: { type: string, example: list }
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string, example: masidyn-word:latest }
                        object: { type: string, example: model }
                        owned_by: { type: string, example: masidyn }
  /v1/chat/completions:
    post:
      tags: [inference]
      summary: Chat / completion (native dialect)
      operationId: createChatCompletion
      parameters:
        - in: header
          name: X-Masidyn-Evidence-Mode
          required: false
          schema: { type: string, enum: [frozen-v1] }
          description: Opt into frozen-evidence mode (non-streaming only).
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ChatRequest" }
      responses:
        "200":
          description: A completion, or an SSE stream when stream=true.
          headers:
            X-Masidyn-Instance-Id: { schema: { type: string } }
            X-Masidyn-Boot-Id: { schema: { type: string } }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ChatResponse" }
            text/event-stream:
              schema:
                type: string
                description: >
                  Sequence of `data: {chat.completion.chunk}` events terminated by
                  `data: [DONE]`. Each chunk carries choices[].delta.content.
        "400": { $ref: "#/components/responses/RequestError" }
        "404": { $ref: "#/components/responses/RequestError" }
  /v1/embeddings:
    post:
      tags: [inference]
      summary: Text embeddings
      operationId: createEmbeddings
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/EmbeddingRequest" }
      responses:
        "200":
          description: One 768-dim vector per input.
          headers:
            X-Masidyn-Embed-Backend:
              schema: { type: string, enum: [native, feature-hash] }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/EmbeddingResponse" }
        "400": { $ref: "#/components/responses/RequestError" }
        "404": { $ref: "#/components/responses/RequestError" }
components:
  responses:
    RequestError:
      description: Validation or routing error.
      content:
        application/json:
          schema:
            type: object
            properties:
              error: { type: string }
  schemas:
    Identity:
      type: object
      properties:
        object: { type: string, example: masidyn.identity }
        product: { type: string, example: masidyn }
        version: { type: string, example: "0.masidyn-1.3" }
        instance_id: { type: string }
        boot_id: { type: string }
        server_source_sha256: { type: string }
        backend_sha256: { type: string, nullable: true }
        native_ready: { type: boolean }
        default_model: { type: string, example: masidyn-word:latest }
        endpoints:
          type: object
          additionalProperties: { type: string }
        tool_protocol:
          type: object
          properties:
            name: { type: string, example: masidyn-tool-envelope }
            version: { type: integer, example: 1 }
            max_calls_per_response: { type: integer, example: 1 }
    ChatMessage:
      type: object
      required: [role, content]
      properties:
        role: { type: string, enum: [system, user, assistant, tool] }
        content: { type: string }
    ChatRequest:
      type: object
      required: [model, messages]
      properties:
        model: { type: string, example: masidyn-word:latest }
        messages:
          type: array
          items: { $ref: "#/components/schemas/ChatMessage" }
        temperature: { type: number }
        max_tokens: { type: integer, description: Alias mapped to num_predict. }
        num_predict: { type: integer }
        num_ctx: { type: integer, description: Requested context window. }
        stream: { type: boolean, default: false }
        tools:
          type: array
          description: Optional tool definitions (masidyn-tool-envelope).
          items: { type: object }
    ChatResponse:
      type: object
      properties:
        id: { type: string, example: chatcmpl-masidyn-1737331200 }
        object: { type: string, example: chat.completion }
        created: { type: integer }
        model: { type: string }
        choices:
          type: array
          items:
            type: object
            properties:
              index: { type: integer }
              message: { $ref: "#/components/schemas/ChatMessage" }
              finish_reason: { type: string, example: stop }
        usage:
          type: object
          properties:
            prompt_tokens: { type: integer }
            completion_tokens: { type: integer }
            total_tokens: { type: integer }
    EmbeddingRequest:
      type: object
      required: [model, input]
      properties:
        model: { type: string, example: nomic-embed-text }
        input:
          oneOf:
            - { type: string }
            - { type: array, items: { type: string } }
    EmbeddingResponse:
      type: object
      properties:
        object: { type: string, example: list }
        data:
          type: array
          items:
            type: object
            properties:
              object: { type: string, example: embedding }
              index: { type: integer }
              embedding:
                type: array
                items: { type: number }
                description: 768 floats.
        model: { type: string }
        usage:
          type: object
          properties:
            prompt_tokens: { type: integer }
            total_tokens: { type: integer }
