Tester un endpoint d'API : DNS, TLS, en-têtes et CORS
Point clé
« L'API est en panne » n'est presque jamais un seul problème. Testez six couches (DNS, port 443, TLS, statut HTTP, CORS, charge utile) et arrêtez-vous à la première qui échoue. Les coupables habituels : un enregistrement DNS jamais propagé, un certificat expiré dans la nuit, ou un en-tête Access-Control-Allow-Origin absent qui casse les appels du navigateur alors que curl fonctionne très bien.
Vérifiez maintenant

Commencer la lecture
- Pourquoi « l'API est en panne » n'est jamais un seul problème
- Choisir un vrai endpoint que vous pouvez solliciter à volonté
- Couche 1 : le nom d'hôte se résout-il ?
- Couche 2 : le port 443 est-il ouvert ?
- Couche 3 : le certificat TLS est-il valide ?
- Couche 4 : lire les en-têtes de réponse
- À quoi ressemble une réponse JSON saine
- Couche 5 : l'en-tête qui ne casse que les appels du navigateur
- Le piège du preflight : ça marche jusqu'à l'ajout d'un en-tête
- Couche 6 : la charge utile a-t-elle la forme attendue ?
- Symptôme → couche → outil
- Questions fréquentes
Pourquoi « l'API est en panne » n'est jamais un seul problème
Quand un appel d'API échoue, l'erreur affichée par votre client est généralement produite plusieurs couches au-dessus de ce qui est réellement cassé. Un navigateur qui affiche Failed to fetch ne peut pas dire si le nom d'hôte n'a pas été résolu, si la connexion TCP a été refusée, si la poignée de main TLS a été rejetée, ou si le serveur a renvoyé un 200 parfaitement sain que le navigateur a ensuite jeté faute d'en-tête CORS. Les quatre cas donnent le même message.
La solution : arrêter de deviner et parcourir la pile dans l'ordre. Chaque couche n'a de sens que si celle du dessous a réussi ; la première couche qui échoue est donc votre vrai bug, et tout ce qui suit n'est que du bruit.
Six couches méritent d'être vérifiées, et l'ensemble prend environ deux minutes :
- DNS : le nom d'hôte se résout-il en une IP ?
- Port 443 : quelque chose écoute-t-il, ou la connexion est-elle refusée ou filtrée ?
- TLS : le certificat est-il valide, non expiré, et servi avec une chaîne complète ?
- Statut HTTP : que renvoie réellement le serveur : 200, 401, 429, 503 ?
- CORS : la réponse porte-t-elle les en-têtes dont un navigateur a besoin pour vous remettre le corps ?
- Charge utile : le JSON a-t-il la forme attendue par votre code ?
Vérifiez dès maintenant le statut et les en-têtes d'un endpoint
Choisir un vrai endpoint que vous pouvez solliciter à volonté
Pour vous entraîner, il vous faut un endpoint en ligne qui renvoie de vraies données, n'exige aucune clé d'API et ne vous limitera pas si vous lancez vingt fois la même requête. Cela exclut la plupart des candidats évidents : presque toutes les API publiques courantes exigent désormais un formulaire d'inscription et un jeton, si bien qu'une requête en échec pourrait toujours venir de vos identifiants plutôt que de la couche testée.
Les exemples ci-dessous utilisent l'API gratuite d'horaires de prière de Waqt Azan (مواقيت الصلاة), un service arabe d'horaires de prière couvrant plus de 60 000 lieux dans plus de 100 pays. Il convient bien comme endpoint d'apprentissage pour trois raisons : ni clé ni en-tête d'authentification, du JSON structuré plutôt que du HTML, et une politique CORS permissive. Les commandes curl s'exécutent donc telles quelles, et l'exemple fetch() fonctionne depuis la console d'une page sans Content-Security-Policy stricte.
L'endpoint prend un slug de pays et de ville directement dans le chemin :
# 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.icsRemplacez-le par l'hôte que vous déboguez réellement : les six couches sont identiques, qu'il s'agisse d'une API publique, de votre propre serveur de préproduction ou d'un récepteur de webhook tiers.
Couche 1 : le nom d'hôte se résout-il ?
Le DNS est un coupable fréquent. Un enregistrement a été modifié et ne s'est pas propagé, un wildcard a été supprimé, ou l'API vit sur un sous-domaine qui n'a jamais été créé.
Résolvez le nom d'hôte avant toute autre chose. Si cette étape ne renvoie rien, redémarrer votre application n'y changera rien : votre client n'arrive même pas à ouvrir un socket.
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.8Si deux résolveurs publics ne sont pas d'accord juste après une modification, l'enregistrement est encore en cours de propagation et l'API semblera cassée par intermittence selon le résolveur utilisé par chaque client (un CDN à routage géographique peut tout le temps renvoyer une adresse différente à chaque résolveur : comparez donc avec l'enregistrement que vous avez défini). Lancez une recherche DNS pour voir l'ensemble des enregistrements, ou vérifiez la propagation sur les résolveurs mondiaux avec le vérificateur de propagation DNS juste après la modification.
Une réponse vide ici correspond directement à ERR_NAME_NOT_RESOLVED dans Chrome et à getaddrinfo ENOTFOUND dans Node : ERR_NAME_NOT_RESOLVED donne la liste complète des corrections.
Un point à ne pas surveiller : beaucoup d'API se trouvent derrière un CDN, donc l'enregistrement A pointe vers un point de présence du CDN plutôt que vers le serveur d'origine. C'est normal. Ce qui compte, c'est d'obtenir une réponse, pas de savoir quelle IP.
Couche 2 : le port 443 est-il ouvert ?
Le nom d'hôte se résout, donc il y a une adresse. Reste à savoir si quelque chose y écoute. C'est ici que l'on sépare trois pannes très différentes que les clients rapportent pourtant de la même façon :
- Ouvert : la poignée de main TCP aboutit. Passez à la couche suivante.
- Refusé : l'hôte a répondu par un RST immédiat. Il y a bien quelque chose, mais rien n'est lié à ce port : en général le service a planté, ou n'écoute que sur localhost.
- Filtré : la connexion reste en attente jusqu'au délai d'expiration. Un pare-feu ou un groupe de sécurité abandonne les paquets en silence, d'où une requête qui patiente une minute ou plus avant d'échouer (75 secondes sur macOS).
# 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/Une connexion refusée et une connexion filtrée demandent des corrections totalement différentes ; mieux vaut donc savoir laquelle vous avez. Le vérificateur de ports les distingue et teste depuis l'extérieur de votre réseau, ce qui compte : un port ouvert depuis le VPN de votre bureau peut être bloqué par un pare-feu côté Internet public.
Si curl affiche 000 comme code de statut, aucune réponse HTTP n'est revenue. En général la requête a échoué dans les couches 1 à 3 (pas de réponse DNS, port refusé ou filtré, poignée de main TLS en échec), mais un serveur qui accepte la connexion puis la ferme, ou ne répond jamais, donne aussi 000 ; ajoutez -v pour voir la dernière étape réussie. ERR_CONNECTION_REFUSED détaille le cas du refus.
Couche 3 : le certificat TLS est-il valide ?
Le port 443 est ouvert, mais un port ouvert ne signifie pas un endpoint HTTPS fonctionnel. Les certificats sont une cause classique de panne nocturne : ils expirent à date fixe et l'automatisation du renouvellement échoue sans bruit.
Trois conditions doivent être réunies : le certificat n'est pas expiré, son nom correspond au nom d'hôte demandé, et le serveur envoie la chaîne intermédiaire complète. La troisième est sournoise : une chaîne incomplète passe souvent dans les navigateurs, car Chrome télécharge lui-même l'intermédiaire manquant et Firefox les précharge, alors que Node, Python, Java et les builds de curl basés sur OpenSSL échouent (le curl livré avec macOS utilise le vérificateur d'Apple et peut passer). C'est l'origine du classique rapport de bug « ça marche dans mon navigateur mais pas en production ».
# 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/nullComptez les certificats dans la sortie de -showcerts. Un seul certificat signifie généralement un intermédiaire manquant. Le vérificateur de certificat SSL indique l'expiration, la complétude de la chaîne et la correspondance du nom d'hôte en une seule passe, et ce qu'est une chaîne de certificats SSL explique pourquoi l'intermédiaire est si important.
Gardez -servername : OpenSSL 1.1.1 et suivants renseignent le SNI à partir du nom d'hôte de -connect, mais le openssl LibreSSL livré avec macOS ne le fait pas, et aucune version ne le fait quand vous vous connectez à une adresse IP. Sans SNI, un hébergement mutualisé vous renvoie son certificat par défaut ou, comme Cloudflare, interrompt la poignée de main, et vous traquez une erreur de nom qui n'existe pas.
Couche 4 : lire les en-têtes de réponse
TLS est bon : vous avez enfin une vraie conversation HTTP. Lisez maintenant ce que le serveur a réellement dit, plutôt que ce que votre client en a résumé. N'affichez que les en-têtes : le corps est quand même téléchargé puis jeté, c'est donc le même GET que celui de votre client.
curl -s -D - -o /dev/null https://waqtazan.com/api/v1/timings/egypt/cairoLisez d'abord la ligne de statut, puis content-type, puis le reste. Ces deux champs vous disent si la requête a réussi et si le corps est du JSON avant que vous ne lisiez quoi que ce soit d'autre.
À quoi ressemble une réponse JSON saine
La commande ci-dessus, lancée le 27 octobre 2026 sur l'endpoint de test, a renvoyé ces en-têtes, entre autres :
| En-tête | Valeur | Ce que cela vous apprend |
|---|---|---|
HTTP/2 | 200 | La requête a réussi, et le serveur parle HTTP/2 |
content-type | application/json | Vous recevrez du JSON, pas une page d'erreur HTML |
content-length | 567 | Une vraie charge utile, pas un corps vide |
access-control-allow-origin | * | Les navigateurs ont le droit de lire cette réponse |
server | cloudflare | Un CDN se trouve devant l'origine |
alt-svc | h3=":443" | HTTP/3 est annoncé pour les requêtes suivantes |
Couche 5 : l'en-tête qui ne casse que les appels du navigateur
C'est ici que les heures s'évaporent, car c'est la couche où curl et le navigateur divergent vraiment. CORS est appliqué par le navigateur, pas par le serveur. Si Access-Control-Allow-Origin est absent, le serveur renvoie quand même un 200 complet et correct : le navigateur refuse simplement de remettre le corps à votre JavaScript.
Le symptôme est donc : curl fonctionne parfaitement, Postman fonctionne parfaitement, et la console du navigateur indique que la requête a été bloquée par la politique CORS. Rien ne cloche côté serveur. Il manque simplement un en-tête à la réponse.
L'endpoint de test renvoie access-control-allow-origin: *, c'est pourquoi un simple fetch() depuis n'importe quelle origine fonctionne :
// 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())Le piège du preflight : ça marche jusqu'à l'ajout d'un en-tête
Il existe un second piège, que cet endpoint illustre bien. Une requête simple (un GET sans en-tête personnalisé) réussit. Mais dès que vous ajoutez un en-tête personnalisé, ou faites un POST avec Content-Type: application/json, le navigateur envoie d'abord un preflight OPTIONS, et la réponse au preflight doit porter ses propres en-têtes CORS (MDN).
Testez le preflight séparément, car c'est une autre réponse, issue d'un autre chemin de code :
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/cairoAu 27 octobre 2026, ce preflight renvoie un 200 avec content-type: text/html et aucun en-tête access-control-allow-origin, de sorte qu'une requête avec preflight depuis un navigateur serait bloquée, alors même que le simple GET fonctionne.
C'est exactement le piège : votre intégration fonctionne en développement, vous ajoutez un en-tête Authorization ou X-Request-Id, et elle casse sans aucun changement côté serveur. Si vous n'avez besoin que de données publiques, gardez la requête simple et elle continuera de fonctionner.
Couche 6 : la charge utile a-t-elle la forme attendue ?
La dernière couche est celle qui intéresse vraiment votre code. Un 200 avec du JSON valide peut quand même vous casser si un champ a été renommé, si un type est passé de nombre à chaîne, ou si un horodatage arrive dans un format que vous ne gérez pas.
Regardez la vraie réponse avant d'écrire le parseur. Cet endpoint renvoie à la fois des heures formatées pour l'affichage et des horodatages ISO, un schéma à copier : les chaînes localisées sont pour les humains, les valeurs ISO pour le code :
{
"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"
}Parsez toujours le champ lisible par machine. Ici, c'est timesISO, jamais times : les chaînes d'affichage sont localisées et changeront de format dès que la locale change.
Passez la réponse dans jq pour inspecter rapidement la structure : curl -s <url> | jq keys liste les champs de premier niveau, et jq '.times' descend dans un champ sans faire défiler le corps brut.
Symptôme → couche → outil
Utilisez ce tableau pour aller directement à la bonne couche, au lieu de parcourir les six à chaque fois :
| Ce que vous voyez | Couche en échec | À vérifier avec |
|---|---|---|
ERR_NAME_NOT_RESOLVED, ENOTFOUND | 1 — DNS | Recherche DNS |
ECONNREFUSED, échec immédiat | 2 — Port | Vérificateur de ports |
| La requête se bloque, puis expire | 2 — Port filtré | Vérificateur de ports |
CERT_HAS_EXPIRED, UNABLE_TO_VERIFY_LEAF_SIGNATURE | 3 — TLS | Vérificateur SSL |
| Fonctionne dans le navigateur, échoue avec curl ou Node | 3 — Chaîne incomplète | Vérificateur SSL |
401, 429, 503 | 4 — Statut HTTP | Vérificateur d'en-têtes HTTP |
| Fonctionne avec curl, bloqué dans le navigateur | 5 — CORS | Vérificateur d'en-têtes HTTP |
Unexpected token '<', "<!DOCTYPE "... is not valid JSON | 4 — Page d'erreur HTML | Vérificateur d'en-têtes HTTP |
| Champs manquants ou de mauvais type | 6 — Charge utile | curl … | jq |
La latence se situe un peu en dehors de cette échelle. Si toutes les couches passent mais que les réponses sont lentes, mesurez l'aller-retour vers l'hôte avec ping et vérifiez si la résolution de noms elle-même est le goulot d'étranglement avec un test de vitesse DNS.
Vérifiez les vrais en-têtes de réponse de n'importe quel endpoint
Voyez le code de statut, le content-type et les en-têtes CORS exacts renvoyés par une API, demandés côté serveur : aucun cache de navigateur ni extension ne peut fausser le résultat. Gratuit, sans inscription.
Sources
Questions fréquentes
Comment tester un endpoint d'API sans Postman ?
Pourquoi mon API fonctionne avec curl mais échoue dans le navigateur ?
Que signifie le code de statut 000 renvoyé par curl ?
Comment savoir si une API est en panne pour tout le monde ou seulement pour moi ?
Pourquoi une API renvoie 200 alors que mon parseur JSON lève une erreur ?
Qu'est-ce qu'un preflight CORS et quand se produit-il ?
Dois-je tester le DNS de l'API avec mon propre résolveur ou un résolveur public ?
Un port 443 ouvert signifie-t-il que l'endpoint HTTPS fonctionne ?
Articles associés
Outils associés
Vérificateur d'en-têtes HTTP
Analysez les en-têtes de réponse HTTP et la notation des en-têtes de sécurité
Recherche DNS
Tous les enregistrements DNS depuis les serveurs du domaine, à côté du cache de Google et Cloudflare
Vérificateur de Ports
Testez la connectivité des ports et la disponibilité des services
Vérification du Certificat SSL
Vérifiez la validité et les détails de sécurité du certificat SSL
Test de Ping
Mesurez votre ping, la gigue et la perte de paquets, votre ping vers 34 régions cloud, et pinguez un site ou une IP depuis 11 villes



