Aller au contenu

Pour commencer

Les plus lues

Contrat d'API

PostShip apprend la forme d'une réponse JSON sur 3 passages, puis alerte dès qu'elle dérive.

Mis à jour le 25 septembre 2026

Sur cette page

Une API qui répond 200 peut casser vos clients : price devient une chaîne, items disparaît, une date n'en est plus une. Rien ne le signale, jusqu'à ce que l'application mobile plante ou que l'intégration d'un partenaire s'arrête. Le contrat d'API demande une URL qui rend du JSON, apprend sa forme, puis compare chaque passage à ce qu'il a appris. Vous ne rédigez aucun schéma.

Ce que PostShip vérifie

À chaque passage, PostShip fait un GET avec Accept: application/json (12 secondes, 5 redirections, en-tête privé possible) et procède dans l'ordre :

  1. Le statut. Il doit être exactement le statut attendu (200 par défaut). Sinon, échec franc, avant même de lire le corps : le contrat commence par « répondre ».
  2. Le JSON. Le corps doit être du JSON valide et tenir en 512 Ko — au-delà, PostShip considère qu'il lit un export plutôt qu'un contrat, et le passage est en erreur.
  3. L'apprentissage. Les 3 premiers passages nourrissent le schéma sans jamais juger. Un seul échantillon ne dit pas ce qui est optionnel ni ce qui est une énumération ; 2, à peine plus.
  4. La comparaison. À partir du quatrième passage, la réponse est comparée au schéma appris. Chaque écart est une dérive, cassante ou informative.

Ce que le schéma retient

Pour chaque valeur : son type (null, booléen, nombre — entier ou non —, chaîne, tableau, objet), les types alternatifs vus au même endroit (string | null), et pour les chaînes un format reconnu (date, datetime, url, email, uuid) ou les valeurs distinctes vues tant qu'elles sont peu nombreuses (8 au plus, de 64 caractères au plus) — c'est ainsi qu'un status devient une énumération paid | pending. Une propriété présente dans tous les échantillons est requise ; présente dans certains, optionnelle. Au-delà de 12 niveaux d'imbrication ou de 200 clés dans un objet, le conteneur est tronqué : c'est un dictionnaire indexé, pas une structure.

Cassant ou informatif

DériveGravitéPhrase
type_changeCassante$.price : number → string
prop_disparueCassante$.items a disparu
requis_absentCassante$.items[].sku n'est plus toujours présent
format_perduCassante$.created_at n'est plus une date-heure
prop_nouvelleInformative$.currency : nouvelle propriété
enum_nouvelleInformative$.status : nouvelle valeur "refunded"
tableau_videInformative$.items est vide

Cassant : un client typé ne s'en relève pas. Informatif : rien ne casse, mais le contrat bouge. Les dérives informatives sont apprises en silence — la propriété nouvelle fait désormais partie du contrat et ne sera plus signalée. Le schéma continue donc de grandir avec l'API, et « cassant » garde le sens que vous attendez : quelque chose que votre code n'aurait pas prévu.

Régler

Projet → URLs → « Surveiller… » → « Une API ». L'écran « Ce qu'on attend de l'API » demande le statut attendu et, pour une API privée, un en-tête d'accès (Authorization, X-Api-Key, X-Monitoring-Key ou X-Health-Token) envoyé à l'hôte d'origine seulement.

Sur le détail de l'URL, le panneau « Contrat d'API » montre : pendant l'apprentissage, « Apprentissage en cours : 2 passages sur 3. Rien n'est jugé avant. » ; ensuite, le schéma appris en clair (6 niveaux affichés au plus), les dérives cassantes en rouge, les informatives en gris avec la mention « appris », et le nombre de passages d'apprentissage.

Le bouton « Réapprendre le contrat » efface le schéma. Après un changement d'API volontaire, c'est ce qu'on veut : 3 passages plus tard, le nouveau contrat est la référence, sans avoir à le décrire.

Ce qui déclenche une alerte

