Pagination et synchronisation incrémentale

Parcourir une liste page par page avec un curseur, puis ne récupérer que les nouveautés à chaque passage.

Documentation de l'APIGuides · Pagination et synchronisation

Forme d'une liste

Toutes les opérations de liste renvoient le même objet :

{
  "object": "list",
  "data": [{ "object": "review", "id": "jd7f3k2m9x8w1q4z" }],
  "hasMore": true,
  "nextCursor": "eyJ2IjoxLCJjIjoi..."
}
  • data contient les éléments de la page.
  • hasMore vaut true tant qu'il reste des éléments après cette page.
  • nextCursor est le curseur de la page suivante, ou null sur la dernière page.

Paramètres

ParamètreRôle
limitNombre d'éléments par page, de 1 à 100. 25 par défaut.
cursorValeur de nextCursor reçue avec la page précédente. Absent pour la première page.

Le curseur est opaque : ne le décodez pas et ne le fabriquez pas. Il ne se réutilise qu'avec les mêmes filtres que la requête qui l'a renvoyé : changer un filtre en cours de route répond 400 invalid_cursor. La valeur de limit, elle, peut changer d'une page à l'autre.

Un paramètre inconnu, répété ou hors bornes répond 400 invalid_request, avec le détail dans errors (par exemple query.limit : « Must be less than or equal to 100 »).

Ordre des éléments

Les avis, les retours privés et les contacts sont listés du plus récent au plus ancien. Pour les autres listes, ne vous appuyez pas sur un ordre particulier.

Parcourir toutes les pages

En JavaScript, dans un module Node 18 ou plus :

async function listAll(path, params = {}) {
  const items = [];
  let cursor = null;
  do {
    const query = new URLSearchParams({ ...params, limit: "100" });
    if (cursor) query.set("cursor", cursor);
    const response = await fetch(`https://api.mabellenote.com/v1${path}?${query}`, {
      headers: { Authorization: `Bearer ${process.env.MBN_API_KEY}` },
    });
    const page = await response.json();
    if (!response.ok) throw new Error(`${page.status} ${page.code} (${page.requestId})`);
    items.push(...page.data);
    cursor = page.nextCursor;
  } while (cursor);
  return items;
}

const reviews = await listAll("/reviews", { rating: "1" });

En Python, avec requests :

import os
import requests

def list_all(path, **params):
    items, cursor = [], None
    while True:
        query = {**params, "limit": 100}
        if cursor:
            query["cursor"] = cursor
        response = requests.get(
            "https://api.mabellenote.com/v1" + path,
            params=query,
            headers={"Authorization": "Bearer " + os.environ["MBN_API_KEY"]},
            timeout=30,
        )
        page = response.json()
        if not response.ok:
            raise RuntimeError(f"{page['status']} {page['code']} ({page['requestId']})")
        items.extend(page["data"])
        cursor = page["nextCursor"]
        if cursor is None:
            return items

Chaque page compte pour un appel dans la limite de débit : lire 1 000 avis par pages de 100 coûte 10 appels.

Filtres utiles à la synchronisation

FiltreListesEffet
establishmentIdavis, retours privés, contactsNe garde que les éléments de cet établissement.
createdAfteravis, retours privés, contactsNe garde que les éléments créés à partir de cette date, incluse.
createdBeforeavis, retours privésNe garde que les éléments créés jusqu'à cette date, incluse.
includeDeletedavistrue ne liste que les avis supprimés de la plateforme.

Les dates s'écrivent au format ISO 8601, par exemple 2026-10-24 ou 2026-10-24T09:15:02.311Z. La référence liste tous les filtres de chaque opération.

Synchronisation incrémentale

Une première synchronisation parcourt toutes les pages. Les suivantes ne demandent que ce qui a été créé depuis :

  1. Conservez la date createdAt la plus récente reçue, par ressource.
  2. Au passage suivant, envoyez-la dans createdAfter. La borne étant incluse, le dernier élément déjà reçu revient : dédoublonnez par id, en mettant à jour l'élément existant plutôt qu'en l'ajoutant.
  3. Parcourez les pages jusqu'à hasMore: false.

Un élément déjà synchronisé peut encore changer :

  • Avis : une réponse publiée remplit reply, une modification par son auteur met à jour updatedAt, l'analyse remplit analysis.sentiment une fois l'avis analysé, et une suppression sur la plateforme remplit deletedAt. Relisez une fenêtre glissante récente, par exemple les 30 derniers jours, à chaque passage, et listez les avis supprimés avec includeDeleted=true.
  • Contacts : les consentements, les désinscriptions et les passages modifient updatedAt. Aucun filtre ne porte sur cette date : resynchronisez la base clients complète à intervalle régulier.
Une date de publication, pas d'arrivée

Le createdAt d'un avis est sa date de publication sur la plateforme, pas sa date d'arrivée dans Ma Belle Note. Quand un établissement rejoint le compte, ou quand son historique est importé, des avis anciens apparaissent avec des dates passées. Surveillez GET /v1/establishments : un nouvel id, ou un reviews.count qui augmente plus que ce que vous avez reçu, appelle une resynchronisation complète de cet établissement avec establishmentId.

Suivre les changements avec le journal d'événements

La relecture d'une fenêtre glissante a ses limites : elle coûte des appels et ne voit pas toutes les modifications. Le journal d'événements suit chaque changement du compte, dans l'ordre où il est publié. C'est la façon la plus sûre de rester synchronisé :

  1. Faites une première synchronisation complète avec les listes, comme ci-dessus.
  2. Lisez ensuite GET /v1/events (droit events:read), en conservant la position nextCursor d'un appel à l'autre.
curl "https://api.mabellenote.com/v1/events?types=review.created,review.updated,review.reply_published&limit=100" \
  -H "Authorization: Bearer $MBN_API_KEY"
  • Les événements arrivent du plus ancien au plus récent. Sans after, la lecture part du plus ancien événement conservé.
  • Passez le nextCursor reçu dans after à l'appel suivant. Il n'est jamais null : quand hasMore vaut false, vous êtes à jour, et ce même nextCursor sert au passage suivant. Conservez-le d'une synchronisation à l'autre.
  • Comme pour les listes, une position ne se réutilise qu'avec les mêmes filtres types et establishmentId.
  • Chaque événement porte l'objet concerné dans sa forme publique (data.object) : un avis, un retour privé ou un contact. Mettez à jour votre copie par son id.
  • Les événements restent consultables 30 jours. Une intégration arrêtée plus longtemps repart d'une synchronisation complète.
  • Les événements ne sont produits que tant qu'au moins une clé API ou un point de réception webhook du compte est actif, et jamais pour les faits antérieurs.

GET /v1/events/{eventId} relit un événement précis. Pour être prévenu sans interroger l'API, les webhooks poussent ces mêmes événements vers votre serveur, et le journal sert alors au rattrapage.

Mis à jour le 25 octobre 2026