L'API REST

Piloter Page Observer depuis vos propres outils : clés, portées, endpoints, pagination, codes d'erreur.

L'API REST permet de piloter Page Observer depuis vos propres outils : créer des monitors, les ranger, suivre leur état de santé, récupérer les changements déjà qualifiés et relire le contenu d'une page, sans jamais ouvrir l'interface. Elle est pensée pour un usage machine à machine — un script, un worker, une automatisation — et non pour un navigateur.

Adresse de base : https://app.pageobserver.com/api/v1. Tout est en JSON, les dates sont en ISO 8601 UTC, et l'accès est réservé aux comptes Pro.

Créer une clé API

Rendez-vous sur la page Compte, section « Clés API ». Donnez un nom à la clé (par exemple le nom de l'outil qui va s'en servir) et validez.

La clé complète, de la forme po_live_…, s'affiche une seule fois. Copiez-la immédiatement : nous n'en conservons que l'empreinte cryptographique, elle ne peut donc jamais être réaffichée ni récupérée. Si vous la perdez, révoquez-la et créez-en une nouvelle. Seul le préfixe reste visible par la suite, pour vous permettre d'identifier vos clés.

La section n'apparaît que pour les comptes Pro, et uniquement pour le titulaire du compte : un membre invité dans une organisation ne peut pas émettre de clé, car c'est le compte propriétaire qui porte les crédits qu'elle consomme.

S'authentifier

Chaque requête porte la clé dans un en-tête Authorization :

curl -H "Authorization: Bearer po_live_VotreCleIci" \
     https://app.pageobserver.com/api/v1/quota

Il n'y a ni cookie, ni session, ni jeton CSRF à gérer : l'API vit délibérément en dehors du circuit d'authentification du navigateur.

Les portées

Chaque clé porte des portées qui décrivent ce qu'elle a le droit de faire. Une clé créée depuis l'interface les reçoit toutes les quatre :

  • monitors.read — lire les monitors et les dossiers ;
  • monitors.write — créer, modifier et supprimer monitors et dossiers ;
  • changes.read — récupérer les changements détectés ;
  • content.read — lire le contenu d'une page surveillée.

Une requête dont la clé n'a pas la portée requise reçoit un 403 forbidden nommant la portée manquante.

content.read est une portée à part, et c'est voulu. Lire le contenu entier d'une page est d'une autre nature que lire son adresse et son état de santé : une clé ne gagne pas ce droit sans qu'on l'ait décidé. Conséquence pratique — une clé émise avant l'ouverture de cette portée ne la possède pas et recevra un 403 sur la lecture de contenu. Émettez-en une nouvelle.

Limites de débit

Deux plafonds s'appliquent : 600 requêtes par quart d'heure et par adresse IP (avant authentification), puis 300 requêtes par tranche de 5 minutes et par clé. Au-delà, la réponse est un 429 rate_limited. Les en-têtes standards RateLimit-* indiquent où vous en êtes.

Le corps d'une requête est limité à 32 ko.

Forme des erreurs

Toute erreur suit la même enveloppe, sans jamais exposer de détail interne :

{ "error": { "code": "invalid_request", "message": "Invalid or forbidden url" } }
Code HTTPcodeSignification
400invalid_requestUn paramètre est absent, malformé ou hors des valeurs admises.
401unauthorizedEn-tête absent, clé malformée, inconnue ou révoquée.
402subscription_requiredLe compte porteur de la clé n'est pas (ou plus) Pro.
403forbiddenPortée manquante, ou réglage réservé au Pro (fréquence horaire).
404not_foundLa ressource n'existe pas, ou n'appartient pas à ce compte.
409credit_quota_exceededCrédits insuffisants. La réponse porte quota, used et cost.
409folder_quota_exceededNombre maximal de dossiers atteint.
429rate_limitedPlafond de débit dépassé.
500server_errorErreur interne. Réessayez plus tard.