CodePhrase
statut:200:503L'API répond 503 au lieu de 200.
json_invalideLa réponse n'est plus du JSON valide.
contrat:type_change:$.priceContrat d'API : $.price a changé de type.
contrat:prop_disparue:$.itemsContrat d'API : $.items a disparu.
contrat:requis_absent:$.items[].skuContrat d'API : $.items[].sku n'est plus toujours présent.
contrat:format_perdu:$.created_atContrat d'API : $.created_at n'a plus le format attendu.

L'alerte cite les 2 premières dérives cassantes et compte les autres : « 3 changements cassants : $.price : number → string, $.items a disparu et 1 autre ». Un code par dérive, stable d'un passage à l'autre : la même dérive n'alerte pas à chaque cycle, une dérive supplémentaire réalerte. Les dérives informatives n'alertent jamais. Un contrat en échec coûte 10 points au Ship Score.

Limites et plans

Disponible sur tous les plans ; une cible compte pour une URL du quota (Découverte 5, Pro 25, Pro Plus 100). Le rythme est celui du cycle — 5 minutes sur Découverte, 2 sur Pro, 1 sur Pro Plus (sa sonde de 30 secondes ne regarde que les pages) — plus chaque déploiement, et l'apprentissage demande donc de quelques minutes à un quart d'heure avant le premier verdict.

Dépannage

Pourquoi une propriété optionnelle est signalée « n'est plus toujours présent » ?

Parce qu'elle était présente dans les 3 échantillons d'apprentissage : PostShip l'a apprise requise. Cliquez sur « Réapprendre le contrat » à un moment où la réponse la contient parfois, ou acceptez le verdict comme le signal qu'un client typé aurait eu.

Pourquoi le passage est en erreur plutôt qu'en échec ?

Une erreur veut dire que PostShip n'a pas pu juger : délai dépassé, réponse de plus de 512 Ko, boucle de redirection. Un échec est un verdict sur le contrat. Les deux ouvrent un incident, mais seule l'erreur dit « La réponse dépasse 512 Ko : trop grosse pour un contrat. ».

L'API répond du JSON mais avec Content-Type: text/html

Le type annoncé n'est pas vérifié : seul le corps compte. Si JSON.parse réussit, le contrat s'apprend.

Tâche planifiée (battement)

Surveiller ce qui tourne sans page — un cron, un worker, une sauvegarde — par l'absence d'appel.

Tout ce qui tourne sans page : un cron de sauvegarde, un worker qui vide une file, un script de facturation nocturne. Aucune URL à vérifier — et quand ça s'arrête, rien ne le dit. Le site répond 200, la sauvegarde n'a pas tourné depuis 3 semaines. Le battement de cœur inverse la logique : c'est votre script qui appelle PostShip à la fin de chaque passage, et c'est l'absence d'appel qui déclenche l'alerte.

Ce que PostShip fait

Un battement est une cible sans URL à demander. PostShip fabrique une adresse secrète de la forme https://postship.fr/api/heartbeat/<jeton>, et attend qu'elle soit appelée :

  • en GET ou en POST, indifféremment — un curl en fin de script est un GET, un webhook d'outil est un POST ;
  • le corps est ignoré ; tout ce qui compte est « on nous a appelés, et quand » ;
  • la réponse est OK, en texte, statut 200. Un jeton inconnu ou désactivé répond 404 « Jeton inconnu. » — les deux se répondent pareil, pour qu'un jeton désactivé ne révèle pas qu'il a existé.

À chaque cycle du projet (toutes les 5 minutes sur Découverte, 2 sur Pro, chaque minute sur Pro Plus ; la sonde de 30 secondes de Pro Plus ne regarde que les pages), PostShip relit la date du dernier appel et la compare à l'intervalle promis, avec une marge d'un quart d'intervalle, plafonnée à une heure : un cron horaire qui appelle à 10:00:00 puis 11:00:04 ne doit pas alerter à 11:00:01. Assez pour absorber un démarrage lent, pas assez pour laisser passer une exécution manquée.

Le premier passage. Un battement qui n'a encore jamais été appelé n'est pas en panne — le script n'est peut-être pas encore déployé. Il passe pendant un intervalle plein (plus sa marge) à compter de sa création, puis échoue : au bout de ce délai, « jamais appelé » est un vrai silence.

