Calendar Feed (iCal)
Summary
Publishes your completed time logs as a read-only iCalendar (.ics) feed that
any calendar app can subscribe to. Free on every plan — unlike API keys, it
is not a paid feature. Authenticated by a per-feed token in the URL, and
deliberately minimal about what it exposes.
What the feed is
Use case: your client asks "what did you actually do in June?" — and you'd rather they saw it in their calendar than read a spreadsheet.
Each completed time log becomes a calendar event. The feed is read-only: nothing a calendar app does can write back to Moonstreak.
Two limits worth knowing before you rely on it:
- It's a rolling 90-day window. Older entries drop off silently. The feed is for "what am I working on lately", not an archive — use Settings → Export for full history.
- A running timer is not in it. In-progress timers live in a separate table; only committed logs appear. Stop the timer and it shows up on the next refresh.
Creating a feed
Settings → Calendar feed → Create feed. You get a URL like:
https://your-domain.example/api/calendar/msf_9f3a2b1c8d7e6f504132a5b6c7d8e9f0/moonstreak.ics
You can create up to 5 feeds per workspace — enough to give different calendars different exposure (see Minimal mode).
The token in that URL is the credential. There is no separate login, so anyone with the link can read the feed. Treat it like a password: don't post it in a shared channel, and prefer a minimal-mode feed for anything you hand to someone else.
Unlike an API key, the token is shown again whenever you re-open Settings — you don't have to save it now. This matches how webhook signing secrets already work in this app. The trade-off is deliberate: a feed URL has to be re-pasted into every device that subscribes, so a show-once token would mean rotating and re-configuring every device just because you didn't write it down. The token is read-only, rate-limited, revocable, regenerable, and 160 bits of entropy.
Subscribing
The URL must be subscribed to, not downloaded — downloading gives you a frozen snapshot that never updates.
- Google Calendar — Other calendars → + → From URL → paste → Add calendar. Google refreshes on its own schedule, typically every 8–24 hours. That cadence is Google's, not something the feed controls.
- Apple Calendar (macOS) — File → New Calendar Subscription… → paste → set Auto-refresh to your liking.
- Apple Calendar (iOS) — Settings → Calendar → Accounts → Add Account → Other → Add Subscribed Calendar.
- Outlook — Add calendar → Subscribe from web → paste.
Paste the URL exactly as shown in Settings. Don't rebuild it by hand from the token.
What the feed exposes
Each event carries the event title and the log's start and end in UTC:
BEGIN:VEVENT
UID:9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d@moonstreak
DTSTAMP:20260714T100000Z
DTSTART:20260713T093000Z
DTEND:20260713T100000Z
SUMMARY:Client call + follow-up
END:VEVENT
SUMMARY is the task title. If a log has no task title, it falls back to the
log's own description, and only then to Tracked time. That fallback is
worth knowing: whatever you typed as a description on an untitled entry is what
lands in the calendar.
The feed never includes:
- your notes,
- the client's name,
- whether an entry is billable,
- any rate, amount, or currency.
A calendar feed is a URL that gets shared, forwarded, and synced onto devices you don't control, so it carries the minimum that still makes it useful.
Minimal mode
Toggle it per-feed and every event becomes duration-only:
SUMMARY:Worked 2h 15m
Use it when the calendar is shared with someone who should see that you worked but not what you worked on. Because the cap is 5 feeds, you can keep a detailed feed for yourself and a minimal one for a shared calendar.
Rotating and revoking
- Regenerate issues a new token and immediately breaks every subscribed device. Every calendar pointed at the old URL must be re-subscribed to the new one. After regenerating, "Last fetched" resets to Never — which is how you can tell whether a device has actually picked the new URL up yet.
- Delete revokes the feed outright. The old URL starts returning
404.
"Last fetched" is only refreshed about once an hour, so it can read up to an hour stale. Never is exact.
Access is re-checked on every fetch: if you leave the organization, or the organization is deleted, the feed stops working immediately even though the token is unchanged.
Troubleshooting
- Calendar shows nothing — you have no completed logs in the 90-day window. A running timer is not in the feed; only finished entries are. A brand-new feed shows a single placeholder event until you log time.
- Entries are stale, or new ones are missing — that's your calendar app's refresh interval, not the feed. Force a refresh in the app. Google in particular can lag several hours.
- Older entries vanished — expected. The window is the last 90 days.
404— the feed was deleted or regenerated (or you no longer have access to the workspace). Copy the current URL from Settings and re-subscribe.429— the feed is rate-limited. Calendar apps that poll aggressively can trip it; it clears on its own.
Related
api-cookbook.md— recipes for the REST API, if you need more than a calendar.public-api-v1.md—GET /api/v1/time-logsreads the same hours as data.