Le 409 credit_quota_exceeded mérite un traitement à part : ce n'est pas une requête invalide mais un plafond de capacité. Votre outil doit le distinguer d'un 400 et alerter un humain, plutôt que de réessayer en boucle.

Les monitors

Créer un monitor

POST /api/v1/monitors — portée monitors.write

ChampObligatoireDescription
urlouiPage à surveiller, en http(s). Les adresses internes ou privées sont refusées.
check_frequencyouiEn minutes : 10080 (hebdomadaire), 1440 (quotidien) ou 60 (horaire).
intentouiPhrase décrivant ce que vous suivez. 500 caractères maximum.
external_refouiVotre propre identifiant pour ce monitor. 200 caractères, A-Z a-z 0-9 _ . : @ + / -.
css_selectornonRestreint le monitor à une zone de la page.
folder_idsnonTableau d'identifiants de dossiers, qui doivent vous appartenir.
curl -X POST https://app.pageobserver.com/api/v1/monitors \
  -H "Authorization: Bearer po_live_VotreCleIci" \
  -H "Content-Type: application/json" \
  -d '{
        "url": "https://exemple.fr/politique-de-confidentialite",
        "check_frequency": 10080,
        "intent": "Signaler tout changement de durée de conservation ou de transfert hors UE.",
        "external_ref": "doc-4217"
      }'

Réponse : 201 avec l'objet créé.

L'intent n'est pas décoratif — contrairement à l'interface, où il est facultatif, il est obligatoire ici. C'est lui qui pilote toute la qualification par l'IA : le résumé de chaque changement, son niveau d'importance et le fait qu'il soit jugé dans le périmètre ou hors sujet. Un monitor créé sans intention utile remonterait des changements non qualifiés, sans qu'aucune erreur ne vous le signale. Il n'est pas modifiable après coup.

L'appel est idempotent. Rejouer la même requête avec le même external_ref ne crée pas de doublon : vous recevez un 200 avec le monitor existant. Cette garantie est assurée par la base de données, elle tient donc même si deux appels partent exactement en même temps. C'est ce qui rend une file de travail rejouable sans précaution particulière.

Lister les monitors

GET /api/v1/monitors — portée monitors.read

ParamètreValeursEffet
healthbroken · okNe renvoie que les monitors en panne, ou que ceux dont le dernier passage s'est bien déroulé.
is_activetrue · falseActives, ou en pause.
external_refchaîneRetrouve un monitor par votre propre identifiant.
limit1 à 200Taille de page. 100 par défaut.
cursoropaqueVoir la pagination ci-dessous.

C'est l'endpoint de supervision, et le seul moyen d'apprendre qu'un monitor s'est arrêté. Une page en panne cesse de produire des changements : elle disparaît donc du flux /changes au lieu d'y signaler quoi que ce soit. Sondez ?health=broken régulièrement.

Les filtres sont stricts : une valeur non reconnue est refusée par un 400, jamais ignorée. Une faute de frappe ne peut donc pas vous renvoyer toute la flotte en la faisant passer pour le sous-ensemble demandé.

{
  "monitors": [
    {
      "id": "8f1c…",
      "url": "https://exemple.fr/politique-de-confidentialite",
      "external_ref": "doc-4217",
      "css_selector": null,
      "check_frequency": 10080,
      "is_active": true,
      "monitor_type": "diff",
      "intent": "Signaler tout changement de durée…",
      "change_type": ["policy_change"],
      "last_checked_at": "2026-09-19T04:12:07.000Z",
      "last_outcome": "ok",
      "last_http_status": 200,
      "consecutive_failures": 0,
      "failing_since": null,
      "credits_reserved": 1,
      "folder_ids": ["2ab9…"],
      "created_at": "2026-09-01T10:00:00.000Z"
    }
  ],
  "next_cursor": "MjAyNi0wOS0wMV…",
  "has_more": false
}

Lire un monitor

GET /api/v1/monitors/:id — portée monitors.read. Renvoie le même objet que la liste, pour un seul monitor. Un identifiant qui ne vous appartient pas renvoie 404, exactement comme un identifiant inexistant.