Le jeton est le seul secret, et il vaut ce qu'il vaut : quelqu'un qui l'a peut faire croire qu'un cron tourne. C'est le modèle de Healthchecks.io et Cronitor, et c'est acceptable parce que le pire effet est un silence — pas un accès à quoi que ce soit.

Régler

Projet → URLs → « Surveiller… » → « Une tâche planifiée » :

  1. Comment s'appelle-t-elle ? « sauvegarde nocturne », 80 caractères au plus.
  2. À quel rythme doit-elle tourner ? Toutes les 5 minutes, 15 minutes, toutes les heures, toutes les 6 heures, tous les jours (par défaut), toutes les semaines. Une liste et non un champ libre : ces 6 valeurs couvrent les crons réels, et un intervalle de 37 minutes est presque toujours une faute de frappe.
  3. « Créer le battement ». L'adresse à appeler est sur la page de la cible, panneau « Battement de cœur », avec le délai attendu, le dernier appel, et 2 boutons « Copier » — l'adresse seule, ou la ligne curl prête à coller.

Puis, à la fin de votre script :

À la fin du script
curl -fsS https://postship.fr/api/heartbeat/VOTRE_JETON > /dev/null

-fsS : un échec de l'appel lui-même se voit dans les journaux du cron, sans bruit quand tout va bien. Appelez après le travail, pas avant : un appel en début de script dirait « ça tourne » d'une sauvegarde qui échoue à la deuxième ligne.

Désactiver un battement (menu de la cible) fait ignorer les appels et n'envoie aucune alerte — pour une maintenance, ou un cron retiré.

Ce qui déclenche une alerte

CodePhrase de l'écranPhrase de l'alerte
heartbeat_lateLe script n'a pas appelé PostShip dans le délai prévu.Le battement de cœur n'est plus reçu — sauvegarde nocturne
heartbeat_neverLe script n'a jamais appelé PostShip depuis la création de ce battement.Le battement de cœur n'a jamais été reçu — sauvegarde nocturne

Le détail du passage donne le retard en minutes. L'alerte part au franchissement du délai — après la confirmation du projet, qui compte ici en cycles — et une seule fois par silence ; « Rétabli » suit le prochain appel reçu. Un battement en retard ouvre un incident comme n'importe quelle cible.

Limites et plans

Disponible sur tous les plans. Un battement compte pour une URL du quota (Découverte 5, Pro 25, Pro Plus 100) : c'est une cible que vous demandez. Le verdict est relu à chaque cycle du projet : un silence est donc constaté avec au plus 5 minutes de retard sur Découverte, 2 minutes sur Pro, 1 sur Pro Plus — la sonde de 30 secondes de Pro Plus ne regarde que les pages, pas les battements.

IntervalleMargeAlerte au plus tôt après le dernier appel
5 min1 min 156 min 15
1 h15 min1 h 15
6 h1 h7 h
1 jour1 h25 h
1 semaine1 h7 jours et 1 h

Dépannage

Pourquoi l'alerte n'est pas partie alors que le cron a sauté une exécution ?

Regardez la marge : un cron toutes les 5 minutes a 1 min 15 de marge, et le verdict n'est relu qu'au cycle du projet — 2 minutes sur Pro, 1 sur Pro Plus, 5 sur Découverte ; la sonde de 30 secondes de Pro Plus n'y change rien. Un retard de 3 minutes peut donc passer entre 2 cycles, et sur Découverte un battement de 5 minutes n'est relu qu'une fois par période : choisissez plutôt un intervalle d'au moins 15 minutes.

Mon script appelle l'adresse mais le panneau dit « jamais »

L'appel n'a pas atteint PostShip : curl sans -f masque un 404, une variable d'environnement vide donne une adresse tronquée, un pare-feu sortant bloque postship.fr. Lancez la ligne à la main et lisez la réponse : elle doit être OK.

Puis-je changer l'intervalle ?

Supprimez le battement et recréez-le avec le bon intervalle : le jeton change, mettez à jour votre script. L'ancien jeton répond 404 immédiatement.