{"openapi":"3.1.0","info":{"title":"Transcritor API","description":"API do TranscritorAi — a **mesma** que o site usa: plano, créditos, histórico e armazenamento são da conta.\n\n**Autenticação.** Crie a conta em `POST /api/auth/registrar` ou entre em `POST /api/auth/login`; ambos devolvem um\n`token`. Clique em **Authorize** e cole só o token (o Swagger monta o `Authorization: Bearer <token>`). Endpoints com\ncadeado exigem o token; os demais são públicos.\n\n**Créditos (contabilização).** Cada arquivo ou link custa 1 crédito, descontado **no envio** (`POST /api/transcricoes`):\nprimeiro da cota mensal do plano, depois do saldo avulso. Vídeo custa 1 crédito a cada 5 min, áudio a cada 10 min e\nPDF/DOCX/TXT a cada 100 páginas. Falha e cancelamento **devolvem** os créditos; \"pular\" um arquivo já processado\n(`pular`) e reprocessar uma falha (`POST /api/transcricoes/reprocessar`) não cobram. Formato ou link inválido nunca\ncobra. O que não couber na cota volta em `fora_da_cota`. Consulte `GET /api/usuarios/eu/capacidade` antes de enviar.\n","version":"0.3.0"},"paths":{"/api/health":{"get":{"tags":["infra"],"summary":"Health","description":"Checagem simples de que o serviço está no ar.","operationId":"health_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Api Health Get"}}}}}}},"/api/planos":{"get":{"tags":["planos"],"summary":"Listar Planos","description":"Planos recorrentes e pacotes avulsos, com preços em BRL e USD.\n\nEra `GET /planos` até a v46 — mudou pra `/api/planos` pra liberar\n\"/planos\" como URL \"limpa\" da página `planos.html` (ver o bloco de\nURLs sem extensão no fim deste arquivo).","operationId":"listar_planos_api_planos_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"anyOf":[{"items":{"$ref":"#/components/schemas/PlanoRecorrente"},"type":"array"},{"items":{"$ref":"#/components/schemas/PacoteAvulso"},"type":"array"}]},"type":"object","title":"Response Listar Planos Api Planos Get"}}}}}}},"/api/auth/registrar":{"post":{"tags":["autenticação"],"summary":"Registrar","operationId":"registrar_api_auth_registrar_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrarEntrada"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessaoResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/login":{"post":{"tags":["autenticação"],"summary":"Login","operationId":"login_api_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginEntrada"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessaoResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/logout":{"post":{"tags":["autenticação"],"summary":"Logout","operationId":"logout_api_auth_logout_post","responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/auth/logout-todos":{"post":{"tags":["autenticação"],"summary":"Logout Todos","description":"v74 — \"Sair de todos os dispositivos\": encerra TODAS as sessões desta conta (inclusive a atual).\n\nÉ o botão de emergência pra token vazado (XSS, computador compartilhado, celular perdido) —\nantes não existia nenhuma forma de revogar uma sessão antes dos 30 dias.","operationId":"logout_todos_api_auth_logout_todos_post","responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/auth/logout-outros":{"post":{"tags":["autenticação"],"summary":"Logout Outros","description":"v74 — encerra todas as sessões desta conta EXCETO a que fez esta chamada.","operationId":"logout_outros_api_auth_logout_outros_post","responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/auth/eu":{"get":{"tags":["autenticação"],"summary":"Eu","operationId":"eu_api_auth_eu_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsuarioPublico"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/auth/verificar-email":{"post":{"tags":["autenticação"],"summary":"Verificar Email","description":"v75 — confirma o e-mail a partir do link recebido. Público (o link abre numa página sem login).","operationId":"verificar_email_api_auth_verificar_email_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificarEmailEntrada"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Verificar Email Api Auth Verificar Email Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/reenviar-verificacao":{"post":{"tags":["autenticação"],"summary":"Reenviar Verificacao","description":"v75 — reenvia o link de confirmação (espera mínima de 60 s; no máximo 5 por dia por conta).","operationId":"reenviar_verificacao_api_auth_reenviar_verificacao_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reenviar Verificacao Api Auth Reenviar Verificacao Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/auth/esqueci-senha":{"post":{"tags":["autenticação"],"summary":"Esqueci Senha","description":"v75 — pede o e-mail de redefinição de senha. A resposta é SEMPRE a mesma (não revela se o e-mail existe) e\no envio acontece em segundo plano (o tempo de resposta também não revela). Limitado por IP e por e-mail.","operationId":"esqueci_senha_api_auth_esqueci_senha_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EsqueciSenhaEntrada"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Esqueci Senha Api Auth Esqueci Senha Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/redefinir-senha":{"post":{"tags":["autenticação"],"summary":"Redefinir Senha","description":"v75 — define a senha nova a partir do link do e-mail. Derruba TODAS as sessões da conta (quem estava logado\ncom a senha antiga — inclusive um invasor — sai) e marca o e-mail como verificado (o link provou o controle dele).","operationId":"redefinir_senha_api_auth_redefinir_senha_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedefinirSenhaEntrada"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Redefinir Senha Api Auth Redefinir Senha Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/config/google-login":{"get":{"tags":["autenticação"],"summary":"Configuracao Login Google","description":"Endpoint público (sem login) que diz ao frontend se o botão \"Continuar\ncom o Google\" deve aparecer, e com qual `client_id` inicializar o Google\nIdentity Services — mesmo padrão gracioso de `google_drive.configurado()`\n(`picker_disponivel()` no frontend do Drive): sem `GOOGLE_LOGIN_CLIENT_ID`\nno `.env`, o botão simplesmente não aparece, sem quebrar o resto da tela.","operationId":"configuracao_login_google_api_config_google_login_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfiguracaoLoginGoogleResposta"}}}}}}},"/api/config/turnstile":{"get":{"tags":["autenticação"],"summary":"Configuracao Turnstile","description":"v77 — diz ao frontend se deve mostrar o captcha do Cloudflare Turnstile e com qual Site Key.\n\nAntes o widget ficava fixo no HTML: com o servidor sem `TURNSTILE_SECRET_KEY` (captcha desligado) ou com o\nwidget mal configurado na Cloudflare, o visitante via um erro (ex.: 110200) num captcha que nem era exigido.\nAgora o widget só aparece quando o servidor exige o token, e a Site Key mora no `.env` (`TURNSTILE_SITE_KEY`).","operationId":"configuracao_turnstile_api_config_turnstile_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfiguracaoTurnstileResposta"}}}}}}},"/api/config/mercadopago":{"get":{"tags":["pagamentos"],"summary":"Configuracao Mercadopago","description":"v69 — diz ao frontend se deve mostrar o formulário de cartão (`js/pagamento-cartao.js`)\ne com qual Public Key inicializar o SDK do Mercado Pago no navegador. Mesmo\npadrão gracioso de `configuracao_login_google` acima.","operationId":"configuracao_mercadopago_api_config_mercadopago_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfiguracaoMercadoPagoResposta"}}}}}}},"/api/auth/google":{"post":{"tags":["autenticação"],"summary":"Login Google","description":"Login/cadastro via \"Continuar com o Google\" (v58, ROADMAP_LOGIN_GOOGLE.md).\n\nRecebe o ID token que o Google Identity Services devolveu no navegador,\nvalida a assinatura dele (`auth_google.verificar_id_token`), e:\n  1. Já existe conta com esse `google_id`? Login de retorno.\n  2. Não, mas existe conta com esse e-mail (criada por senha)? Vincula\n     `google_id` a ela — seguro porque só chega aqui com\n     `email_verified=true` (ver `auth_google.py`), então o Google já\n     confirmou que quem está pedindo login é dono desse e-mail.\n  3. Nenhum dos dois? Cria conta nova, sem senha (só via Google).\nTermina criando uma sessão do MESMO jeito que login por senha\n(`_criar_sessao`) — o resto do site não precisa saber a diferença.","operationId":"login_google_api_auth_google_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginGoogleEntrada"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessaoResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/usuarios/eu/plano":{"patch":{"tags":["autenticação"],"summary":"Escolher Plano","operationId":"escolher_plano_api_usuarios_eu_plano_patch","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EscolherPlanoEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsuarioPublico"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/usuarios/eu/plano/cancelar":{"post":{"tags":["autenticação"],"summary":"Cancelar Plano","description":"v61 — botão \"Cancelar plano\". Ao contrário de `escolher_plano`, NUNCA é bloqueado\npor ter transcrição disponível — cancelar precisa sempre ser possível, mesmo com\nsaldo sobrando (é justamente parar de pagar por um plano que não vai mais usar tanto).\n\nCancela a assinatura recorrente no Mercado Pago primeiro (pra parar a cobrança\nautomática do mês que vem) e só depois rebaixa a conta pro Free localmente — nessa\nordem, pra nunca ficar num estado em que a cobrança automática continua mas a conta\njá não é mais paga aqui.","operationId":"cancelar_plano_api_usuarios_eu_plano_cancelar_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsuarioPublico"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/usuarios/eu/pacotes":{"post":{"tags":["autenticação"],"summary":"Comprar Pacote","description":"v60: pacotes avulsos não têm versão gratuita — este endpoint só existia\nporque não havia cobrança real ainda. Agora sempre recusa, apontando pro\ncheckout de verdade (`POST /api/pagamentos/checkout`).","operationId":"comprar_pacote_api_usuarios_eu_pacotes_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComprarPacoteEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsuarioPublico"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/pagamentos/checkout":{"post":{"tags":["pagamentos"],"summary":"Iniciar Checkout","description":"v60 (armazenamento na v62): cria a cobrança de verdade no Mercado Pago\n(assinatura pra plano recorrente ou de armazenamento, preferência pra\npacote avulso) e devolve a URL de checkout pro frontend redirecionar. O\nplano/pacote só é aplicado na conta depois que o webhook\n(`POST /api/pagamentos/webhook/mercadopago`) confirma o pagamento.","operationId":"iniciar_checkout_api_pagamentos_checkout_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/pagamentos/{pagamento_id}/status":{"get":{"tags":["pagamentos"],"summary":"Status Pagamento","description":"v67 — consultado pelo frontend enquanto o popup de checkout está aberto\n(`js/auth.js:observarConfirmacaoPagamento`), pra fechar a janela sozinho e\natualizar a tela assim que o pagamento for confirmado — sem depender só do\nusuário fechar a janela na mão. 404 se o pagamento não existir ou não for\ndesta conta (nunca vaza status de pagamento de outra conta).","operationId":"status_pagamento_api_pagamentos__pagamento_id__status_get","parameters":[{"name":"pagamento_id","in":"path","required":true,"schema":{"type":"integer","title":"Pagamento Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusPagamentoResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/pagamentos/cartao":{"post":{"tags":["pagamentos"],"summary":"Salvar Cartao E Assinar","description":"v69 (armazenamento na v70, Perfil de Pagamento na v71, pacote avulso na\nv72) — substitui tanto a assinatura via Preapproval quanto o Checkout Pro:\nem vez de redirecionar pro Mercado Pago (que exigia o e-mail de quem paga\nbater com a conta logada lá — bug real reportado pelo Leandro — e, no\nCheckout Pro, tirava a pessoa do site), recebe o token do cartão (já\ntokenizado no navegador, `js/pagamento-cartao.js`) e cobra na hora.\n\n\"plano\"/\"armazenamento\" (recorrentes) criam um Perfil de Pagamento pra\nesse cartão (`pagamentos.cobrar_primeiro_pagamento_com_cartao`) — os dois\nprodutos compartilham o MESMO perfil salvo\n(`usuarios.mercadopago_payment_profile_id`), reaproveitado sozinho pra\nrenovar todo mês (`pagamentos.verificar_renovacoes_pendentes`), sem CVV,\nsem abrir esse formulário de novo. \"pacote\" (avulso, sem recorrência) só\ncobra uma vez (`pagamentos.cobrar_pagamento_unico_com_cartao`) — nada fica\nsalvo pra renovar, porque não existe renovação. Aprovado, aplica o\nplano/armazenamento/saldo avulso igual sempre aplicou.","operationId":"salvar_cartao_e_assinar_api_pagamentos_cartao_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SalvarCartaoEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsuarioPublico"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/pagamentos/webhook/mercadopago":{"post":{"tags":["pagamentos"],"summary":"Webhook Mercadopago","description":"Recebe a notificação assíncrona do Mercado Pago (pagamento de pacote\naprovado, ou assinatura de plano autorizada) e aplica o plano/pacote na\nconta — ver `app/pagamentos.py:processar_webhook`.\n\nEndpoint público (sem `Depends(usuario_atual)` — a notificação vem dos\nservidores do Mercado Pago, não de uma sessão de usuário logado no\nsite) e sempre devolve `200`, mesmo quando ignora a notificação (tipo\ndesconhecido, referência não encontrada) — só NÃO devolve 200 se a\nconsulta ao Mercado Pago falhar de verdade, pra ele saber que precisa\nreenviar depois.\n\nv66 — antes de processar, valida a assinatura HMAC do header\n`x-signature` (ver `pagamentos.assinatura_webhook_valida`). Sem\n`MERCADOPAGO_WEBHOOK_SECRET` no `.env`, essa validação é pulada (devolve\nsempre válida) — o mesmo padrão gracioso do resto do módulo.","operationId":"webhook_mercadopago_api_pagamentos_webhook_mercadopago_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Webhook Mercadopago Api Pagamentos Webhook Mercadopago Post"}}}}}}},"/api/usuarios/eu/capacidade":{"get":{"tags":["transcrições"],"summary":"Capacidade Atual","description":"Quantos arquivos a conta ainda pode processar agora — usado pelo frontend para\navisar ANTES de clicar em \"Processar\" se o envio vai estourar a cota do mês.","operationId":"capacidade_atual_api_usuarios_eu_capacidade_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CapacidadeResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/verificar-duplicados":{"post":{"tags":["transcrições"],"summary":"Verificar Duplicados","description":"Para cada nome de arquivo, diz se essa conta já processou algo com esse nome antes.","operationId":"verificar_duplicados_api_transcricoes_verificar_duplicados_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DuplicataEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificarDuplicadosResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/site/descobrir":{"post":{"tags":["transcrições"],"summary":"Descobrir Site","description":"Rastreia um site a partir de uma URL terminada em `/*` e devolve as\npáginas/arquivos encontrados — SEM gastar nenhum crédito (v41).\n\nPensado como um passo de confirmação: o frontend mostra ao usuário\nquantas páginas/arquivos foram encontrados (e quantos créditos isso\ncustaria) ANTES de processar de verdade. Se o usuário confirmar, o\nfrontend reenvia essa mesma lista (`paginas` + `arquivos`) como URLs\nnormais em `POST /transcricoes` — não existe um endpoint de confirmação\nseparado, porque pra esse endpoint uma URL a mais na lista já é\nexatamente 1 crédito a mais, do jeito que já funciona hoje pra qualquer\nlista de URLs enviada manualmente.","operationId":"descobrir_site_api_transcricoes_site_descobrir_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DescobrirSiteEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DescobrirSiteResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes":{"post":{"tags":["transcrições"],"summary":"Criar Transcricao","description":"Cria um trabalho a partir de arquivos enviados e/ou páginas web (v19: `urls`).\n\nUma URL conta como \"1 arquivo\" pra tudo que já existia — cota diária,\n`total_enviados`/`a_processar`, dedup por nome (aqui, a própria URL) —\nsem precisar duplicar toda essa lógica: `trabalhos.criar_trabalho`\naceita `Path` (arquivo) e `str` (URL) misturados na mesma lista (ver\n`motor.eh_url`/`motor.identificador`/`motor.nome_exibicao`).","operationId":"criar_transcricao_api_transcricoes_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_criar_transcricao_api_transcricoes_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrabalhoCriadoResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/reprocessar":{"post":{"tags":["transcrições"],"summary":"Reprocessar Arquivo Falho","description":"Reprocessa UM item (arquivo OU URL) que falhou num trabalho anterior, sem gastar cota (v19).\n\nO botão \"Reprocessar\" que aparece ao lado do selo \"Falhou\" na tela de\nupload chama este endpoint em vez de `POST /transcricoes` — o item já\ntinha consumido crédito(s) da cota quando foi enviado a primeira vez e\nnão deu certo, então cobrar de novo por uma tentativa nova seria cobrar\nduas vezes pelo mesmo item. `trabalhos.falha_registrada` garante que só\ndá pra usar essa \"vaga grátis\" para um item que genuinamente falhou num\ntrabalho desta conta — não é um jeito de reprocessar qualquer coisa de\ngraça. Aceita exatamente um dos dois: `arquivo` (multipart) ou `url`\n(link http(s) que falhou como página web).","operationId":"reprocessar_arquivo_falho_api_transcricoes_reprocessar_post","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_reprocessar_arquivo_falho_api_transcricoes_reprocessar_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrabalhoCriadoResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/ativo":{"get":{"tags":["transcrições"],"summary":"Transcricao Ativa","description":"Existe algum trabalho \"processando\" desta conta agora? (v30)\n\nA thread de um trabalho continua rodando no backend mesmo que a aba seja\nfechada ou a página recarregada — só o(s) `job_id` que o navegador estava\nacompanhando se perdia(m) nessa hora, deixando a tela sem nenhum jeito de\nsaber que processamento procurar. O frontend chama este endpoint ao\ncarregar a home (`reconectarTrabalhosEmAndamento` em `upload.js`) e, se\nvier algum `job_ids`, volta a acompanhar cada trabalho por polling —\nexatamente como se tivesse acabado de clicar em \"Processar\" pra cada um.\n\nDesde a v62: devolve TODOS os trabalhos \"processando\" da conta (não só o\nmais recente) — o frontend passou a permitir vários lotes rodando ao\nmesmo tempo (ver `site/js/upload.js`), então reconectar precisa\nreconstruir a tela inteira, não só o último lote enviado.\n\nPrecisa vir ANTES de `GET /transcricoes/{job_id}` no arquivo: como\n\"ativo\" bate no mesmo formato de rota (um segmento só depois de\n`/transcricoes/`), a rota registrada primeiro é que ganha.","operationId":"transcricao_ativa_api_transcricoes_ativo_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Transcricao Ativa Api Transcricoes Ativo Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/{job_id}":{"get":{"tags":["transcrições"],"summary":"Status Transcricao","operationId":"status_transcricao_api_transcricoes__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Status Transcricao Api Transcricoes  Job Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/{job_id}/arquivo":{"get":{"tags":["transcrições"],"summary":"Baixar Transcricao","operationId":"baixar_transcricao_api_transcricoes__job_id__arquivo_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/{job_id}/cancelar":{"post":{"tags":["transcrições"],"summary":"Cancelar Transcricao","description":"Cancela um trabalho em andamento (v36): encerra quem está processando agora e esvazia a fila do resto.\n\nNão perde o que já tinha sido concluído: se algum arquivo já tinha\nterminado com sucesso antes do pedido de cancelar, o trabalho termina\ncom `status: \"cancelado\"` mas ainda com um zip pra baixar (mesmo\n`GET /transcricoes/{job_id}/arquivo` de sempre) — só o que ainda não\ntinha terminado é que se perde. Devolve 409 se não havia nada\n\"processando\" pra cancelar (já tinha terminado, dado erro, ou sido\ncancelado antes).","operationId":"cancelar_transcricao_api_transcricoes__job_id__cancelar_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cancelar Transcricao Api Transcricoes  Job Id  Cancelar Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/{job_id}/iniciar-login":{"post":{"tags":["transcrições"],"summary":"Iniciar Login Transcricao","description":"Abre uma sessão de navegador ao vivo pra um item \"aguardando login\" (v40).\n\nO popup do frontend chama isso ao clicar no selo amarelo \"Requer login\"\n— devolve um `sessao_id`, usado nos endpoints de\ntela/evento/confirmar/cancelar abaixo (ver `app/login_ao_vivo.py`).","operationId":"iniciar_login_transcricao_api_transcricoes__job_id__iniciar_login_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IniciarLoginRequisicao"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IniciarLoginResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/login/{sessao_id}/tela":{"get":{"tags":["transcrições"],"summary":"Tela Login Ao Vivo","description":"PNG com a tela atual da sessão de navegador ao vivo — consultado em polling curto pelo popup.","operationId":"tela_login_ao_vivo_api_transcricoes_login__sessao_id__tela_get","parameters":[{"name":"sessao_id","in":"path","required":true,"schema":{"type":"string","title":"Sessao Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/login/{sessao_id}/evento":{"post":{"tags":["transcrições"],"summary":"Evento Login Ao Vivo","description":"Retransmite um clique/digitação/tecla/scroll do popup pra sessão de navegador ao vivo.","operationId":"evento_login_ao_vivo_api_transcricoes_login__sessao_id__evento_post","parameters":[{"name":"sessao_id","in":"path","required":true,"schema":{"type":"string","title":"Sessao Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventoLoginRequisicao"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Evento Login Ao Vivo Api Transcricoes Login  Sessao Id  Evento Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/login/{sessao_id}/confirmar":{"post":{"tags":["transcrições"],"summary":"Confirmar Login Ao Vivo","description":"Usuário concluiu o login no popup: captura a sessão autenticada e dispara a nova tentativa de raspagem em segundo plano.\n\nv41: a resposta agora também traz `credencial_detectada` — se `True`, o\npopup mostra um prompt extra perguntando se o usuário quer salvar esse\nusuário/senha pra próxima transcrição do mesmo site entrar sozinha\n(ver `POST .../salvar-credencial` abaixo).","operationId":"confirmar_login_ao_vivo_api_transcricoes_login__sessao_id__confirmar_post","parameters":[{"name":"sessao_id","in":"path","required":true,"schema":{"type":"string","title":"Sessao Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Confirmar Login Ao Vivo Api Transcricoes Login  Sessao Id  Confirmar Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/login/{sessao_id}/salvar-credencial":{"post":{"tags":["transcrições"],"summary":"Salvar Credencial Login Ao Vivo","description":"Usuário respondeu ao prompt \"salvar esse login pra da próxima vez entrar sozinho?\" (v41).\n\nSó chega a existir uma credencial candidata pendente pra essa sessão se\n`confirmar_login_ao_vivo` acima tiver devolvido `credencial_detectada:\ntrue` — chamar isso sem nenhuma pendente (já respondida, ou expirada)\nsimplesmente não faz nada (`salvou: false`), sem erro.","operationId":"salvar_credencial_login_ao_vivo_api_transcricoes_login__sessao_id__salvar_credencial_post","parameters":[{"name":"sessao_id","in":"path","required":true,"schema":{"type":"string","title":"Sessao Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SalvarCredencialRequisicao"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Salvar Credencial Login Ao Vivo Api Transcricoes Login  Sessao Id  Salvar Credencial Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/login/{sessao_id}/cancelar":{"post":{"tags":["transcrições"],"summary":"Cancelar Login Ao Vivo","description":"Usuário fechou o popup sem concluir o login: encerra a sessão e marca o item como falha comum (crédito devolvido).","operationId":"cancelar_login_ao_vivo_api_transcricoes_login__sessao_id__cancelar_post","parameters":[{"name":"sessao_id","in":"path","required":true,"schema":{"type":"string","title":"Sessao Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cancelar Login Ao Vivo Api Transcricoes Login  Sessao Id  Cancelar Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/credenciais-login":{"get":{"tags":["transcrições"],"summary":"Listar Credenciais Login","description":"Domínios com login salvo pra essa conta (v41) — nunca inclui a senha, nem criptografada.","operationId":"listar_credenciais_login_api_credenciais_login_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CredencialLoginResposta"},"title":"Response Listar Credenciais Login Api Credenciais Login Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/credenciais-login/{dominio}":{"delete":{"tags":["transcrições"],"summary":"Remover Credencial Login","description":"Remove um login salvo (v41) — a próxima transcrição desse domínio volta a abrir o popup normalmente.","operationId":"remover_credencial_login_api_credenciais_login__dominio__delete","parameters":[{"name":"dominio","in":"path","required":true,"schema":{"type":"string","title":"Dominio"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Remover Credencial Login Api Credenciais Login  Dominio  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/{job_id}/arquivo-parcial":{"get":{"tags":["transcrições"],"summary":"Baixar Transcricao Parcial","description":"Baixa um `.zip` só com os arquivos já concluídos até agora, sem esperar o trabalho terminar.","operationId":"baixar_transcricao_parcial_api_transcricoes__job_id__arquivo_parcial_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/transcricoes/{job_id}/conteudo":{"get":{"tags":["transcrições"],"summary":"Conteudo Texto Transcricao","description":"Texto (.md) do que já terminou nesse trabalho, pronto pra copiar e mandar pra uma IA externa (v23, item 6).\n\nNão espera o trabalho terminar — funciona com o que já estiver pronto,\nigual `/arquivo-parcial`, só que devolve texto em vez de um `.zip`.","operationId":"conteudo_texto_transcricao_api_transcricoes__job_id__conteudo_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConteudoTextoResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/historico":{"get":{"tags":["histórico"],"summary":"Listar Historico","operationId":"listar_historico_api_historico_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filtra pelo nome do arquivo de origem (busca parcial).","title":"Q"},"description":"Filtra pelo nome do arquivo de origem (busca parcial)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoricoListaResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/historico/{historico_id}/arquivo":{"get":{"tags":["histórico"],"summary":"Baixar Historico","operationId":"baixar_historico_api_historico__historico_id__arquivo_get","parameters":[{"name":"historico_id","in":"path","required":true,"schema":{"type":"integer","title":"Historico Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/historico/zip":{"post":{"tags":["histórico"],"summary":"Baixar Historico Em Lote","description":"Baixa vários itens do histórico de uma vez, combinados num `.zip` só (Fase B: seleção múltipla).","operationId":"baixar_historico_em_lote_api_historico_zip_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoricoIdsEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/historico/excluir":{"post":{"tags":["histórico"],"summary":"Excluir Historico Em Lote","description":"Apaga um ou vários itens do histórico (Fase B: botão excluir, com ou sem seleção múltipla).\n\nIds que não existem ou não são dessa conta são ignorados em silêncio —\ndevolve quantos foram apagados de verdade, sem 404 por causa de um `id`\nruim isolado no meio de uma seleção maior.","operationId":"excluir_historico_em_lote_api_historico_excluir_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoricoIdsEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoricoExcluidosResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/historico/excluidos":{"get":{"tags":["histórico"],"summary":"Listar Historico Excluidos","description":"v77 — a lixeira de \"Meus arquivos\" (\"Excluídos\"): itens excluídos ainda dentro do prazo de 30 dias.\n\nAntes de listar, apaga de vez o que dessa conta já passou do prazo, então\nnunca aparece como restaurável algo que a limpeza periódica ainda não\nalcançou.","operationId":"listar_historico_excluidos_api_historico_excluidos_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LixeiraListaResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/historico/restaurar":{"post":{"tags":["histórico"],"summary":"Restaurar Historico Em Lote","description":"v77 — tira itens da lixeira: voltam para a lista, buscas e grupos de \"Meus arquivos\".\n\nIds que não estão na lixeira (ou não são dessa conta) são ignorados;\n`excluidos` devolve quantos foram restaurados de verdade.","operationId":"restaurar_historico_em_lote_api_historico_restaurar_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoricoIdsEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoricoExcluidosResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/historico/excluir-definitivamente":{"post":{"tags":["histórico"],"summary":"Excluir Historico Definitivamente","description":"v77 — apaga de vez itens que JÁ estão na lixeira (sem volta): libera o espaço no armazenamento.\n\nSó age em itens da lixeira — um item ativo de \"Meus arquivos\" passado\naqui é ignorado, precisa ser excluído (lixeira) antes. A cópia que a\nconta tenha no próprio Google Drive não é apagada.","operationId":"excluir_historico_definitivamente_api_historico_excluir_definitivamente_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoricoIdsEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HistoricoExcluidosResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/historico/{historico_id}/visualizar":{"get":{"tags":["histórico"],"summary":"Visualizar Historico","operationId":"visualizar_historico_api_historico__historico_id__visualizar_get","parameters":[{"name":"historico_id","in":"path","required":true,"schema":{"type":"integer","title":"Historico Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisualizarHistoricoResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/historico/{historico_id}/arquivo-origem":{"get":{"tags":["histórico"],"summary":"Baixar Arquivo Origem Historico","description":"Bytes do arquivo de ORIGEM guardado (vídeo/imagem/PDF/planilha original) — só existe em planos pagos.","operationId":"baixar_arquivo_origem_historico_api_historico__historico_id__arquivo_origem_get","parameters":[{"name":"historico_id","in":"path","required":true,"schema":{"type":"integer","title":"Historico Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/historico/{historico_id}/site-original":{"get":{"tags":["histórico"],"summary":"Visualizar Site Original Historico","description":"Busca a página/arquivo ORIGINAL (não o resultado convertido) aqui no servidor e devolve pro\nnavegador a partir do NOSSO domínio — ver o bloco de comentário acima para o porquê.\n\nSó funciona pra item cujo `nome_arquivo_origem` é uma URL de verdade (o mesmo item que\n`GET /historico/{id}/visualizar` já devolveria com `tipo=\"site\"`).\n\nHTML recebe um `<base href=\"...\">` injetado logo depois de `<head>` antes de devolver — imagens/CSS/JS\nrelativos da página original continuam carregando DIRETO do site de origem (o navegador do usuário\nbusca cada um, não este backend); só o documento HTML em si passa por aqui. Evita ter que proxiar cada\nsub-recurso da página (que também não precisaria: `X-Frame-Options` só afeta o documento sendo\nENQUADRADO num iframe, nunca o carregamento comum de uma imagem/script/CSS).","operationId":"visualizar_site_original_historico_api_historico__historico_id__site_original_get","parameters":[{"name":"historico_id","in":"path","required":true,"schema":{"type":"integer","title":"Historico Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/historico/{historico_id}/conteudo":{"get":{"tags":["histórico"],"summary":"Conteudo Texto Historico","description":"Texto (.md) do resultado convertido, pronto pra copiar e mandar pra uma IA externa (v23, item 6).\n\nDiferente de `GET /historico/{id}/visualizar` (que devolve o arquivo de\nORIGEM quando a conta guardou um, pra mostrar o vídeo/imagem/PDF\noriginal no visualizador), este endpoint sempre devolve o `.md`\nconvertido — é o único conteúdo que faz sentido colar num chat de\ntexto, mesmo quando a conta também guardou a origem binária.","operationId":"conteudo_texto_historico_api_historico__historico_id__conteudo_get","parameters":[{"name":"historico_id","in":"path","required":true,"schema":{"type":"integer","title":"Historico Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConteudoTextoResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/armazenamento/planos":{"get":{"tags":["armazenamento"],"summary":"Listar Planos Armazenamento","operationId":"listar_planos_armazenamento_api_armazenamento_planos_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/PlanoArmazenamento"},"type":"array","title":"Response Listar Planos Armazenamento Api Armazenamento Planos Get"}}}}}}},"/api/armazenamento/uso":{"get":{"tags":["armazenamento"],"summary":"Uso Armazenamento","operationId":"uso_armazenamento_api_armazenamento_uso_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsoArmazenamentoResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/usuarios/eu/plano-armazenamento":{"patch":{"tags":["armazenamento"],"summary":"Escolher Plano Armazenamento","operationId":"escolher_plano_armazenamento_api_usuarios_eu_plano_armazenamento_patch","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EscolherPlanoArmazenamentoEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsuarioPublico"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/usuarios/eu/idioma-interface":{"patch":{"tags":["autenticação"],"summary":"Escolher Idioma Interface","description":"v42: espelha na conta o idioma escolhido no seletor do site (`#seletor-idioma`, `Site/js/i18n.js`).\n\nChamado pelo próprio `i18n.js` (via `Site/js/auth.js:definirIdiomaInterface`)\ntoda vez que o usuário troca de idioma manualmente — o valor salvo aqui\né o que `criar_transcricao`/`reprocessar_arquivo_falho` usam pra montar\no `--idioma-destino` do motor, então passa a valer em qualquer\ndispositivo onde a conta fizer login, não só no navegador onde a troca\nfoi feita.","operationId":"escolher_idioma_interface_api_usuarios_eu_idioma_interface_patch","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EscolherIdiomaInterfaceEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsuarioPublico"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/usuarios/eu/perfil":{"patch":{"tags":["autenticação"],"summary":"Atualizar Perfil","operationId":"atualizar_perfil_api_usuarios_eu_perfil_patch","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PerfilAtualizarEntrada"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsuarioPublico"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/usuarios/eu/indicacoes":{"get":{"tags":["autenticação"],"summary":"Obter Indicacoes","operationId":"obter_indicacoes_api_usuarios_eu_indicacoes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndicacoesResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/usuarios/eu/pagamentos":{"get":{"tags":["autenticação"],"summary":"Listar Meus Pagamentos","description":"Aba \"Histórico de pagamentos\" do painel de conta — `db.listar_pagamentos_do_usuario`\njá existia (v60), só nunca tinha sido exposta por um endpoint.","operationId":"listar_meus_pagamentos_api_usuarios_eu_pagamentos_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PagamentoItem"},"title":"Response Listar Meus Pagamentos Api Usuarios Eu Pagamentos Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/usuarios/eu":{"delete":{"tags":["autenticação"],"summary":"Excluir Minha Conta","description":"Exclusão de conta de verdade, autoatendimento — a confirmação (digitar\no e-mail de novo) é responsabilidade do frontend (`site/js/conta.js`); o\ntoken válido já é o controle de segurança real aqui. Ordem importa: cada\npasso é melhor-esforço e nunca impede o próximo, porque uma conta\npresa por causa de um efeito colateral (assinatura que não cancelou,\ntoken do Google que não revogou) é pior do que um resíduo externo\nesquecido — a linha em `usuarios` sempre é apagada no final.","operationId":"excluir_minha_conta_api_usuarios_eu_delete","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExcluirContaEntrada"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/integracoes/google-drive/status":{"get":{"tags":["google-drive"],"summary":"Status Google Drive","operationId":"status_google_drive_api_integracoes_google_drive_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleDriveStatusResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/integracoes/google-drive/config-picker":{"get":{"tags":["google-drive"],"summary":"Config Picker Google Drive","description":"v29, item 1 — o que o navegador precisa pra abrir o Google Picker e importar arquivos do Drive.\n\nNenhum download acontece aqui: o navegador usa `access_token` direto\ncontra a API do Drive pra baixar os bytes do arquivo escolhido, que\nentra na fila de upload como um `File` normal — mesma validação de\nformato/cota de sempre, sem caminho especial no backend.","operationId":"config_picker_google_drive_api_integracoes_google_drive_config_picker_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleDrivePickerConfigResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/integracoes/google-drive/autorizar":{"get":{"tags":["google-drive"],"summary":"Autorizar Google Drive","description":"Devolve a URL de consentimento do Google — o frontend navega o navegador inteiro pra lá (`window.location`).\n\nO `state` (opaco, uso único) é como o callback abaixo — uma requisição do\nPRÓPRIO GOOGLE redirecionando o navegador, sem cabeçalho `Authorization`\nnenhum — sabe a qual conta pertence essa autorização.","operationId":"autorizar_google_drive_api_integracoes_google_drive_autorizar_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleDriveAutorizarResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/integracoes/google-drive/callback":{"get":{"tags":["google-drive"],"summary":"Callback Google Drive","description":"Pra onde o Google redireciona o navegador depois da tela de consentimento — SEM autenticação por Bearer.\n\nNão dá pra exigir `Authorization` aqui (é o navegador do usuário sendo\nredirecionado pelo Google, não uma chamada da nossa própria página) — por\nisso o `state` de uso único (ver `autorizar_google_drive`) é quem garante\nque só quem de fato iniciou esse fluxo tem a conexão gravada na conta\ncerta. Termina sempre redirecionando de volta pro site, com\n`?google_drive=conectado` ou `?google_drive=erro` na URL — a página lê\nisso e mostra um aviso (ver `js/historico.js`).","operationId":"callback_google_drive_api_integracoes_google_drive_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/integracoes/google-drive/desconectar":{"post":{"tags":["google-drive"],"summary":"Desconectar Google Drive","description":"Remove a integração local e tenta revogar o token no Google (melhor esforço).\n\nNão apaga nada que já foi enviado pro Drive da conta — os arquivos\ncontinuam lá, na pasta \"Transcritor (backups)\", sob controle do próprio\nusuário (ver limitação conhecida no README/memória). A partir daqui, o\narquivo de origem de um processamento novo volta a seguir a regra de\nantes (plano de armazenamento local pago, ou nada).","operationId":"desconectar_google_drive_api_integracoes_google_drive_desconectar_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleDriveDesconectarResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/suporte/chamados":{"post":{"tags":["suporte"],"summary":"Abrir Chamado Suporte","operationId":"abrir_chamado_suporte_api_suporte_chamados_post","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_abrir_chamado_suporte_api_suporte_chamados_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChamadoSuporteCriadoResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]},"get":{"tags":["suporte"],"summary":"Listar Chamados Suporte","description":"Chamados abertos por essa conta, mais recente primeiro, com seus anexos.","operationId":"listar_chamados_suporte_api_suporte_chamados_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChamadoSuporteListaResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/suporte/ideias":{"post":{"tags":["suporte"],"summary":"Abrir Ideia","operationId":"abrir_ideia_api_suporte_ideias_post","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_abrir_ideia_api_suporte_ideias_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdeiaCriadaResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]},"get":{"tags":["suporte"],"summary":"Listar Ideias","description":"Ideias enviadas por essa conta, mais recente primeiro, com seus anexos.","operationId":"listar_ideias_api_suporte_ideias_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdeiaListaResposta"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/suporte/chamados/{chamado_id}/anexos/{anexo_id}":{"get":{"tags":["suporte"],"summary":"Baixar Anexo Chamado Suporte","description":"Bytes de um anexo enviado num chamado — só o dono do chamado pode baixar.","operationId":"baixar_anexo_chamado_suporte_api_suporte_chamados__chamado_id__anexos__anexo_id__get","parameters":[{"name":"chamado_id","in":"path","required":true,"schema":{"type":"integer","title":"Chamado Id"}},{"name":"anexo_id","in":"path","required":true,"schema":{"type":"integer","title":"Anexo Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}},"/api/suporte/ideias/{ideia_id}/anexos/{anexo_id}":{"get":{"tags":["suporte"],"summary":"Baixar Anexo Ideia","description":"Bytes de um anexo enviado numa ideia — v48, item 1 do pedido. Mesma\nlógica de `baixar_anexo_chamado_suporte` acima: só o dono pode baixar.","operationId":"baixar_anexo_ideia_api_suporte_ideias__ideia_id__anexos__anexo_id__get","parameters":[{"name":"ideia_id","in":"path","required":true,"schema":{"type":"integer","title":"Ideia Id"}},{"name":"anexo_id","in":"path","required":true,"schema":{"type":"integer","title":"Anexo Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"Bearer":[]}]}}},"components":{"schemas":{"AnexoItem":{"properties":{"id":{"type":"integer","title":"Id"},"nome_arquivo":{"type":"string","title":"Nome Arquivo"},"tamanho_bytes":{"type":"integer","title":"Tamanho Bytes"}},"type":"object","required":["id","nome_arquivo","tamanho_bytes"],"title":"AnexoItem","description":"Um anexo já salvo — de um chamado (v28) ou de uma ideia (v48), item 3/1\ndo pedido do Leandro respectivamente. Mesmo formato pros dois porque em\ndisco/banco só muda a tabela (`anexos_chamado_suporte`/`anexos_ideia_usuario`),\nnunca o que é exposto pra API."},"Body_abrir_chamado_suporte_api_suporte_chamados_post":{"properties":{"assunto":{"type":"string","maxLength":200,"minLength":1,"title":"Assunto"},"mensagem":{"type":"string","maxLength":5000,"minLength":1,"title":"Mensagem"},"anexos":{"items":{"type":"string","contentMediaType":"application/octet-stream"},"type":"array","title":"Anexos","default":[]}},"type":"object","required":["assunto","mensagem"],"title":"Body_abrir_chamado_suporte_api_suporte_chamados_post"},"Body_abrir_ideia_api_suporte_ideias_post":{"properties":{"titulo":{"type":"string","maxLength":200,"minLength":1,"title":"Titulo"},"descricao":{"type":"string","maxLength":5000,"minLength":1,"title":"Descricao"},"anexos":{"items":{"type":"string","contentMediaType":"application/octet-stream"},"type":"array","title":"Anexos","default":[]}},"type":"object","required":["titulo","descricao"],"title":"Body_abrir_ideia_api_suporte_ideias_post"},"Body_criar_transcricao_api_transcricoes_post":{"properties":{"arquivos":{"items":{"type":"string","contentMediaType":"application/octet-stream"},"type":"array","title":"Arquivos","default":[]},"urls":{"items":{"type":"string"},"type":"array","title":"Urls","default":[]},"pular":{"items":{"type":"string"},"type":"array","title":"Pular","default":[]}},"type":"object","title":"Body_criar_transcricao_api_transcricoes_post"},"Body_reprocessar_arquivo_falho_api_transcricoes_reprocessar_post":{"properties":{"job_id_origem":{"type":"string","title":"Job Id Origem"},"arquivo":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"Arquivo"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},"type":"object","required":["job_id_origem"],"title":"Body_reprocessar_arquivo_falho_api_transcricoes_reprocessar_post"},"CapacidadeResposta":{"properties":{"plano":{"type":"string","title":"Plano"},"vagas_no_mes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Vagas No Mes"},"limite_mensal":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limite Mensal"},"usados_no_mes":{"type":"integer","title":"Usados No Mes"},"saldo_avulso":{"type":"integer","title":"Saldo Avulso"}},"type":"object","required":["plano","vagas_no_mes","limite_mensal","usados_no_mes","saldo_avulso"],"title":"CapacidadeResposta"},"ChamadoSuporteCriadoResposta":{"properties":{"id":{"type":"integer","title":"Id"}},"type":"object","required":["id"],"title":"ChamadoSuporteCriadoResposta"},"ChamadoSuporteItem":{"properties":{"id":{"type":"integer","title":"Id"},"assunto":{"type":"string","title":"Assunto"},"mensagem":{"type":"string","title":"Mensagem"},"status":{"type":"string","title":"Status"},"criado_em":{"type":"string","title":"Criado Em"},"anexos":{"items":{"$ref":"#/components/schemas/AnexoItem"},"type":"array","title":"Anexos","default":[]}},"type":"object","required":["id","assunto","mensagem","status","criado_em"],"title":"ChamadoSuporteItem"},"ChamadoSuporteListaResposta":{"properties":{"itens":{"items":{"$ref":"#/components/schemas/ChamadoSuporteItem"},"type":"array","title":"Itens"}},"type":"object","required":["itens"],"title":"ChamadoSuporteListaResposta"},"CheckoutEntrada":{"properties":{"tipo":{"type":"string","maxLength":30,"title":"Tipo"},"referencia_id":{"type":"string","maxLength":50,"title":"Referencia Id"}},"type":"object","required":["tipo","referencia_id"],"title":"CheckoutEntrada"},"CheckoutResposta":{"properties":{"url_checkout":{"type":"string","title":"Url Checkout"},"pagamento_id":{"type":"integer","title":"Pagamento Id"}},"type":"object","required":["url_checkout","pagamento_id"],"title":"CheckoutResposta"},"ComprarPacoteEntrada":{"properties":{"pacote_id":{"type":"string","maxLength":50,"title":"Pacote Id"}},"type":"object","required":["pacote_id"],"title":"ComprarPacoteEntrada"},"ConfiguracaoLoginGoogleResposta":{"properties":{"disponivel":{"type":"boolean","title":"Disponivel"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"}},"type":"object","required":["disponivel"],"title":"ConfiguracaoLoginGoogleResposta"},"ConfiguracaoMercadoPagoResposta":{"properties":{"disponivel":{"type":"boolean","title":"Disponivel"},"public_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Key"}},"type":"object","required":["disponivel"],"title":"ConfiguracaoMercadoPagoResposta"},"ConfiguracaoTurnstileResposta":{"properties":{"disponivel":{"type":"boolean","title":"Disponivel"},"site_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Key"}},"type":"object","required":["disponivel"],"title":"ConfiguracaoTurnstileResposta"},"ConteudoTextoResposta":{"properties":{"conteudo":{"type":"string","title":"Conteudo"},"nome_sugerido":{"type":"string","title":"Nome Sugerido"}},"type":"object","required":["conteudo","nome_sugerido"],"title":"ConteudoTextoResposta","description":"`GET /historico/{id}/conteudo` e `GET /transcricoes/{job_id}/conteudo` — v23, item 6.\n\nTexto pronto pra copiar e colar num chat de IA externa (Claude, ChatGPT,\nGemini, Copilot, Perplexity) — sempre o `.md` já convertido, nunca o\narquivo de origem binário (áudio/vídeo/PDF...), que não faria sentido\ncolar num chat de texto."},"CredencialLoginResposta":{"properties":{"dominio":{"type":"string","title":"Dominio"},"usuario_site":{"type":"string","title":"Usuario Site"},"atualizado_em":{"type":"string","title":"Atualizado Em"}},"type":"object","required":["dominio","usuario_site","atualizado_em"],"title":"CredencialLoginResposta"},"DescobrirSiteEntrada":{"properties":{"url":{"type":"string","maxLength":2048,"title":"Url"}},"type":"object","required":["url"],"title":"DescobrirSiteEntrada"},"DescobrirSiteResposta":{"properties":{"paginas":{"items":{"type":"string"},"type":"array","title":"Paginas"},"arquivos":{"items":{"type":"string"},"type":"array","title":"Arquivos"},"paginas_login_ignoradas":{"items":{"type":"string"},"type":"array","title":"Paginas Login Ignoradas"},"limite_atingido":{"type":"boolean","title":"Limite Atingido"},"total_creditos":{"type":"integer","title":"Total Creditos"}},"type":"object","required":["paginas","arquivos","paginas_login_ignoradas","limite_atingido","total_creditos"],"title":"DescobrirSiteResposta"},"DuplicataEntrada":{"properties":{"nomes_arquivos":{"items":{"type":"string","maxLength":1024},"type":"array","maxItems":1000,"title":"Nomes Arquivos"}},"type":"object","required":["nomes_arquivos"],"title":"DuplicataEntrada"},"DuplicataInfo":{"properties":{"nome_arquivo":{"type":"string","title":"Nome Arquivo"},"historico_id":{"type":"integer","title":"Historico Id"},"processado_em":{"type":"string","title":"Processado Em"}},"type":"object","required":["nome_arquivo","historico_id","processado_em"],"title":"DuplicataInfo"},"EscolherIdiomaInterfaceEntrada":{"properties":{"idioma_interface":{"type":"string","maxLength":16,"title":"Idioma Interface"}},"type":"object","required":["idioma_interface"],"title":"EscolherIdiomaInterfaceEntrada","description":"v42: corpo de `PATCH /usuarios/eu/idioma-interface` — espelha na conta o idioma do seletor do site."},"EscolherPlanoArmazenamentoEntrada":{"properties":{"plano_armazenamento_id":{"type":"string","maxLength":50,"title":"Plano Armazenamento Id"}},"type":"object","required":["plano_armazenamento_id"],"title":"EscolherPlanoArmazenamentoEntrada"},"EscolherPlanoEntrada":{"properties":{"plano_id":{"type":"string","maxLength":50,"title":"Plano Id"}},"type":"object","required":["plano_id"],"title":"EscolherPlanoEntrada"},"EsqueciSenhaEntrada":{"properties":{"email":{"type":"string","maxLength":254,"title":"Email"},"idioma":{"anyOf":[{"type":"string","maxLength":16},{"type":"null"}],"title":"Idioma"}},"type":"object","required":["email"],"title":"EsqueciSenhaEntrada"},"EventoLoginRequisicao":{"properties":{"tipo":{"type":"string","maxLength":16,"title":"Tipo"},"x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"X"},"y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Y"},"texto":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Texto"},"tecla":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Tecla"},"deltaX":{"type":"number","title":"Deltax","default":0},"deltaY":{"type":"number","title":"Deltay","default":0}},"type":"object","required":["tipo"],"title":"EventoLoginRequisicao"},"ExcluirContaEntrada":{"properties":{"email":{"type":"string","maxLength":254,"title":"Email"},"senha":{"anyOf":[{"type":"string","maxLength":1024},{"type":"null"}],"title":"Senha"}},"type":"object","required":["email"],"title":"ExcluirContaEntrada","description":"v74 — reautenticação pra apagar a conta: o e-mail digitado (o frontend já pedia, mas o\nservidor nunca conferia) e, se a conta tem senha, a senha atual."},"GoogleDriveAutorizarResposta":{"properties":{"url":{"type":"string","title":"Url"}},"type":"object","required":["url"],"title":"GoogleDriveAutorizarResposta"},"GoogleDriveDesconectarResposta":{"properties":{"desconectado":{"type":"boolean","title":"Desconectado"}},"type":"object","required":["desconectado"],"title":"GoogleDriveDesconectarResposta"},"GoogleDrivePickerConfigResposta":{"properties":{"disponivel":{"type":"boolean","title":"Disponivel"},"api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Key"},"client_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Id"},"access_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Access Token"}},"type":"object","required":["disponivel"],"title":"GoogleDrivePickerConfigResposta","description":"`GET /integracoes/google-drive/config-picker` — v29, item 1 do pedido do Leandro.\n\nTudo que o navegador precisa pra abrir o Google Picker e importar\narquivos do Drive da conta pra fila de processamento, sem o backend\nbaixar nada: `api_key`/`client_id` não são segredo (vão pro navegador\nde qualquer forma, restritos por domínio no Google Cloud Console);\n`access_token` é o de curta duração (~1h) já usado pra falar com a API\ndo Drive — vem `None` quando a conta ainda não conectou o Drive, mesmo\ncom `disponivel=True` (recurso configurado no servidor)."},"GoogleDriveStatusResposta":{"properties":{"configurado":{"type":"boolean","title":"Configurado"},"conectado":{"type":"boolean","title":"Conectado"},"picker_disponivel":{"type":"boolean","title":"Picker Disponivel","default":false},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"bytes_enviados_pelo_transcritor":{"type":"integer","title":"Bytes Enviados Pelo Transcritor","default":0},"uso_conta_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Uso Conta Bytes"},"limite_conta_bytes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limite Conta Bytes"}},"type":"object","required":["configurado","conectado"],"title":"GoogleDriveStatusResposta","description":"`GET /integracoes/google-drive/status` — v21, item 1 e item 3 do pedido do Leandro.\n\n`configurado` diz se o SERVIDOR tem as credenciais OAuth (Leandro\nconfigurou `.env`) — sem isso o botão de conectar nem aparece de verdade\nno site. `conectado` diz se ESSA CONTA já autorizou o acesso. Os campos\nde uso (`uso_conta_bytes`/`limite_conta_bytes`) são a cota do Google\nDrive INTEIRO da conta conectada — não só da pasta do Transcritor —\nporque é o único jeito de saber \"quanto ainda cabe\" (a API do Drive não\ntem como perguntar só sobre uma pasta); `limite_conta_bytes=None` quando\na conta não tem limite (raro, contas Google Workspace). Ficam `None`\ntambém quando a consulta ao Google falhar (rede instável, token\nrevogado) — o frontend mostra \"não consegui consultar agora\" nesse caso,\nsem quebrar o resto da tela."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HistoricoExcluidosResposta":{"properties":{"excluidos":{"type":"integer","title":"Excluidos"}},"type":"object","required":["excluidos"],"title":"HistoricoExcluidosResposta","description":"Resposta de `POST /historico/excluir` (manda para a lixeira), `POST /historico/restaurar` e\n`POST /historico/excluir-definitivamente` — `excluidos` é quantos itens foram afetados."},"HistoricoIdsEntrada":{"properties":{"ids":{"items":{"type":"integer"},"type":"array","maxItems":1000,"minItems":1,"title":"Ids"}},"type":"object","required":["ids"],"title":"HistoricoIdsEntrada","description":"Corpo comum a `POST /historico/zip` e `POST /historico/excluir` (Fase B: seleção múltipla)."},"HistoricoItem":{"properties":{"id":{"type":"integer","title":"Id"},"nome_arquivo_origem":{"type":"string","title":"Nome Arquivo Origem"},"nome_arquivo_destino":{"type":"string","title":"Nome Arquivo Destino"},"criado_em":{"type":"string","title":"Criado Em"},"tamanho_destino_bytes":{"type":"integer","title":"Tamanho Destino Bytes","default":0},"tem_arquivo_origem":{"type":"boolean","title":"Tem Arquivo Origem","default":false},"excluido_em":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Excluido Em"},"dias_restantes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Dias Restantes"}},"type":"object","required":["id","nome_arquivo_origem","nome_arquivo_destino","criado_em"],"title":"HistoricoItem"},"HistoricoListaResposta":{"properties":{"itens":{"items":{"$ref":"#/components/schemas/HistoricoItem"},"type":"array","title":"Itens"}},"type":"object","required":["itens"],"title":"HistoricoListaResposta"},"IdeiaCriadaResposta":{"properties":{"id":{"type":"integer","title":"Id"}},"type":"object","required":["id"],"title":"IdeiaCriadaResposta"},"IdeiaItem":{"properties":{"id":{"type":"integer","title":"Id"},"titulo":{"type":"string","title":"Titulo"},"descricao":{"type":"string","title":"Descricao"},"status":{"type":"string","title":"Status"},"criado_em":{"type":"string","title":"Criado Em"},"anexos":{"items":{"$ref":"#/components/schemas/AnexoItem"},"type":"array","title":"Anexos","default":[]}},"type":"object","required":["id","titulo","descricao","status","criado_em"],"title":"IdeiaItem","description":"Uma ideia enviada pelo usuário para a plataforma — pedido do Leandro:\n\n\"acrescente no suporte uma forma de ideias, onde os usuários poderão\nacrescentar ideias para a plataforma\". `status` nasce sempre 'recebida'\n(sem painel de triagem ainda — ver `db.py`, comentário acima de\n`criar_ideia`). `anexos` — v48, item 1 do pedido: \"acrescente para o\nusuário colocar anexos nas ideias\"."},"IdeiaListaResposta":{"properties":{"itens":{"items":{"$ref":"#/components/schemas/IdeiaItem"},"type":"array","title":"Itens"}},"type":"object","required":["itens"],"title":"IdeiaListaResposta"},"IndicacaoCreditoItem":{"properties":{"nome_indicado":{"type":"string","title":"Nome Indicado"},"email_indicado_mascarado":{"type":"string","title":"Email Indicado Mascarado"},"plano":{"type":"string","title":"Plano"},"quantidade_creditada":{"type":"integer","title":"Quantidade Creditada"},"criado_em":{"type":"string","title":"Criado Em"}},"type":"object","required":["nome_indicado","email_indicado_mascarado","plano","quantidade_creditada","criado_em"],"title":"IndicacaoCreditoItem"},"IndicacoesResposta":{"properties":{"codigo_indicacao":{"type":"string","title":"Codigo Indicacao"},"link":{"type":"string","title":"Link"},"creditos":{"items":{"$ref":"#/components/schemas/IndicacaoCreditoItem"},"type":"array","title":"Creditos"}},"type":"object","required":["codigo_indicacao","link","creditos"],"title":"IndicacoesResposta"},"IniciarLoginRequisicao":{"properties":{"nome":{"type":"string","maxLength":2048,"title":"Nome"}},"type":"object","required":["nome"],"title":"IniciarLoginRequisicao"},"IniciarLoginResposta":{"properties":{"sessao_id":{"type":"string","title":"Sessao Id"}},"type":"object","required":["sessao_id"],"title":"IniciarLoginResposta"},"LixeiraListaResposta":{"properties":{"itens":{"items":{"$ref":"#/components/schemas/HistoricoItem"},"type":"array","title":"Itens"},"dias_retencao":{"type":"integer","title":"Dias Retencao"},"uso_excluidos_bytes":{"type":"integer","title":"Uso Excluidos Bytes"}},"type":"object","required":["itens","dias_retencao","uso_excluidos_bytes"],"title":"LixeiraListaResposta","description":"`GET /historico/excluidos` — v77. `dias_retencao` é o prazo (30) para o frontend explicar na tela."},"LoginEntrada":{"properties":{"email":{"type":"string","maxLength":254,"title":"Email"},"senha":{"type":"string","maxLength":1024,"minLength":1,"title":"Senha"},"turnstile_token":{"anyOf":[{"type":"string","maxLength":4096},{"type":"null"}],"title":"Turnstile Token"}},"type":"object","required":["email","senha"],"title":"LoginEntrada"},"LoginGoogleEntrada":{"properties":{"credencial":{"type":"string","maxLength":8192,"minLength":1,"title":"Credencial"},"codigo_indicacao":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Codigo Indicacao"},"idioma":{"anyOf":[{"type":"string","maxLength":16},{"type":"null"}],"title":"Idioma"}},"type":"object","required":["credencial"],"title":"LoginGoogleEntrada"},"PacoteAvulso":{"properties":{"id":{"type":"string","title":"Id"},"conversoes":{"type":"integer","title":"Conversoes"},"preco_brl":{"type":"number","title":"Preco Brl"},"preco_usd":{"type":"number","title":"Preco Usd"}},"type":"object","required":["id","conversoes","preco_brl","preco_usd"],"title":"PacoteAvulso"},"PagamentoItem":{"properties":{"id":{"type":"integer","title":"Id"},"tipo":{"type":"string","title":"Tipo"},"referencia_id":{"type":"string","title":"Referencia Id"},"valor_centavos":{"type":"integer","title":"Valor Centavos"},"moeda":{"type":"string","title":"Moeda"},"status":{"type":"string","title":"Status"},"mercadopago_payment_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mercadopago Payment Id"},"motivo_recusa":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Motivo Recusa"},"criado_em":{"type":"string","title":"Criado Em"}},"type":"object","required":["id","tipo","referencia_id","valor_centavos","moeda","status","mercadopago_payment_id","criado_em"],"title":"PagamentoItem"},"PerfilAtualizarEntrada":{"properties":{"nome":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Nome"},"endereco_rua":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Endereco Rua"},"endereco_cidade":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Endereco Cidade"},"endereco_cep":{"anyOf":[{"type":"string","maxLength":20},{"type":"null"}],"title":"Endereco Cep"},"endereco_pais":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}],"title":"Endereco Pais"},"telefone_celular":{"anyOf":[{"type":"string","maxLength":30},{"type":"null"}],"title":"Telefone Celular"},"telefone_whatsapp":{"anyOf":[{"type":"string","maxLength":30},{"type":"null"}],"title":"Telefone Whatsapp"},"notificacoes_email":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notificacoes Email"}},"type":"object","title":"PerfilAtualizarEntrada","description":"Todos os campos são opcionais — `PATCH` parcial, só grava o que veio\nno corpo (ver `db.atualizar_perfil_pessoal`). E-mail não está aqui de\npropósito: login fica preso a e-mail, trocar exigiria um fluxo de\nverificação separado (fora de escopo). `nome` já vem preenchido do Google\npra quem se cadastrou por lá (`login_google`, `identidade.nome`), mas é\nsempre editável daqui pra frente, pra qualquer conta — diferente de\nendereço/telefone, que o Google nunca fornece no login (só escopo básico,\nsem People API) e por isso são só preenchimento manual (v49)."},"PlanoArmazenamento":{"properties":{"id":{"type":"string","title":"Id"},"nome":{"type":"string","title":"Nome"},"limite_bytes":{"type":"integer","title":"Limite Bytes"},"preco_brl":{"type":"number","title":"Preco Brl"},"preco_usd":{"type":"number","title":"Preco Usd"},"permite_arquivo_origem":{"type":"boolean","title":"Permite Arquivo Origem"}},"type":"object","required":["id","nome","limite_bytes","preco_brl","preco_usd","permite_arquivo_origem"],"title":"PlanoArmazenamento","description":"Plano de armazenamento em nuvem — produto novo (v20), separado do plano de transcrições acima.\n\nUm usuário tem, ao mesmo tempo, um `plano` (transcrições/dia) e um\n`plano_armazenamento` (quantos bytes de resultado — e, nos planos pagos,\ntambém de arquivo de origem — a conta pode guardar). Os dois são\ncobrados e trocados de forma independente (`PATCH /usuarios/eu/plano` x\n`PATCH /usuarios/eu/plano-armazenamento`)."},"PlanoRecorrente":{"properties":{"id":{"type":"string","title":"Id"},"nome":{"type":"string","title":"Nome"},"limite_mensal":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limite Mensal"},"preco_brl":{"type":"number","title":"Preco Brl"},"preco_usd":{"type":"number","title":"Preco Usd"}},"type":"object","required":["id","nome","limite_mensal","preco_brl","preco_usd"],"title":"PlanoRecorrente"},"RedefinirSenhaEntrada":{"properties":{"token":{"type":"string","maxLength":200,"minLength":10,"title":"Token"},"senha":{"type":"string","maxLength":128,"minLength":1,"title":"Senha"}},"type":"object","required":["token","senha"],"title":"RedefinirSenhaEntrada"},"RegistrarEntrada":{"properties":{"nome":{"type":"string","maxLength":120,"minLength":1,"title":"Nome"},"email":{"type":"string","maxLength":254,"title":"Email"},"senha":{"type":"string","maxLength":128,"minLength":1,"title":"Senha"},"codigo_indicacao":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Codigo Indicacao"},"idioma":{"anyOf":[{"type":"string","maxLength":16},{"type":"null"}],"title":"Idioma"},"turnstile_token":{"anyOf":[{"type":"string","maxLength":4096},{"type":"null"}],"title":"Turnstile Token"}},"type":"object","required":["nome","email","senha"],"title":"RegistrarEntrada"},"SalvarCartaoEntrada":{"properties":{"card_token":{"type":"string","maxLength":512,"title":"Card Token"},"payment_method_id":{"type":"string","maxLength":64,"title":"Payment Method Id"},"identification_type":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Identification Type"},"identification_number":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Identification Number"},"device_id":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Device Id"},"plano_id":{"type":"string","maxLength":50,"title":"Plano Id"},"tipo":{"type":"string","enum":["plano","armazenamento","pacote"],"title":"Tipo","default":"plano"}},"type":"object","required":["card_token","payment_method_id","plano_id"],"title":"SalvarCartaoEntrada"},"SalvarCredencialRequisicao":{"properties":{"salvar":{"type":"boolean","title":"Salvar"}},"type":"object","required":["salvar"],"title":"SalvarCredencialRequisicao"},"SessaoResposta":{"properties":{"token":{"type":"string","title":"Token"},"usuario":{"$ref":"#/components/schemas/UsuarioPublico"}},"type":"object","required":["token","usuario"],"title":"SessaoResposta"},"StatusPagamentoResposta":{"properties":{"status":{"type":"string","title":"Status"}},"type":"object","required":["status"],"title":"StatusPagamentoResposta"},"TrabalhoCriadoResposta":{"properties":{"job_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Job Id"},"total_enviados":{"type":"integer","title":"Total Enviados"},"a_processar":{"type":"integer","title":"A Processar"},"fora_da_cota":{"items":{"type":"string"},"type":"array","title":"Fora Da Cota"},"nao_suportados":{"items":{"type":"string"},"type":"array","title":"Nao Suportados","default":[]}},"type":"object","required":["job_id","total_enviados","a_processar","fora_da_cota"],"title":"TrabalhoCriadoResposta"},"UsoArmazenamentoResposta":{"properties":{"plano_armazenamento_id":{"type":"string","title":"Plano Armazenamento Id"},"nome":{"type":"string","title":"Nome"},"limite_bytes":{"type":"integer","title":"Limite Bytes"},"uso_convertido_bytes":{"type":"integer","title":"Uso Convertido Bytes"},"uso_origem_bytes":{"type":"integer","title":"Uso Origem Bytes"},"uso_total_bytes":{"type":"integer","title":"Uso Total Bytes"},"permite_arquivo_origem":{"type":"boolean","title":"Permite Arquivo Origem"},"cheio":{"type":"boolean","title":"Cheio"},"uso_excluidos_bytes":{"type":"integer","title":"Uso Excluidos Bytes","default":0}},"type":"object","required":["plano_armazenamento_id","nome","limite_bytes","uso_convertido_bytes","uso_origem_bytes","uso_total_bytes","permite_arquivo_origem","cheio"],"title":"UsoArmazenamentoResposta"},"UsuarioPublico":{"properties":{"id":{"type":"integer","title":"Id"},"nome":{"type":"string","title":"Nome"},"email":{"type":"string","title":"Email"},"plano":{"type":"string","title":"Plano"},"plano_ativado_em":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plano Ativado Em"},"plano_armazenamento":{"type":"string","title":"Plano Armazenamento"},"saldo_avulso":{"type":"integer","title":"Saldo Avulso"},"criado_em":{"type":"string","title":"Criado Em"},"idioma_interface":{"type":"string","title":"Idioma Interface"},"origem_cadastro":{"type":"string","title":"Origem Cadastro"},"endereco_rua":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endereco Rua"},"endereco_cidade":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endereco Cidade"},"endereco_cep":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endereco Cep"},"endereco_pais":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endereco Pais"},"telefone_celular":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Telefone Celular"},"telefone_whatsapp":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Telefone Whatsapp"},"notificacoes_email":{"type":"boolean","title":"Notificacoes Email"},"tem_senha":{"type":"boolean","title":"Tem Senha","default":true},"email_verificado":{"type":"boolean","title":"Email Verificado","default":true},"codigo_indicacao":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Codigo Indicacao"}},"type":"object","required":["id","nome","email","plano","plano_ativado_em","plano_armazenamento","saldo_avulso","criado_em","idioma_interface","origem_cadastro","notificacoes_email"],"title":"UsuarioPublico"},"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"},"VerificarDuplicadosResposta":{"properties":{"duplicados":{"items":{"$ref":"#/components/schemas/DuplicataInfo"},"type":"array","title":"Duplicados"}},"type":"object","required":["duplicados"],"title":"VerificarDuplicadosResposta"},"VerificarEmailEntrada":{"properties":{"token":{"type":"string","maxLength":200,"minLength":10,"title":"Token"}},"type":"object","required":["token"],"title":"VerificarEmailEntrada"},"VisualizarHistoricoResposta":{"properties":{"tipo":{"type":"string","title":"Tipo"},"conteudo_texto":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conteudo Texto"},"nome_arquivo_origem":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nome Arquivo Origem"},"url_original":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url Original"}},"type":"object","required":["tipo"],"title":"VisualizarHistoricoResposta","description":"Resposta de `GET /historico/{id}/visualizar` — diz ao frontend QUAL visualizador abrir.\n\n`tipo=\"md\"` vem com `conteudo_texto` já pronto (o `.md` convertido, o\núnico conteúdo que o plano grátis sempre tem). `tipo=\"site\"` (v49, pedido\ndo Leandro: \"a visualização de arquivos não está permitindo visualizar\nsites\") é o caso de um item processado a partir de uma URL — vem com\n`url_original` (pra abrir/incorporar a página de verdade, via\n`GET /historico/{id}/site-original`, v69) E `conteudo_texto` (o `.md`\nconvertido, sempre disponível como alternativa, já que muitos sites\nbloqueiam ser exibidos dentro de um iframe de terceiros).\n\n`tipo` igual a \"video\"/\"imagem\"/\"audio\"/\"pdf\" significa que existe um\narquivo de ORIGEM guardado (só planos pagos) — o frontend busca os bytes\ndele em `GET /historico/{id}/arquivo-origem`. **Desde a v69** (pedido do\nLeandro: \"no visualizador de vídeos, imagens e áudios não está mostrando\no texto transcrito\"), esses tipos TAMBÉM vêm com `conteudo_texto`\n(mesmo `.md` convertido, lido do zip do resultado) sempre que existir,\npra mostrar ao lado da mídia com um botão de copiar — `None` só se o\n`.md` não puder ser lido por algum motivo (nunca impede a pré-visualização\nda mídia em si)."}},"securitySchemes":{"Bearer":{"type":"http","scheme":"bearer","description":"Token devolvido por `POST /api/auth/login` ou `POST /api/auth/registrar` (cole só o token)."}}}}