Como testar um endpoint de API: DNS, TLS, cabeçalhos e CORS

Ponto-chave

"A API caiu" quase nunca é um problema só. Teste em seis camadas (DNS, porta 443, TLS, status HTTP, CORS, payload) e pare na primeira que falhar. Os culpados típicos: um registro DNS que nunca propagou, um certificado que expirou durante a noite, ou um cabeçalho Access-Control-Allow-Origin ausente que quebra as chamadas do navegador enquanto o curl funciona normalmente.

Verifique agora

Diagrama das seis camadas para testar um endpoint de API público: resolução de DNS, porta 443, certificado TLS, cabeçalhos HTTP, CORS e payload JSON
Diagrama das seis camadas para testar um endpoint de API público: resolução de DNS, porta 443, certificado TLS, cabeçalhos HTTP, CORS e payload JSON
Comece a ler
  1. Por que "a API caiu" nunca é um problema só
  2. Escolha um endpoint real que você possa testar à vontade
  3. Camada 1: o nome do host resolve?
  4. Camada 2: a porta 443 está aberta?
  5. Camada 3: o certificado TLS é válido?
  6. Camada 4: leia os cabeçalhos da resposta
  7. Como é uma resposta JSON saudável
  8. Camada 5: o cabeçalho que só quebra chamadas do navegador
  9. A armadilha do preflight: funciona até você adicionar um cabeçalho
  10. Camada 6: o payload tem o formato esperado?
  11. Sintoma → camada → ferramenta
  12. Perguntas frequentes

Por que "a API caiu" nunca é um problema só

Quando uma chamada de API falha, o erro que seu cliente mostra costuma ser gerado várias camadas acima do que realmente quebrou. Um navegador que informa Failed to fetch não consegue dizer se o nome do host não resolveu, se a conexão TCP foi recusada, se o handshake TLS foi rejeitado, ou se o servidor devolveu um 200 perfeitamente saudável que o navegador descartou por falta de um cabeçalho CORS. Os quatro casos geram a mesma mensagem.

A solução é parar de adivinhar e percorrer a pilha em ordem. Cada camada só faz sentido se a de baixo funcionou, então a primeira camada que falha é o seu bug de verdade; tudo que vem depois é ruído.

Há seis camadas que valem a verificação, e juntas levam cerca de dois minutos:

  • DNS: o nome do host resolve para algum IP?
  • Porta 443: tem algo escutando, ou a conexão é recusada ou filtrada?
  • TLS: o certificado é válido, não expirou e é servido com a cadeia completa?
  • Status HTTP: o que o servidor realmente retorna: 200, 401, 429, 503?
  • CORS: a resposta traz os cabeçalhos de que um navegador precisa para entregar o corpo a você?
  • Payload: o JSON tem o formato que seu código espera?

Verifique agora o status e os cabeçalhos de um endpoint

Escolha um endpoint real que você possa testar à vontade

Para praticar você precisa de um endpoint no ar que retorne dados reais, não exija chave de API e não bloqueie você por repetir a mesma requisição vinte vezes. Isso descarta a maioria dos candidatos óbvios: quase toda API pública conhecida agora exige um formulário de cadastro e um token, o que significa que uma requisição com falha pode sempre ser culpa das suas credenciais, e não da camada que você está testando.

Os exemplos abaixo usam a API gratuita de horários de oração do Waqt Azan (مواقيت الصلاة), um serviço árabe de horários de oração que cobre mais de 60.000 lugares em mais de 100 países. Ela funciona bem como endpoint de aprendizado por três motivos: não há chave nem cabeçalho de autenticação, retorna JSON estruturado em vez de HTML e define uma política CORS permissiva. Assim, os comandos curl rodam exatamente como estão, e o exemplo com fetch() funciona no console de uma página sem uma Content-Security-Policy rígida.

O endpoint recebe o slug do país e da cidade direto no caminho:

bash
# Prayer times for a city — no API key, no headers required
curl -s https://waqtazan.com/api/v1/timings/saudi-arabia/riyadh

