Collections et enregistrements

Les vingt-quatre types d'enregistrement au-delà des livraisons — l'enveloppe d'instantané qu'ils partagent, ce qu'est un instantané, et comment en parcourir une.

Les livraisons sont une ressource façonnée à la main, parce que le contrôle à réception est la première chose qu'on nous ait demandée. Les collections, c'est tout le reste de l'enregistrement : températures, cycles de refroidissement, nettoyage, contrôles des friteuses, étiquettes, vingt-quatre en tout, servies à travers une seule forme générique.

Là où l'endpoint de livraison vous donne un objet conçu pour vous, une collection vous donne l'enregistrement tel que l'application le détient, glissé dans une enveloppe qui vous dit quand il a été saisi et s'il existe encore. Ce compromis est délibéré : c'est lui qui permet à un nouveau module d'atteindre cette API la semaine où il sort, plutôt que le trimestre suivant.

Chaque collection est une autorisation distincte. Une clé lit temperature-records parce que quelqu'un a accordé temperature-records:read, et rien d'autre ne vient avec.

Ce que vous pouvez lire

GET /v1/restaurants/{restaurantId}/collections
GET /v1/restaurants/{restaurantId}/collections/{collection}/records?limit=…&cursor=…
GET /v1/restaurants/{restaurantId}/collections/{collection}/records/{recordId}
GET /v1/restaurants/{restaurantId}/collections/{collection}/records/{recordId}/assets

Commencez par le premier. Il liste uniquement les collections qui ont été accordées à votre clé, chacune avec la portée qui l'ouvre, vous n'avez donc jamais à deviner :

{
  "data": [
    {
      "collection": "temperature-records",
      "readScope": "temperature-records:read",
      "description": "Received shadow snapshots for temperature-records; not complete primary-store history.",
      "stateKind": "received-shadow-snapshot",
      "payloadVersion": 1
    }
  ]
}

Un data vide n'est pas une erreur et pas une panne : c'est une clé sans aucune autorisation sur les collections. La plupart des clés émises avant que cette surface n'existe sont exactement dans ce cas, et l'élargir tient en un e-mail.

L'enveloppe

Chaque enregistrement de chaque collection porte les mêmes champs extérieurs. Seul data change de forme.

ChampTypeSignification
idstringL'identifiant de l'enregistrement, stable et unique au sein de la collection.
restaurantIdstringLe restaurant auquel il appartient. Reprend le chemin.
collectionstringLa collection d'où il vient. Reprend le chemin.
dataobject | absentL'enregistrement lui-même. Une pierre tombale est le seul cas où il peut manquer, considérez-le donc comme présent partout ailleurs.
deletedbooleantrue signifie une pierre tombale : l'enregistrement a été supprimé dans l'application.
capturedAtstringQuand l'appareil a consigné le changement, en millisecondes depuis l'epoch sous forme de chaîne de caractères.
receivedAtstringQuand cette API l'a reçu. Postérieur à capturedAt, parfois de plusieurs heures.
sequencestringUne position croissante de façon monotone dans le flux des changements. Comparez-en deux ; ne faites pas d'arithmétique sur une seule.
mutationIdstringL'identifiant du changement qui a produit cet état. Clé d'idempotence de notre côté ; clé de déduplication du vôtre.
sourceVersionstring | nullLe marqueur de version de l'enregistrement d'origine, quand il en a un.
payloadVersionnumberToujours 1 aujourd'hui. Il augmente si le sens de data vient à changer.
stateKindstringToujours received-shadow-snapshot. Lisez la section suivante.

capturedAt et receivedAt comptent tous les deux. Une tablette dans une chambre froide sans réseau enregistre à 09:00 et téléverse à 14:00 ; ordonner votre propre pipeline sur receivedAt le garde correct, et faire vos rapports sur capturedAt les garde justes.

Ce qu'est un instantané, et ce qu'il n'est pas

