{
  "openapi": "3.1.0",
  "info": {
    "title": "VoxCraft Local Wi-Fi API",
    "version": "1.0.0",
    "description": "Local voice, transcription, and chat inference through the VoxCraft REST/JSON API server. Replace the example LAN host with the address shown in Device & Wi-Fi. Browser-origin requests are rejected by the API server."
  },
  "servers": [
    {
      "url": "http://192.168.1.10:4177",
      "description": "Example only; use the host address shown in the studio."
    }
  ],
  "security": [
    {
      "BearerToken": []
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Check whether the local studio is ready",
        "responses": {
          "200": {
            "description": "Host heartbeat and pending queue count.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "example": {
                  "ready": true,
                  "pending": 0
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/chat": {
      "post": {
        "operationId": "createChatResponse",
        "summary": "Generate a chat response",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatRequest"
              },
              "example": {
                "messages": [
                  {
                    "role": "user",
                    "content": "Write a friendly welcome."
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Generated text with exact input and output token counts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatResponse"
                },
                "example": {
                  "text": "Welcome! We’re glad you’re here.",
                  "inputTokens": 42,
                  "outputTokens": 9
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/QueueFull"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/TimedOut"
          }
        }
      }
    },
    "/api/transcribe": {
      "post": {
        "operationId": "transcribeAudio",
        "summary": "Transcribe a local audio file",
        "description": "Base64-encode a browser-supported audio file up to 100 MB. The API accepts up to 180 MiB of JSON for this endpoint to accommodate Base64 expansion.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TranscribeRequest"
              },
              "example": {
                "audioBase64": "REPLACE_WITH_BASE64_ENCODED_AUDIO",
                "model": "tiny",
                "language": "km"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transcript and timestamped segments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscribeResponse"
                },
                "example": {
                  "text": "សួស្តី។",
                  "segments": [
                    {
                      "text": "សួស្តី។",
                      "start": 0.1,
                      "end": 0.9
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/QueueFull"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/TimedOut"
          }
        }
      }
    },
    "/api/speech": {
      "post": {
        "operationId": "generateSpeech",
        "summary": "Generate a WAV voice recording and SRT captions",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SpeechRequest"
              },
              "example": {
                "text": "សួស្តី!",
                "mode": "read",
                "style": "A warm, gentle voice",
                "cfg": 2,
                "steps": 10,
                "seed": 42
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Base64-encoded audio/WAV and SRT caption data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SpeechResponse"
                },
                "example": {
                  "audioBase64": "BASE64_ENCODED_WAV_DATA",
                  "subtitleBase64": "BASE64_ENCODED_SRT_DATA",
                  "mimeType": "audio/wav",
                  "sampleRate": 48000
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/QueueFull"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          },
          "504": {
            "$ref": "#/components/responses/TimedOut"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerToken": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Random per-start token"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "Health": {
        "type": "object",
        "required": [
          "ready",
          "pending"
        ],
        "properties": {
          "ready": {
            "type": "boolean"
          },
          "pending": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "ChatMessage": {
        "type": "object",
        "required": [
          "role",
          "content"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "user",
              "assistant"
            ]
          },
          "content": {
            "type": "string"
          }
        }
      },
      "ChatRequest": {
        "type": "object",
        "required": [
          "messages"
        ],
        "properties": {
          "messages": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            }
          }
        }
      },
      "ChatResponse": {
        "type": "object",
        "required": [
          "text",
          "inputTokens",
          "outputTokens"
        ],
        "properties": {
          "text": {
            "type": "string"
          },
          "inputTokens": {
            "type": "integer",
            "minimum": 0
          },
          "outputTokens": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "TranscribeRequest": {
        "type": "object",
        "required": [
          "audioBase64"
        ],
        "properties": {
          "audioBase64": {
            "type": "string",
            "maxLength": 139810200,
            "description": "Base64-encoded audio file, up to 100 MB before encoding."
          },
          "model": {
            "type": "string",
            "enum": [
              "tiny",
              "base",
              "edge"
            ],
            "default": "tiny"
          },
          "language": {
            "type": "string",
            "enum": [
              "en",
              "zh"
            ],
            "default": "en",
            "description": "English or Chinese speech."
          }
        }
      },
      "TranscriptSegment": {
        "type": "object",
        "required": [
          "text",
          "start",
          "end"
        ],
        "properties": {
          "text": {
            "type": "string"
          },
          "start": {
            "type": "number",
            "minimum": 0
          },
          "end": {
            "type": "number",
            "minimum": 0
          }
        }
      },
      "TranscribeResponse": {
        "type": "object",
        "required": [
          "text",
          "segments"
        ],
        "properties": {
          "text": {
            "type": "string"
          },
          "segments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TranscriptSegment"
            }
          }
        }
      },
      "SpeechRequest": {
        "type": "object",
        "required": [
          "text",
          "referenceBase64"
        ],
        "allOf": [
          {
            "if": {
              "properties": {
                "mode": {
                  "const": "clone"
                }
              },
              "required": [
                "mode"
              ]
            },
            "then": {
              "required": [
                "referenceBase64",
                "rightsConfirmed"
              ],
              "properties": {
                "rightsConfirmed": {
                  "const": true
                }
              }
            }
          }
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "One take; the loaded model tokenizer enforces its 512-token budget including speaker features."
          },
          "mode": {
            "type": "string",
            "enum": [
              "read",
              "clone"
            ],
            "default": "read",
            "description": "Manon saved speaker in read mode or a permission-confirmed uploaded speaker in clone mode."
          },
          "cfg": {
            "type": "number",
            "minimum": 1,
            "maximum": 3,
            "default": 2
          },
          "steps": {
            "type": "integer",
            "minimum": 4,
            "maximum": 20,
            "default": 10
          },
          "seed": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2147483647,
            "default": 42
          },
          "referenceBase64": {
            "type": "string",
            "maxLength": 1800000,
            "description": "Required for read mode with the saved Manon default; required for clone mode. Float32 speaker samples encoded as Base64, 3–20 seconds."
          },
          "rightsConfirmed": {
            "type": "boolean",
            "description": "Must be true for clone mode, confirming the caller owns or is authorized to use the recording."
          }
        }
      },
      "SpeechResponse": {
        "type": "object",
        "required": [
          "audioBase64",
          "subtitleBase64",
          "mimeType",
          "sampleRate"
        ],
        "properties": {
          "audioBase64": {
            "type": "string",
            "description": "Base64-encoded WAV data."
          },
          "subtitleBase64": {
            "type": "string",
            "description": "Base64-encoded SRT subtitle data."
          },
          "mimeType": {
            "type": "string",
            "const": "audio/wav"
          },
          "sampleRate": {
            "type": "integer",
            "const": 48000
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid JSON, content type, or request fields.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Provide 1–100 messages."
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid bearer token.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Valid bearer token required."
            }
          }
        }
      },
      "Forbidden": {
        "description": "Request host or browser Origin is not allowed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Use the displayed API address from a trusted local client."
            }
          }
        }
      },
      "QueueFull": {
        "description": "The task queue already has three requests.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Queue full. Try again later."
            }
          }
        }
      },
      "Unavailable": {
        "description": "The local studio heartbeat has expired or the API stopped.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Open the local VoxCraft studio first."
            }
          }
        }
      },
      "TimedOut": {
        "description": "Inference did not complete within ten minutes.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Request timed out. Load the model in the studio, then retry."
            }
          }
        }
      }
    }
  }
}