# The same data as a subscribable calendar feed
curl -s https://waqtazan.com/api/v1/ical/saudi-arabia/riyadh.ics

Troque pelo host que você está realmente depurando: as seis camadas são idênticas, seja uma API pública, seu próprio servidor de staging ou um receptor de webhook de terceiros.

Camada 1: o nome do host resolve?

O DNS é um culpado frequente. Um registro foi alterado e não propagou, um wildcard foi removido, ou a API fica em um subdomínio que nunca foi criado.

Resolva o nome do host antes de qualquer outra coisa. Se este passo não retornar nada, reiniciar a aplicação não vai ajudar: o seu cliente nem chega a abrir um socket.

bash
dig +short waqtazan.com A
nslookup waqtazan.com

# Compare what different resolvers see — catches mid-propagation records
dig +short waqtazan.com A @1.1.1.1
dig +short waqtazan.com A @8.8.8.8

Se dois resolvedores públicos discordam logo depois de você alterar um registro, ele ainda está propagando, e a API vai parecer quebrada de forma intermitente conforme o resolvedor que cada cliente usa (uma CDN com roteamento geográfico pode entregar sempre um endereço diferente a cada resolvedor, então compare com o registro que você definiu). Faça uma consulta DNS para ver o conjunto completo de registros, ou confira a propagação em resolvedores do mundo todo no verificador de propagação de DNS logo após alterar um registro.

Uma resposta vazia aqui corresponde diretamente a ERR_NAME_NOT_RESOLVED no Chrome e a getaddrinfo ENOTFOUND no Node: ERR_NAME_NOT_RESOLVED traz a lista completa de correções.

Uma coisa com que não se preocupar: muitas APIs ficam atrás de uma CDN, então o registro A aponta para uma borda da CDN e não para o servidor de origem. Isso é esperado. O que importa é obter uma resposta, não qual é o IP.

Camada 2: a porta 443 está aberta?

O nome do host resolve, então existe um endereço. Agora descubra se algo está escutando nele. É aqui que você separa três falhas bem diferentes que os clientes adoram reportar do mesmo jeito:

  • Aberta: o handshake TCP é concluído. Passe para a próxima camada.
  • Recusada: o host respondeu com um RST imediato. Há algo ali, mas nada está associado àquela porta: geralmente o serviço travou, ou escuta apenas em localhost.
  • Filtrada: a conexão fica pendurada até estourar o tempo limite. Um firewall ou grupo de segurança descarta pacotes em silêncio, e por isso sua requisição espera um minuto ou mais antes de falhar (75 segundos no macOS).
bash
# Time-boxed TCP check: -w limits the connect on Linux; on macOS use -G 5 instead
nc -vz -w 5 waqtazan.com 443

# Or with curl: --connect-timeout caps the connect; 000 means no response came back
curl -s -o /dev/null -w '%{http_code}\n' --connect-timeout 5 https://waqtazan.com/

Uma conexão recusada e uma filtrada pedem correções completamente diferentes, então vale saber qual é o seu caso. O verificador de portas distingue as duas e testa de fora da sua rede, o que importa: uma porta aberta pela VPN do escritório pode estar bloqueada por firewall na internet pública.

Se o curl imprimir 000 como código de status, nenhuma resposta HTTP voltou. Geralmente a requisição falhou nas camadas 1 a 3 (sem resposta de DNS, porta recusada ou filtrada, handshake TLS com falha), mas um servidor que aceita a conexão e depois a fecha, ou nunca responde, também gera 000; adicione -v para ver o último passo que funcionou. ERR_CONNECTION_REFUSED cobre o caso da recusa em detalhes.

Camada 3: o certificado TLS é válido?

A porta 443 está aberta, mas porta aberta não significa endpoint HTTPS funcionando. Certificados são uma quebra clássica da noite para o dia, porque expiram em data marcada e a automação de renovação falha em silêncio.

