This is the same HTTP API Loggo’s own web UI calls — there’s no separate public API surface. It’s useful for scripting against a self-hosted instance (bulk importing markdown, automating log creation), but it’s internal and unversioned: no API keys, no stability guarantee across releases, and every route lives under the app’s own origin (http://localhost:3000 by default), mounted under /api.
Authentication
There’s no bearer token — identity comes from which user is active in the browser, tracked by the loggo_session cookie (httpOnly, session row in SQLite).
POST /api/auth/login sets that cookie: pass an email and password. Every other endpoint reads the user from the cookie already on the request — script against this API with a cookie jar, the same way a browser session would. See Auth for login, logout, /auth/me, and first-run /setup.
Three permission levels apply, checked server-side on every request that needs them:
| Level | Applies to |
|---|---|
| Signed in | /api/profile, /api/workspaces, /api/auth/me. Any authenticated user. |
| Workspace Member | Everything under /api/workspaces/:workspaceId/* — logs, tags, tasks, search, attachments, templates. Checked once, in middleware, before the route handler runs. |
| Admin only | Everything under /api/admin/* — creating/deleting workspaces, managing users, updating instance settings. |
Conventions
- All request and response bodies are JSON, except file uploads (
multipart/form-data, Attachments) and file downloads (raw bytes, the attachment/fileroute). - A failed request returns a JSON body shaped
{ "error": { "code": "...", "message": "..." } }with a matching status:VALIDATION→ 400,UNAUTHORIZED→ 401,FORBIDDEN→ 403,NOT_FOUND→ 404,CONFLICT→ 409. Anything unhandled isINTERNAL→ 500. - IDs are opaque strings — ULIDs, which sort by creation time.
- Endpoints that create a resource return the resource itself, not wrapped in an envelope —
POST /logsreturns the log directly, not{ "log": ... }. Endpoints that only perform an action return{ "ok": true }(two template endpoints return{ "success": true }instead — noted where it applies). - List endpoints paginate. Pass
day(orboard, for tasks) to fetch everything for that one day unpaginated (capped at 100 rows) — this is what the day board itself uses. Without it, results page using the instance’sdefaultPageSizesetting (10 by default): passpage(1-indexed), and readhasMore/limitoff the response to know whether to fetch the next page. - On the read-only Cloudflare demo, every non-
GETrequest under/api/profile,/api/workspaces/:workspaceId/*, and/api/admin/*is rejected with403 FORBIDDEN— enforced by one middleware, not hidden in the UI.
Endpoints by resource
| Resource | Page |
|---|---|
| Setup, Login, Logout, Me | Auth |
| Workspaces — the ones you belong to | Workspaces |
| Logs — add, read, update, position/size/z-index, duplicate, move | Logs |
| Tags, Tasks, Search, Attachments, Templates | Workspace Data |
| Users, Workspaces, Instance Settings (admin only) | Admin |
Internal
POST /api/internal/reseed-demo exists purely so the Cloudflare demo’s daily Cron Trigger can wipe and reseed itself with fresh dates (see Deploy to Cloudflare). It’s guarded by an x-reseed-secret header matched against the RESEED_SECRET server env var, and 404s outright on any install that hasn’t set one — which is every self-hosted install. Not part of the API surface described above; nothing outside that one Cron Trigger should ever call it.