stateKind dit received-shadow-snapshot, et la formulation est choisie avec soin.

  • C'est l'état actuel d'un enregistrement, pas un journal de chaque modification. Lisez un enregistrement deux fois et vous obtenez son état du moment, les deux fois.
  • C'est ce que nous avons reçu, pas ce que le restaurant détient. Un appareil qui n'a jamais téléversé un changement, c'est un enregistrement que cette API n'a jamais vu.
  • Ce n'est pas une reprise de l'historique. Une collection commence pour un restaurant le jour où la capture est activée pour lui. Les enregistrements créés avant sont dans l'application, pas ici.

Un enregistrement absent est donc réellement ambigu : jamais saisi, ou saisi et pas encore arrivé. N'en tirez pas un chiffre qui se lit comme un audit, et voyez la note sur l'exhaustivité avant de communiquer un décompte à qui que ce soit.

Les pierres tombales sont le seul cas où l'absence est sans ambiguïté. deleted: true sans data signifie que l'enregistrement a existé et a été supprimé, et c'est la seule façon d'apprendre que quelque chose a disparu.

Les vingt-quatre collections

Chacune d'elles prend <collection>:read comme portée — cooling exige cooling:read, et ainsi de suite jusqu'au bas de la liste.

CollectionCe qu'est un enregistrement
restaurantsLe site lui-même : nom, adresse, jours de fermeture, abonnement et paramètres.
usersLes comptes du personnel qui enregistrent les contrôles, et les modules qu'ils utilisent.
areasLes zones dans lesquelles un restaurant est découpé — cuisine, chambre froide, bar.
equipmentRéfrigérateurs, congélateurs et équipements de la chaîne du froid, avec leurs seuils min/max.
sensorsLes sondes sans fil, par adresse MAC, et l'équipement que chacune surveille.
temperature-recordsUne température relevée par une personne sur un équipement, avec le service et l'éventuelle action corrective.
temperature-readingsUne température remontée par une sonde d'elle-même, sans intervention.
suppliersQui livre, avec les moyens de contact et le numéro de compte.
productsLes produits réceptionnés et utilisés.
preparationsLes préparations maison, avec leur durée de vie et leurs allergènes.
cleaning-tasksLe plan de nettoyage : chaque tâche, sa zone, sa récurrence, et si elle exige une photographie.
cleaning-task-recordsUne tâche de nettoyage réellement effectuée — quand, et par qui.
cleaning-task-picturesLa photographie qui le prouve. Porte un fichier ; voyez la section sur les fichiers ci-dessous.
coolingUn cycle de refroidissement : produit, température de début et de fin, heure de début et de fin.
freezingUne opération de congélation, de même forme que le refroidissement.
reheatingUne opération de remise en température, de même forme que le refroidissement.
transportUn produit transporté, avec lieu de départ et d'arrivée, heure et température.
fryer-equipmentLes friteuses.
fryer-checksUn contrôle de la qualité de l'huile et ce qui a été décidé — filtrée, changée, laissée.
cooking-equipmentLes fours et les équipements de cuisson, avec leurs seuils.
cooking-temperature-recordsUne température de cuisson, relevée par une personne, sur un équipement de cuisson.
surface-analysesUn prélèvement de surface : ce qui a été testé, s'il est conforme, le plan d'action sinon.
traceability-labelsUne étiquette de traçabilité imprimée. Porte un fichier ; voyez la section sur les fichiers ci-dessous.
drive-filesUn document classé dans l'espace documentaire du restaurant. Porte un fichier ; voyez la section sur les fichiers ci-dessous.

Les formes champ par champ de chaque objet data sont dans le document OpenAPI, généré à partir du service en fonctionnement : c'est la seule description qui ne peut pas s'écarter de ce qui est déployé.

Parcourir une collection

GET /v1/restaurants/{restaurantId}/collections/{collection}/records?limit=100
ParamètreObligatoireRègles
limitnonDe 1 à 100. 50 par défaut.
cursornonLe nextCursor de la page précédente, tel quel.

Il n'y a pas de plage de temps ici, contrairement aux livraisons : vous parcourez une collection, pas une fenêtre de celle-ci.

Un parcours est un instantané cohérent. La première page fixe la position dans le flux, et chaque page suivante est servie à cette même position. Les enregistrements écrits pendant que vous paginez ne décalent pas les pages sous vos pieds et n'apparaissent pas en cours de parcours — vous les verrez au parcours suivant. Le tri se fait sur sequence, croissant.

