{"openapi":"3.0.3","info":{"title":"Kastor API","version":"0.1.0","description":"API REST do Kastor (by Kaffspiel). Modelo multi-tenant: User é a tabela central; os demais recursos são isolados por usuário via PodcastConfig."},"paths":{"/api/users":{"get":{"summary":"Listar Users","parameters":[{"name":"take","in":"query","schema":{"type":"integer"},"description":"Limite (default 50)"},{"name":"skip","in":"query","schema":{"type":"integer"},"description":"Offset"}],"responses":{"200":{"description":"Lista de registros","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Users"}}}}}}},"post":{"summary":"Criar User","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsersInput"}}}},"responses":{"201":{"description":"Registro criado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Users"}}}}}}},"/api/users/{id}":{"get":{"summary":"Obter User por id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Registro","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Users"}}}},"404":{"description":"Não encontrado"}}},"patch":{"summary":"Atualizar User","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsersInput"}}}},"responses":{"200":{"description":"Registro atualizado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Users"}}}},"404":{"description":"Não encontrado"}}},"delete":{"summary":"Remover User","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removido"},"404":{"description":"Não encontrado"}}}},"/api/subscriptions":{"get":{"summary":"Listar Subscriptions","parameters":[{"name":"take","in":"query","schema":{"type":"integer"},"description":"Limite (default 50)"},{"name":"skip","in":"query","schema":{"type":"integer"},"description":"Offset"}],"responses":{"200":{"description":"Lista de registros","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Subscriptions"}}}}}}},"post":{"summary":"Criar Subscription","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionsInput"}}}},"responses":{"201":{"description":"Registro criado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscriptions"}}}}}}},"/api/subscriptions/{id}":{"get":{"summary":"Obter Subscription por id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Registro","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscriptions"}}}},"404":{"description":"Não encontrado"}}},"patch":{"summary":"Atualizar Subscription","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionsInput"}}}},"responses":{"200":{"description":"Registro atualizado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Subscriptions"}}}},"404":{"description":"Não encontrado"}}},"delete":{"summary":"Remover Subscription","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removido"},"404":{"description":"Não encontrado"}}}},"/api/podcast-configs":{"get":{"summary":"Listar PodcastConfigs","parameters":[{"name":"take","in":"query","schema":{"type":"integer"},"description":"Limite (default 50)"},{"name":"skip","in":"query","schema":{"type":"integer"},"description":"Offset"}],"responses":{"200":{"description":"Lista de registros","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PodcastConfigs"}}}}}}},"post":{"summary":"Criar PodcastConfig","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PodcastConfigsInput"}}}},"responses":{"201":{"description":"Registro criado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PodcastConfigs"}}}}}}},"/api/podcast-configs/{id}":{"get":{"summary":"Obter PodcastConfig por id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Registro","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PodcastConfigs"}}}},"404":{"description":"Não encontrado"}}},"patch":{"summary":"Atualizar PodcastConfig","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PodcastConfigsInput"}}}},"responses":{"200":{"description":"Registro atualizado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PodcastConfigs"}}}},"404":{"description":"Não encontrado"}}},"delete":{"summary":"Remover PodcastConfig","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removido"},"404":{"description":"Não encontrado"}}}},"/api/speakers":{"get":{"summary":"Listar Speakers","parameters":[{"name":"take","in":"query","schema":{"type":"integer"},"description":"Limite (default 50)"},{"name":"skip","in":"query","schema":{"type":"integer"},"description":"Offset"}],"responses":{"200":{"description":"Lista de registros","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Speakers"}}}}}}},"post":{"summary":"Criar Speaker","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakersInput"}}}},"responses":{"201":{"description":"Registro criado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Speakers"}}}}}}},"/api/speakers/{id}":{"get":{"summary":"Obter Speaker por id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Registro","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Speakers"}}}},"404":{"description":"Não encontrado"}}},"patch":{"summary":"Atualizar Speaker","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakersInput"}}}},"responses":{"200":{"description":"Registro atualizado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Speakers"}}}},"404":{"description":"Não encontrado"}}},"delete":{"summary":"Remover Speaker","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removido"},"404":{"description":"Não encontrado"}}}},"/api/topics":{"get":{"summary":"Listar Topics","parameters":[{"name":"take","in":"query","schema":{"type":"integer"},"description":"Limite (default 50)"},{"name":"skip","in":"query","schema":{"type":"integer"},"description":"Offset"}],"responses":{"200":{"description":"Lista de registros","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Topics"}}}}}}},"post":{"summary":"Criar Topic","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopicsInput"}}}},"responses":{"201":{"description":"Registro criado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Topics"}}}}}}},"/api/topics/{id}":{"get":{"summary":"Obter Topic por id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Registro","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Topics"}}}},"404":{"description":"Não encontrado"}}},"patch":{"summary":"Atualizar Topic","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopicsInput"}}}},"responses":{"200":{"description":"Registro atualizado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Topics"}}}},"404":{"description":"Não encontrado"}}},"delete":{"summary":"Remover Topic","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removido"},"404":{"description":"Não encontrado"}}}},"/api/episodes":{"get":{"summary":"Listar Episodes","parameters":[{"name":"take","in":"query","schema":{"type":"integer"},"description":"Limite (default 50)"},{"name":"skip","in":"query","schema":{"type":"integer"},"description":"Offset"}],"responses":{"200":{"description":"Lista de registros","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Episodes"}}}}}}},"post":{"summary":"Criar Episode","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EpisodesInput"}}}},"responses":{"201":{"description":"Registro criado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Episodes"}}}}}}},"/api/episodes/{id}":{"get":{"summary":"Obter Episode por id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Registro","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Episodes"}}}},"404":{"description":"Não encontrado"}}},"patch":{"summary":"Atualizar Episode","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EpisodesInput"}}}},"responses":{"200":{"description":"Registro atualizado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Episodes"}}}},"404":{"description":"Não encontrado"}}},"delete":{"summary":"Remover Episode","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removido"},"404":{"description":"Não encontrado"}}}},"/api/channels":{"get":{"summary":"Listar Channels","parameters":[{"name":"take","in":"query","schema":{"type":"integer"},"description":"Limite (default 50)"},{"name":"skip","in":"query","schema":{"type":"integer"},"description":"Offset"}],"responses":{"200":{"description":"Lista de registros","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Channels"}}}}}}},"post":{"summary":"Criar Channel","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelsInput"}}}},"responses":{"201":{"description":"Registro criado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Channels"}}}}}}},"/api/channels/{id}":{"get":{"summary":"Obter Channel por id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Registro","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Channels"}}}},"404":{"description":"Não encontrado"}}},"patch":{"summary":"Atualizar Channel","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelsInput"}}}},"responses":{"200":{"description":"Registro atualizado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Channels"}}}},"404":{"description":"Não encontrado"}}},"delete":{"summary":"Remover Channel","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removido"},"404":{"description":"Não encontrado"}}}},"/api/episodes/{id}/audio":{"get":{"summary":"Streaming do MP3 do episódio (?download=1 para baixar)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"download","in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Áudio MP3","content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"Episódio sem áudio ou arquivo não encontrado"}}}},"/api/rss/{configId}":{"get":{"summary":"Feed RSS da estação (episódios prontos)","parameters":[{"name":"configId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"RSS XML","content":{"application/rss+xml":{"schema":{"type":"string"}}}},"404":{"description":"Estação não encontrada"}}}},"/api/speakers/{id}/test":{"post":{"summary":"Sintetiza amostra de ~5s da voz (requer GOOGLE_TTS_API_KEY)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string"}}}}}},"responses":{"200":{"description":"Áudio MP3","content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}}}},"501":{"description":"GOOGLE_TTS_API_KEY não configurada"}}}}},"components":{"schemas":{"Users":{"type":"object","required":["email","passwordHash"],"properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","description":"E-mail do usuário"},"passwordHash":{"type":"string","description":"Hash da senha"},"name":{"type":"string"},"role":{"type":"string","enum":["admin","client"]},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"UsersInput":{"type":"object","required":["email","passwordHash"],"properties":{"email":{"type":"string"},"passwordHash":{"type":"string"},"name":{"type":"string"},"role":{"type":"string","enum":["admin","client"]}}},"Subscriptions":{"type":"object","required":["userId","cycleResetDate"],"properties":{"id":{"type":"string","format":"uuid"},"userId":{"type":"string","description":"FK -> users.id"},"planTier":{"type":"string","enum":["starter","pro"]},"monthlyCharLimit":{"type":"integer","description":"Limite mensal de caracteres"},"charsUsedCycle":{"type":"integer","description":"Caracteres consumidos no ciclo"},"cycleResetDate":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["active","past_due","canceled"]},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"SubscriptionsInput":{"type":"object","required":["userId","cycleResetDate"],"properties":{"userId":{"type":"string"},"planTier":{"type":"string","enum":["starter","pro"]},"monthlyCharLimit":{"type":"integer"},"charsUsedCycle":{"type":"integer"},"cycleResetDate":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["active","past_due","canceled"]}}},"PodcastConfigs":{"type":"object","required":["userId","title","deliveryTime"],"properties":{"id":{"type":"string","format":"uuid"},"userId":{"type":"string","description":"FK -> users.id"},"title":{"type":"string","description":"Título da estação"},"deliveryTime":{"type":"string","description":"Horário de disparo (ex.: 07:00)"},"daysOfWeek":{"type":"array","items":{"type":"integer"},"description":"Dias ativos [1..7]"},"pauseTurnsMs":{"type":"integer","description":"Silêncio entre falas (ms)"},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"PodcastConfigsInput":{"type":"object","required":["userId","title","deliveryTime"],"properties":{"userId":{"type":"string"},"title":{"type":"string"},"deliveryTime":{"type":"string"},"daysOfWeek":{"type":"array","items":{"type":"integer"}},"pauseTurnsMs":{"type":"integer"},"isActive":{"type":"boolean"}}},"Speakers":{"type":"object","required":["configId"],"properties":{"id":{"type":"string","format":"uuid"},"configId":{"type":"string","description":"FK -> podcast_configs.id"},"tag":{"type":"string","enum":["speaker_1","speaker_2"]},"displayName":{"type":"string"},"gcpVoiceName":{"type":"string","description":"Voz do Google Cloud TTS"},"speakingRate":{"type":"number","description":"Velocidade (ex.: 1.05)"},"pitch":{"type":"number","description":"Tom (ex.: 0.00)"},"systemRole":{"type":"string"},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"SpeakersInput":{"type":"object","required":["configId"],"properties":{"configId":{"type":"string"},"tag":{"type":"string","enum":["speaker_1","speaker_2"]},"displayName":{"type":"string"},"gcpVoiceName":{"type":"string"},"speakingRate":{"type":"number"},"pitch":{"type":"number"},"systemRole":{"type":"string"}}},"Topics":{"type":"object","required":["configId"],"properties":{"id":{"type":"string","format":"uuid"},"configId":{"type":"string","description":"FK -> podcast_configs.id"},"category":{"type":"string"},"promptKeywords":{"type":"string","description":"Palavras-chave de pesquisa"},"priority":{"type":"integer","description":"Prioridade 1 a 5"},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"TopicsInput":{"type":"object","required":["configId"],"properties":{"configId":{"type":"string"},"category":{"type":"string"},"promptKeywords":{"type":"string"},"priority":{"type":"integer"},"isActive":{"type":"boolean"}}},"Episodes":{"type":"object","required":["configId","referenceDate"],"properties":{"id":{"type":"string","format":"uuid"},"configId":{"type":"string","description":"FK -> podcast_configs.id"},"referenceDate":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["pending","generating","ready","failed"]},"scriptJson":{"type":"object","description":"Roteiro em JSON"},"fileDiskPath":{"type":"string"},"publicAudioUrl":{"type":"string"},"durationSeconds":{"type":"integer"},"charsConsumed":{"type":"integer","description":"Caracteres consumidos na API"},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"EpisodesInput":{"type":"object","required":["configId","referenceDate"],"properties":{"configId":{"type":"string"},"referenceDate":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["pending","generating","ready","failed"]},"scriptJson":{"type":"object"},"fileDiskPath":{"type":"string"},"publicAudioUrl":{"type":"string"},"durationSeconds":{"type":"integer"},"charsConsumed":{"type":"integer"}}},"Channels":{"type":"object","required":["configId"],"properties":{"id":{"type":"string","format":"uuid"},"configId":{"type":"string","description":"FK -> podcast_configs.id"},"type":{"type":"string","enum":["telegram","whatsapp","rss"]},"externalId":{"type":"string","description":"Chat ID / token externo"},"settings":{"type":"object"},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"ChannelsInput":{"type":"object","required":["configId"],"properties":{"configId":{"type":"string"},"type":{"type":"string","enum":["telegram","whatsapp","rss"]},"externalId":{"type":"string"},"settings":{"type":"object"},"isActive":{"type":"boolean"}}}}}}