Only a registered user can perform this action
Authentication - string - required
Example of Authentication header : "Bearer TOKEN_FETCHED_FROM_SERVER_DURING_REGISTRATION"
{
"url": "https://calendar.example.com/team/games.ics"
}
Content-Type: text/calendar The RAW feed body is returned byte-for-byte exactly as served by the upstream host — nothing is parsed, selected, dropped or renamed. Every VEVENT (with all properties), VTIMEZONE and calendar headers are passed through untouched; the frontend parses SUMMARY/DTSTART/UID/all-day/ timezones. A valid feed with no events (empty VCALENDAR) is a success and is returned as-is. BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Gipper//Autocreate//EN BEGIN:VEVENT UID:event-001@gipper.test SUMMARY:Varsity Football vs Central DTSTART:20260910T190000Z DTEND:20260910T210000Z LOCATION:Home Stadium END:VEVENT END:VCALENDAR
A feed of up to 2 MB is returned in one response and cached for 15 minutes; `refresh` bypasses that cache and repopulates it. A larger feed is streamed (chunked, Cache-Control: no-transform, no Content-Length) and never cached. A transfer that breaks mid-stream is aborted: the response is left UNTERMINATED (no final zero-length chunk) rather than closed cleanly, so the client raises a network error instead of accepting a silently truncated calendar as a complete one. Treat a 200 whose body did not finish arriving as a failed request, not as a short calendar.
Concurrent requests for one feed are coalesced: exactly ONE goes to the
provider while the others are answered from the cache. A caller that has no
cached body to serve AND is not the one refreshing receives a 202 telling it
to poll again — the frontend retries after `retry_after` seconds and gets the
200 once the leader has warmed the cache. A stale cached body is ALWAYS
preferred over a 202 (data beats "please wait").
HTTP/1.1 202 Accepted
Content-Type: application/json
Retry-After: 2
Cache-Control: no-store
{
"status": "updating",
"message": "The calendar is updating",
"retry_after": 2,
"stage": "fetching"
}
`retry_after` is set by the server and duplicated in the body (a cross-domain
client cannot read the header without Access-Control-Expose-Headers): 2s
while the leader is fetching, 10s (`"stage": "streaming"`) once it has crossed
the 2 MB streaming boundary. This interim answer is behind ICAL_WAIT_202_ENABLED
(off by default); with it off a caller in this position simply fetches instead.
The upstream status is passed through when the provider ANSWERED but with a
non-200 (404 the feed is gone, 429 the provider is throttling us, 5xx the
provider is down). When nothing usable answered at all — SSRF-blocked host,
DNS failure, timeout, malformed URL — the status is 422. The body is always
ours; an upstream error page is never proxied back.
{
"message": "The iCal URL you entered is not valid"
}
While CALENDARS_BETA_ENABLED=true only users granted `calendars_enabled`
may call this endpoint; open to everyone once the variable is off.
{
"message": "This feature is not available for your account yet.",
"code": "feature_not_available"
}
| Param name | Description |
|---|---|
|
url
required |
iCal feed URL (webcal:// or https://) Validations:
|
|
refresh
optional |
Bypass the 15-minute cache and re-fetch from the provider (Refresh button) Validations:
|