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>
This commit is contained in:
+161
@@ -0,0 +1,161 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user