Grove API Reference
<!-- grove:last-verified v1.98 -->
> Build identity is per-release โ check grove version or /api/version.
Base URL: http://<cell-ip>:5678
Authentication Model
Auth is per-route (P0-5 Phase 3). Each route declares its requirement with one of four decorators; the before_request gate check_dashboard_auth() reads ROUTE_AUTH[request.endpoint] and enforces it. The old URL-prefix matching no longer gates โ a route's own decorator is authoritative.
| Requirement |
Decorator |
Who gets in |
public |
@public_route |
Anyone. The gate opens; routes that need it (portal, site, invite) do their own in-route audience/session check |
owner |
@owner_required |
Owner dashboard session, or localhost CLI. Rejected over the public WAN edge |
owner_or_peer |
@owner_or_peer |
The authenticated /api/* surface: owner session or a valid peer secret. Owner half blocked on the public edge |
peer_private |
@peer_private_only |
Replication data plane. 403 on the public edge; off-edge the in-route verify_peer_request() does the real crypto auth |
Owner auth on localhost (#189): a per-cell owner_token (~/.grove/owner_token, mode 0600, never handed to peers) is now the preferred credential for localhost owner paths (CLI, self-update). check_dashboard_auth() and verify_owner_request() prefer it and still accept the fleet-shared peer_secret transitionally, recording each shared-secret use via _note_shared_secret_use() (the #189 retirement gauge).
Peer secret on /api/*: send X-Grove-Secret: <secret> (global or per-peer ECDH-derived) + X-Grove-Sender: <your_ed25519_pubkey>. See Notes for the newer X-Grove-Sig request signing, which authenticates relay-forwarded peers with no bearer secret.
Public-edge restriction (WAN-facing nodes, e.g. familynook): nginx sets X-Grove-Public-Edge: 1. Owner sessions, portal-admin sessions, and every peer_private route are rejected over the public edge โ owner dashboard + admin /api/* + replication stay tailnet/private-transport only. The public face serves public routes (portal, site, invite, relay, version/health) only.
Auth labels in this doc
The friendly Auth column below maps to the requirements above:
- Public โ
public, no auth (portal/site/invite still audience-gate in-route)
- Peer โ
peer_private replication routes, or the owner_or_peer peer half; X-Grove-Secret (global or per-peer ECDH) + X-Grove-Sender
- Dashboard โ
owner_or_peer owner half: owner session or localhost CLI on /api/*
- Owner โ
owner / verify_owner_request(): owner session and not over public edge
- Portal โ
/portal/login session (session['portal_user']); role = portal | admin | owner
> PUBLIC_PREFIXES (web.py) still exists but is no longer the steady-state gate โ it only lists the no-auth paths for the credentials-file-missing recovery branch. It also now includes /lib/alphatab/ (vendored Guitar Pro renderer).
Page Routes (HTML)
| Route |
Auth |
Notes |
GET / |
Public |
Landing / Home tab |
GET /dashboard |
Dashboard |
Main dashboard |
GET /files |
Dashboard |
Files tab |
GET /feed |
Dashboard |
Feed (files shared with you) |
GET /browse |
Dashboard |
Redirects to /?path= or /dashboard |
GET /grove-files |
Dashboard |
Power-user global file list with replication tiers |
GET /login POST /login |
Public |
Dashboard login |
POST /logout |
Dashboard |
End dashboard session |
GET /setup POST /setup |
Public (localhost recovery) |
First-boot setup or password recovery |
GET /splash |
Public |
Splash/loading page |
GET /docs GET /docs/ GET /docs/<page> |
Public |
Embedded docs |
GET /reset-password/<token> |
Public |
Password reset landing |
POST /reset-password/<token>/submit |
Public |
Submit new password |
GET /restart |
Public (redirects to POST flow) |
Legacy restart shim |
GET /invite/<token_id> |
Public |
Invite landing page or install script (curl UA) |
GET /site/<slug>/ GET /site/<slug>/<path> |
Audience-gated |
Site renderer (public/observers/specific) |
GET /site-preview/<path:rel> |
Owner |
Owner-only live preview of a GroveHome folder rendered as a site (its index.html, or the sole .html, plus relative sub-assets) without publishing a /site/<slug>/ URL |
Core Info APIs
| Route |
Method |
Auth |
Description |
/api/version |
GET |
Public |
Build hash, build time, started timestamp, file sizes |
/api/pubkey |
GET |
Public |
signing_pubkey (Ed25519) + encryption_pubkey (X25519) |
/api/health |
GET |
Public |
Disk, chunk count, peer count โ used by peer health checks |
/api/status |
GET |
Dashboard |
Cell status overview: chunks, peers, manifests, bounty |
/api/busy-status |
GET |
Public |
Idle/busy signal for watchdog (tasks, sync, last request) |
/api/breaker-status |
GET |
Dashboard |
Per-peer circuit breaker debug state |
/api/source |
GET |
Dashboard |
Serve source files for update/inspection |
/api/tasks |
GET |
Dashboard |
Active + recently-finished background jobs |
/api/update-status |
GET |
Dashboard |
Current build + pending update info |
/api/update-install |
POST |
Owner |
Apply staged update and restart |
/api/update-dismiss |
POST |
Owner |
Discard pending update |
/api/update-fleet-status |
GET |
Dashboard |
Build status across all peers |
File Operations
Dashboard file actions (session auth)
| Route |
Method |
Auth |
Description |
POST /upload |
POST |
Dashboard |
Upload files to GroveHome. Multipart files field |
GET /download |
GET |
Dashboard |
Download file. Query: ?path=<grove_home_path> or ?manifest=<name> |
POST /download/<manifest> |
POST |
Dashboard |
Legacy: download by manifest name to Downloads/ |
GET /view/<manifest> |
GET |
Dashboard |
Inline view (images, video, audio). Honors HTTP Range for media: 206/200/416 with Accept-Ranges/Content-Range, streaming only the overlapping 4MB chunks (instant seek) |
GET /preview |
GET |
Dashboard |
Inline preview from GroveHome. Query: ?path=<path> |
POST /add-to-grove |
POST |
Dashboard |
Ingest a file path as a Grove-tracked file |
POST /add-to-grove/<manifest> |
POST |
Dashboard |
Save shared file from manifest โ GroveHome โ ingest โ sync |
POST /add-local |
POST |
Dashboard |
Add a local file reference to Grove |
GET /static/sticker/<category>/<name> |
GET |
Public |
Serve pixel-art sticker assets |
File / folder management
| Route |
Method |
Auth |
Description |
POST /api/file/move |
POST |
Dashboard |
Move file within GroveHome; updates manifest source_path. JSON: {source, dest_dir} |
POST /api/file/rename |
POST |
Dashboard |
Rename a file in place; updates manifest. JSON: {path, new_name} |
POST /api/file/delete-untracked |
POST |
Dashboard |
Permanently delete an untracked file inside GroveHome. JSON: {path} |
GET /api/file/decrypt-preview |
GET |
Dashboard |
Decrypt a file for inline preview |
GET /api/file/decrypt-stream |
GET |
Dashboard |
Streaming decrypt for large files |
POST /api/folder/create |
POST |
Dashboard |
Create a folder under GroveHome. JSON: {path} |
POST /api/folder/rename |
POST |
Dashboard |
Rename a folder; updates manifests. JSON: {path, new_name} |
POST /api/folder/move |
POST |
Dashboard |
Move a folder within GroveHome. JSON: {source, dest_dir} |
POST /api/folder/delete |
POST |
Dashboard |
Delete a folder and its contents |
POST /api/folder/encrypt |
POST |
Dashboard |
Encrypt all files in a folder with a password (local-only vault). JSON: {path, password} |
POST /api/folder/unlock |
POST |
Dashboard |
Unlock an encrypted folder for this session. JSON: {path, password} |
POST /api/folder/decrypt |
POST |
Dashboard |
Permanently decrypt an encrypted folder back to plaintext. JSON: {path, password?} |
POST /api/folder/lock |
POST |
Dashboard |
Lock (evict in-memory key for) an encrypted folder |
POST /api/folder-role |
POST |
Owner |
Set/clear folder role. Form: {path, role, slug, audience, audience_list[]}. Valid role: photos, notes, site, audio, video, mixed, or "" (revert to auto-detect). Persisted in folder_roles.json |
GET /api/folder-list |
GET |
Dashboard |
List folders in GroveHome |
POST /api/folder-manifests |
POST |
Dashboard |
Get manifests under a folder |
GET /api/browse |
GET |
Dashboard |
Browse GroveHome directory tree |
POST /api/archive-preflight |
POST |
Dashboard |
Check if archiving a file is safe |
POST /archive-by-path |
POST |
Dashboard |
Remove local copy of a file (keep on peers). Form: {path} |
POST /archive/<manifest> |
POST |
Dashboard |
Archive by manifest name |
POST /remove-from-grove/<manifest> |
POST |
Dashboard |
Remove a file from Grove tracking |
Batch operations
| Route |
Method |
Auth |
Description |
POST /batch-add-to-grove |
POST |
Dashboard |
Add multiple shared files to Grove |
POST /batch-remove-local |
POST |
Dashboard |
Remove local copies of multiple files |
POST /batch-remove-grove |
POST |
Dashboard |
Remove multiple files from Grove tracking |
POST /batch-delete |
POST |
Dashboard |
Bulk delete files |
Recycle bin
| Route |
Method |
Auth |
Description |
GET /api/recycle-bin/list |
GET |
Dashboard |
List pending-delete files with expiry countdown |
POST /api/recycle-bin/restore/<manifest> |
POST |
Dashboard |
Restore a file from the 24h recycle bin |
POST /api/recycle-bin/force-delete/<manifest> |
POST |
Dashboard |
Skip 24h wait; hard-delete immediately |
POST /api/recycle-bin/empty |
POST |
Dashboard |
Bulk hard-delete everything in recycle bin |
Source-Missing review bin (#175)
Quarantine for tracked files whose plaintext source vanished โ reviewed, then restored or deleted (never silently dropped).
| Route |
Method |
Auth |
Description |
POST /api/source-missing/restore/<manifest> |
POST |
Owner |
Rebuild plaintext from chunks back to the original source_path, then un-quarantine. Runs in a background thread |
POST /api/source-missing/delete/<manifest> |
POST |
Owner |
Permanently hard-delete manifest + chunks (co-reference-checked) and broadcast a hard-delete tombstone |
POST /api/source-missing/delete-bulk |
POST |
Owner |
Folder-level bulk hard-delete from the grouped UI. JSON: {manifests: [...]}; cross-manifest keep-set built once for the batch |
Soft-delete / tombstone
| Route |
Method |
Auth |
Description |
POST /api/delete-everywhere/<manifest> |
POST |
Dashboard |
Send file to 24h recycle bin (soft delete) |
POST /api/delete |
POST |
Dashboard |
Delete a file |
POST /delete-permanently |
POST |
Dashboard |
Permanently delete a file |
POST /api/tombstone-action |
POST |
Peer |
Receive a signed TombstoneAction from the file's creator |
POST /api/unlink-local |
POST |
Dashboard |
Delete an untracked file inside GroveHome. JSON: {path} |
Sync / restore
| Route |
Method |
Auth |
Description |
POST /sync |
POST |
Dashboard |
Sync a specific file to peers. Form: {path} |
POST /sync-all |
POST |
Dashboard |
Sync all files to peers |
POST /restore/<manifest> |
POST |
Dashboard |
Restore a file from peers (pull chunks) |
POST /restore-all |
POST |
Dashboard |
Restore all missing files from peers |
POST /restore-version |
POST |
Dashboard |
Restore a specific version of a file |
POST /fetch-remote |
POST |
Dashboard |
Fetch a file from a specific remote peer |
GET /api/serve-chunk/<chunk_hash> |
GET |
Dashboard |
Serve a chunk locally (owner-side) |
GET /api/history |
GET |
Dashboard |
Get version history for a file. Query: ?path=<path> |
Watched folders / ingest
| Route |
Method |
Auth |
Description |
GET /api/watched-folders |
GET |
Dashboard |
List all watched folders with exists flag |
POST /watch |
POST |
Dashboard |
Start watching a folder. JSON or form: {path} |
POST /unwatch |
POST |
Owner |
Stop watching a folder. JSON or form: {path} |
POST /batch-watch |
POST |
Dashboard |
Watch multiple folders. JSON: {paths: [...]} |
POST /batch-unwatch |
POST |
Dashboard |
Unwatch multiple folders. Form: paths[] |
POST /api/watch-scan |
POST |
Owner |
Scan a folder and return stats before watching. JSON: {path} |
POST /api/watch-start |
POST |
Owner |
Add folder to watch list + optionally queue ingest. JSON: {path, ingest_existing} |
GET /api/ingest-status |
GET |
Dashboard |
List all active/recent ingest jobs |
POST /api/ingest-pause |
POST |
Dashboard |
Pause an ingest job. JSON: {job_id} |
POST /api/ingest-resume |
POST |
Dashboard |
Resume a paused ingest job. JSON: {job_id} |
POST /api/ingest-cancel |
POST |
Dashboard |
Cancel an ingest job. JSON: {job_id} |
POST /api/ingest-throttle |
POST |
Dashboard |
Set ingest delay. JSON: {job_id, delay_ms} |
GET /api/ingest-interrupted |
GET |
Dashboard |
List jobs interrupted by a restart |
Media renderer (audio + video)
Folders annotated (or auto-detected) as audio/video render as a streaming media library. Streaming uses the chunk-wise, Range-aware spine (_manifest_range_response) so seeking a large FLAC/movie decrypts only the overlapping 4MB chunks. Auto-detection: โฅ3 files of a kind AND โฅ50% of the folder (_detect_folder_role).
| Route |
Method |
Auth |
Description |
GET /api/media-library |
GET |
Dashboard |
Folder-derived media library tree (owner). Also renders Shared-with-me media folders as libraries (path under SHARED_WITH_ME_PATH, owner parity with the portal) and lists never-ingested on-disk plaintext media (served via /download?path=). Query: `?path=<folder>&kinds=audio\ |
video\ |
audio,video (default both). Stream URLs point at /view/<manifest>` |
GET /api/audio-library |
GET |
Dashboard |
Back-compat alias of /api/media-library |
GET /portal/media-library |
GET |
Portal |
Same shape, scoped to the account's visible files. Query: ?path=<subpath>&kinds=โฆ. Stream URLs point at /portal/stream/<manifest> |
GET /portal/audio-library |
GET |
Portal |
Back-compat alias of /portal/media-library |
GET /portal/stream/<manifest_file> |
GET |
Portal |
Range-aware, chunk-wise media stream of a portal-visible file (206/200/416); same access + key resolution as /portal/download |
Library tree JSON ({ok, count, library}): library is a nested tree of {kind: 'dir'\|'track', โฆ}. dir nodes carry name + children. track nodes carry: title, name, manifest, stream (URL), size, media ('audio'\|'video'), format, lossless (bool), web_playable (bool).
Sharing
| Route |
Method |
Auth |
Description |
POST /share |
POST |
Dashboard |
Share file(s) with peers. Form: {manifest, peer} |
POST /unshare |
POST |
Dashboard |
Revoke sharing. Form: {manifest, peer} |
POST /reshare |
POST |
Dashboard |
Re-share an updated version. Form: {manifest} |
GET /api/shared-folders |
GET |
Dashboard |
Get auto-share folder configuration |
POST /api/shared-folders |
POST |
Dashboard |
Set auto-share folder configuration |
POST /api/share-grant |
POST |
Peer |
Receive a share grant from a peer cell (portal user access). JSON accepts rel_path (GroveHome-relative, plaintext, per-recipient) stored on the grant so a portal account can rebuild the folder tree for ยง7-opaque manifests |
POST /api/folder-manifests |
POST |
Dashboard |
List manifests under a folder path |
POST /api/portal/show-in-portal |
POST |
Dashboard |
Toggle file(s) visible in owner's remote portal. JSON: `{manifest\ |
folder, action: on\ |
off\ |
toggle}` |
GET /api/portal/show-in-portal/list |
GET |
Dashboard |
List manifests currently shown in the owner's remote portal |
Peers / Sync (P2P)
These are called between cells during sync. Most require X-Grove-Secret + X-Grove-Sender.
Chunk transport
| Route |
Method |
Auth |
Description |
GET /api/chunk-inventory |
GET |
Public (Peer) |
All chunk hashes this cell holds. Returns {chunks, count, total_size} |
POST /api/receive-chunks |
POST |
Public (Peer) |
Push chunks here. Multipart form: each field is {hash}: (data, "application/octet-stream") |
POST /api/receive-chunks-b64 |
POST |
Peer |
Push chunks as base64 JSON (relay-transport alternative) |
GET /api/serve-chunk-peer/<chunk_hash> |
GET |
Public (Peer) |
Download a chunk by hash |
POST /api/delete-chunks |
POST |
Peer |
Delete chunks. JSON: {hashes: [...]} |
POST /api/purge-chunks |
POST |
Peer |
Purge chunks for a departing peer. JSON: {pubkey, chunks, action: "purge"} |
Manifest transport
| Route |
Method |
Auth |
Description |
GET /api/manifest-inventory |
GET |
Public (Peer) |
All manifest filenames. Returns {manifests: [...]} |
POST /api/receive-manifests |
POST |
Public (Peer) |
Push manifests here. JSON array of manifest objects |
GET /api/serve-manifest/<filename> |
GET |
Public (Peer) |
Download a specific manifest file |
POST /api/delete-manifests |
POST |
Peer |
Delete specific manifests by name (test cleanup). JSON: {names: [...]} |
POST /api/gc-manifests |
POST |
Public (Peer) |
Creator-scoped manifest GC โ drops manifests by creator_pubkey + optional prefix |
Grant transport
| Route |
Method |
Auth |
Description |
GET /api/grants-for/<pubkey_prefix> |
GET |
Public (Peer) |
Get grants created by a pubkey (for sync) |
POST /api/receive-grant |
POST |
Public (Peer) |
Push a share grant here |
POST /api/revoke-grant |
POST |
Public (Peer) |
Revoke a grant. Requires X-Grove-Sender == granted_by |
Network / routing
| Route |
Method |
Auth |
Description |
GET /api/reachability POST /api/reachability |
GET/POST |
Public (Peer) |
Exchange reachability lists |
GET /api/routing-table |
GET |
Dashboard |
Computed routing table |
GET /api/route-speeds |
GET |
Dashboard |
Routes to all peers with speeds and selected route |
POST /api/benchmark |
POST |
Dashboard |
Trigger manual route benchmark |
GET /api/opp-sync-status |
GET |
Dashboard |
Background opportunistic sync status |
GET /api/peer-speeds |
GET |
Dashboard |
Peer speed measurements and rankings |
GET /api/peer-health |
GET |
Dashboard |
Peer reliability + probe history |
GET /api/peer-detail/<user> |
GET |
Dashboard |
Detailed stats for one peer |
POST /api/exchange-secret |
POST |
Public |
Per-peer ECDH secret handshake (v0.4.1+) |
POST /api/peer-leaving |
POST |
Peer |
Fast hint that a peer is going away (signed tombstone does the actual purge) |
GET /api/replication-status |
GET |
Dashboard |
Under-replicated file count + fix panel HTML |
GET /api/replication-summary |
GET |
Dashboard |
Owner-scoped or global replication factor summary |
GET /api/replication-health |
GET |
Dashboard |
Per-chunk factor breakdown |
GET /api/network-health |
GET |
Dashboard |
Fleet-wide health overview |
GET /api/durability-facts |
GET |
Peer |
Durability ground-truth for the honest fleet gate (#172): owned current-version chunks, which are plaintext-backed, what's held on disk, desired factor, dev_cell/role โ lets a PROD-only, device-independent gate verify every owned chunk has โฅ its desired copies |
Relay
| Route |
Method |
Auth |
Description |
GET /api/relay/status |
GET |
Public |
Relay connection status + host stats |
POST /api/relay/allowed-peers |
POST |
Dashboard |
Add/remove extra relay allowed peers. JSON: `{action: add\ |
remove, pubkey}` |
GET /api/relay/preferences POST /api/relay/preferences |
GET/POST |
Dashboard |
Read/write relay picker preferences. POST body: {preferred: [...], avoid: [...]} |
GET /api/relay/contribution |
GET |
Dashboard |
This cell's relay contribution stats (messages/bytes forwarded) |
UDP punch
| Route |
Method |
Auth |
Description |
GET /api/udp/status |
GET |
Dashboard |
UDP hole punch status (listening, port, external addr, connections) |
POST /api/udp/punch |
POST |
Dashboard |
Initiate a hole punch to a peer. JSON: {pubkey} |
Peer Management
| Route |
Method |
Auth |
Description |
POST /peers/add |
POST |
Dashboard |
Add a peer. Form: {host, port, user} |
POST /peers/remove |
POST |
Dashboard |
Remove a peer. Form: `{host, mode: list-only\ |
and-files}` |
POST /peers/favorite |
POST |
Dashboard |
Toggle favorite status. Form: {user} |
POST /peers/accept |
POST |
Dashboard |
Accept a pending peer connection |
POST /peers/deny |
POST |
Dashboard |
Deny a pending peer connection |
POST /friends/add |
POST |
Dashboard |
Promote peer to friend. Form: {host} |
POST /friends/remove |
POST |
Dashboard |
Demote friend to peer. Form: {host} |
POST /friends/accept |
POST |
Dashboard |
Accept a friend request |
POST /friends/deny |
POST |
Dashboard |
Deny a friend request |
POST /api/peer-request |
POST |
Public (Peer) |
Initiate peer pairing request |
POST /api/peer-accept |
POST |
Dashboard |
Accept a peer pairing request |
POST /api/peer-remove |
POST |
Peer |
Peer-authenticated peer removal |
POST /api/friend-request |
POST |
Public (Peer) |
Send a friend request |
GET /api/friend-check POST /api/friend-check |
GET/POST |
Public |
Check friend status between two pubkeys |
GET /api/peer-check POST /api/peer-check |
GET/POST |
Public |
Peer-layer mirror of /api/friend-check: does this cell hold the caller as a peer? Used by _reconcile_mutual_peers to detect + heal half-open peering (#94) |
POST /api/friend-accept |
POST |
Public (Peer) |
Accept a friend request |
POST /api/friend-remove |
POST |
Peer |
Remove a friend |
GET /api/peer-storage-caps POST /api/peer-storage-caps |
GET/POST |
Dashboard |
Get or set storage capacity caps per peer |
POST /api/peers/set |
POST |
Peer |
Replace peer list (fleet config management) |
POST /api/peer/rename |
POST |
Dashboard |
Rename a peer. JSON: {pubkey, name} |
POST /set-peer-cap |
POST |
Dashboard |
Set storage cap for a peer |
POST /set-primary |
POST |
Dashboard |
Set primary host for a peer |
GET /api/integrity |
GET |
Dashboard |
Last integrity sweep results |
POST /api/integrity/sweep |
POST |
Dashboard |
Trigger immediate integrity check. JSON: {sample_size?} |
Peer discovery / recommendations
| Route |
Method |
Auth |
Description |
GET /api/discoverable-peers |
GET |
Peer |
Return this cell's peers for network discovery |
GET /api/discovery |
GET |
Dashboard |
Get cached network discovery results |
POST /api/discovery/scan |
POST |
Dashboard |
Trigger network discovery scan. JSON: {max_hops?} |
POST /api/discovery/add |
POST |
Dashboard |
Add a discovered peer. JSON: {pubkey} |
POST /api/recommend |
POST |
Peer |
Receive a peer recommendation from a trusted peer |
GET /api/recommendations |
GET |
Dashboard |
List pending peer recommendations |
POST /api/recommendations/accept |
POST |
Dashboard |
Accept a peer recommendation. JSON: {pubkey} |
POST /api/recommendations/deny |
POST |
Dashboard |
Deny a peer recommendation. JSON: {pubkey} |
Invite System
| Route |
Method |
Auth |
Description |
POST /api/invite/create |
POST |
Dashboard |
Create an invite. JSON: {message, name, bounty} โ {ok, token_id, url} |
GET /api/invite/list |
GET |
Dashboard |
List all invites with status |
POST /api/invite/revoke |
POST |
Dashboard |
Revoke a token. JSON: {token_id} |
POST /api/invite/receive |
POST |
Dashboard |
Receive an incoming invite from a peer |
GET /invite/<token_id> |
GET |
Public |
Invite landing page (browser) or install script (curl/wget) |
POST /api/invite/<token_id>/accept |
POST |
Public |
Accept invite (install script). JSON: {pubkey, self_name, join_proof}. join_proof (Ed25519 over grove-invite-join\n<token_id>\n<pubkey>) is hard-required โ absent or invalid โ 403, checked before the token is consumed. Optional signed bilateral onboarding: {proposed_secret, secret_sig, secret_nonce, secret_ts} routes to owner approval and hands back no global secret |
POST /invite/<token_id>/download |
POST |
Public |
Download install script with name baked in |
POST /invite/<token_id>/portal-signup |
POST |
Public |
Sign up for a portal account via invite |
POST /api/invite/claim-joiner |
POST |
Public |
#94 gateway broker: a gateway that served our invite calls this to tell us who joined, so the inviter bonds the joiner to itself, not the gateway. Signed + known-gateway-gated + joiner key-proof; issues a fresh per-pair secret |
GET /api/grove-binary/<filename> |
GET |
Public |
Serve Grove source files for installation |
GET /install.sh |
GET |
Public |
Serve install script |
GET /releases/latest/<filename> |
GET |
Public |
Serve release files (grove.py, web.py, etc.) |
AI (GroveAI)
/api/ai/status is Public (peer-discovery shape); all other AI endpoints require Dashboard auth unless noted.
Status and configuration
| Route |
Method |
Auth |
Description |
GET /api/ai/status |
GET |
Public |
AI capability, hardware, model info, e2e, timeout_s, has_local_model |
GET /api/ai/tiers |
GET |
Dashboard |
Available model tiers (Low/Mid/High) with host, params, timeout |
GET /api/ai/network |
GET |
Dashboard |
AI status across the fleet (deduplicated by peer name) |
POST /api/ai/setup |
POST |
Dashboard |
Multi-action: detect / models / download / download_status / switch / delete / enable / disable / set_peer_policy / build_runtime / build_status / set_default_tier / set_hf_token / hf_search / hf_files / download_hf / benchmark / benchmark_status / benchmark_results |
GET /api/ai/hf-token-status |
GET |
Dashboard |
Whether a HuggingFace token is configured |
POST /api/ai/use-model |
POST |
Dashboard |
BYOG: copy a .gguf from any path into the models folder and activate it. JSON: {path, filename} |
GET /api/ai/reindex |
GET |
Dashboard |
Rebuild the RAG index from local .md files and grove.py docstrings |
Chat
| Route |
Method |
Auth |
Description |
POST /api/ai/chat |
POST |
Dashboard |
Send a message; routes to best available LLM. JSON: {text, tier?, history?} |
POST /api/ai/chat/result |
POST |
Dashboard |
Poll for async chat result. JSON: {request_id} |
GET /api/ai/chat/history |
GET |
Dashboard |
Retrieve chat history |
POST /api/ai/chat/clear |
POST |
Dashboard |
Clear chat history |
POST /api/ai/proxy |
POST |
Peer |
Proxy a chat request to this cell's AI (E2E encrypted payload). JSON: {payload, sender_pubkey} |
Image generation (Mac only โ requires stable-diffusion-webui)
| Route |
Method |
Auth |
Description |
GET /api/ai/image/status |
GET |
Dashboard |
Image gen availability, model, backend status |
POST /api/ai/image/generate |
POST |
Owner |
Enqueue image-gen job. JSON body (text-to-image) or multipart with init_image + payload JSON (img2img). Returns {job_id} immediately |
GET /api/ai/image/jobs |
GET |
Dashboard |
List all image-gen jobs (terminal jobs expire after 5 min) |
GET /api/ai/image/job/<job_id> |
GET |
Dashboard |
Status of a single job |
POST /api/ai/image/cancel-queue |
POST |
Dashboard |
Cancel queued (not yet running) image-gen jobs |
Home Tab
| Route |
Method |
Auth |
Description |
GET /api/home/insights |
GET |
Dashboard |
Grove age, file-type composition, activity sparkline (cached 30s) |
GET /api/home/replication |
GET |
Dashboard |
Replication card data for Home tab |
GET /api/home/peers |
GET |
Dashboard |
Authoritative peer list with health, live chunk counts, routing |
GET /api/home/peer/<pubkey> |
GET |
Dashboard |
Detail for a single peer (Home peer-card modal) |
Storage / Drives
| Route |
Method |
Auth |
Description |
GET /api/drives |
GET |
Dashboard |
All configured drives: mode, online, chunks, free/total bytes |
GET /api/drives/available |
GET |
Dashboard |
Mounted filesystems suitable for a new drive target |
GET /api/drives/block-devices |
GET |
Owner |
List UNMOUNTED removable partitions for the Mount & Add picker (via grove-mountd root helper) โ a freshly-plugged USB that /api/drives/available can't see |
POST /api/drives/mount-add |
POST |
Owner |
Mount a discovered USB at GroveHome/<label> transiently via grove-mountd โ no fstab write yet โ then hand back the path for the unprivileged /api/drives/add. JSON: {uuid, label?}. Returns "persisted": false |
POST /api/drives/persist-mount |
POST |
Owner |
Write the permanent fstab line (re-mount at boot) after the owner confirms the drive in the Add Drive modal; Cancel ejects the transient mount instead, so a declined add leaves no fstab orphan. JSON: {uuid, mountpoint, fstype} |
POST /api/drives/eject |
POST |
Owner |
Safely unmount + de-persist a Grove-mounted USB (grove-mountd). JSON: {mountpoint, uuid?} |
POST /api/drives/scan |
POST |
Dashboard |
Scan a drive path before adding it |
POST /api/drives/add |
POST |
Dashboard |
Add a storage drive (mirror, ingest, backup). JSON: {path, mode, target_folder?} |
POST /api/drives/start-ingest |
POST |
Dashboard |
Start ingest for a drive. JSON: {path} |
POST /api/drives/stop-ingest |
POST |
Dashboard |
Stop ingest for a drive. JSON: {path} |
POST /api/drives/remove |
POST |
Dashboard |
Remove a configured drive. JSON: {path} |
GET /api/grove-home-status |
GET |
Dashboard |
GroveHome disk usage and status |
GET /api/placement |
GET |
Dashboard |
Replication health summary |
GET /api/placement/context |
GET |
Dashboard |
Owner placement summary (desired_factor, min, total_chunks) |
POST /api/set-redundancy |
POST |
Owner |
Set desired/minimum replication factors. Form: {desired, min} |
GET /api/owner-policy POST /api/owner-policy |
GET/POST |
Dashboard |
Get or set owner replication policy. POST JSON: {desired_factor, min_factor} |
GET /api/bounty |
GET |
Dashboard |
Full bounty status including peer history |
POST /api/bounty/accrue |
POST |
Dashboard |
Trigger manual bounty accrual |
Maintenance
| Route |
Method |
Auth |
Description |
POST /api/gc |
POST |
Dashboard |
Garbage collect orphaned chunks. Form: {force: true/false} |
POST /api/tidy |
POST |
Dashboard |
Full tidy chain (peers dedup, doctor, gc, vacuum). Form: {force, skip} |
GET /api/prune-preview |
GET |
Dashboard |
Preview what pruning would remove |
POST /api/prune-excess |
POST |
Dashboard |
Remove over-replicated chunks |
POST /api/delete-manifests |
POST |
Peer |
Delete specific manifests by name |
POST /api/purge-chunks |
POST |
Peer |
Purge chunks for a departing peer |
GET /api/integrity |
GET |
Dashboard |
Last integrity sweep results |
POST /api/integrity/sweep |
POST |
Dashboard |
Trigger immediate integrity check. JSON: {sample_size?} |
Settings
| Route |
Method |
Auth |
Description |
GET /api/settings |
GET |
Dashboard |
Current cell settings |
POST /api/settings |
POST |
Dashboard |
Update settings. JSON with fields to change |
POST /api/settings/password |
POST |
Dashboard |
Change dashboard password. JSON: {current, new} |
POST /api/settings/reset-token |
POST |
Dashboard |
Generate single-use 24h password reset link |
POST /api/sync-window |
POST |
Owner |
Set/clear the ยง11 sync window โ restrict heavy background sync (replication / integrity sweep / durability) to a daily window in the cell's local time. Serving + presence stay live. JSON or form: {enabled, start:'HH:MM', end:'HH:MM'} |
GET /api/backup-keys |
GET |
Dashboard |
Download encryption keys as ZIP |
POST /api/restore-keys |
POST |
Dashboard |
Restore keys from backup ZIP |
POST /api/uninstall |
POST |
Dashboard |
Web-initiated software-only uninstall. JSON: {"confirm": "LEAVE"} |
POST /api/restart |
POST |
Owner |
Trigger graceful restart (watchdog respawns) |
POST /api/dismiss-tutorial |
POST |
Dashboard |
Dismiss first-boot tutorial |
POST /api/peers/set |
POST |
Peer |
Replace peer list (fleet config management) |
Gateway / TLS
| Route |
Method |
Auth |
Description |
GET /api/gateway/status |
GET |
Dashboard |
Domain, mode, cert status |
POST /api/gateway/dns-check |
POST |
Dashboard |
Validate DNS for domain. JSON: {domain} |
POST /api/gateway/provision |
POST |
Dashboard |
Provision Let's Encrypt cert. JSON: {domain, email, staging?} |
POST /api/gateway/set-mode |
POST |
Dashboard |
Set gateway mode: auto or external. JSON: {mode} |
Growth / Creature
| Route |
Method |
Auth |
Description |
GET /api/growth |
GET |
Dashboard |
Score, milestones, recent events, 30-day history |
GET /api/challenges/suggested |
GET |
Dashboard |
Top N attainable unearned milestones. Query: ?limit=5 |
POST /api/growth/snapshot |
POST |
Dashboard |
Take a growth stats snapshot (called periodically) |
POST /api/growth/milestone/<key>/notified |
POST |
Dashboard |
Mark a milestone as user-notified |
GET /api/creature |
GET |
Dashboard |
Creature state |
Chat
| Route |
Method |
Auth |
Description |
GET /api/chat |
GET |
Dashboard |
Chat messages with a peer. Query: `?peer=<host\ |
portal:cell:user>` |
GET /api/chat/conversations |
GET |
Dashboard |
All conversations with last-message preview |
POST /api/chat/delete |
POST |
Dashboard |
Delete a chat message |
POST /api/chat/delete-conversation |
POST |
Dashboard |
Delete an entire conversation |
POST /api/chat-message |
POST |
Dashboard |
Send an encrypted chat message. JSON: {peer, message} |
POST /api/message |
POST |
Peer |
Receive an inter-cell message |
POST /send-message |
POST |
Dashboard |
Send a message to a peer |
POST /mark-read |
POST |
Dashboard |
Mark all messages as read |
POST /clear-notifications |
POST |
Dashboard |
Clear all system notifications |
POST /clear-notification/<idx> |
POST |
Dashboard |
Clear a single notification by index |
GET /api/portal-chat-history |
GET |
Peer |
Cell owner โ portal user chat history |
POST /api/portal-chat-relay |
POST |
Dashboard |
Relay a chat message to a portal user |
Reactions and Comments
| Route |
Method |
Auth |
Description |
GET /api/reactions/<manifest_file> |
GET |
Dashboard |
Get emoji reactions for a file |
POST /api/react/<manifest_file> |
POST |
Dashboard |
Toggle a reaction. JSON: {emoji} |
GET /api/comments/<manifest_file> |
GET |
Dashboard |
Get comments for a file |
POST /api/comments/<manifest_file> |
POST |
Dashboard |
Add a comment. JSON: {content} |
DELETE /api/comments/<manifest_file>/<id> |
DELETE |
Dashboard |
Delete own comment |
Feed
| Route |
Method |
Auth |
Description |
GET /feed |
GET |
Dashboard |
Feed page (files shared with you) |
POST /api/feed/dismiss |
POST |
Dashboard |
Dismiss a feed item |
Portal
Portal page routes require a portal session (/portal/login). API routes require dashboard or portal-admin session.
Portal pages
| Route |
Method |
Auth |
Description |
GET /portal |
GET |
Public |
Portal landing / login redirect |
GET /portal/login POST /portal/login |
GET/POST |
Public |
Portal login |
GET /portal/logout |
GET |
Portal |
Portal logout |
GET /portal/files GET /portal/files/<subpath> |
GET |
Portal |
Browse file tree |
GET /portal/feed |
GET |
Portal |
Portal feed |
POST /portal/folder-role |
POST |
Portal (admin) |
Set folder role |
POST /portal/upload |
POST |
Portal |
Upload file. Multipart: file, optional folder |
GET /portal/download/<manifest> |
GET |
Portal |
Download a file (decrypts and serves) |
GET /portal/thumbnail/<manifest> |
GET |
Portal |
File thumbnail |
GET /portal/preview/<manifest> |
GET |
Portal |
Inline file preview |
POST /portal/rename/<manifest> |
POST |
Portal |
Rename file. JSON: {new_name} |
POST /portal/move/<manifest> |
POST |
Portal |
Move file. JSON: {folder} |
GET /portal/users-list |
GET |
Portal (admin) |
List portal users (HTML) |
POST /portal/share/<manifest> |
POST |
Portal |
Share a file with another portal user |
POST /portal/unshare/<manifest> |
POST |
Portal |
Unshare |
GET /portal/shared-with/<manifest> |
GET |
Portal |
Who a file is shared with |
GET /portal/comments/<manifest> |
GET |
Portal |
Comments on a file |
POST /portal/comment/<manifest> |
POST |
Portal |
Add a comment |
DELETE /portal/comment/<id> |
DELETE |
Portal |
Delete a comment |
POST /portal/react/<manifest> |
POST |
Portal |
Toggle a reaction |
GET /portal/chat |
GET |
Portal |
Portal chat UI |
GET /portal/chat/conversations |
GET |
Portal |
Chat conversations list |
POST /portal/chat/delete-room |
POST |
Portal |
Delete a chat room |
POST /portal/chat/create |
POST |
Portal |
Create a chat room |
GET /portal/chat/<room_id>/messages |
GET |
Portal |
Messages in a room |
POST /portal/chat/<room_id>/send |
POST |
Portal |
Send a message |
POST /portal/chat/<room_id>/members |
POST |
Portal |
Manage room members |
GET /portal/chat/cell |
GET |
Portal |
Cell owner โ portal user direct chat |
POST /portal/chat/cell/send |
POST |
Portal |
Send message to cell owner |
GET /portal/ai |
GET |
Portal |
Portal AI tab |
POST /portal/ai/ask |
POST |
Portal |
Ask the AI (routed to cell's AI stack) |
GET /portal/settings POST /portal/settings |
GET/POST |
Portal |
Portal user settings |
GET /portal/request-access |
GET |
Public |
Landing for unauthenticated visit-peer links |
Portal user management (admin API)
| Route |
Method |
Auth |
Description |
GET /api/portal/users |
GET |
Dashboard |
List all portal users |
POST /api/portal/users |
POST |
Dashboard |
Create portal user. JSON: `{username, password, role: portal\ |
admin\ |
owner, storage_cap_gb?, home_cell?, owner?} โ {ok, username, pubkey, role}` |
DELETE /api/portal/users/<username> |
DELETE |
Dashboard |
Delete a portal user |
PATCH /api/portal/users/<username> |
PATCH |
Dashboard |
Update user (password, role, storage_cap_gb) |
GET /api/portal/owner-account POST /api/portal/owner-account |
GET/POST |
Dashboard |
Get/set which portal account is the cell owner's own account |
GET /api/portal/friends |
GET |
Portal |
List friends and invite status |
POST /api/portal/friend-invite |
POST |
Portal |
Send a friend invite |
POST /api/portal/friend-invite/respond |
POST |
Portal |
Accept/reject a friend invite |
POST /api/portal/friend-invite/revoke |
POST |
Portal |
Revoke a sent invite |
POST /api/portal/friend/remove |
POST |
Portal |
Remove a friend |
POST /api/portal/suggestions |
POST |
Portal |
Get portal suggestions |
POST /api/portal/suggestions/dismiss |
POST |
Portal |
Dismiss a suggestion |
GET /api/portal/folder-list |
GET |
Portal |
List virtual folders for current portal user |
POST /api/portal/folder/create |
POST |
Portal |
Create a virtual folder. JSON: {name} |
POST /api/portal/feed/dismiss |
POST |
Portal |
Dismiss a portal feed item |
Portal proxy (cell โ gateway user management)
| Route |
Method |
Auth |
Description |
POST /api/portal/proxy/create-user |
POST |
Dashboard |
Proxy user creation to the portal host cell |
GET /api/portal/proxy/list-users |
GET |
Dashboard |
Proxy: list users on the portal host cell |
DELETE /api/portal/proxy/delete-user/<username> |
DELETE |
Dashboard |
Proxy: delete a user on the portal host cell |
Portal availability and hosting (home cell side)
| Route |
Method |
Auth |
Description |
GET /api/portal/availability |
GET |
Public |
Whether portal-account signup is available (invite UI) |
POST /api/portal/host/enable |
POST |
Dashboard |
Enable/disable portal hosting. JSON: {enabled} |
GET /api/portal/host/slots |
GET |
Dashboard |
List approved + pending portal hosting slots |
GET /api/portal/eligible-hosts |
GET |
Dashboard |
List peers able to host portals |
POST /api/portal/host/request |
POST |
Peer |
Receive a portal hosting request from another cell |
POST /api/portal/host/approve |
POST |
Dashboard |
Approve a pending portal hosting request |
POST /api/portal/host/reject |
POST |
Dashboard |
Reject a portal hosting request |
POST /api/portal/host/receive-keys |
POST |
Peer |
Receive encrypted identity keys from a home cell |
POST /api/portal/host/remove |
POST |
Dashboard |
Remove a portal hosting slot |
Portal relay (home cell โ gateway)
| Route |
Method |
Auth |
Description |
GET /api/portal/relay/status |
GET |
Dashboard |
Portal relay status (home cell side) |
POST /api/portal/relay/request |
POST |
Dashboard |
Send a portal hosting request to a gateway peer. JSON: {host_pubkey} |
POST /api/portal/relay/push-keys |
POST |
Dashboard |
Push encrypted identity keys to the portal host |
GET /api/portal/relay/check |
GET |
Dashboard |
Check if portal request has been approved |
Portal remote access (owner cell โ gateway)
These provision the cell owner's own portal account on a WAN gateway (Option B model: no key import).
| Route |
Method |
Auth |
Description |
GET /api/portal/remote/status |
GET |
Dashboard |
Remote portal account config status |
POST /api/portal/remote/setup |
POST |
Dashboard |
Provision owner account on a gateway peer. JSON: {host_pubkey, username, password} โ {ok, host_name, username, remote_folder} |
POST /api/portal/remote/clear |
POST |
Dashboard |
Forget remote account locally (+ optionally delete on gateway). JSON: {delete_account?} |
Portal visit-peer
| Route |
Method |
Auth |
Description |
GET /api/visit-peer |
GET |
Owner |
Redirect browser to a peer cell with signed auto-mint token. Query: ?cell=<pubkey>&path=<return_path> |
Peer Messaging (legacy form-based)
| Route |
Method |
Auth |
Description |
POST /api/folder-role |
POST |
Owner |
Set/clear folder role (handler api_set_folder_role; same route detailed under File / folder management above) |
PWA / Assets
| Route |
Method |
Auth |
Description |
GET /manifest.json |
GET |
Public |
PWA manifest |
GET /sw.js |
GET |
Public |
Service worker |
GET /icon-192.png |
GET |
Public |
PWA icon 192ร192 |
GET /icon-512.png |
GET |
Public |
PWA icon 512ร512 |
GET /favicon.ico |
GET |
Public |
SVG favicon (inline pine tree) |
GET /lib/alphatab/<path:fname> |
GET |
Public |
Serve the vendored Guitar Pro tab renderer (ยง8) โ whitelisted alphaTab.min.js + Bravura.woff2 only, long-cache immutable (bytes are pinned into the build hash) |
Notes and Edge Cases
Per-peer derived secrets (v0.4.1+)
Peers now derive a bilateral secret: BLAKE2b(ECDH(my_x25519_priv, their_x25519_pub)). Send X-Grove-Secret: <derived> + X-Grove-Sender: <your_ed25519_pubkey>. The receiving cell tries the global secret first (backward compat), then the derived secret.
Signed peer requests โ X-Grove-Sig (#124)
A peer request carrying X-Grove-Sig + X-Grove-Ts + X-Grove-Sender authenticates with no replayable bearer secret. The Ed25519 signature is bound to method + path + body-hash + recipient pubkey + a fresh timestamp, and is single-use (replay-checked). verify_peer_request() accepts it everywhere the derived secret is accepted (and it satisfies strict identity-binding). This lets a relay forward a peer request without the relay being able to forge or replay it. As of #183 the fleet no longer sends the shared X-Grove-Secret over the relay on the signed peer/public endpoints (chunk-inventory, reachability, revoke-grant, version, ai/status, the replication set, and the serve/grants prefixes) โ the receiver still accepts it transitionally. The owner-tier endpoints (friend-remove/peer-remove/delete-chunks/ai-proxy) are staged behind config relay_secret_drop_owner_tier (default OFF). Signed relay bodies are always JSON (multipart carries no signature).
receive-chunks-b64
Alternative to receive-chunks for relay transport where multipart is impractical. Chunks sent as base64-encoded JSON.
Ingest jobs vs. tasks
/api/ingest-status covers file-ingest background jobs (watches + drives). /api/tasks covers all background jobs (uploads, extracts, etc.) shown in the Home progress tray. Both are separate systems.
/api/gc-manifests vs /api/gc
/api/gc โ garbage collects orphaned chunks (no manifest references them). Dry-run by default.
/api/gc-manifests โ creator-scoped: a peer can ask this cell to drop manifests the peer created. Safe because it's scoped to creator_pubkey.
/api/delete-everywhere vs Recycle Bin
/api/delete-everywhere/<manifest> moves a file to the 24h recycle bin (soft delete). It's still accessible from /api/recycle-bin/list and restorable. Hard deletion (chunks + manifest) happens either at 24h expiry or via /api/recycle-bin/force-delete/<manifest>.
Portal roles
portal โ normal user; sees only files shared with them
admin โ can access /api/* routes (tailnet only), manage users
owner โ the cell owner's own account; sees the cell's drive (set via portal_owner_account config)
/api/ai/setup actions summary
All actions are POST body {action: "...", ...}:
| action |
What it does |
detect |
Detect hardware + recommended model |
models |
List installed models + catalog |
download |
Download a catalog model by model_file |
download_status |
Poll download progress |
switch |
Switch active model |
delete |
Delete an installed model |
enable / disable |
Enable/disable local AI |
set_peer_policy |
Set peer model access policy (all/friends/none) |
build_runtime |
Build llama.cpp runtime |
build_status |
Poll build progress |
set_default_tier |
Set default chat tier (low/mid/high) |
set_hf_token |
Store HuggingFace API token |
hf_search |
Search HuggingFace for GGUF models |
hf_files |
List GGUF files for a repo |
download_hf |
Download a GGUF by URL |
benchmark |
Benchmark a model |
benchmark_status |
Poll benchmark progress |
benchmark_results |
Retrieve benchmark results |