{
  "openapi": "3.0.3",
  "info": {
    "title": "Masky Avatar Speak API",
    "version": "1.0.0",
    "description": "Avatar TTS and optional lipsynced talking video. Firebase Bearer auth."
  },
  "servers": [{ "url": "https://masky.ai/api" }],
  "paths": {
    "/avatars/{avatarId}/speak": {
      "post": {
        "summary": "Start avatar speech / video generation",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          {
            "name": "avatarId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["text"],
                "properties": {
                  "text": { "type": "string", "maxLength": 500 },
                  "textMode": {
                    "type": "string",
                    "enum": ["literal", "personality"],
                    "default": "literal"
                  },
                  "output": {
                    "type": "string",
                    "enum": ["audio", "video"],
                    "default": "audio"
                  },
                  "avatarImageUrl": {
                    "type": "string",
                    "description": "Optional. Exact still-image URL from this avatar group's assets or group avatarUrl; must match Firestore. If omitted, server picks group default then first asset."
                  },
                  "avatarOwnerUserId": {
                    "type": "string",
                    "description": "Optional. Firebase user id that owns the avatarGroups doc (e.g. streamer page owner). Defaults to the authenticated user. Required when using another user's avatars (viewer redeems on a streamer's page)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Reused or completed inline" },
          "202": { "description": "Job accepted; poll Firestore or GET" },
          "400": { "description": "Bad request" },
          "401": { "description": "Unauthorized" },
          "402": { "description": "Insufficient credits" },
          "404": { "description": "Avatar not found" }
        }
      }
    },
    "/avatars/speak/{generationId}": {
      "get": {
        "summary": "Get generation + signed URLs",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          {
            "name": "generationId",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": { "description": "OK" },
          "401": { "description": "Unauthorized" },
          "404": { "description": "Not found" }
        }
      }
    },
    "/g-{slug}": {
      "get": {
        "summary": "Short share link (public): redirect to signed media",
        "description": "Increments play counters keyed by Referer (or ?from= / ?ref=). Slug format: g- plus YY-MM- plus four [a-z0-9] characters.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^\\d{2}-\\d{2}-[a-z0-9]{4}$",
              "example": "26-05-ab12"
            },
            "description": "Path is /g-{slug}, e.g. /g-26-05-ab12"
          }
        ],
        "responses": {
          "302": { "description": "Redirect to signed read URL for video or audio" },
          "400": { "description": "Invalid slug shape" },
          "404": { "description": "Unknown slug or no playable media" },
          "502": { "description": "Could not sign storage URL" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Firebase ID token"
      }
    }
  }
}
