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
| Method | Path | Notes |
|---|---|---|
GET | /api/health | {"status":"ok"}. No auth. |
GET | /api/auth/config | What sign-in methods this instance offers, and whether registration is open. |
Authentication — /api/auth
| Method | Path | Notes |
|---|---|---|
POST | /api/auth/register | Open self-registration (unless the admin disabled it). The first account registered becomes the admin. Rate limited: 5/min per IP. |
POST | /api/auth/login | Username + password. If TOTP is enabled, returns a pending-login handle instead of a session. Rate limited: 10/min per IP. |
POST | /api/auth/login/totp | Second step: pending-login handle + TOTP code or recovery code. Rate limited: 10/min per IP. |
POST | /api/auth/logout | Clears the session and the cookie. |
POST | /api/auth/passkey/login/options | WebAuthn assertion options. |
POST | /api/auth/passkey/login/verify | Verify the assertion, mint a session. |
Account — /api/me
| Method | Path | Notes |
|---|---|---|
GET | /api/me | The current Identity — includes isAdmin. |
PATCH | /api/me | Update display name, email, password, discovery visibility. Rate limited: 10/min. |
DELETE | /api/me | Delete the account and everything it owns. |
GET PATCH | /api/me/settings | Notification toggle, default reminders, all-day reminder time. |
POST | /api/me/avatar | Upload an avatar (stored as a blob in users). |
DELETE | /api/me/avatar | Remove it. |
GET | /api/avatars/:userId | Serve another user's avatar (for shared-calendar UI). |
Passkeys — /api/me/passkeys
| Method | Path |
|---|---|
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
| Method | Path | Notes |
|---|---|---|
GET | /api/me/totp | Enrollment state. |
POST | /api/me/totp/setup | Generate a secret + provisioning URI. |
POST | /api/me/totp/enable | Confirm with a code; returns recovery codes once. |
POST | /api/me/totp/disable | |
POST | /api/me/totp/recovery/regenerate | Invalidates the previous set. |
App passwords — /api/me/app-passwords
Revocable Basic-auth credentials for CalDAV clients (DAVx5 and friends).
| Method | Path | Notes |
|---|---|---|
GET | /api/me/app-passwords | Metadata only — the token is never returned again. |
POST | /api/me/app-passwords | Returns the plaintext token once. |
DELETE | /api/me/app-passwords/:id |
Push — /api/me/push
| Method | Path | Notes |
|---|---|---|
GET | /api/me/push/key | The VAPID public key, or null if the server has no keys configured. |
POST | /api/me/push/subscribe | Register this device's endpoint. |
POST | /api/me/push/unsubscribe |
End-to-end encryption — /api/me/encryption
| Method | Path | Notes |
|---|---|---|
GET | /api/me/encryption | The wrapped-key bundle, verbatim. |
PUT | /api/me/encryption | Store a new bundle. The server never sees the data key. See Security Model. |
Calendars — /api/calendars
| Method | Path | Notes |
|---|---|---|
GET | /api/calendars | Owned calendars plus ones shared with you. |
POST | /api/calendars | |
PATCH | /api/calendars/:id | Name, color, description, timezone, default reminders, sort order, CalDAV exposure. |
DELETE | /api/calendars/:id | |
PATCH | /api/calendars/:id/visibility | Show/hide — for a shared calendar this sets hidden_by_grantee, not the owner's flag. |
GET | /api/calendars/:id/conversion | Preview converting a calendar to/from encrypted. |
POST | /api/calendars/:id/convert | Perform the conversion. |
GET | /api/calendars/:id/shares | |
POST | /api/calendars/:id/shares | Grant a role to another user. |
DELETE | /api/calendars/:id/shares/:granteeId |
Events — /api/events
Reading
| Method | Path | Notes |
|---|---|---|
GET | /api/events | Range 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/search | Backed by the event_search_tokens / override_search_tokens index tables. |
GET | /api/events/trash | Soft-deleted events (deleted_at set). |
GET | /api/events/invitations | Events you've been invited to. |
GET | /api/events/stream | Server-sent events — live updates for open clients. |
Writing
| Method | Path | Notes |
|---|---|---|
POST | /api/events | |
PATCH | /api/events/:id | Optimistic concurrency via the row's version. |
DELETE | /api/events/:id | Soft delete → trash. |
POST | /api/events/:id/restore | Undelete from trash. |
POST | /api/events/sync | Batch replay of the offline queue; returns per-item results and conflicts. |
Recurrence
| Method | Path | Notes |
|---|---|---|
POST | /api/events/:id/override | Edit 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
| Method | Path |
|---|---|
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
| Method | Path | Notes |
|---|---|---|
POST | /api/events/:id/attachments | Multipart. Enforces the admin-configured per-file limit under the global body cap. |
GET | /api/events/:id/attachments/:attId | Served with nosniff and a Content-Disposition. |
DELETE | /api/events/:id/attachments/:attId |
Tasks — /api/tasks
| Method | Path |
|---|---|
GET POST | /api/tasks |
PATCH DELETE | /api/tasks/:id |
Collaboration — /api
| Method | Path | Notes |
|---|---|---|
GET | /api/users | Directory for invites. Users who set discovery_disabled are excluded. |
GET | /api/freebusy | Opaque busy blocks for the given users and range — no titles, no details. Backs the "find a time" overlay. |
Import / export — /api
| Method | Path | Notes |
|---|---|---|
GET | /api/export.ics | Standard iCalendar. Encrypted calendars are excluded — the server only holds ciphertext. |
POST | /api/import | Import an .ics file. |
GET | /api/export.hypercalendar | Native 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.
| Method | Path | Notes |
|---|---|---|
POST | /api/subscriptions | Add a feed. The URL is checked against the SSRF guard (core/src/lib/ssrf.ts) before fetching. |
POST | /api/subscriptions/:id/refresh | Force a refresh now. |
CalDAV links — /api/caldav
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.
| Method | Path | Notes |
|---|---|---|
POST | /api/caldav/links | Link 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/sync | Sync 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.
| Method | Path |
|---|---|
GET | /api/backups |
POST | /api/backups |
GET | /api/backups/:date |
POST | /api/backups/:date/restore |
Admin — /api/admin
Requires users.is_admin.
| Method | Path | Notes |
|---|---|---|
GET | /api/admin/settings | |
PATCH | /api/admin/settings | Notably: close registration once the household's accounts exist. |
GET | /api/admin/db-backups | List database snapshots. |
POST | /api/admin/db-backups | Take one now (VACUUM INTO). |
GET | /api/admin/db-backups/:name | Download 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,DELETEon the collection and its objects./.well-known/caldav→301to/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.