Le curseur est lié au restaurant, à la collection et à la limite. Changez la taille de page en cours de parcours et il est rejeté : choisissez une limite, gardez-la pour tout le parcours.

async function* records({ apiKey, restaurantId, collection }) {
  const base = `https://api.backresto.com/v1/restaurants/${restaurantId}/collections/${collection}/records`;
  let cursor = null;

  do {
    const url = new URL(base);
    url.searchParams.set('limit', '100');
    if (cursor !== null) {
      url.searchParams.set('cursor', cursor);
    }

    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${apiKey}` }
    });
    if (!response.ok) {
      throw new Error(`BackResto ${response.status}`);
    }

    const page = await response.json();
    yield* page.data;
    cursor = page.nextCursor;
  } while (cursor !== null);
}

Maintenir une copie à jour

Il n'y a pas de paramètre since aujourd'hui. Une actualisation, c'est un nouveau parcours, et vous réconciliez avec ce que vous détenez déjà :

  • Indexez sur id au sein d'une collection, et écrasez quand le sequence que vous recevez est supérieur à celui que vous aviez stocké.
  • Respectez les pierres tombales. deleted: true est l'instruction de suppression ; l'appliquer est la seule façon d'empêcher votre copie de diverger.
  • Parcourez à une heure raisonnable. Une collection entière à limit=100, c'est une poignée de requêtes pour un seul restaurant, et le budget est de 300 par minute — mais plusieurs centaines de restaurants sur la même minute de cron, c'est un pic qui vous appartient.

Si un curseur incrémental changeait ce que vous pouvez construire, dites-le. C'est un petit changement sur un flux déjà ordonné — s'il n'existe pas, c'est que personne n'en a encore eu besoin.

Fichiers attachés

Trois collections portent un fichier : cleaning-task-pictures, traceability-labels et drive-files. L'enregistrement le nomme dans data.asset ; les octets viennent de l'endpoint des fichiers.

GET /v1/restaurants/{restaurantId}/collections/{collection}/records/{recordId}/assets
{
  "data": [
    {
      "id": "a17c93be4f02",
      "contentType": "application/pdf",
      "byteLength": 184320,
      "sha256": "9f2a…",
      "uploadedAt": "1789000000000",
      "url": "https://…?X-Amz-Signature=…",
      "urlExpiresAt": "1789000900000"
    }
  ]
}

Il répond à peu près comme les photographies de livraison : une liste d'URL signées, valables quinze minutes chacune, portant chacune sa propre autorisation, si bien que le téléchargement ne demande aucun en-tête. Deux différences méritent d'être connues.

Ce sont des fichiers, pas seulement des photos. Une preuve de nettoyage est une photographie, mais une étiquette de traçabilité ou un document de l'espace documentaire peut être un PDF, un CSV, un tableur ou un document Word — lisez contentType plutôt que de supposer une image, et ne renommez pas tout en .jpg à l'entrée.

sha256 est là pour vous éviter du travail. C'est l'empreinte des octets : si elle correspond à quelque chose que vous avez déjà stocké, vous n'avez pas besoin de le retélécharger.

Seuls apparaissent les fichiers dont le téléversement s'est achevé et a été vérifié — un fichier encore en transit est absent plutôt que cassé, et c'est également vrai une fois l'enregistrement parent supprimé.

Le reste des conseils est identique, et mérite d'être répété parce que l'échec est silencieux : récupérez les octets maintenant, stockez les octets plutôt que le lien, et rappelez l'endpoint quand vous avez besoin d'une URL fraîche.

Les échecs que vous rencontrerez

403 — votre clé atteint le restaurant, mais pas avec la portée de cette collection. L'endpoint des collections est le moyen peu coûteux de savoir lesquelles elle détient bel et bien.

404 — aucune autorisation pour ce restaurant, ou pas d'enregistrement de ce nom. Les deux sont volontairement indiscernables.

400 — une collection hors des vingt-quatre ci-dessus, ou un curseur qui n'appartient pas à ce restaurant, cette collection et cette limite.

Les trois sont des documents problème avec un requestId.

Dernière mise à jour 2026-09-19.