O certificado precisa atender a três condições ao mesmo tempo: não estar expirado, ter um nome que corresponda ao host solicitado e o servidor enviar a cadeia intermediária completa. A terceira é a traiçoeira: uma cadeia incompleta muitas vezes funciona nos navegadores, porque o Chrome baixa sozinho o intermediário que falta e o Firefox os pré-carrega, enquanto Node, Python, Java e builds do curl com OpenSSL falham (o curl que vem com o macOS usa o verificador da Apple e pode passar). Essa é a origem do clássico relato "funciona no meu navegador, mas não em produção".

bash
# Expiry dates and subject
echo | openssl s_client -connect waqtazan.com:443 -servername waqtazan.com 2>/dev/null \
  | openssl x509 -noout -subject -dates

# Full chain as presented by the server
openssl s_client -connect waqtazan.com:443 -servername waqtazan.com -showcerts < /dev/null

Conte os certificados na saída de -showcerts. Um único certificado geralmente significa um intermediário ausente. O verificador de certificado SSL informa validade, completude da cadeia e correspondência do nome do host de uma só vez, e o que é uma cadeia de certificados SSL explica por que o intermediário importa tanto.

Mantenha -servername: o OpenSSL 1.1.1 e posteriores preenchem o SNI a partir do host de -connect, mas o openssl LibreSSL que vem com o macOS não preenche, e nenhuma versão preenche quando você se conecta a um endereço IP. Sem SNI, uma hospedagem compartilhada entrega o certificado padrão dela ou, como a Cloudflare, aborta o handshake, e você acaba perseguindo uma divergência de nome que não existe.

Camada 4: leia os cabeçalhos da resposta

O TLS está bom, então você finalmente tem uma conversa HTTP de verdade. Agora leia o que o servidor realmente disse, em vez do que seu cliente resumiu para você. Imprima só os cabeçalhos: o corpo ainda é baixado e descartado, então é o mesmo GET que seu cliente envia.

bash
curl -s -D - -o /dev/null https://waqtazan.com/api/v1/timings/egypt/cairo

Leia primeiro a linha de status, depois o content-type, depois o resto. Esses dois campos dizem se a requisição teve sucesso e se o corpo é JSON antes de você ler qualquer outra coisa.

Como é uma resposta JSON saudável

Rodar o comando acima contra o endpoint de teste em 27 de outubro de 2026 retornou estes cabeçalhos, entre outros:

CabeçalhoValorO que ele diz
HTTP/2200A requisição teve sucesso e o servidor fala HTTP/2
content-typeapplication/jsonVocê vai receber JSON, não uma página de erro HTML
content-length567Um payload de verdade, não um corpo vazio
access-control-allow-origin*Os navegadores podem ler esta resposta
servercloudflareUma CDN fica na frente da origem
alt-svch3=":443"HTTP/3 é anunciado para as requisições seguintes

Camada 5: o cabeçalho que só quebra chamadas do navegador

É nesta camada que as horas somem, porque é onde o curl e o navegador realmente discordam. O CORS é aplicado pelo navegador, não pelo servidor. Se Access-Control-Allow-Origin estiver ausente, o servidor ainda devolve um 200 completo e correto: o navegador simplesmente se recusa a entregar o corpo ao seu JavaScript.

Então o sintoma é este: o curl funciona perfeitamente, o Postman funciona perfeitamente, e o console do navegador diz que a requisição foi bloqueada pela política CORS. Não há nada de errado com o servidor. A resposta só está sem um cabeçalho.

O endpoint de teste retorna access-control-allow-origin: *, e é por isso que um fetch() simples a partir de qualquer origem funciona:

javascript
// Run it in the console of a tab on a site without a strict CSP, such as example.com
// (this page's own CSP blocks it). It works there because ACAO is set to *
const r = await fetch('https://waqtazan.com/api/v1/timings/egypt/cairo')
console.log(await r.json())

A armadilha do preflight: funciona até você adicionar um cabeçalho

Existe uma segunda pegadinha, e este endpoint a demonstra muito bem. Uma requisição simples (um GET sem cabeçalhos personalizados) funciona. Mas no momento em que você adiciona um cabeçalho personalizado, ou faz POST com Content-Type: application/json, o navegador envia antes um preflight OPTIONS, e a resposta ao preflight precisa trazer seus próprios cabeçalhos CORS (MDN).

