Collections & Datensätze

Die sechsundzwanzig Datensatz-Collections, der Snapshot-Umschlag, den sie teilen, und wie Sie eine durchlaufen.

Eine Collection ist ein Datensatztyp: temperature-records, cooling, deliveries und dreiundzwanzig weitere. Jede wird auf dieselbe Weise gelesen, über dieselben vier Endpunkte, und jeder Datensatz kommt im selben Umschlag zurück: den Feldern um data herum, die sagen, wann er erfasst wurde und ob es ihn noch gibt.

Jede Collection ist eine eigene Zuweisung. Ein Schlüssel liest temperature-records, weil jemand temperature-records:read zugewiesen hat, und nichts sonst kommt damit.

Was Sie lesen können

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

Beginnen Sie mit dem ersten. Er listet nur die Collections, die Ihrem Schlüssel zugewiesen wurden, jede mit dem Scope, der sie geöffnet hat. Sie müssen also nie raten:

{
  "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
    }
  ]
}

Ein leeres data ist kein Fehler und keine Störung: Es ist ein Schlüssel ohne Collection-Zuweisungen. Die meisten Schlüssel, die ausgestellt wurden, bevor es diese Oberfläche gab, haben genau das, und einen zu erweitern ist eine E-Mail.

Der Umschlag

Jeder Datensatz in jeder Collection trägt dieselben äußeren Felder. Nur data ändert seine Struktur.

FeldTypBedeutung
idstringDie Kennung des Datensatzes, stabil und eindeutig innerhalb der Collection.
restaurantIdstringDas Restaurant, zu dem er gehört. Spiegelt den Pfad wider.
collectionstringAus welcher Collection er stammt. Spiegelt den Pfad wider.
dataobject | fehltDer Datensatz selbst. Ein Tombstone ist der einzige Fall, in dem es fehlen kann; überall sonst dürfen Sie es als vorhanden behandeln.
deletedbooleantrue bedeutet einen Tombstone: Der Datensatz wurde in der App gelöscht.
capturedAtstringWann das Gerät die Änderung erfasst hat, als String mit Millisekunden seit Epoch.
receivedAtstringWann diese API sie empfangen hat. Später als capturedAt, manchmal um Stunden.
sequencestringEine monoton steigende Position im Strom der Änderungen. Vergleichen Sie zwei davon; rechnen Sie nicht mit einer.
mutationIdstringDie Kennung der Änderung, die diesen Zustand erzeugt hat. Auf unserer Seite ein Idempotenzschlüssel, auf Ihrer ein Schlüssel zur Deduplizierung.
sourceVersionstring | nullDer Versionsstempel des ursprünglichen Datensatzes, sofern er einen hat.
payloadVersionnumberDerzeit immer 1. Er steigt, falls sich die Bedeutung von data je ändert.
stateKindstringImmer received-shadow-snapshot. Lesen Sie den nächsten Abschnitt.

capturedAt und receivedAt zählen beide. Ein Tablet in einem Kühlraum ohne Empfang erfasst um 09:00 Uhr und lädt um 14:00 Uhr hoch; Ihre eigene Pipeline nach receivedAt zu ordnen hält sie korrekt, und nach capturedAt zu berichten hält sie wahr.

Was ein Snapshot ist und was nicht

stateKind sagt received-shadow-snapshot, und die Formulierung ist mit Bedacht gewählt.

  • Es ist der aktuelle Zustand eines Datensatzes, kein Protokoll jeder Änderung. Lesen Sie einen Datensatz zweimal, und Sie bekommen beide Male, wie er jetzt aussieht.
  • Es ist, was wir empfangen haben, nicht, was das Restaurant hält. Ein Gerät, das eine Änderung nie hochgeladen hat, heißt: ein Datensatz, den diese API nie gesehen hat.
  • Es ist kein Backfill. Eine Collection beginnt für ein Restaurant an dem Tag, an dem die Erfassung dafür eingeschaltet wird. Datensätze, die davor entstanden sind, liegen in der App und nicht hier. Lieferungen sind die einzige Ausnahme: Diese beiden Collections tragen die Historie, die die App noch hielt, als sie geöffnet wurden.

Ein fehlender Datensatz ist also wirklich mehrdeutig: nie erfasst, oder erfasst und noch nicht angekommen. Bauen Sie darauf keine Zahl, die sich wie ein Audit liest, und lesen Sie die Anmerkung zur Vollständigkeit, bevor Sie irgendjemandem eine Anzahl nennen.

