{
  "info": {
    "name": "TTS Server · XTTS-v2 API",
    "description": "API pública compatible con OpenAI para síntesis de voz Coqui XTTS-v2. Importa esta colección y el entorno en mock.ceballosleon.com (Mock/GetMan). Cada petición incluye ejemplos de respuesta. Configura tts_api_key con una clave creada por el administrador de TTS Server. POST /v1/audio/speech responde audio binario; GET /v1/voices lista voces disponibles.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [{ "key": "token", "value": "{{tts_api_key}}", "type": "string" }]
  },
  "variable": [
    { "key": "base_url", "value": "https://tts.ceballosleon.com", "type": "string" },
    { "key": "tts_api_key", "value": "PEGA_AQUI_TU_TTS_API_KEY", "type": "string" },
    { "key": "voice_id", "value": "builtin:VOICE_NAME", "type": "string" },
    { "key": "speech_job_id", "value": "PEGA_AQUI_EL_JOB_ID", "type": "string" }
  ],
  "item": [
    {
      "name": "Servicio",
      "item": [
        {
          "name": "Health check",
          "request": {
            "method": "GET",
            "description": "Comprueba que XTTS-v2 está activo. No requiere autenticación.",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/health" },
            "auth": { "type": "noauth" }
          },
          "response": [
            {
              "name": "Servicio saludable (200)",
              "originalRequest": { "method": "GET", "header": [{ "key": "Accept", "value": "application/json" }], "url": { "raw": "{{base_url}}/health" }, "auth": { "type": "noauth" } },
              "status": "OK", "code": 200, "_postman_previewlanguage": "json",
              "header": [{ "key": "Content-Type", "value": "application/json" }], "cookie": [],
              "body": "{\n  \"ok\": true,\n  \"engine\": \"xtts_v2\"\n}"
            }
          ]
        },
        {
          "name": "Listar modelos",
          "request": {
            "method": "GET",
            "description": "Lista modelos TTS. Requiere una API key activa en Authorization: Bearer.",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/v1/models" }
          },
          "response": [
            {
              "name": "Modelos disponibles (200)",
              "originalRequest": { "method": "GET", "header": [{ "key": "Accept", "value": "application/json" }], "url": { "raw": "{{base_url}}/v1/models" } },
              "status": "OK", "code": 200, "_postman_previewlanguage": "json",
              "header": [{ "key": "Content-Type", "value": "application/json" }], "cookie": [],
              "body": "{\n  \"object\": \"list\",\n  \"data\": [\n    { \"id\": \"tts-1\", \"object\": \"model\", \"owned_by\": \"tts-server\" },\n    { \"id\": \"xtts-v2\", \"object\": \"model\", \"owned_by\": \"coqui\" }\n  ]\n}"
            },
            {
              "name": "Token inválido (401)",
              "originalRequest": { "method": "GET", "header": [{ "key": "Accept", "value": "application/json" }], "url": { "raw": "{{base_url}}/v1/models" } },
              "status": "Unauthorized", "code": 401, "_postman_previewlanguage": "json",
              "header": [{ "key": "Content-Type", "value": "application/json" }], "cookie": [],
              "body": "{\n  \"error\": {\n    \"message\": \"Inicia sesión o envía un Bearer token válido.\",\n    \"type\": \"authentication_error\"\n  }\n}"
            }
          ]
        },
        {
          "name": "Listar voces",
          "request": {
            "method": "GET",
            "description": "Lista voces integradas y clonadas. Cada voz integrada incluye gender (f, m o u = sin clasificar) para agruparlas en femeninas, masculinas y otras. La prueba guarda la primera voz devuelta en la variable voice_id para usarla en Generar audio. Requiere Bearer token.",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/v1/voices" }
          },
          "response": [
            {
              "name": "Voces disponibles (200)",
              "originalRequest": { "method": "GET", "header": [{ "key": "Accept", "value": "application/json" }], "url": { "raw": "{{base_url}}/v1/voices" } },
              "status": "OK", "code": 200, "_postman_previewlanguage": "json",
              "header": [{ "key": "Content-Type", "value": "application/json" }], "cookie": [],
              "body": "{\n  \"data\": [\n    { \"id\": \"builtin:Ana Florence\", \"name\": \"Ana Florence\", \"kind\": \"builtin\", \"gender\": \"f\" },\n    { \"id\": \"cloned:mi_voz\", \"name\": \"mi_voz\", \"kind\": \"cloned\", \"created_at\": 1790706600.0 }\n  ]\n}"
            }
          ],
          "event": [{
            "listen": "test",
            "script": { "type": "text/javascript", "exec": [
              "if (pm.response.code === 200) {",
              "  const result = pm.response.json();",
              "  if (result.data?.[0]?.id) pm.collectionVariables.set('voice_id', result.data[0].id);",
              "}"
            ] }
          }]
        }
      ]
    },
    {
      "name": "Síntesis de voz",
      "item": [
        {
          "name": "Generar audio (OpenAI compatible)",
          "request": {
            "method": "POST",
            "description": "El body requiere input y voice. Ejecuta antes Listar voces para completar voice_id automáticamente. model es tts-1, tts-1-hd o xtts-v2. language usa un código XTTS como es, en, fr, de o ja. speed acepta 0.5–2. response_format: mp3, wav, opus, aac, flac o pcm (predeterminado mp3). Devuelve audio binario; el ejemplo guardado muestra Content-Type y X-Request-Id, no incluye los bytes del audio. Si el texto es largo (estimación superior a ~80 s) o envías \"async\": true, responde 202 con un job-id: consulta Consultar trabajo hasta que status sea done y descarga con Descargar audio del trabajo. Las claves tienen límites por minuto, día y caracteres configurados por el administrador.",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Accept", "value": "audio/mpeg" }
            ],
            "url": { "raw": "{{base_url}}/v1/audio/speech" },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"model\": \"xtts-v2\",\n  \"input\": \"Hola, esta es una prueba de síntesis de voz.\",\n  \"voice\": \"{{voice_id}}\",\n  \"language\": \"es\",\n  \"speed\": 1,\n  \"response_format\": \"mp3\"\n}",
              "options": { "raw": { "language": "json" } }
            }
          },
          "response": [
            {
              "name": "Audio generado (200)",
              "originalRequest": {
                "method": "POST",
                "header": [{ "key": "Content-Type", "value": "application/json" }, { "key": "Accept", "value": "audio/mpeg" }],
                "url": { "raw": "{{base_url}}/v1/audio/speech" },
                "body": { "mode": "raw", "raw": "{\n  \"model\": \"xtts-v2\",\n  \"input\": \"Hola, esta es una prueba de síntesis de voz.\",\n  \"voice\": \"{{voice_id}}\",\n  \"language\": \"es\",\n  \"speed\": 1,\n  \"response_format\": \"mp3\"\n}" }
              },
              "status": "OK", "code": 200, "_postman_previewlanguage": "text",
              "header": [
                { "key": "Content-Type", "value": "audio/mpeg" },
                { "key": "X-Request-Id", "value": "1389f3d4-23e0-47e2-bf6f-a2344e598761" }
              ],
              "cookie": [],
              "body": "[Audio binario MP3 omitido del ejemplo. Ejecuta la petición para generar y descargar el audio.]"
            },
            {
              "name": "Falta el campo input (400)",
              "originalRequest": {
                "method": "POST",
                "header": [{ "key": "Content-Type", "value": "application/json" }, { "key": "Accept", "value": "application/json" }],
                "url": { "raw": "{{base_url}}/v1/audio/speech" },
                "body": { "mode": "raw", "raw": "{\n  \"voice\": \"{{voice_id}}\"\n}" }
              },
              "status": "Bad Request", "code": 400, "_postman_previewlanguage": "json",
              "header": [{ "key": "Content-Type", "value": "application/json" }], "cookie": [],
              "body": "{\n  \"error\": {\n    \"message\": \"El campo input es obligatorio.\",\n    \"type\": \"invalid_request_error\"\n  }\n}"
            },
            {
              "name": "Texto largo aceptado (202)",
              "originalRequest": {
                "method": "POST",
                "header": [{ "key": "Content-Type", "value": "application/json" }, { "key": "Accept", "value": "application/json" }],
                "url": { "raw": "{{base_url}}/v1/audio/speech" },
                "body": { "mode": "raw", "raw": "{\n  \"model\": \"xtts-v2\",\n  \"input\": \"Texto de varios minutos…\",\n  \"voice\": \"{{voice_id}}\",\n  \"language\": \"es\",\n  \"response_format\": \"mp3\",\n  \"async\": true\n}" }
              },
              "status": "Accepted", "code": 202, "_postman_previewlanguage": "json",
              "header": [{ "key": "Content-Type", "value": "application/json" }], "cookie": [],
              "body": "{\n  \"id\": \"a1b2c3d4e5f6\",\n  \"job_id\": \"a1b2c3d4e5f6\",\n  \"status\": \"queued\",\n  \"estimated_seconds\": 190.5,\n  \"status_url\": \"/v1/audio/speech/jobs/a1b2c3d4e5f6\",\n  \"audio_url\": \"/v1/audio/speech/jobs/a1b2c3d4e5f6/audio\",\n  \"history_id\": 42,\n  \"message\": \"Texto largo: la síntesis supera el presupuesto de respuesta directa. Consulta status_url hasta que status sea done y descarga el audio desde audio_url.\"\n}"
            }
          ],
          "event": [{
            "listen": "test",
            "script": { "type": "text/javascript", "exec": [
              "if (pm.response.code === 202) {",
              "  const result = pm.response.json();",
              "  if (result.job_id || result.id) pm.collectionVariables.set('speech_job_id', result.job_id || result.id);",
              "}"
            ] }
          }]
        },
        {
          "name": "Consultar trabajo (async)",
          "request": {
            "method": "GET",
            "description": "Consulta el estado de un trabajo aceptado con 202. Repite hasta que status sea done (entonces incluye audio_url) o error. Requiere Bearer token.",
            "header": [{ "key": "Accept", "value": "application/json" }],
            "url": { "raw": "{{base_url}}/v1/audio/speech/jobs/{{speech_job_id}}" }
          },
          "response": [
            {
              "name": "Trabajo en curso (200)",
              "originalRequest": { "method": "GET", "header": [{ "key": "Accept", "value": "application/json" }], "url": { "raw": "{{base_url}}/v1/audio/speech/jobs/{{speech_job_id}}" } },
              "status": "OK", "code": 200, "_postman_previewlanguage": "json",
              "header": [{ "key": "Content-Type", "value": "application/json" }], "cookie": [],
              "body": "{\n  \"id\": \"a1b2c3d4e5f6\",\n  \"job_id\": \"a1b2c3d4e5f6\",\n  \"status\": \"running\",\n  \"estimated_seconds\": 190.5,\n  \"status_url\": \"/v1/audio/speech/jobs/a1b2c3d4e5f6\",\n  \"message\": \"Síntesis en curso. Reintenta este endpoint hasta que status sea done.\"\n}"
            },
            {
              "name": "Trabajo listo (200)",
              "originalRequest": { "method": "GET", "header": [{ "key": "Accept", "value": "application/json" }], "url": { "raw": "{{base_url}}/v1/audio/speech/jobs/{{speech_job_id}}" } },
              "status": "OK", "code": 200, "_postman_previewlanguage": "json",
              "header": [{ "key": "Content-Type", "value": "application/json" }], "cookie": [],
              "body": "{\n  \"id\": \"a1b2c3d4e5f6\",\n  \"job_id\": \"a1b2c3d4e5f6\",\n  \"status\": \"done\",\n  \"duration_seconds\": 61.4,\n  \"content_type\": \"audio/mpeg\",\n  \"audio_url\": \"/v1/audio/speech/jobs/a1b2c3d4e5f6/audio\",\n  \"history_id\": 42\n}"
            }
          ]
        },
        {
          "name": "Descargar audio del trabajo",
          "request": {
            "method": "GET",
            "description": "Descarga el audio cuando el trabajo está done. Devuelve 409 si aún no está listo. Requiere Bearer token.",
            "header": [{ "key": "Accept", "value": "audio/mpeg" }],
            "url": { "raw": "{{base_url}}/v1/audio/speech/jobs/{{speech_job_id}}/audio" }
          },
          "response": [
            {
              "name": "Audio del trabajo (200)",
              "originalRequest": { "method": "GET", "header": [{ "key": "Accept", "value": "audio/mpeg" }], "url": { "raw": "{{base_url}}/v1/audio/speech/jobs/{{speech_job_id}}/audio" } },
              "status": "OK", "code": 200, "_postman_previewlanguage": "text",
              "header": [{ "key": "Content-Type", "value": "audio/mpeg" }], "cookie": [],
              "body": "[Audio binario MP3 omitido del ejemplo. Ejecuta la petición cuando el trabajo esté done.]"
            }
          ]
        }
      ]
    }
  ]
}