Lire le contenu d'une page

GET /api/v1/monitors/:id/content — portée content.read

Renvoie la page telle que Page Observer l'a vue au dernier relevé. Là où /changes vous dit ce qui a bougé, cet appel vous dit ce qui est là : c'est ce qu'il faut pour reconstituer un inventaire — une liste de sous-traitants, un tableau de tarifs, un catalogue — plutôt que d'essayer de le rebâtir à partir d'une succession de différences.

curl https://app.pageobserver.com/api/v1/monitors/<id>/content \
  -H "Authorization: Bearer po_live_…"
{
  "snapshot": {
    "monitor_id": "3f2a…",
    "check_id": "91c4…",
    "checked_at": "2026-09-18T03:12:00Z",
    "hash": "8d1e…",
    "format": "cleaned_html",
    "bytes": 184233,
    "truncated": false,
    "content": "<table>…</table>"
  }
}
  • format vaut toujours cleaned_html. Ce n'est pas le HTML d'origine : scripts, styles, attributs de mise en forme et paramètres d'URL en ont été retirés, et seule la zone visée par votre sélecteur CSS est conservée. Les balises de structure — tableaux, listes, titres — sont intactes. Y chercher une classe ou un data-* serait vain : ils n'y sont plus.
  • checked_at et hash identifient le relevé. Un hash inchangé signifie que la page n'a pas bougé : inutile de retraiter.
  • truncated passe à true au-delà de 512 ko, et le contenu est alors coupé. Ne l'ignorez pas : sur une liste, une coupe se lit comme une disparition massive d'entrées. Resserrez plutôt le sélecteur CSS.
  • snapshot vaut null, avec un 200, pour un monitor qui n'a pas encore été relevé une seule fois. C'est un état normal, pas une erreur — un monitor fraîchement créé y passe jusqu'à son premier passage.

Un identifiant qui ne vous appartient pas renvoie 404, exactement comme un identifiant inexistant.

Un seul relevé est disponible : le dernier. L'appel ne donne pas accès à l'historique des versions d'une page. Pour suivre l'évolution, c'est le fil des changements qui fait foi.

La lecture ne consomme aucun crédit : elle ne déclenche pas de relevé et ne fait que restituer ce qui est déjà stocké.

Modifier un monitor

PATCH /api/v1/monitors/:id — portée monitors.write

Quatre champs seulement sont modifiables :

  • check_frequency — soumis au contrôle de crédits, et l'horaire reste réservé au Pro ;
  • css_selector — voir l'avertissement ci-dessous ;
  • is_active — booléen JSON strict : la chaîne "true" est refusée par un 400, plutôt que d'être interprétée à l'envers ;
  • folder_ids — remplace l'intégralité du rangement.

Changer le sélecteur efface l'historique. Diffs, captures et relevés antérieurs sont supprimés, et le monitor repart d'une base neuve au prochain passage. C'est volontaire : comparer un ancien périmètre à un nouveau produirait un faux changement massif. Ne modifiez donc pas un sélecteur à la légère sur un monitor de longue date.

L'url, le type et l'intent sont figés. Les changer suppose de supprimer le monitor et d'en créer un autre.

Supprimer un monitor

DELETE /api/v1/monitors/:id — portée monitors.write. Réponse 204 sans corps. L'appel est idempotent : un identifiant inconnu renvoie lui aussi 204, ce qui permet de rejouer une file de suppressions sans cas particulier. Les crédits sont libérés immédiatement.

Les dossiers

GET /api/v1/folders (portée monitors.read) liste vos dossiers. POST /api/v1/folders en crée un à partir d'un champ name, et DELETE /api/v1/folders/:id le supprime — les deux sous la portée monitors.write.

Supprimer un dossier ne supprime pas les monitors qu'il contenait : ils deviennent simplement « sans dossier ».

