Concepts
Pagination et synchronisation
Les listes répondent page par page avec un curseur ; updatedSince ne récupère que ce qui a changé depuis votre dernier passage.
Pages et curseurs
Toutes les listes répondent de la même façon : les éléments, le curseur de la page suivante, et s’il y en a une.
{
"data": [ { "sessionId": "b3ac0004-0000-4000-8000-000000000000", "title": "Registration and coffee" } ],
"nextCursor": "YzE6MDYzOTI3NjEwODQ4MjgyMDAwMHxiM2Fj...",
"hasMore": true
}Renvoyez nextCursor comme cursor, avec les mêmes filtres, jusqu’à ce qu’il vaille null. limit fixe la taille de la page : 100 par défaut, 500 au maximum. Un curseur est opaque : conservez-le tel que vous l’avez reçu, et n’en construisez pas vous-même. Un curseur que le serveur n’a pas créé est refusé avec invalidCursor, plutôt que de recommencer silencieusement depuis le début.
Les listes suivent un ordre fixe, la modification la plus ancienne d’abord, l’identifiant départageant les ex aequo : une page ne saute ni ne répète jamais un élément qui n’a pas changé pendant votre lecture. Un élément modifié pendant que vous parcourez les pages passe à la fin et vous le revoyez, ce qui est exactement ce que veut une synchronisation.
Les listes de participants, de commandes et d’entrées acceptent aussi page et pageSize, comme avant l’arrivée des curseurs. Ils fonctionnent toujours ; préférez le curseur, qui ne compte jamais.
Synchroniser : seulement ce qui a changé
Les listes d’éléments qui changent indiquent quand chaque élément a changé pour la dernière fois, dans updatedUtc, et acceptent updatedSince. Une synchronisation conserve le updatedUtc le plus récent qu’elle a vu et repart de là la fois suivante :
- Lisez chaque page avec
updatedSincefixé à la valeur conservée la dernière fois (rien la première fois). - Enregistrez ou mettez à jour chaque élément par son identifiant.
- Conservez le
updatedUtcle plus récent du passage pour la prochaine fois.
updatedSince est inclusif : un élément modifié dans la même milliseconde que votre repère revient plutôt que d’être manqué. Mettre à jour un élément que vous avez déjà est sans conséquence ; en manquer un ne l’est pas.
const base = "https://attendbeacon.com/api/v1/events/b3ac0000-0000-4000-8000-000000000001/attendees";
const headers = { Authorization: `Bearer ${process.env.ATTENDBEACON_KEY}` };
let since = await loadMark(); // null on the first run
let newest = since;
let cursor = null;
do {
const url = new URL(base);
url.searchParams.set("limit", "500");
if (since) url.searchParams.set("updatedSince", since);
if (cursor) url.searchParams.set("cursor", cursor);
const page = await fetch(url, { headers }).then((r) => r.json());
for (const attendee of page.data) {
await upsert(attendee); // by attendee.attendeeId
if (!newest || attendee.updatedUtc > newest) newest = attendee.updatedUtc;
}
cursor = page.nextCursor;
} while (cursor);
await saveMark(newest);var since = await LoadMarkAsync(); // null on the first run
var newest = since;
string? cursor = null;
do
{
var query = $"events/b3ac0000-0000-4000-8000-000000000001/attendees?limit=500"
+ (since is null ? "" : $"&updatedSince={Uri.EscapeDataString(since)}")
+ (cursor is null ? "" : $"&cursor={cursor}");
var page = await client.GetFromJsonAsync<JsonElement>(query);
foreach (var attendee in page.GetProperty("data").EnumerateArray())
{
await UpsertAsync(attendee); // by attendeeId
var updated = attendee.GetProperty("updatedUtc").GetString()!;
if (newest is null || string.CompareOrdinal(updated, newest) > 0) newest = updated;
}
cursor = page.GetProperty("nextCursor").GetString();
}
while (cursor is not null);
await SaveMarkAsync(newest);base = "https://attendbeacon.com/api/v1/events/b3ac0000-0000-4000-8000-000000000001/attendees"
headers = {"Authorization": f"Bearer {os.environ['ATTENDBEACON_KEY']}"}
since = load_mark() # None on the first run
newest = since
cursor = None
while True:
params = {"limit": 500}
if since: params["updatedSince"] = since
if cursor: params["cursor"] = cursor
page = requests.get(base, params=params, headers=headers).json()
for attendee in page["data"]:
upsert(attendee) # by attendee["attendeeId"]
if newest is None or attendee["updatedUtc"] > newest:
newest = attendee["updatedUtc"]
cursor = page["nextCursor"]
if cursor is None:
break
save_mark(newest)$base = "https://attendbeacon.com/api/v1/events/b3ac0000-0000-4000-8000-000000000001/attendees"
$since = Get-Content mark.txt -ErrorAction SilentlyContinue # nothing on the first run
$newest = $since
$cursor = $null
do {
$query = "?limit=500"
if ($since) { $query += "&updatedSince=" + [uri]::EscapeDataString($since) }
if ($cursor) { $query += "&cursor=$cursor" }
$page = Invoke-RestMethod -Uri ($base + $query) -Headers $headers
foreach ($attendee in $page.data) {
Save-Attendee $attendee # by attendeeId
if (-not $newest -or $attendee.updatedUtc -gt $newest) { $newest = $attendee.updatedUtc }
}
$cursor = $page.nextCursor
} while ($cursor)
Set-Content mark.txt $newestLes heures sont au format ISO 8601 en UTC, avec trois décimales et un Z : elles se comparent donc aussi correctement comme du texte.
Ce qui n’a pas d’heure de modification
Les salles, les parcours, les intervenants, les sponsors et les niveaux de sponsoring sont de petites listes sans heure de modification propre : lisez-les en entier, avec le curseur si vous le souhaitez. Leurs ressources individuelles et l’événement lui-même répondent avec un ETag : redemander avec If-None-Match renvoie alors 304 Not Modified, sans corps, si rien n’a changé.
Les entrées ne changent jamais une fois enregistrées : parcourez-les avec cursor, ou avec after, le dernier checkInId que vous avez, qui ne fait que croître.
Suppressions et effacement
Un billet annulé reste dans la liste avec "status": "cancelled" : une synchronisation voit donc l’annulation. Quand quelqu’un supprime ses données depuis l’application de l’événement, son participant reste avec "erased": true et rien de personnel ; le webhook attendee.erased vous prévient aussitôt, pour que vous supprimiez votre propre copie. Voir Confidentialité et effacement.
Webhooks ou interrogation périodique
Interroger l’API avec updatedSince toutes les quelques minutes est simple et ne manque rien. Les webhooks vous préviennent en quelques secondes. Beaucoup d’intégrations font les deux : les webhooks pour la rapidité, une synchronisation nocturne pour la certitude.