Teste o preflight separadamente, porque é uma resposta diferente, vinda de um caminho de código diferente:

bash
curl -s -D - -o /dev/null -X OPTIONS \
  -H 'Origin: https://example.com' \
  -H 'Access-Control-Request-Method: GET' \
  https://waqtazan.com/api/v1/timings/egypt/cairo

Em 27 de outubro de 2026, esse preflight retorna um 200 com content-type: text/html e nenhum cabeçalho access-control-allow-origin, então uma requisição com preflight vinda de um navegador seria bloqueada, mesmo que o GET simples funcione bem.

Essa é exatamente a armadilha: sua integração funciona em desenvolvimento, você adiciona um cabeçalho Authorization ou X-Request-Id, e ela quebra sem nenhuma mudança no servidor. Se você só precisa de dados públicos, mantenha a requisição simples e ela continuará funcionando.

Camada 6: o payload tem o formato esperado?

A última camada é a que seu código realmente importa. Um 200 com JSON válido ainda pode quebrar você se um campo foi renomeado, um tipo mudou de número para string, ou um timestamp chega em um formato que você não interpreta.

Olhe a resposta real antes de escrever o parser. Este endpoint retorna tanto horários formatados para exibição quanto timestamps ISO, um padrão que vale copiar: as strings localizadas são para pessoas, os valores ISO são para o código:

json
{
  "city": { "slug": "riyadh", "name_ar": "الرياض", "name_en": "Riyadh" },
  "country": "saudi-arabia",
  "date": "2026-10-26",
  "times":    { "fajr": "4:38 ص", "dhuhr": "11:37 ص", "maghrib": "5:17 م" },
  "timesISO": { "fajr": "2026-10-26T01:38:00.000Z", "dhuhr": "2026-10-26T08:37:00.000Z" },
  "method": "umm_al_qura"
}

Sempre faça o parse do campo legível por máquina. Aqui isso significa timesISO, nunca times: as strings de exibição são localizadas e mudarão de formato assim que o idioma mudar.

Passe a resposta pelo jq para inspecionar a estrutura rapidamente: curl -s <url> | jq keys lista os campos de primeiro nível, e jq '.times' entra em um campo sem rolar pelo corpo bruto.

Sintoma → camada → ferramenta

Use esta tabela para ir direto à camada que importa, em vez de percorrer as seis toda vez:

O que você vêCamada com falhaVerifique com
ERR_NAME_NOT_RESOLVED, ENOTFOUND1 — DNSConsulta DNS
ECONNREFUSED, falha instantânea2 — PortaVerificador de portas
A requisição trava e depois expira2 — Porta filtradaVerificador de portas
CERT_HAS_EXPIRED, UNABLE_TO_VERIFY_LEAF_SIGNATURE3 — TLSVerificador SSL
Funciona no navegador, falha no curl ou no Node3 — Cadeia incompletaVerificador SSL
401, 429, 5034 — Status HTTPVerificador de Cabeçalhos HTTP
Funciona no curl, bloqueado no navegador5 — CORSVerificador de Cabeçalhos HTTP
Unexpected token '<', "<!DOCTYPE "... is not valid JSON4 — Página de erro HTMLVerificador de Cabeçalhos HTTP
Campos ausentes ou com tipo errado6 — Payloadcurl … | jq

A latência fica um pouco fora desta escada. Se todas as camadas passam mas as respostas são lentas, meça o tempo de ida e volta até o host com o ping e veja se a própria resolução de nomes é o gargalo com um teste de velocidade de DNS.

Veja os cabeçalhos de resposta reais de qualquer endpoint

Veja o código de status, o content-type e os cabeçalhos CORS exatos que uma API retorna, solicitados pelo servidor, então nenhum cache de navegador ou extensão distorce o resultado. Grátis, sem cadastro.

Fontes

  1. Cross-Origin Resource Sharing (CORS), MDN
  2. Fetch Standard: CORS-safelisted request headers, WHATWG

