Prerequisites
- An API key with read access to the library
- The Python SDK
gumnut-sdk0.168.0 or later, for the examples on this page
How the feed behaves
GET /api/events returns events in pages, each with an opaque cursor:
- Nothing is skipped. Reading forward from a stored cursor returns every event committed after it, each once. A cursor is the only checkpoint you need.
- Events are not in commit order. Two changes to one entity can arrive in either order, so apply an event by reading the entity’s current state, not by replaying the change.
- Events can trail their commit. An event appears once every write that started before it has finished, so a change can take a moment to show up.
created_at is when the change’s transaction started. Use it for display only:
it is neither the feed order nor a checkpoint, and a later event can carry an
earlier time.
Sync a library
- Load your stored cursor, or start with none for a first sync.
- Request a page with
after_cursorset to that cursor. - Apply each event, as described below.
- After applying the page, store its
next_cursor. - Repeat until
has_moreisfalse. You are caught up; the next sync starts again at step 1.
store stands for your local storage, keyed by entity type and ID. Save the
cursor in the same place as the data, ideally in the same transaction, so the
two can’t drift apart.
Make applying an event idempotent. If your client stops after applying a page
but before storing its cursor, the next sync replays that page.
While has_more is true, next_cursor also bounds the read to events that
were ready when it began, so a busy library can’t keep a sync running forever.
Changes made during the sync arrive in the next one. Pass the same library_id,
entity_types, and created_at_gte on every page.
Apply events
Treat every event as “this changed”: read the entity’s current state and upsert it, or remove your copy when the read no longer returns it. Because you always apply current state, events that arrive out of order still leave you correct. Re-reads count against your rate limit. The list endpoints accept up to 200ids, so read a page’s entities of each type in one
call rather than one call per event.
- Deletions and trash. Event types ending in
_deletedor_removedmean the entity is gone, andasset_trashedmoves an asset to the trash. A default read omits both, so the same rule removes your copy.asset_restoredbrings an asset back. Trashing or restoring an asset also hides or reveals its faces and album memberships, and changes its people’s and stack’s counts, without events of their own: re-read those byasset_idtoo. - Album membership. Each add creates a new membership with its own ID, so
re-read memberships by ID like any other entity. Deleting an asset or album,
or trashing an asset, removes or hides its memberships without separate
events, so drop yours along with it, as the example does. When you remove an
asset from an album,
album_asset_removedalso names thealbum_idandasset_idin itspayload. - People and faces. A person’s face and asset counts change through its
faces, so they arrive as
face_*events, notperson_updated. Aface_updatedpayload names the face’sperson_idandprevious_person_id, and aface_deletedpayload carriesprevious_person_id. Refetch each person that isn’tnull, and expect a404for one deleted in the same change.
READERS
and apply the rules above. A metadata event’s entity_id is an asset ID.
The GET /api/events page in the API Reference tab lists the event types.
Request only the entity types you apply. Within them, re-read state for any
event type your client doesn’t recognize rather than failing.
Sync several entity types together
Some clients read one entity type at a time, such as albums before the photos in them. Each read stops at its own point, so a later read could include a change an earlier one stopped short of. Every response includesas_of, the point that read stopped at. Pass the first
response’s as_of to every other read in the same sync so they all stop at the
same point:
as_of: it belongs to one
sync. A shared as_of stops every read at one point, but an entity can still
arrive before an entity it refers to. Let your client tolerate a missing
reference until a later event fills it in.
Handle errors
GET /api/events returns 400 for:
- an unknown entity type
- a malformed
after_cursororas_of - an
after_cursororas_ofahead of the server, as after a restore that went back in time
created_at_lt is deprecated and ignored; a time bound could skip events. Bound
a read with next_cursor and as_of instead.