API de dados públicos: licença, limite de chamadas e dado pessoal antes de integrar
Antes de montar um produto em cima de uma API pública, leia a licença, respeite o limite com cache e filtre campos pessoais. Com exemplo de cliente em Node.js.
Domingo à noite, o script que baixa o cadastro de escolas de uma API pública roda em laço, faz quarenta mil chamadas em uma hora e começa a receber 429 Too Many Requests. Na segunda, a chave está bloqueada, e o protótipo que ia ao ar mostra nome e telefone de diretoras numa página aberta. A resposta curta: dado público é fácil de ler, mas não vem sem regra. Antes de integrar, leia a licença (o que ela deixa mostrar, redistribuir e vender), respeite o limite de chamadas com cache e espera, e filtre qualquer dado pessoal por uma lista de campos permitidos antes de publicar.
O exemplo deste guia é o Escola Perto, um site que ajuda famílias a achar escolas próximas usando um conjunto de dados público de cadastro escolar.
Por que "público" não quer dizer "livre de regra"
Quando um órgão publica dados, ele decide três coisas que valem para você. A primeira é a licença, que diz o que pode ser feito com a cópia. A segunda são os termos de uso da API, que tratam de limite de chamadas, cadastro e chave. A terceira é a natureza do dado: se o retorno contém informação sobre pessoas, a proteção de dados pessoais continua valendo, mesmo que o acesso seja aberto.
Um mesmo catálogo pode misturar APIs de acesso livre, APIs que exigem credenciamento e APIs destinadas só a órgãos públicos. O fato de um endereço responder JSON no seu navegador não diz em qual categoria ele está. Isso fica na página do conjunto de dados, nos termos de uso e na documentação.
Licença: o que a sua cópia pode fazer
Em portais de dados abertos é comum ver licenças como Creative Commons Atribuição e Open Database License (ODbL). Elas têm algo em comum: exigem que você cite a fonte. A ODbL vai além e exige que bases derivadas sejam compartilhadas sob a mesma licença quando você as distribui. Há também conjuntos com termos próprios, que podem proibir uso comercial ou redistribuição em massa.
Para o Escola Perto, a pergunta é concreta: o site mostra os dados, guarda uma cópia local e talvez venda um relatório. Cada uma dessas ações precisa estar coberta pelo texto da licença, não pela impressão de que "é do governo, então pode".
Dado pessoal no meio do retorno
Cadastros públicos às vezes trazem nome de responsável, telefone pessoal, CPF parcial ou e-mail. A LGPD continua valendo para dado pessoal de acesso público: o uso tem de respeitar a finalidade e o interesse público que justificaram a publicação. Republicar o telefone celular de uma diretora numa vitrine comercial é outra finalidade. "Estava na API" não é base legal.
Como integrar sem ser bloqueado e sem vazar
- Ache a página oficial do conjunto e salve o link da licença e dos termos de uso no repositório, junto com a data em que você leu.
- Verifique se existe download completo. Muitos conjuntos oferecem um arquivo CSV ou JSON inteiro; baixar o arquivo uma vez é melhor para todos do que paginar a API inteira.
- Descubra o limite de chamadas na documentação e configure o seu cliente abaixo dele.
- Faça cache de tudo o que não muda a cada minuto. Cadastro de escola muda por mês, não por segundo.
- Trate
429e erros 5xx com espera, respeitando o cabeçalhoRetry-Afterquando ele vier. - Guarde a chave, se houver, no servidor, em variável de ambiente. O navegador chama o seu backend, nunca a API com a sua chave.
- Passe cada registro por uma lista de campos permitidos antes de gravar no banco da vitrine ou devolver ao front.
- Coloque a atribuição na página, no formato que a licença pede.
Um cliente com cache, ritmo e espera
O cliente abaixo guarda cada resposta em disco por 24 horas, faz no máximo uma chamada por segundo e, ao receber 429 ou erro do servidor, espera o tempo indicado em Retry-After ou um tempo que dobra a cada tentativa. Funciona no Node 18 em diante, que tem fetch nativo.
const crypto = require('node:crypto');
const fs = require('node:fs/promises');
const path = require('node:path');
const CACHE_DIR = path.join(__dirname, 'cache');
const TTL_MS = 24 * 60 * 60 * 1000;
const INTERVALO_MS = 1000;
let ultimaChamada = 0;
const espera = (ms) => new Promise((r) => setTimeout(r, ms));
async function buscar(url) {
const nome = crypto.createHash('sha256').update(url).digest('hex') + '.json';
const arquivo = path.join(CACHE_DIR, nome);
try {
const info = await fs.stat(arquivo);
if (Date.now() - info.mtimeMs < TTL_MS) {
return JSON.parse(await fs.readFile(arquivo, 'utf8'));
}
} catch {
// sem cache ainda
}
for (let tentativa = 1; tentativa <= 5; tentativa++) {
const falta = ultimaChamada + INTERVALO_MS - Date.now();
if (falta > 0) await espera(falta);
ultimaChamada = Date.now();
const resp = await fetch(url, {
headers: { 'User-Agent': 'escola-perto/1.0 (contato@escolaperto.com.br)' },
signal: AbortSignal.timeout(15000)
});
if (resp.status === 429 || resp.status >= 500) {
const retryAfter = Number(resp.headers.get('retry-after'));
const ms = retryAfter > 0 ? retryAfter * 1000 : 2 ** tentativa * 1000;
await espera(ms);
continue;
}
if (!resp.ok) throw new Error('HTTP ' + resp.status + ' em ' + url);
const dados = await resp.json();
await fs.mkdir(CACHE_DIR, { recursive: true });
await fs.writeFile(arquivo, JSON.stringify(dados));
return dados;
}
throw new Error('Desisti depois de 5 tentativas: ' + url);
}
module.exports = { buscar };
O User-Agent com contato ajuda quem opera a API a falar com você antes de bloquear. O tempo limite de 15 segundos evita que um servidor lento prenda o seu processo para sempre; o assunto está em timeout antes do infinito. Se Retry-After vier como data em vez de segundos, a conversão dá NaN e o código cai na espera progressiva, que é um comportamento seguro.
A lista de campos permitidos
const CAMPOS_PUBLICOS = ['codigo', 'nome', 'municipio', 'uf', 'endereco', 'etapas'];
function paraVitrine(registro) {
const saida = {};
for (const campo of CAMPOS_PUBLICOS) {
if (campo in registro) saida[campo] = registro[campo];
}
return saida;
}
Lista de permitidos, não de proibidos. Se a API passar a devolver um campo novo, como telefone_responsavel, ele não entra na vitrine até alguém decidir que pode entrar. Com uma lista de proibidos, o campo novo vazaria no dia seguinte à mudança.
Produção, homologação e mudança de versão
APIs públicas também têm ambientes e versões. O endereço que aparece no exemplo da documentação pode ser o de testes, com dados fictícios ou desatualizados. Confira qual é o de produção e assine o canal de avisos, se existir. O outro lado desse problema está em API de órgão público tem ambiente.
Erros comuns
- Baixar tudo pela API numa noite. A chave é bloqueada e o órgão pode recusar o seu cadastro depois; o download completo, quando existe, resolveria em minutos.
- Ignorar a cláusula de atribuição. A licença deixa de valer para o seu uso, e o site passa a usar os dados sem autorização.
- Chave da API no JavaScript do front. Qualquer visitante a usa em seu nome e consome a sua cota até o bloqueio.
- Filtrar por lista de campos proibidos. Um campo pessoal novo no retorno vai direto para a página pública.
- Sem cache para dado que muda por mês. Cada visita gera uma chamada, a cota acaba no primeiro pico e o site cai junto com o limite.
- Assumir que acesso aberto libera qualquer uso de dado pessoal. A finalidade original continua valendo e o risco jurídico fica com quem republicou.
Checklist
- O link da licença e dos termos está salvo no repositório, com data.
- O produto faz só o que a licença permite (mostrar, redistribuir, vender).
- A página mostra a atribuição exigida.
- O cliente faz cache e fica abaixo do limite de chamadas documentado.
429e erros 5xx geram espera, não repetição imediata.- A chave, se houver, está só no servidor.
- Cada registro passa por uma lista de campos permitidos antes de ser exibido.
Este guia explica o mecanismo e não substitui a leitura da licença nem a orientação de um profissional de direito em casos de dúvida. Para organizar o lado da proteção de dados, veja adequação à LGPD num sistema pequeno.
Abra uma resposta real da API que você usa e olhe campo por campo. Qual deles você mostraria numa página aberta, e a licença permite o que o seu produto faz com a cópia local?
Referência: https://www.gov.br/conecta/catalogo/