Files
keeporg/docs/PROTOCOL.md
T
root 0e83acb9b1 Add Android client, packaging, Makefile entrypoint and handover docs
Why: the set has to be installable and publishable, not just buildable, and
the next person needs the reasoning, not only the code.

What:
- android/: Kotlin client with no third-party dependencies. A
  DocumentsProvider publishes the mirrored databases to every file picker, so
  KeePassDX opens them as ordinary documents; closing a file it wrote
  triggers a sync. A foreground service holds the event stream open.
  Mirror.decide transcribes the Go merge table, so both platforms resolve a
  conflict identically.
- Makefile as the single entrypoint, with file targets so only what changed
  is rebuilt: host and cross builds, both .deb packages, the macOS DMG with
  its /Applications symlink, the Windows zip, the APK under its F-Droid
  publishing name, and manual-only android-publish.
- versions.mk holds one version per platform plus the Android versionCode;
  `make bump` and `make changelog` move them. Debian changelog is tracked.
- systemd units (hardened system unit for the server, user unit for the
  agent), macOS bundle and launch agent, Windows Task Scheduler job.
- docs/HANDOVER.md on architecture and every decision that shaped it,
  docs/PROTOCOL.md for the API, README for the quick start.

Proved: make check and make test-race both pass; built both .deb packages and
inspected their contents, built the Windows zip, cross-compiled all five
desktop targets, and exercised make bump. The Android module is written but
not compiled: no JDK or Android SDK in this environment, which HANDOVER.md
states plainly.
2026-10-29 16:26:33 +01:00

4.7 KiB
Raw Blame History

KeepOrg HTTP API

Every /api/ endpoint takes and returns JSON and needs Authorization: Bearer <token>, except the login, device and invite endpoints. Errors carry {"code": "...", "error": "..."}; code is stable and meant to be matched on, error is for humans.

Three endpoints serve HTML, because a browser is the right place for them: /device, /invite/{token} and /oidc/callback.

Authentication

Method Path Body Answer
POST /api/v1/auth/login {username, password, totp, label} {token, expires_at, user}; 401 totp_required when a second factor is enrolled and missing, 401 totp_invalid, 401 bad_credentials
POST /api/v1/auth/logout – revokes the calling token
GET /api/v1/auth/whoami – the account and its group memberships
POST /api/v1/auth/totp/enroll – {secret, uri}; not active until confirmed
POST /api/v1/auth/totp/confirm {code} {totp_enabled: true}
POST /api/v1/auth/device/start {label} {device_code, user_code, verification_url, interval, expires_in}
POST /api/v1/auth/device/poll {device_code} 202 {status: "pending"}, then 200 {token, ...} once; 410 expired

A device code is single use: the first successful poll consumes it.

Synchronization

Method Path Notes
GET /api/v1/databases {databases: [{id, name, perm, merge_strategy, version, sha256, size, updated_at, updated_by}]}
GET /api/v1/databases/{id}/blob bytes; ETag: "<version>", X-KeepOrg-SHA256; 204 when the database has no content yet; ?version=<n> reads a stored older version
PUT /api/v1/databases/{id}/blob body is the KDBX file. If-Match is required
GET /api/v1/events SSE. Accepts ?access_token= because an event stream cannot always set headers

PUT semantics

If-Match carries the version the edit was based on, or * to force.

Condition Status Body
If-Match missing 428 precondition_required –
version matches, bytes differ 200 {version, sha256, unchanged: false}
version matches, bytes identical 200 {version, sha256, unchanged: true}
version stale 409 conflict {current_version, merge_strategy}, plus ETag and X-KeepOrg-Strategy
If-Match: *, strategy is client-wins 200 the server copy is replaced
If-Match: *, any other strategy 409 force_not_allowed –
read-only access 403 forbidden –
no access 404 not_found a database you cannot read does not exist to you
larger than max_db_size_mb 413 too_large –

X-KeepOrg-SHA256 on a PUT is optional; when present it must match the body or the request is rejected with 400 checksum_mismatch.

Events

event: db-updated
data: {"type":"db-updated","database_id":"...","name":"Team","version":7,"sha256":"...","by":"alice"}

event: db-list-changed
data: {"type":"db-list-changed","database_id":"..."}

db-updated follows an accepted push; db-list-changed follows a grant, rename, strategy change or deletion. A comment line (: keepalive) arrives every 25 seconds. Reconnect on any error and reconcile fully; events are notifications, never state.

Administration

Permission needed is in the last column. "Group admin" means administering the group named or any group above it.

Method Path Body / query Needs
GET /api/v1/groups – any account; lists what you can see
POST /api/v1/groups {name, parent_id} group admin of the parent; server admin for a root group
DELETE /api/v1/groups/{id} – group admin; refuses while it has children, members or databases
POST /api/v1/groups/{id}/members {username or user_id, role} group admin
DELETE /api/v1/groups/{id}/members/{user} – group admin
POST /api/v1/databases {name, owner_group_id, merge_strategy} group admin of the owner
PATCH /api/v1/databases/{id} {name?, merge_strategy?} database admin
DELETE /api/v1/databases/{id} – database admin; deletes every stored version
POST /api/v1/grants {group_id, database_id, perm} database admin
DELETE /api/v1/grants ?group_id=&database_id= database admin
POST /api/v1/invites {group_id, role, ttl_hours, max_uses} group admin
GET /api/v1/invites – your own invites, and those of groups you administer
GET /api/v1/users – yourself, plus members of groups you administer
GET /healthz – unauthenticated liveness

merge_strategy is one of server-wins, client-wins, manual. perm is one of read, write, admin. role is member or admin.