Files
librenotes/docs/api.md
T
libretechandClaude Opus 4.7 84a2116bc5 Add user guide, self-hosting, API, and contributing docs
docs/user-guide.md — magic-link sign-in, note basics, wikilink
syntax, keyboard shortcuts, offline behaviour, and privacy
notes (sessionStorage for tokens, tenant-scoped localStorage).

docs/self-hosting.md — system requirements, Docker Compose
quick-start, the full LIBRENOTES_* env-var matrix (which are
required, which conditional), reverse proxy snippets for Caddy
and nginx, volume layout, the in-binary healthcheck, and
update/rollback procedure.

docs/api.md — every public endpoint: auth (login/verify),
notes CRUD, /api/whoami, /healthz. Status codes per endpoint,
the optimistic-locking ?base=<unix> contract for PUT/DELETE,
note-ID regex, and the rate-limit policy.

CONTRIBUTING.md — dev setup (Nix flake .#dev, plain Go, Docker),
package layout overview, coding standards (one-way dep flow,
tenant FS gateway requirement), branch naming, commit format,
and the PR process. Also points security reports at
security@librete.ch rather than public issues.

Closes #29.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:52:09 +02:00

162 lines
4.1 KiB
Markdown

# API reference
This is the public HTTP API of librenotes. All endpoints return
JSON unless otherwise noted.
Base URL: whatever you configured as `LIBRENOTES_BASE_URL` (for
the cloud: `https://librenot.es`).
## Authentication
librenotes uses passwordless email magic links plus short-lived
JWTs. The flow is:
1. `POST /auth/login` with an email — server emails a one-time
token.
2. The recipient clicks the link, which is `GET /auth/verify?token=...`.
3. The verify endpoint returns a session JWT (24-hour lifetime).
4. Subsequent calls send `Authorization: Bearer <jwt>`.
### `POST /auth/login`
Request:
```http
POST /auth/login
Content-Type: application/json
{ "email": "you@example.com" }
```
Response:
| Status | Meaning |
| ------ | ------- |
| `202 Accepted` | Email accepted; magic link sent (or rate-limited silently). |
| `400 Bad Request` | Body is malformed. |
| `429 Too Many Requests` | Rate limit hit (5 per 15 min per email). |
The response body is `{"status":"sent"}` on 202. The endpoint
deliberately returns 202 even for unknown addresses to avoid
account enumeration.
### `GET /auth/verify?token=<token>` and `POST /auth/verify?token=<token>`
Consumes a magic-link token. Single-use. 15-minute expiry.
```json
{
"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
"user_id": "f7b2e2c3-4a51-4d7c-bdc0-...",
"email": "you@example.com",
"expires_at": 1777411035
}
```
| Status | Meaning |
| ------ | ------- |
| `200 OK` | Verified; JWT issued. |
| `400 Bad Request` | `token` parameter missing. |
| `401 Unauthorized` | Token unknown, expired, or already used. |
## Notes
All `/api/*` endpoints require `Authorization: Bearer <jwt>`. A
401 means the token is missing/invalid; the client should
redirect to the login page.
### `GET /api/whoami`
Returns the verified tenant identity.
```json
{ "user_id": "f7b2e2c3-...", "email": "you@example.com" }
```
### `GET /api/notes`
List all notes for the current tenant.
```json
[
{ "id": "ideas", "title": "Ideas", "updated_at": 1777400000 },
{ "id": "shopping", "title": "Shopping", "updated_at": 1777300000 }
]
```
### `GET /api/notes/{id}`
Read a single note.
```json
{
"id": "ideas",
"title": "Ideas",
"content": "A few directions...\n",
"updated_at": 1777400000
}
```
| Status | Meaning |
| ------ | ------- |
| `200 OK` | Found. |
| `400 Bad Request` | Invalid ID (must match `^[a-z0-9][a-z0-9_-]{0,127}$`). |
| `404 Not Found` | No such note for this tenant. |
### `PUT /api/notes/{id}`
Create or update a note. Supports optimistic locking via
`?base=<unix-seconds>`: the server checks the current `updated_at`
against `base` and returns 409 if the note has been modified
since the client last saw it.
Request:
```http
PUT /api/notes/ideas?base=1777400000
Content-Type: application/json
{ "title": "Ideas", "content": "A few directions...\n" }
```
| Status | Meaning |
| ------ | ------- |
| `200 OK` | Saved; body returns the new `updated_at`. |
| `400 Bad Request` | Bad ID, missing fields, or invalid `base`. |
| `409 Conflict` | Note has been modified after `base`; body returns the current note so the client can resolve. |
### `DELETE /api/notes/{id}`
Delete a note. Same `?base=` semantics as PUT.
| Status | Meaning |
| ------ | ------- |
| `204 No Content` | Deleted (or already gone). |
| `409 Conflict` | Modified after `base`. |
## Health
### `GET /healthz`
Unauthenticated. Returns `{"status":"ok"}` when the binary is
serving requests. Used by load balancers and the Docker
HEALTHCHECK.
## Errors
Beyond the per-endpoint codes above, all endpoints may return
`500` for unhandled internal errors. Bodies are plain text
(`internal server error`), not JSON, to avoid leaking internals.
## Rate limits
`POST /auth/login` is the only currently rate-limited endpoint:
5 requests per 15-minute window per email address. The window
resets on the oldest attempt expiring.
## Versioning
The current public API surface is unversioned. Breaking changes
will be signalled via a major version tag in the project as a
whole, with a deprecation window announced in `CHANGELOG.md`.