{"openapi":"3.1.0","info":{"title":"Avia Audio","summary":"Escuta (transcrição, emoção, sons e quem falou) e fala (TTS) da Ávia.","description":"\n## Integração rápida\n\nO módulo de áudio da Ávia junta a escuta (AUS) e a fala (TTS): `POST /v1/tts` e `POST /v1/tts/stream`\ndevolvem a voz da assistente em pt-BR, com emoção, no mesmo token.\n\n### Autenticação\n\nTodas as rotas /v1/* exigem Authorization: Bearer <token>. No Swagger, use o botão **Authorize**. O WebSocket aceita o header Bearer ou ?token=<token> para clientes de navegador.\n\n\n\nO AUS oferece dois modos que usam o mesmo pipeline de ASR, VAD, emoção e eventos sonoros:\n\n1. **Upload assíncrono**: envie `POST /v1/audio/analyze`, guarde o `job_id` e consulte\n   `GET /v1/jobs/{job_id}` até `completed` ou `failed`.\n2. **Streaming WebSocket**: conecte em\n   `wss://aus.amplavia.com.br/v1/audio/ws?language=pt` e envie áudio PCM16 mono,\n   little-endian, 16 kHz em frames binários.\n\n### Idioma\n\nUse `pt`, `en`, `es` ou outro código ISO curto. Códigos regionais, como `pt-BR`,\nsão normalizados para `pt`. Use `auto` para detecção automática.\n\n### WebSocket (não representado como operação pelo OpenAPI)\n\nApós conectar, opcionalmente envie `{\"type\":\"start\",\"language\":\"pt\"}`. Envie o áudio\ncomo frames **binários**, não Base64. Para sessões longas, envie\n`{\"type\":\"heartbeat\"}` periodicamente. O servidor responde com eventos JSON:\n`transcript_partial`, `transcript_correction`, `transcript_final`, `emotion_update`\ne `sound_event`. O campo `sequence` define a ordem dos eventos.\n\nConsulte o README do projeto para exemplos completos em cURL e JavaScript.\n","contact":{"name":"AmplaVia Soluções Digitais"},"license":{"name":"Proprietário — AmplaVia Soluções Digitais"},"version":"3.0.0"},"servers":[{"url":"https://aus.amplavia.com.br","description":"Produção (túnel Cloudflare + Caddy)"},{"url":"http://127.0.0.1:8500","description":"Acesso local"}],"paths":{"/v1/audio/analyze":{"post":{"tags":["Upload"],"summary":"Enviar áudio para análise assíncrona","description":"Recebe um arquivo de áudio e o idioma desejado. Retorna HTTP 202 com um job_id imediatamente. Consulte GET /v1/jobs/{job_id} até completed ou failed. O arquivo percorre o mesmo pipeline de ASR, VAD, emoção e sons usado no streaming.","operationId":"analyze_v1_audio_analyze_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_analyze_v1_audio_analyze_post"}}},"required":true},"responses":{"202":{"description":"Arquivo aceito e job criado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeJobResponse"}}}},"422":{"description":"Arquivo, formulário ou idioma inválido."},"413":{"description":"Arquivo acima de `upload.max_bytes` (100 MB, o teto do proxy da Cloudflare). Envie em Opus ou FLAC."},"429":{"description":"Fila de uploads cheia (upload.max_queued_jobs); tente de novo depois."}},"security":[{"HTTPBearer":[]}]}},"/v1/jobs/{job_id}":{"get":{"tags":["Upload"],"summary":"Consultar estado e resultado do job","description":"Faça polling neste endpoint. Estados terminais: completed, failed e cancelled. O campo result só é preenchido em completed; error é preenchido em failed.","operationId":"get_job_v1_jobs__job_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","description":"ID retornado por POST /v1/audio/analyze.","title":"Job Id"},"description":"ID retornado por POST /v1/audio/analyze."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusResponse"}}}},"404":{"description":"Job não encontrado (ou expirado: upload.job_ttl_s depois de terminar)."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/health":{"get":{"tags":["Observabilidade"],"summary":"Liveness: processo está ativo","operationId":"health_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/ready":{"get":{"tags":["Observabilidade"],"summary":"Readiness: escuta (ASR) e fala (TTS) carregadas","operationId":"readiness_v1_ready_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadinessResponse"}}}},"503":{"description":"Algum componente essencial (ASR, VAD, TTS) ainda carrega ou falhou; o corpo diz qual.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotReadyResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/models":{"get":{"tags":["Serviço"],"summary":"Consultar modelos e backends ativos","operationId":"models_v1_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelsResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/logs":{"get":{"tags":["Observabilidade"],"summary":"Consultar os logs recentes do servico AUS","description":"Retorna somente audio_output.log e audio_error.log. Caminhos arbitrarios nao sao aceitos.","operationId":"application_logs_v1_logs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":20,"default":300,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationLogsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Observabilidade"],"summary":"Limpar os logs reais do servico AUS","description":"Apaga somente o conteúdo de audio_output.log e audio_error.log; os arquivos são preservados.","operationId":"clear_application_logs_v1_logs_delete","security":[{"HTTPBearer":[]}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogsClearResponse"}}}}}}},"/v1/metrics":{"get":{"tags":["Observabilidade"],"summary":"Obter métricas no formato Prometheus","operationId":"metrics_v1_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}},"text/plain":{"example":"aus_jobs_total 12\n"}}}},"security":[{"HTTPBearer":[]}]}},"/v1/config":{"put":{"tags":["Serviço"],"summary":"Atualizar configurações dinâmicas","description":"Tudo ou nada: campo que não muda em runtime, desconhecido ou com valor inválido recusa o patch inteiro com 422 (o detalhe diz quais e por quê). A resposta diz quando cada campo passa a valer.","operationId":"update_config_v1_config_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigPatchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfigPatchResponse"}}}},"422":{"description":"Campos recusados: {campo: motivo}. Nada foi aplicado."}},"security":[{"HTTPBearer":[]}]}},"/v1/speakers":{"get":{"tags":["Pessoas"],"summary":"Pessoas cadastradas","operationId":"list_speakers_v1_speakers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakersResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/speakers/identify":{"post":{"tags":["Pessoas"],"summary":"Quem está falando neste áudio","operationId":"identify_speaker_v1_speakers_identify_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_identify_speaker_v1_speakers_identify_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerIdentifyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/speakers/{name}":{"post":{"tags":["Pessoas"],"summary":"Cadastrar a voz de uma pessoa (arquivo)","description":"Mais uma amostra da voz (2 s ou mais; 10 a 30 s lendo um texto dá a melhor impressão).","operationId":"enroll_speaker_v1_speakers__name__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","description":"Nome da pessoa.","title":"Name"},"description":"Nome da pessoa."}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_enroll_speaker_v1_speakers__name__post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerEnrollResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Pessoas"],"summary":"Apagar uma pessoa cadastrada","operationId":"delete_speaker_v1_speakers__name__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","description":"Nome da pessoa.","title":"Name"},"description":"Nome da pessoa."}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/speakers/{name}/from-session":{"post":{"tags":["Pessoas"],"summary":"Cadastrar a voz de uma pessoa com as frases que ela disse no stream","description":"Usa o áudio dos `transcript_final` indicados de uma sessão WebSocket ainda aberta (os 30 últimos ficam guardados).","operationId":"enroll_speaker_from_session_v1_speakers__name__from_session_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","description":"Nome da pessoa.","title":"Name"},"description":"Nome da pessoa."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerFromSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerEnrollResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/vozes":{"get":{"tags":["Fala"],"summary":"Motor de fala e emoções disponíveis","operationId":"vozes_v1_vozes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Vozes V1 Vozes Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/tts":{"post":{"tags":["Fala"],"summary":"Falar um texto (WAV inteiro)","operationId":"falar_v1_tts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PedidoDeFala"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"audio/wav":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/tts/stream":{"post":{"tags":["Fala"],"summary":"Falar um texto em fluxo (toca enquanto gera)","description":"O texto inteiro de uma vez; o áudio volta em fluxo, frase a frase (a primeira, curta, sai antes). Como o\nmodelo gera mais rápido que a fala, o player toca sem buracos enquanto o resto é gerado.","operationId":"falar_em_fluxo_v1_tts_stream_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PedidoDeStream"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"audio/wav":{},"application/octet-stream":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}}},"components":{"schemas":{"AnalyzeJobResponse":{"properties":{"job_id":{"type":"string","title":"Job Id","description":"Identificador usado para consultar o processamento."},"state":{"type":"string","enum":["queued","waiting_gpu","running","completed","cancelled","failed"],"title":"State","description":"Estado inicial do job."}},"type":"object","required":["job_id","state"],"title":"AnalyzeJobResponse","example":{"job_id":"job_a1b2c3d4e5f6","state":"queued"}},"ApplicationLogsResponse":{"properties":{"service":{"type":"string","const":"audio","title":"Service"},"fetched_at":{"type":"string","title":"Fetched At"},"output":{"$ref":"#/components/schemas/LogStreamResponse"},"error":{"$ref":"#/components/schemas/LogStreamResponse"}},"type":"object","required":["service","fetched_at","output","error"],"title":"ApplicationLogsResponse"},"Body_analyze_v1_audio_analyze_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"Arquivo de áudio. WAV é recomendado; outros formatos dependem do decoder instalado."},"language":{"type":"string","title":"Language","description":"Código do idioma (pt, en, es etc.), código regional (pt-BR) ou auto.","default":"pt","examples":["pt"]}},"type":"object","required":["file"],"title":"Body_analyze_v1_audio_analyze_post"},"Body_enroll_speaker_v1_speakers__name__post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"Áudio só com a voz da pessoa."}},"type":"object","required":["file"],"title":"Body_enroll_speaker_v1_speakers__name__post"},"Body_identify_speaker_v1_speakers_identify_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"Áudio com uma pessoa falando."}},"type":"object","required":["file"],"title":"Body_identify_speaker_v1_speakers_identify_post"},"ConfigPatchRequest":{"properties":{"patch":{"additionalProperties":true,"type":"object","title":"Patch","description":"Configurações dinâmicas. Campos aceitos: asr.beam_size (1 a 10), vad.threshold (0 a 1), emotion.enabled, sound_detection.enabled e audio.chunk_ms (10 a 1000). Qualquer outro campo, ou valor inválido, recusa o patch inteiro com 422."}},"type":"object","required":["patch"],"title":"ConfigPatchRequest","example":{"patch":{"asr.beam_size":5,"emotion.enabled":true,"vad.threshold":0.55}}},"ConfigPatchResponse":{"properties":{"applied_fields":{"items":{"type":"string"},"type":"array","title":"Applied Fields","description":"Campos aplicados."},"effective":{"additionalProperties":{"type":"string"},"type":"object","title":"Effective","description":"Quando cada campo aplicado passa a valer."}},"type":"object","required":["applied_fields"],"title":"ConfigPatchResponse","example":{"applied_fields":["vad.threshold","asr.beam_size"],"effective":{"asr.beam_size":"na próxima inferência do ASR","vad.threshold":"nas sessões e uploads novos"}}},"EmotionAnalysisResponse":{"properties":{"dominant":{"type":"string","title":"Dominant","description":"Emoção dominante no último segmento analisado."},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence","description":"Confiança da emoção dominante."},"scores":{"additionalProperties":{"type":"number"},"type":"object","title":"Scores","description":"Pontuação por classe de emoção."}},"type":"object","required":["dominant","confidence"],"title":"EmotionAnalysisResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","const":"healthy","title":"Status"}},"type":"object","required":["status"],"title":"HealthResponse"},"JobStatusResponse":{"properties":{"job_id":{"type":"string","title":"Job Id"},"state":{"type":"string","enum":["queued","waiting_gpu","running","completed","cancelled","failed"],"title":"State"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Mensagem de erro quando state=failed; caso contrário, null."},"result":{"anyOf":[{"$ref":"#/components/schemas/UploadAnalysisResult"},{"type":"null"}],"description":"Disponível somente quando state=completed."}},"type":"object","required":["job_id","state"],"title":"JobStatusResponse","examples":[{"job_id":"job_a1b2c3d4e5f6","state":"running"},{"job_id":"job_a1b2c3d4e5f6","result":{"emotion":{"confidence":0.82,"dominant":"neutral","scores":{"happy":0.12,"neutral":0.82}},"language":"pt","sounds":[],"timeline":[{"duration_ms":2100,"language":"pt","text":"Olá, este é um teste.","type":"transcript_final"}],"transcript":"Olá, este é um teste."},"state":"completed"}]},"LogStreamResponse":{"properties":{"name":{"type":"string","enum":["output","error"],"title":"Name"},"exists":{"type":"boolean","title":"Exists"},"lines":{"items":{"type":"string"},"type":"array","title":"Lines"}},"type":"object","required":["name","exists","lines"],"title":"LogStreamResponse"},"LogsClearResponse":{"properties":{"service":{"type":"string","const":"audio","title":"Service"},"cleared":{"items":{"type":"string"},"type":"array","title":"Cleared","description":"Arquivos AUS cujo conteúdo foi apagado."},"missing":{"items":{"type":"string"},"type":"array","title":"Missing","description":"Arquivos AUS que ainda não existiam."}},"type":"object","required":["service","cleared","missing"],"title":"LogsClearResponse"},"ModelsResponse":{"properties":{"asr":{"type":"string","title":"Asr","description":"Modelo ASR configurado."},"emotion":{"type":"string","title":"Emotion","description":"Backend de reconhecimento de emoção."},"sound":{"type":"string","title":"Sound","description":"Backend de eventos sonoros."},"vram_mib":{"additionalProperties":{"type":"integer"},"type":"object","title":"Vram Mib","description":"VRAM da placa que cada modelo ocupou ao carregar, em MiB (diferença antes/depois; inclui o cache do PyTorch). `inicial`: a placa logo depois de criar o contexto CUDA do processo. Vazio sem CUDA."}},"type":"object","required":["asr","emotion","sound"],"title":"ModelsResponse"},"NotReadyResponse":{"properties":{"asr":{"type":"string","title":"Asr","description":"unavailable = o Whisper não carregou e caiu no stub: o stream não transcreve."},"vad":{"type":"string","title":"Vad"},"emotion":{"type":"string","title":"Emotion"},"sound":{"type":"string","title":"Sound"},"speaker":{"type":"string","title":"Speaker"},"tts":{"type":"string","title":"Tts"},"status":{"type":"string","const":"not_ready","title":"Status"},"detail":{"type":"string","title":"Detail","description":"O motivo: 'ASR unavailable', 'TTS error: ...', 'TTS model is not ready'..."}},"type":"object","required":["asr","vad","emotion","sound","speaker","tts","status","detail"],"title":"NotReadyResponse"},"PedidoDeFala":{"properties":{"texto":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Texto","description":"O que falar. Markdown é ignorado; começando com <speak>, é lido como SSML."},"ssml":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ssml","description":"SSML 1.1 (<speak>...</speak>). Use texto ou ssml."},"emocao":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emocao","description":"Uma de: neutro, calma, feliz, triste, compreensiva, medo, sussurro. No SSML, é a emoção de partida."}},"type":"object","title":"PedidoDeFala"},"PedidoDeStream":{"properties":{"texto":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Texto","description":"O que falar. Markdown é ignorado; começando com <speak>, é lido como SSML."},"ssml":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ssml","description":"SSML 1.1 (<speak>...</speak>). Use texto ou ssml."},"emocao":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Emocao","description":"Uma de: neutro, calma, feliz, triste, compreensiva, medo, sussurro. No SSML, é a emoção de partida."},"formato":{"type":"string","enum":["wav","pcm"],"title":"Formato","description":"wav: cabeçalho com tamanho indefinido + PCM (toca enquanto chega no ffplay, VLC e navegador); pcm: só o PCM.","default":"wav"}},"type":"object","title":"PedidoDeStream"},"ReadinessResponse":{"properties":{"asr":{"type":"string","title":"Asr","description":"unavailable = o Whisper não carregou e caiu no stub: o stream não transcreve."},"vad":{"type":"string","title":"Vad"},"emotion":{"type":"string","title":"Emotion"},"sound":{"type":"string","title":"Sound"},"speaker":{"type":"string","title":"Speaker"},"tts":{"type":"string","title":"Tts"},"status":{"type":"string","const":"ready","title":"Status"},"asr_loaded":{"type":"boolean","title":"Asr Loaded"},"tts_loaded":{"type":"boolean","title":"Tts Loaded","description":"Motor de fala carregado (o /v1/tts responde)."},"tts_backend":{"type":"string","title":"Tts Backend","description":"Motor de fala: qwen, f5 ou stub."}},"type":"object","required":["asr","vad","emotion","sound","speaker","tts","status","asr_loaded","tts_loaded","tts_backend"],"title":"ReadinessResponse","example":{"asr":"ready","asr_loaded":true,"emotion":"ready","sound":"ready","speaker":"ready","status":"ready","tts":"ready","tts_backend":"qwen","tts_loaded":true,"vad":"ready"}},"SoundAnalysisResponse":{"properties":{"label":{"type":"string","title":"Label","description":"Rótulo do evento sonoro."},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence","description":"Confiança da detecção."},"continuous":{"type":"boolean","title":"Continuous","description":"Indica se o evento é contínuo."},"start_ms":{"type":"integer","minimum":0.0,"title":"Start Ms","description":"Início no arquivo em milissegundos."},"end_ms":{"type":"integer","minimum":0.0,"title":"End Ms","description":"Fim no arquivo em milissegundos."}},"type":"object","required":["label","confidence","continuous","start_ms","end_ms"],"title":"SoundAnalysisResponse"},"SpeakerEnrollResponse":{"properties":{"name":{"type":"string","title":"Name"},"samples":{"type":"integer","title":"Samples","description":"Amostras da pessoa depois desta."},"audio_ms":{"type":"integer","title":"Audio Ms","description":"Duração do áudio usado."},"processing_ms":{"type":"number","title":"Processing Ms","description":"Tempo do modelo para tirar a impressão da voz."}},"type":"object","required":["name","samples","audio_ms","processing_ms"],"title":"SpeakerEnrollResponse"},"SpeakerFromSessionRequest":{"properties":{"session_id":{"type":"string","title":"Session Id","description":"Sessão do WebSocket ainda aberta (o session_id dos eventos)."},"sequences":{"items":{"type":"integer"},"type":"array","minItems":1,"title":"Sequences","description":"O `sequence` dos transcript_final com a voz da pessoa."}},"type":"object","required":["session_id","sequences"],"title":"SpeakerFromSessionRequest","example":{"sequences":[12,15,19],"session_id":"ses_a1b2c3"}},"SpeakerIdentifyResponse":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"A pessoa, se passou do limiar; null = não cadastrada."},"confidence":{"type":"number","title":"Confidence","description":"Cosseno com a pessoa mais parecida."},"candidate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate","description":"A pessoa mais parecida (mesmo abaixo do limiar)."},"processing_ms":{"type":"number","title":"Processing Ms"}},"type":"object","required":["confidence","processing_ms"],"title":"SpeakerIdentifyResponse"},"SpeakerSummary":{"properties":{"name":{"type":"string","title":"Name","description":"Nome da pessoa cadastrada."},"samples":{"type":"integer","title":"Samples","description":"Amostras da voz guardadas (até 20)."},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At","description":"Última amostra (ISO 8601, UTC)."}},"type":"object","required":["name","samples"],"title":"SpeakerSummary"},"SpeakersResponse":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"speaker.enabled: o transcript_final leva quem falou."},"device":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device","description":"Onde o modelo roda (cpu, cuda:0)."},"threshold":{"type":"number","title":"Threshold","description":"Cosseno mínimo para dizer o nome."},"speakers":{"items":{"$ref":"#/components/schemas/SpeakerSummary"},"type":"array","title":"Speakers"}},"type":"object","required":["enabled","threshold","speakers"],"title":"SpeakersResponse","example":{"device":"cpu","enabled":true,"speakers":[{"name":"Vinicius","samples":3,"updated_at":"2026-10-28T02:10:00+00:00"}],"threshold":0.7}},"UploadAnalysisResult":{"properties":{"transcript":{"type":"string","title":"Transcript","description":"Transcrição final, unindo todos os turnos de fala."},"language":{"type":"string","title":"Language","description":"Idioma usado ou detectado pelo ASR."},"emotion":{"$ref":"#/components/schemas/EmotionAnalysisResponse"},"sounds":{"items":{"$ref":"#/components/schemas/SoundAnalysisResponse"},"type":"array","title":"Sounds","description":"Eventos sonoros detectados."},"timeline":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Timeline","description":"Eventos completos produzidos pelo mesmo pipeline do streaming, em ordem temporal."}},"type":"object","required":["transcript","language","emotion","sounds","timeline"],"title":"UploadAnalysisResult","example":{"emotion":{"confidence":0.82,"dominant":"neutral","scores":{"happy":0.12,"neutral":0.82}},"language":"pt","sounds":[],"timeline":[{"duration_ms":2100,"language":"pt","text":"Olá, este é um teste.","type":"transcript_final"}],"transcript":"Olá, este é um teste."}},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"HTTPBearer":{"type":"http","description":"Token de escopo `audio` ou `admin` (security.tokens.*; o security.api_token antigo vale como admin).","scheme":"bearer"}}},"tags":[{"name":"Upload","description":"Envio assíncrono de arquivos e consulta de jobs."},{"name":"Serviço","description":"Modelos ativos e configuração dinâmica."},{"name":"Pessoas","description":"Cadastro das vozes; o transcript_final do stream diz quem falou."},{"name":"Fala","description":"A voz da assistente (TTS) em pt-BR, com emoção, inteira ou em fluxo; texto ou SSML."},{"name":"Observabilidade","description":"Liveness, readiness e métricas Prometheus."}]}