โ† Dashboard ยท Docs ยทApi Reference

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_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/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

/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