{"openapi":"3.1.0","info":{"title":"Sem Ódio: API de detecção de preconceito e discurso de ódio","version":"1.0.0","description":"Diz se uma fala em português do Brasil é livre de preconceito e discurso de ódio, qual é o tipo e quanta certeza o motor tem. Sem chave e sem cadastro. O texto enviado não é guardado. Limite por IP: 120 pontos por minuto e 2000 por hora (1 ponto a cada 500 caracteres, no mínimo 1 por texto); os cabeçalhos RateLimit-* dizem quanto ainda cabe e o 429 traz Retry-After. O motor é léxico mais regras de contexto: erra, e não substitui uma pessoa revisando.","contact":{"name":"Rodrigo Borges","url":"https://rodrigoborges.dev"}},"servers":[{"url":"https://rodrigoborges.dev"}],"tags":[{"name":"Análise"},{"name":"Informações"}],"paths":{"/api/sem-odio/analyze":{"post":{"operationId":"analyze","summary":"Analisa uma fala em JSON: { \"text\": \"…\", \"threshold\": 0.5 }. Devolve free, o tipo, a certeza e os trechos que pesaram.","description":"Peso no limite por IP: por tamanho.","tags":["Análise"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeBody"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/AnalyzeBody"}}}},"responses":{"200":{"description":"A análise.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Pedido inválido: texto ausente, parâmetro fora da faixa ou JSON quebrado (veja `code`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Texto, lote ou corpo grande demais.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Limite de pedidos por IP. Veja Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"get":{"operationId":"analyzeGet","summary":"O mesmo por GET (?text=…&threshold=…), para testar no navegador. Até 1000 caracteres; prefira o POST.","description":"Peso no limite por IP: por tamanho.","tags":["Análise"],"parameters":[{"name":"text","in":"query","required":true,"schema":{"type":"string","maxLength":1000},"example":"Todo negro é ladrão."},{"name":"threshold","in":"query","required":false,"schema":{"type":"number","minimum":0.05,"maximum":0.95,"default":0.5,"description":"Nota de corte: com score igual ou acima dela, free é false."}}],"responses":{"200":{"description":"A análise.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Pedido inválido: texto ausente, parâmetro fora da faixa ou JSON quebrado (veja `code`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Texto, lote ou corpo grande demais.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"414":{"description":"Texto longo demais para GET; use POST.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Limite de pedidos por IP. Veja Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/sem-odio/batch":{"post":{"operationId":"batch","summary":"Analisa até 20 falas de uma vez: { \"texts\": [\"…\", \"…\"] }.","description":"Peso no limite por IP: soma dos textos.","tags":["Análise"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchBody"}}}},"responses":{"200":{"description":"Uma análise por texto, na mesma ordem.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchResult"}}}},"400":{"description":"Pedido inválido: texto ausente, parâmetro fora da faixa ou JSON quebrado (veja `code`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Texto, lote ou corpo grande demais.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Limite de pedidos por IP. Veja Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/sem-odio/categories":{"get":{"operationId":"categories","summary":"Lista os tipos que a API identifica, com o id, o nome e a descrição.","description":"Peso no limite por IP: 1.","tags":["Informações"],"responses":{"200":{"description":"Os tipos.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Categories"}}}},"429":{"description":"Limite de pedidos por IP. Veja Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/sem-odio":{"get":{"operationId":"index","summary":"Nome, versão, limites e a lista de endereços.","description":"Peso no limite por IP: 1.","tags":["Informações"],"responses":{"200":{"description":"O índice da API.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"Limite de pedidos por IP. Veja Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/sem-odio/openapi.json":{"get":{"operationId":"openapi","summary":"A especificação OpenAPI 3.1 desta API.","description":"Peso no limite por IP: 1.","tags":["Informações"],"responses":{"200":{"description":"Este documento.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"Limite de pedidos por IP. Veja Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"headers":{"RateLimit-Limit":{"description":"Pontos da janela mais apertada.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Pontos que ainda cabem nessa janela.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Segundos até a janela recomeçar (pior caso).","schema":{"type":"integer"}}},"schemas":{"AnalyzeBody":{"type":"object","required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":5000,"example":"Todo negro é ladrão, essa gente não presta."},"threshold":{"type":"number","minimum":0.05,"maximum":0.95,"default":0.5,"description":"Nota de corte: com score igual ou acima dela, free é false."}}},"BatchBody":{"type":"object","required":["texts"],"properties":{"texts":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"string","maxLength":5000}},"threshold":{"type":"number","minimum":0.05,"maximum":0.95,"default":0.5,"description":"Nota de corte: com score igual ou acima dela, free é false."}}},"Result":{"type":"object","required":["free","type","confidence","score"],"properties":{"free":{"type":"boolean","description":"true se a fala está livre de preconceito e de discurso de ódio; false se não está."},"type":{"type":["string","null"],"enum":["racismo","lgbtfobia","misoginia","xenofobia","intolerancia_religiosa","capacitismo","gordofobia","etarismo","classismo","intolerancia_politica","ameaca","ofensa",null],"description":"O tipo principal identificado (veja a lista de tipos). null quando free é true."},"label":{"type":["string","null"],"description":"O nome do tipo, em português. null quando free é true."},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"Quanto o motor confia no veredito (free ou não), de 0 a 1. Se free é true, é a certeza de estar livre; se é false, a de não estar."},"percent":{"type":"integer","minimum":0,"maximum":100,"description":"A confidence em porcentagem, de 0 a 100."},"score":{"type":"number","minimum":0,"maximum":1,"description":"A chance estimada de a fala NÃO ser livre, de 0 a 1. É a nota que se compara com o threshold."},"threshold":{"type":"number","minimum":0.05,"maximum":0.95,"default":0.5,"description":"A nota de corte usada: com score igual ou acima dela, free é false (padrão 0.5)."},"types":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["racismo","lgbtfobia","misoginia","xenofobia","intolerancia_religiosa","capacitismo","gordofobia","etarismo","classismo","intolerancia_politica","ameaca","ofensa"]},"label":{"type":"string"},"score":{"type":"number"}}},"description":"Todos os tipos com nota a partir de 0,2, do maior para o menor: { id, label, score }."},"matches":{"type":"array","items":{"type":"object","properties":{"start":{"type":"integer"},"end":{"type":"integer"},"text":{"type":"string"},"type":{"type":"string","enum":["racismo","lgbtfobia","misoginia","xenofobia","intolerancia_religiosa","capacitismo","gordofobia","etarismo","classismo","intolerancia_politica","ameaca","ofensa"]},"reason":{"type":"string","enum":["slur","generalization","dehumanization","stereotype","hate","violence","exclusion","threat","insult","phrase","disclaimer"]},"weight":{"type":"number"}}},"description":"Os trechos que pesaram: { start, end, text, type, reason, weight }. start e end são posições no texto enviado; reason diz o motivo e weight o peso do trecho."},"language":{"type":"string","example":"pt-BR","description":"Sempre \"pt-BR\"."},"version":{"type":"string","example":"1.0.0","description":"A versão do motor."}}},"BatchResult":{"type":"object","properties":{"total":{"type":"integer"},"flagged":{"type":"integer"},"results":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Result"},{"type":"object","properties":{"index":{"type":"integer"}}}]}}}},"Categories":{"type":"object","properties":{"total":{"type":"integer"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"}}}},"reasons":{"type":"object"}}},"Problem":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string","enum":["missing_text","invalid_text","text_too_long","get_text_too_long","missing_texts","invalid_texts","batch_too_large","invalid_param","bad_request","too_large","uri_too_long","rate_limited","method_not_allowed","not_found"]},"param":{"type":"string"},"retryAfter":{"type":"integer"}}}}}}