monoki docs Install Source

API reference

Everything the web UI does goes through this API, so anything the UI can do, you can script. Authentication is the session cookie — sign in with POST /api/login and keep the cookie:

curl -c jar -X POST http://localhost:8080/api/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"andre","password":"..."}'

curl -b jar http://localhost:8080/api/groups

Requests and responses are JSON. Unknown fields in a request body are an error rather than being ignored, so a misspelled field tells you immediately instead of silently doing nothing.

Errors are {"error": "..."} with a meaningful status.

Auth in the tables below

Setup and session

Method Path Auth Notes
GET /api/setup-status public {"needs_setup": bool}
POST /api/setup public Only while no account exists. {username, password}
POST /api/login public Rate-limited per address
POST /api/logout session
GET /api/session session The current user
POST /api/password session {current_password, new_password}; revokes other sessions

Groups

Method Path Auth Notes
GET /api/groups session {"owned": [...], "shared": [...]}
POST /api/groups session {name, color?}
GET /api/groups/{id} read
PATCH /api/groups/{id} owner {name?, color?}
DELETE /api/groups/{id} owner Requires {"confirm": true}
PUT /api/groups/order session {ids: [...]} — exactly your own groups
GET /api/groups/{id}/bookmarks read
PUT /api/groups/{id}/bookmarks/order write {ids: [...]} — exactly this group's members

Every group carries access: owner, editor or viewer. The client never infers permissions; the server states them.

Sharing

Method Path Auth Notes
GET /api/groups/{id}/access owner
POST /api/groups/{id}/access owner {username | user_id, role}
PATCH /api/groups/{id}/access/{userId} owner {role}
DELETE /api/groups/{id}/access/{userId} owner Idempotent
DELETE /api/groups/{id}/access owner Stop sharing with everyone

role is viewer or editor.

Bookmarks

Method Path Auth Notes
GET /api/bookmarks session Everything you can reach; ?group_id= to narrow
POST /api/bookmarks write {group_id, url, title?, plate?, icon_data_url?}
GET /api/bookmarks/{id} read
PATCH /api/bookmarks/{id} write {url?, title?, plate?, group_id?}; a new group_id moves it
DELETE /api/bookmarks/{id} write
POST /api/bookmarks/{id}/refresh write {title?: bool, force?: bool}
GET /api/bookmarks/{id}/icon read Image bytes, with ETag
PUT /api/bookmarks/{id}/icon write {data_url}
DELETE /api/bookmarks/{id}/icon write

Omitting title on create makes the server fetch the page itself, so the API is fully usable without the browser's prefill step.

icon_url is present only when the bookmark has an icon; its absence is the signal to draw a letter tile. It carries ?v=<content hash>, so the URL changes exactly when the bytes do and can be cached indefinitely.

plate is plain, light or dark.

Metadata

Method Path Auth Notes
POST /api/metadata session {url}; rate-limited per user

Returns {url, final_url, title, icon_url, icon_data_url, unreachable} and persists nothing. A failed lookup is still 200, with unreachable set to private_address_blocked, timeout or unreachable — the add form has to stay usable when a host is simply switched off.

Users

Method Path Auth Notes
GET /api/users admin
POST /api/users admin {username, password, role}
PATCH /api/users/{id} admin {role?, disabled?, password?}
DELETE /api/users/{id} admin Requires {"confirm": true}
GET /api/users/search?q= session Prefix match, min 2 chars, max 10 results

/api/users/search is not admin-only because a member who owns a group still has to pick someone to share it with. It excludes the caller and disabled accounts, and never lists everybody.

Activity

Method Path Auth Notes
GET /api/audit-log admin ?limit=, ?action=a,b

Health

Method Path Auth Notes
GET /healthz public {"status":"ok","version":"..."}

Example: add a bookmark from a script

GID=$(curl -s -b jar localhost:8080/api/groups | jq -r '.owned[0].id')

curl -s -b jar -X POST localhost:8080/api/bookmarks \
  -H 'Content-Type: application/json' \
  -d "{\"group_id\":\"$GID\",\"url\":\"https://grafana.lan:3000\"}"

The title and icon are filled in server-side.