On Monday your family agent tells a parent: the fall festival is Saturday the tenth, gates at ten. The parent puts it on the calendar. On Thursday the farm moves it a week because of the forecast.
If your agent saved a copy of Monday's answer, it will confidently repeat it on Saturday morning. If it saved an id, it can do better. This post shows how to build the second version with three public endpoints and one habit.
1. What should you save?
Save the listing's id, not a copy of its details. Keep your own note of what you told the parent (the starts_local you quoted, and when you quoted it), because you will want it later to say "this moved."
{ "listing_id": "2f3c9d4e-3d2b-4b7d-9a3e-1b2c3d4e5f60", "type": "event", "told_parent": "2026-10-10T10:00:00" }2. How do you learn something changed?
Poll the change feed. It is a single ordered list across events, classes, camps and places in a metro, and it is the only place a removal shows up:
- since2026-09-29T00:00:00Z
- metrophiladelphia
- typeevent
curl -G -H "X-API-Key: $TJ_KEY" https://api.tailend.dev/v1/changes \
--data-urlencode "since=2026-09-29T00:00:00Z" \
--data-urlencode "metro=philadelphia" \
--data-urlencode "type=event"Each item has a listing_id, a kind (created, updated, date_changed, price_changed, location_changed, archived or restored), the fields that changed, and changed_at. Values are deliberately not included, so you fetch the listing to see what it says now.
Only the listings you recommended matter:
const saved = new Map(plans.map((p) => [p.listing_id, p]));
for (const change of feed.data) {
const plan = saved.get(change.listing_id);
if (!plan) continue;
if (change.kind === "archived") await markGone(plan);
else await refetchAndCompare(plan); // date_changed, price_changed, updated, ...
}
await saveCursor(feed.meta.next_cursor); // only after the whole page is appliedA few habits from the sync guide are worth copying. On the first call use since. After that, pass the last meta.next_cursor back as cursor, and save it only after you have applied the page, so a crash replays the page instead of skipping it. Choose a polling interval that fits your product and back off on 429 or 503 using Retry-After. Change webhooks are not available, so polling is the way today.
3. What do you check right before you send someone somewhere?
The morning of, or whenever your agent is about to say "leave at 9:30," fetch that one listing:
curl -H "X-API-Key: $TJ_KEY" https://api.tailend.dev/v1/events/2f3c9d4e-3d2b-4b7d-9a3e-1b2c3d4e5f60Read these:
starts_localandhas_time. Compare to what you told the parent.is_sold_out,registration_requiredandregistration_url.verified_at. When it was last confirmed.- A
404on the detail fetch means the listing is no longer listed. Tell the parent that, instead of treating it as an error.
Classes and camps carry their own signup state where the organizer states it. A class has registration.status, registration.spots_remaining and registration.waitlist_available. A camp's sessions have spots_remaining and status. These are null when the organizer did not state them, so do not turn null into "plenty of room."
If you called this listing yesterday, send the ETag back as If-None-Match. An unchanged listing costs a 304 with no body.
4. How do you explain what changed?
Ask for the listing's version history:
curl -H "X-API-Key: $TJ_KEY" https://api.tailend.dev/v1/listings/2f3c9d4e-3d2b-4b7d-9a3e-1b2c3d4e5f60/versionsYou get changed_at, kind and fields for each change, newest first. It tells you that the date changed on Thursday, and not what it changed from. That is why you saved the time you quoted. Put the two together and your agent can say the useful sentence: "This was Saturday at ten when I looked on Monday. On Thursday the date changed, and it now says next Saturday."
5. What about weekly classes?
A repeating event is one listing with an occurrence block (a repeating class is one listing with a schedule). occurrence.next_starts_local is the next live date, and it is the one to quote. starts_local on a repeating listing is where the series started. If occurrence.next_starts_at is null, the series has nothing upcoming, which is worth saying instead of guessing.
6. What does the API not have?
Be straightforward with your users about the edges. There is no dedicated rain-date field and no cancellation-reason field. If an organizer wrote "rain date: October 17" in their description, that lives in the text, and your agent should read it and point to the organizer's own page. What the API gives you reliably is the current date, whether it changed, and when it was last confirmed.
Also report it as "as of." A recent verified_at says the organizer's page still stated this at that moment. It does not promise the seats are open or the weather will hold.
Why this matters
A family agent lives or dies on the last mile. The first time it sends someone to a locked gate, they stop believing the Saturday plans. The habit above costs one listing fetch at decision time and one change feed on a timer, and it turns a stale answer into "here is what changed."
How Tailend helps
Tailend looks at listings more closely as their dates approach, keeps a value-free record of what changed and when, and marks each listing with when it was last confirmed. That is the part you would otherwise have to build and run yourself. We built it because we kept hitting these problems running TinyJawns, a family guide in Philadelphia. We start there and are working outward from there.
