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.
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.

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 hyper-calendar 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.