Deux points sur folder_ids, à la création comme à la modification. D'abord, un dossier inconnu ou appartenant à un autre compte fait échouer la requête (400 Unknown folder_ids) au lieu d'être silencieusement ignoré : un outil qui croit avoir rangé un monitor doit l'apprendre. Ensuite, omettre le champ n'est pas la même chose que l'envoyer vide — absent, le rangement n'est pas touché ; à [], le monitor sort de tous ses dossiers.

Récupérer les changements

GET /api/v1/changes — portée changes.read

C'est le cœur de l'API. Elle renvoie les changements déjà qualifiés par l'IA : vous n'avez aucun traitement à refaire de votre côté.

{
  "items": [
    {
      "id": "c31f…",
      "monitor_id": "8f1c…",
      "external_ref": "doc-4217",
      "url": "https://exemple.fr/politique-de-confidentialite",
      "detected_at": "2026-09-19T04:12:09.000Z",
      "ai_summary": "Ajout de trois sous-traitants hébergés en Inde.",
      "ai_importance": "critical",
      "ai_in_scope": true,
      "ai_metadata": { "…": "…" },
      "change_type": ["third_party_change"],
      "diff": { "tokens": [ { "op": "add", "text": "…" } ], "truncated": false }
    }
  ],
  "next_cursor": "MjAyNi0wOS0xO…",
  "has_more": true
}
  • ai_summary — résumé en langage naturel, rédigé dans la langue du compte ;
  • ai_importance — minor, significant ou critical ;
  • ai_in_scope — false quand le changement est jugé hors de l'intention déclarée (mise en forme, date de révision…). null signifie que l'IA n'a pas encore statué ;
  • diff.tokens — le détail mot à mot, chaque entrée portant op (eq inchangé, add ajouté, del supprimé) et text. truncated vaut true quand le changement était trop volumineux pour être renvoyé en entier.

Le HTML brut des pages, avant et après, n'est jamais renvoyé : c'est la charge la plus lourde, et le résumé accompagné des jetons de diff couvre les usages réels.

Pagination et rattrapage

Le paramètre since accepte une date ISO 8601 et borne le début de la période. Il ne peut pas remonter au-delà de la durée de conservation de votre formule.

La pagination se fait par curseur : reprenez le next_cursor de la réponse précédente tant que has_more vaut true. Le curseur est une valeur opaque, à renvoyer telle quelle sans chercher à l'interpréter.

GET /api/v1/changes?since=2026-09-01T00:00:00Z&limit=100
GET /api/v1/changes?cursor=MjAyNi0wOS0xO…&limit=100

Ce choix n'est pas cosmétique : contrairement à une pagination par numéro de page, un curseur ne saute aucune entrée même si de nouveaux changements sont détectés pendant que vous parcourez les pages. Les entrées arrivent par date croissante, ce qui permet d'enregistrer le curseur après chaque lot traité et de reprendre exactement là où vous vous étiez arrêté après une panne.

Surveiller son quota

GET /api/v1/quota — aucune portée requise

{ "quota_credits": 20, "credits_used": 14, "credits_available": 6, "monitors_active": 9 }

Interrogez-le avant une création en série. Les crédits sont un plafond dur : une fois atteint, toute nouvelle création est refusée par un 409. Mieux vaut alerter à 80 % que découvrir le mur. Voir Crédits, formules et quotas.

Ce que l'API ne fait pas encore

  • Seuls les monitors de type « Page diff » sont créables par API ; les types à éléments (RSS Feed, Flux RSS) restent à créer depuis l'interface.
  • Les captures d'écran ne sont pas exposées.
  • L'historique des relevés non plus : la lecture de contenu ne rend que le dernier état d'une page, jamais ses versions antérieures.
  • Il n'y a pas de création en lot : un monitor par requête.
  • Aucune notification sortante : l'API fonctionne par interrogation. Pour être averti par email, passez par les alertes depuis l'interface.
  • Les clés ne se gèrent pas par API : une clé capable d'en émettre d'autres serait une porte dérobée.
Centre d'aide