Tombstones sind der eine Fall, in dem Abwesenheit eindeutig ist. deleted: true ohne data heißt, dass der Datensatz existiert hat und gelöscht wurde, und es ist der einzige Weg, auf dem Sie erfahren, dass etwas verschwunden ist.

Wie lange Datensätze hier bleiben

Betriebliche Datensätze bleiben hier 90 Tage, gezählt ab dem Zeitpunkt, an dem sie in der Küche entstanden sind (data.timestamp), nicht ab ihrem Eingang bei uns. Danach verschiebt ein nächtlicher Job sie in ein gesperrtes Kaltarchiv, das fünf Jahre aufbewahrt wird, und sie erscheinen in keinem Lesezugriff mehr: weder in Listen noch als einzelner Datensatz noch mit ihren Assets.

90 Tage aufbewahrtFür die gesamte Lebensdauer des Kontos aufbewahrt
temperature-readings, temperature-records, cooking-temperature-records, cleaning-task-records, cleaning-task-pictures, cooling, freezing, reheating, transport, fryer-checks, surface-analyses, traceability-labels, deliveries, delivery-picturesrestaurants, users, areas, equipment, sensors, cooking-equipment, suppliers, products, preparations, cleaning-tasks, fryer-equipment, drive-files

Ein archivierter Datensatz ist kein Tombstone, und er ist die einzige Ausnahme von der Tombstone-Regel oben. Er verschwindet ohne deleted: true, weil ihn niemand gelöscht hat: Er wurde verschoben. Wenn Sie eine Kopie führen, behalten Sie, was Sie bereits haben, und werten Sie einen in einem Durchlauf fehlenden Datensatz nie als Löschung. Für alles, was älter als 90 Tage ist, ist Ihre Kopie die Quelle; eine Prüfung, die unser Archiv braucht, läuft über den Support.

Die sechsundzwanzig Collections

Jede nimmt <collection>:read als Scope (cooling braucht cooling:read, und so weiter). Sie sind nach Themengebiet dokumentiert, Feld für Feld, auf den Seiten unten.

SeiteCollections
Temperaturentemperature-records, temperature-readings, cooking-temperature-records
Lieferungendeliveries, delivery-pictures
Rückverfolgbarkeitsetikettentraceability-labels
Reinigungcleaning-tasks, cleaning-task-records, cleaning-task-pictures
Fritteusenfryer-equipment, fryer-checks
Kühlen & Lebensmittelprozessecooling, freezing, reheating, transport
Oberflächenanalysensurface-analyses
Dokumentedrive-files
Katalogsuppliers, products, preparations
Standort & Personenrestaurants, users, areas, equipment, sensors, cooking-equipment

Temperaturen in einer Lieferung sind immer in Celsius: Eine in Fahrenheit genommene Messung wird beim Eingang umgerechnet, und jedes temperatureRecords[].unit sagt "C".

Beginnen Sie mit Standort & Personen, wenn Sie eine der anderen Seiten lesen: Die Schwellen der Geräte und die Bereiche sind das, was aus einer nackten Messung etwas macht, das Sie beurteilen können.

Das OpenAPI-Dokument trägt dieselben Strukturen, erzeugt aus dem laufenden Dienst. Wenn eine Seite hier und jenes Dokument sich widersprechen, hat das Dokument recht und wir haben eine Seite zu korrigieren.

Eine Collection durchlaufen

GET /v1/restaurants/{restaurantId}/collections/{collection}/records?limit=100
ParameterPflichtRegeln
limitnein1 bis 100. Standard ist 50.
cursorneinDer nextCursor der vorherigen Seite, wortgleich.

Hier gibt es keinen Zeitraum: Sie durchlaufen eine Collection, nicht ein Fenster daraus. Um eine Woche an Lieferungen zu lesen, durchlaufen Sie deliveries und behalten die Datensätze, deren data.timestamp in diese Woche fällt.

Ein Durchlauf ist ein konsistenter Snapshot. Die erste Seite fixiert die Position im Strom, und jede spätere Seite wird zu genau dieser Position beantwortet. Datensätze, die geschrieben werden, während Sie blättern, verschieben die Seiten unter Ihnen nicht und tauchen mitten im Durchlauf nicht auf: Sie sehen sie beim nächsten Durchlauf. Sortiert wird aufsteigend nach sequence.