Perguntas frequentes

Como testar um endpoint de API sem o Postman?

O curl cobre tudo que o Postman faz em testes básicos. Use curl -s -D - -o /dev/null <url> para ver só os cabeçalhos da resposta, curl -s <url> | jq para inspecionar o corpo JSON, e curl -s -o /dev/null -w '%{http_code}' para imprimir só o código de status. Para uma verificação rápida sem terminal, um verificador de cabeçalhos HTTP online retorna as mesmas informações a partir de um navegador.

Por que minha API funciona no curl mas falha no navegador?

Quase sempre é CORS. O servidor retorna uma resposta válida nos dois casos, mas os navegadores se recusam a expor o corpo ao JavaScript, a menos que a resposta traga um cabeçalho Access-Control-Allow-Origin que permita a sua origem. O curl ignora o CORS por completo, porque o CORS é uma política de segurança do navegador, não do servidor. Se o cabeçalho estiver ausente, o servidor precisa enviá-lo: não existe correção do lado do cliente.

O que significa o curl retornar o código de status 000?

Significa que nenhuma resposta HTTP foi recebida, então não há código de status para informar. Geralmente a falha aconteceu antes do HTTP: o DNS não resolveu, a conexão TCP foi recusada ou filtrada, ou o handshake TLS falhou. Um servidor que aceita a conexão e depois a fecha, ou nunca responde, também gera 000, então rode o comando de novo com -v para ver o último passo que funcionou.

Como saber se uma API está fora do ar para todo mundo ou só para mim?

Teste de fora da sua própria rede. Um verificador de portas e uma consulta DNS executados no servidor tiram da equação o seu resolvedor local, a VPN, o firewall corporativo e o cache do navegador. Se o endpoint responde a partir de um verificador externo mas não da sua máquina, o problema é local: normalmente cache de DNS ou uma regra de firewall.

Por que uma API retorna 200 mas meu parser JSON lança um erro?

Quase certamente você está recebendo HTML com status 200. Portais cativos, proxies e algumas páginas de erro devolvem um código de sucesso com corpo HTML. Confira o cabeçalho content-type: se disser text/html em vez de application/json, o corpo é uma página web e o erro de parse é um sintoma, não a causa.

O que é um preflight CORS e quando ele acontece?

Um preflight é uma requisição OPTIONS automática que o navegador envia antes da requisição real sempre que ela não é "simples": usa um método diferente de GET, HEAD ou POST, define cabeçalhos personalizados como Authorization, ou envia um Content-Type diferente de dados de formulário ou texto simples (application/json conta). A resposta ao preflight precisa incluir os cabeçalhos CORS por conta própria. É por isso que uma integração pode funcionar bem até você adicionar um cabeçalho de autenticação, e então quebrar sem nenhuma mudança no servidor.

Devo testar o DNS da API com meu próprio resolvedor ou com um público?

Com os dois, e compare. O resolvedor do seu provedor pode servir um registro desatualizado em cache muito depois de o registro autoritativo ter mudado. Consultar 1.1.1.1 e 8.8.8.8 junto com o resolvedor padrão mostra se você está diante de um atraso de propagação ou de um registro realmente ausente.

Uma porta 443 aberta significa que o endpoint HTTPS funciona?

Não. Uma porta aberta só confirma que algo aceitou uma conexão TCP. O handshake TLS ainda pode falhar porque o certificado expirou, o nome do host não corresponde ou a cadeia intermediária está incompleta. Verifique o certificado separadamente depois de confirmar que a porta está aberta.

Sobre o autor

Shaik Vahid

Fundador da DNS Robot. Vahid passou anos no suporte de hospedagem de sites resolvendo domínios, registros DNS, certificados SSL e entrega de e-mails para clientes. Ele cria as ferramentas da DNS Robot e escreve estes guias a partir dos erros que as pessoas realmente encontram.

Verificador de headers HTTP

Analise cabeçalhos de resposta HTTP e classificação de cabeçalhos de segurança