openapi: 3.1.0
info:
  title: CodeVox Local Transcription API
  version: "1.0.0"
  description: >
    Local, on-device, OpenAI-Whisper-compatible transcription API exposed by the
    CodeVox macOS app. Runs on loopback by default (LAN is opt-in). No audio
    leaves the machine. See https://codevox.io/docs/api for the guide.
servers:
  - url: http://127.0.0.1:5273/v1
    description: Default loopback server (port configurable in the app)
security: []
paths:
  /audio/transcriptions:
    post:
      operationId: createTranscription
      summary: Transcribe an audio file on-device (OpenAI-compatible)
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: Audio file (wav, mp3, m4a/aac, aiff, caf, mp4/mov audio).
                model:
                  type: string
                  description: Accepted and reported back as "codevox-parakeet".
                language:
                  type: string
                  description: Optional language hint.
                response_format:
                  type: string
                  enum: [json, text, srt, vtt, verbose_json]
                  default: json
                temperature:
                  type: number
                  description: Accepted as a no-op (greedy decode).
                prompt:
                  type: string
                  description: Accepted as a no-op.
      responses:
        "200":
          description: Transcription result in the requested format.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Transcription"
            text/plain:
              schema: { type: string }
        "400": { $ref: "#/components/responses/Error" }
        "401": { $ref: "#/components/responses/Error" }
        "402": { $ref: "#/components/responses/Error" }
        "413": { $ref: "#/components/responses/Error" }
        "429":
          description: Recognizer busy; retry after the indicated delay.
          headers:
            Retry-After:
              schema: { type: integer }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
  /models:
    get:
      operationId: listModels
      summary: List available models
      responses:
        "200":
          description: 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: codevox-parakeet }
                        object: { type: string, example: model }
                        owned_by: { type: string, example: codevox }
  /events:
    get:
      operationId: streamEvents
      summary: Server-Sent Events stream of transcription events
      description: >
        Streams `data:` frames for partial and final transcripts from both
        hotkey dictation and API jobs. Compute-tier: authless on loopback, token
        required over LAN.
      responses:
        "200":
          description: text/event-stream of TranscriptionEvent objects.
          content:
            text/event-stream:
              schema: { $ref: "#/components/schemas/TranscriptionEvent" }
        "401": { $ref: "#/components/responses/Error" }
        "402": { $ref: "#/components/responses/Error" }
  /control/inject:
    post:
      operationId: injectText
      summary: Inject text into the focused app (control-tier, token required)
      security: [{ bearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [text]
              properties:
                text: { type: string }
      responses:
        "200": { description: Injected. }
        "401": { $ref: "#/components/responses/Error" }
        "422": { $ref: "#/components/responses/Error" }
  /control/mic/start:
    post:
      operationId: startMicCapture
      summary: Start an API-driven mic capture (control-tier, token required)
      security: [{ bearerAuth: [] }]
      responses:
        "200": { description: Capture started; results flow to /events. }
        "401": { $ref: "#/components/responses/Error" }
        "409": { $ref: "#/components/responses/Error" }
  /control/mic/stop:
    post:
      operationId: stopMicCapture
      summary: Stop an API-driven mic capture (control-tier, token required)
      security: [{ bearerAuth: [] }]
      responses:
        "200":
          description: Capture stopped; final transcript returned.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  text: { type: string }
        "401": { $ref: "#/components/responses/Error" }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  responses:
    Error:
      description: OpenAI-shaped error envelope.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
  schemas:
    Transcription:
      type: object
      properties:
        text: { type: string }
    TranscriptionEvent:
      type: object
      properties:
        text: { type: string }
        isFinal: { type: boolean }
        source: { type: string, enum: [hotkey, api] }
        timestamp: { type: number }
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            message: { type: string }
            type: { type: string }
            param: { type: [string, "null"] }
            code: { type: [string, "null"] }