Der Cursor ist an das Restaurant, die Collection und das Limit gebunden. Ändern Sie die Seitengröße mitten im Durchlauf, wird sie abgelehnt: Wählen Sie ein Limit und behalten Sie es für den ganzen Durchlauf.

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);
}

Eine Kopie aktuell halten

Es gibt heute keinen since-Parameter. Eine Auffrischung ist ein weiterer Durchlauf, und Sie gleichen ihn mit dem ab, was Sie schon halten:

  • Nutzen Sie id als Schlüssel innerhalb einer Collection und überschreiben Sie, wenn die sequence, die Sie bekommen, höher ist als die gespeicherte.
  • Werten Sie Tombstones aus. deleted: true ist die Anweisung zu löschen; sie anzuwenden ist der einzige Weg, auf dem Ihre Kopie aufhört auseinanderzulaufen.
  • Durchlaufen Sie zu einer vernünftigen Stunde. Eine vollständige Collection mit limit=100 ist für ein einzelnes Restaurant eine Handvoll Anfragen, und das Budget sind 300 pro Minute, aber mehrere hundert Restaurants in derselben Cron-Minute sind eine Spitze, die Ihnen gehört.

Wenn ein inkrementeller Cursor ändern würde, was Sie bauen können, sagen Sie es. Es ist eine kleine Änderung an einem Strom, der ohnehin geordnet ist. Der Grund, warum es ihn nicht gibt, ist, dass ihn bisher niemand gebraucht hat.

Assets

Vier Collections tragen eine Datei: cleaning-task-pictures, delivery-pictures, traceability-labels und drive-files. Der Datensatz benennt sie in data.asset; die Bytes kommen vom Assets-Endpunkt.

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"
    }
  ]
}

Er antwortet mit einer Liste signierter URLs, jede fünfzehn Minuten gültig, und jede URL einer Antwort hat denselben Ablaufzeitpunkt. Jede trägt ihre eigene Autorisierung, der Download braucht also keinen Authorization-Header, was zugleich heißt, dass der Link selbst ein Bearer-Credential ist: Fünfzehn Minuten lang kann jeder, der ihn hat, diese Datei lesen. Zwei Dinge sollte man wissen.

Das sind Dateien, nicht nur Bilder. Ein Reinigungsbeleg oder ein Lieferfoto ist ein Bild, aber ein Rückverfolgbarkeitsetikett oder eine Drive-Datei kann ein PDF, eine CSV, eine Tabelle oder ein Word-Dokument sein. Lesen Sie contentType, statt ein Bild anzunehmen, und benennen Sie beim Import nicht alles in .jpg um.

sha256 ist da, damit Sie sich Arbeit sparen können. Es ist der Digest der Bytes: Passt er zu etwas, das Sie schon gespeichert haben, müssen Sie es nicht erneut herunterladen.

Es erscheinen nur Assets, deren Upload abgeschlossen und geprüft ist: Eines, das noch unterwegs ist, fehlt schlicht, statt defekt zu erscheinen, und dasselbe gilt, sobald der übergeordnete Datensatz gelöscht ist.

Den Rest des Rats zu wiederholen lohnt sich, weil der Fehlschlag leise ist: Holen Sie die Bytes jetzt, speichern Sie die Bytes statt des Links, legen Sie die URL nie irgendwohin, wo sie ihren Zweck überlebt (eine E-Mail, ein Ticket, ein langlebiger Cache), und rufen Sie den Endpunkt erneut auf, wenn Sie eine frische URL brauchen. Das ist billig, und dabei wird die Zuweisung erneut geprüft.

Die Fehler, die Ihnen begegnen werden

403: Ihr Schlüssel erreicht das Restaurant, aber nicht mit dem Scope dieser Collection. Der Collections-Endpunkt ist der billige Weg herauszufinden, welche Scopes er hat.

404: überhaupt keine Zuweisung für dieses Restaurant, oder es gibt diesen Datensatz nicht. Beides ist absichtlich nicht unterscheidbar.

400: eine Collection außerhalb der sechsundzwanzig oben, oder ein Cursor, der nicht zu diesem Restaurant, dieser Collection und diesem Limit gehört.

Alle drei sind Problemdokumente mit einer requestId.

Zuletzt aktualisiert am 2026-09-29.