Use casesCalendars and apps
Calendars that stay right as plans change.
Per-day counts for the month, every session of a recurring series, and a change feed that tells you what moved.
Real screens from TinyJawns, built on Tailend
/events/calendar · occurrences=expand · changed_since · /v1/changes · /classes
GET /v1/philadelphia/events/calendar?from=2026-10-12&to=2026-10-18Sample shown
The problem
A calendar is only as good as its last sync.
- Month views are heavy.Counting a month by loading every event is slow and drifts from the list.
- Series hide dates.A weekly class is one row on its page, but a calendar needs every date.
- Things move.Times shift and events are cancelled after you have copied them.
How to build it
Month, day, then keep it fresh.
-
Draw the month from counts
One call returns every date in the window with how many events start that day, how many are free, and counts per category. Zeros included.
Try it in the playground{ "data": [ { "date": "2026-10-05", "count": 41, "free_count": 22, "categories": { "storytime_books": 14, "arts_creative": 9, "sports_active_play": 6 } }, { "date": "2026-10-06", "count": 0, "free_count": 0, "categories": {} }, { "date": "2026-10-07", "count": 57, "free_count": 31, "categories": { "storytime_books": 19, "music_movement": 8 } } ], "meta": { "metro": "philadelphia", "window": { "from": "2026-10-05", "to": "2026-10-07", "timezone": "America/New_York" }, "total": 98 } } -
Fill a day with every occurrence
List the day with
Try it in the playgroundoccurrences=expand. Recurring series appear once per date, and the list always matches the day's count.{ "data": [ { "id": "b81a95d2-1d4e-46fd-9cff-322a41e429ad", "name": "Toddler Time (Ages 5 and Under)", "occurrence": { "starts_local": "2026-09-14T09:30:00", "starts_at": "2026-09-14T13:30:00.000Z", "ends_local": null, "index": 0 } }, { "id": "b81a95d2-1d4e-46fd-9cff-322a41e429ad", "name": "Toddler Time (Ages 5 and Under)", "occurrence": { "starts_local": "2026-09-15T09:30:00", "starts_at": "2026-09-15T13:30:00.000Z", "ends_local": null, "index": 1 } } ], "meta": { "count": 2, "has_more": false } } -
Stay in sync with changes
Poll
Try it in the playground/v1/changesfor what moved since your last sync: a closedkindand the field names that changed. Removals appear too.{ "data": [ { "version_id": 18231, "listing_id": "2f3c9d4e-3d2b-4b7d-9a3e-1b2c3d4e5f60", "slug": "storytime-in-the-park-2026-09-12", "type": "event", "metro": "philadelphia", "changed_at": "2026-09-10T06:12:05Z", "kind": "date_changed", "fields": [ "starts_at" ] } ], "meta": { "limit": 1, "count": 1, "has_more": false } }
Key fields
What keeps a calendar honest.
- count, free_count
- Occurrences starting that local date, exactly what the day list returns under the same filters.
- categories
- Per-day counts by category, for colored dots or a filter row without a second call.
- occurrence.index
- Position in the series.
idplusindexnames one session, so you can store and update it. - starts_local, timezone
- Local wall-clock time in the metro's zone, plus the UTC
starts_atfor your own math. - changed_since
- On
/events,/classes,/campsand/places: rows edited after an instant. Delivery is at least once, so upsert byid. - kind, fields
- On
/v1/changes:date_changed,price_changed,archivedand more, with the public field names that changed. - schedule.days, term
- On
/classes: the weekly days and the term dates. Filter withschedule_dayandopenings=true. - verified_at
- When the source last confirmed the listing. Good for a quiet "confirmed this week" line.
Guides and newsletters
Draft "this weekend near you" from confirmed listings.
Filter by neighborhood and a weekend window, then link each item back to its organizer with the include=source add-on.
Anything that could not be confirmed is left out, so you are not sending readers to a cancelled event.
GET /v1/philadelphia/events?from=2026-10-03&to=2026-10-04&include=sourceRequest shape
TinyJawns is a family guide for Philadelphia. Every listing on its calendar and in its weekly newsletter comes through the Tailend API.
Month view. Per-day counts for the whole month, with a free count behind the Free filter.
GET /v1/philadelphia/events/calendar?from=2026-10-01&to=2026-10-31
Week view. Every session in the week, with recurring series expanded.
GET /v1/philadelphia/events?from=2026-10-12&to=2026-10-18&occurrences=expand
Classes. Every class meeting that week, with its days, ages and cost.
GET /v1/philadelphia/classes?from=2026-10-12&to=2026-10-18FAQ
Questions builders ask.
Do the per-day counts match the list?
Yes. Calendar counts are exactly the occurrences /events?occurrences=expand returns for that date under the same filters.
How do I find out an event was removed?
Removals appear on /v1/changes even though the listing itself then returns 404. Drop it from your calendar when you see one.
Can a sync deliver a row twice?
Yes, changed_since is at least once. Upsert by id and you are safe.
How wide can a window be?
Up to 62 days on /events/calendar, and up to 366 days on /events. On /events, to defaults to from plus 30 days.
Can I show weather on the calendar?
Add include=forecast and days with a forecast carry one.
More use cases
Other things people build with it.
Build the calendar families keep open.
Try the requests above with sample data, then get a key from the quickstart.
