Skip to content

API Reference

REST under /api, JSON in and out, validated with the zod schemas in shared/. Routes are assembled in core/src/app.ts; handlers live in core/src/routes/.

Authentication. Everything under /api except /api/health and /api/auth/* requires a valid session cookie (requireAuth). /api/admin/* additionally requires users.is_admin (requireAdmin). The /dav tree does not use sessions — it authenticates with HTTP Basic and an app password.

Scoping. Every handler scopes its queries by the authenticated users.id. There is no cross-user read path except the explicit sharing endpoints.

Errors. Unhandled failures return 500 {"error":"Internal server error"}; oversized bodies return 413. Validation failures return 400 with the zod issue.


Public

MethodPathNotes
GET/api/health{"status":"ok"}. No auth.
GET/api/auth/configWhat sign-in methods this instance offers, and whether registration is open.

Authentication — /api/auth

MethodPathNotes
POST/api/auth/registerOpen self-registration (unless the admin disabled it). The first account registered becomes the admin. Rate limited: 5/min per IP.
POST/api/auth/loginUsername + password. If TOTP is enabled, returns a pending-login handle instead of a session. Rate limited: 10/min per IP.
POST/api/auth/login/totpSecond step: pending-login handle + TOTP code or recovery code. Rate limited: 10/min per IP.
POST/api/auth/logoutClears the session and the cookie.
POST/api/auth/passkey/login/optionsWebAuthn assertion options.
POST/api/auth/passkey/login/verifyVerify the assertion, mint a session.

Account — /api/me

MethodPathNotes
GET/api/meThe current Identity — includes isAdmin.
PATCH/api/meUpdate display name, email, password, discovery visibility. Rate limited: 10/min.
DELETE/api/meDelete the account and everything it owns.
GET PATCH/api/me/settingsNotification toggle, default reminders, all-day reminder time, custom theme.
POST/api/me/avatarUpload an avatar (stored as a blob in users).
DELETE/api/me/avatarRemove it.
GET/api/avatars/:userIdServe another user's avatar (for shared-calendar UI).

Passkeys — /api/me/passkeys

MethodPath
GET/api/me/passkeys
POST/api/me/passkeys/register/options
POST/api/me/passkeys/register/verify
DELETE/api/me/passkeys/:id

TOTP — /api/me/totp

MethodPathNotes
GET/api/me/totpEnrollment state.
POST/api/me/totp/setupGenerate a secret + provisioning URI.
POST/api/me/totp/enableConfirm with a code; returns recovery codes once.
POST/api/me/totp/disable
POST/api/me/totp/recovery/regenerateInvalidates the previous set.

App passwords — /api/me/app-passwords

Revocable Basic-auth credentials for CalDAV clients (DAVx5 and friends).

MethodPathNotes
GET/api/me/app-passwordsMetadata only — the token is never returned again.
POST/api/me/app-passwordsReturns the plaintext token once.
DELETE/api/me/app-passwords/:id

Push — /api/me/push

MethodPathNotes
GET/api/me/push/keyThe VAPID public key, or null if the server has no keys configured.
POST/api/me/push/subscribeRegister this device's endpoint.
POST/api/me/push/unsubscribe

End-to-end encryption — /api/me/encryption

MethodPathNotes
GET/api/me/encryptionThe wrapped-key bundle, verbatim.
PUT/api/me/encryptionStore a new bundle. The server never sees the data key. See Security Model.

Calendars — /api/calendars

MethodPathNotes
GET/api/calendarsOwned calendars plus ones shared with you.
POST/api/calendars
PATCH/api/calendars/:idName, color, description, timezone, default reminders, sort order, CalDAV exposure.
DELETE/api/calendars/:id
PATCH/api/calendars/:id/visibilityShow/hide — for a shared calendar this sets hidden_by_grantee, not the owner's flag.
GET/api/calendars/:id/conversionPreview converting a calendar to/from encrypted.
POST/api/calendars/:id/convertPerform the conversion.
GET/api/calendars/:id/shares
POST/api/calendars/:id/sharesGrant a role to another user.
DELETE/api/calendars/:id/shares/:granteeId

Events — /api/events

Reading

MethodPathNotes
GET/api/eventsRange query (from/to). Recurring masters are expanded and overrides applied. This is the hot path the week view uses, and the one the service worker caches network-first.
GET/api/events/:id
GET/api/events/searchBacked by the event_search_tokens / override_search_tokens index tables.
GET/api/events/trashSoft-deleted events (deleted_at set).
GET/api/events/invitationsEvents you've been invited to.
GET/api/events/streamServer-sent events — live updates for open clients.

Writing

MethodPathNotes
POST/api/events
PATCH/api/events/:idOptimistic concurrency via the row's version.
DELETE/api/events/:idSoft delete → trash.
POST/api/events/:id/restoreUndelete from trash.
POST/api/events/syncBatch replay of the offline queue; returns per-item results and conflicts.

Recurrence

MethodPathNotes
POST/api/events/:id/overrideEdit or cancel a single occurrence.
POST/api/events/:id/split"This and all future" — ends the master and starts a new series.

Sharing and attendees

MethodPath
GET POST/api/events/:id/shares
DELETE/api/events/:id/shares/:granteeId
GET POST/api/events/:id/attendees
DELETE/api/events/:id/attendees/:userId
POST/api/events/:id/rsvp

Attachments

MethodPathNotes
POST/api/events/:id/attachmentsMultipart. Enforces the admin-configured per-file limit under the global body cap.
GET/api/events/:id/attachments/:attIdServed with nosniff and a Content-Disposition.
DELETE/api/events/:id/attachments/:attId

Tasks — /api/tasks

MethodPath
GET POST/api/tasks
PATCH DELETE/api/tasks/:id

Collaboration — /api

MethodPathNotes
GET/api/usersDirectory for invites. Users who set discovery_disabled are excluded.
GET/api/freebusyOpaque busy blocks for the given users and range — no titles, no details. Backs the "find a time" overlay.

Import / export — /api

MethodPathNotes
GET/api/export.icsStandard iCalendar. Encrypted calendars are excluded — the server only holds ciphertext.
POST/api/importImport an .ics file.
GET/api/export.hypercalendarNative JSON format. Preserves what iCalendar can't carry: per-event color, reminders, calendar grouping, per-occurrence override colors.
POST/api/import.hypercalendar

Subscriptions — /api/subscriptions

Read-only external ICS feeds. A subscription is a calendar row with subscription_url set, so listing, editing, and deleting go through /api/calendars.

MethodPathNotes
POST/api/subscriptionsAdd a feed. The URL is checked against the SSRF guard (core/src/lib/ssrf.ts) before fetching.
POST/api/subscriptions/:id/refreshForce a refresh now.

Weather — /api/weather

Hourly forecast and severe-weather warnings for the week-view lens, proxied rather than fetched from the browser: the app's CSP is connect-src 'self', so the page cannot reach a third-party host, and going through the server keeps the user's IP away from the weather source. The outbound call uses the same safeFetch guard as subscriptions, which the standalone Android build swaps for its native-HTTP shim.

MethodPathNotes
GET/api/weather?lat=&lon=&from=&to=from/to are unix ms, half-open, at most 11 days wide.

Responses are cached in memory for 30 minutes (forecast) and 5 minutes (warnings), keyed by provider, coordinates rounded to two decimals, and the window snapped to whole UTC days. A source failure is reported as 502 with a generic message; the upstream body is never echoed. An empty hours array means the source has no data for that window, which is what it returns past its forecast horizon, and is not an error.

Only Bright Sky (Deutscher Wetterdienst, Germany) is implemented. Set WEATHER_BRIGHTSKY_URL to point at a self-hosted instance; that is deliberately server configuration rather than a client setting, so an authenticated user cannot name an arbitrary URL for the server to fetch.

Two-way sync to a remote CalDAV collection (Nextcloud, Radicale, Fastmail, …). Requires CALDAV_SECRET to be set; without it, linking is refused rather than storing a password in the clear.

MethodPathNotes
POST/api/caldav/linksLink a calendar to a remote collection URL. Point it at a single calendar, not the server root — there is no principal discovery.
PATCH/api/caldav/links/:id
DELETE/api/caldav/links/:id
POST/api/caldav/links/:id/syncSync now.

The stored password is never returned by any response, export, or backup — there's a regression test for exactly that (caldav/links.test.ts).

Backups — /api/backups

Per-user .ics snapshots. Requires a host with filesystem access; on the Android build these report the feature as unavailable.

MethodPath
GET/api/backups
POST/api/backups
GET/api/backups/:date
POST/api/backups/:date/restore

Admin — /api/admin

Requires users.is_admin.

MethodPathNotes
GET/api/admin/settings
PATCH/api/admin/settingsNotably: close registration once the household's accounts exist.
GET/api/admin/db-backupsList database snapshots.
POST/api/admin/db-backupsTake one now (VACUUM INTO).
GET/api/admin/db-backups/:nameDownload one.

CalDAV server — /dav

Lets DAVx5, Thunderbird, Apple Calendar, etc. read and write hypercal directly. HTTP Basic auth with an app password — not the session cookie, and not your account password.

  • OPTIONS, PROPFIND, REPORT, GET, PUT, DELETE on the collection and its objects.
  • /.well-known/caldav301 to /dav/ so clients can discover the service from the bare domain.
  • Trailing slashes are optional (the router runs strict: false).

Only calendars with caldav_enabled are exposed. Encrypted calendars never are.