Clipshot exposes a local HTTP API in browser mode and in daemon setups that enable the HTTP server.
Base behavior
- Default bind:
127.0.0.1:<port> - Override bind:
CLIPSHOT_HTTP_BIND=0.0.0.0|127.0.0.1|::1|localhost - JSON envelope for JSON endpoints:
{"success": true|false, "data": ..., "error": null|"message"}
- Unknown GET routes fall back to the embedded SPA
index.html OPTIONSgets204with CORS headers
Canonical response types
DaemonStatusFull
Fields returned by GET /api/status and Tauri get_daemon_status:
running: boolsync_enabled: boolnode_id: stringlisten_addr: stringconnected_peers: numbertotal_peers: numbernode_name: stringuptime_secs: numbershare_uri_expires_secs: numberiroh_enabled: booliroh_addr: string | nullmessages_sent: numbermessages_received: numberhub_connected: boolhub_url: string | nullhub_limit_reason: string | nullhub_limit_current: number | nullhub_limit_max: number | nullhub_limit_manage_url: string | nullhub_limit_message: string | nullsync_state: "idle" | "sending" | "receiving" | "no_peers" | "paused"
FullPeerInfo
Fields returned by GET /api/peers and Tauri get_peers:
nameaddressconnectedstatus: Online | Offline | Connectingnode_id: string | nulladdresses: string[]latency_ms: number | nulllast_sync: number | nulltransport_type: string | nulllimited: boolreject_reason: string | nulldevice_type: string | nulltransfer_bytes_done: number | nulltransfer_bytes_total: number | nulltransfer_speed_bps: number | null
ActivityEvent
idevent_typedescriptiontimestamppeer_namecontent_type?size_bytes?error_message?file_path?
TransferInfo
iddirection: "sending" | "receiving"peer_namepeer_addrcontent_typetotal_bytestransferred_bytesstatus: "in_progress" | "complete" | "failed"started_atcompleted_at?file_path?error_message?chunks_sentchunks_totalspeed_bytes_per_sec
HistoryEntry
identry_typenamesize_bytespeer_namedirectionstatustimestampfile_path?content_typetransferred_bytes?total_bytes?chunks_sent?chunks_total?speed_bytes_per_sec?error_message?completed_at?
Core / SPA / static routes
| Method | Path | Body | Response |
|---|---|---|---|
| GET | / | – | embedded index.html |
| GET | /index.html | – | embedded index.html |
| GET | /assets/* | – | embedded static asset or 404 text/plain |
| GET | /health | – | {"status":"ok"} |
| GET | /api/icon.png | – | PNG chosen from current global sync state |
| GET | /api/permissions | – | ApiResponse<PermissionInfo[]> (macOS Input Monitoring etc.) |
| OPTIONS | any API path | – | 204 + CORS headers |
Status / activity / share routes
| Method | Path | Body | Response |
|---|---|---|---|
| GET | /api/status | – | ApiResponse<DaemonStatusFull> |
| GET | /api/activity | – | ApiResponse<ActivityEvent[]> |
| GET | /api/share_uri | – | ApiResponse<{ uri, node_id, expires_in_secs }> or error if no routable addresses |
| GET | /api/gossip_peers | – | same payload as /api/peers |
| GET | /api/transfers | – | ApiResponse<TransferInfo[]> |
| GET | /api/history?filter=<opt>&limit=<opt> | – | ApiResponse<HistoryEntry[]>, default limit=100 |
| GET | /api/history/:id/content | – | raw file bytes; image/* if history entry is image, else text/plain; charset=utf-8 |
Peer routes
| Method | Path | Body | Response |
|---|---|---|---|
| GET | /api/peers | – | ApiResponse<FullPeerInfo[]> |
| POST | /api/peers | {"name":"...","address":"..."} | queues PeerCommand::Add |
| PUT | /api/peers | {"address":"...","name"?:string,"addresses"?:string[],"auth_code"?:string|null} | queues PeerCommand::Update |
| DELETE | /api/peers/:name | – | queues remove by peer name or stable hex node ID |
| POST | /api/peers/:name/connect | – | queues ConnectByName |
| POST | /api/peers/:name/activate | – | queues ActivateByName |
| POST | /api/peers/:name/deactivate | – | queues DeactivateByName |
| POST | /api/peers/:name/reconnect | – | queues Reconnect |
| POST | /api/add_peer_from_uri | {"uri":"clipshot://..."} | queues AddFromUri |
Validation details:
AddPeerRequest.name: 1..64 charsAddPeerRequest.address: 7..255 charsUpdatePeerRequest.address: 1..255 charsAddPeerFromUriRequest.uri: 1..2048 chars, plus non-blank after trim
Settings routes
| Method | Path | Body | Response |
|---|---|---|---|
| GET | /api/settings | – | ApiResponse<Settings> from daemon-authoritative projection |
| POST | /api/settings | partial settings update body | queues UpdateSettings |
Accepted update fields in POST /api/settings:
auto_sync(sync_enabledalias also accepted)auto_startnotificationssync_interval_msmax_file_size_mbtransfer_timeout_per_mb_mstransfer_timeout_base_mschunk_timeout_secslisten_portenable_irohmax_peersauto_discoveruse_browser_uidirect_send_threshold_mbhotkeyhub_urlgroup_tokenrelay_urlmax_peers_free
Not accepted via HTTP API:
is_pronode_passwordrelay_enabled
Validation ranges enforced by UpdateSettingsRequest:
sync_interval_ms: 100..3_600_000max_file_size_mb: 1..10240transfer_timeout_per_mb_ms: 100..60_000transfer_timeout_base_ms: 1000..300_000chunk_timeout_secs: 1..600listen_port: 1024..65535max_peers: 1..1000direct_send_threshold_mb: 1..100max_peers_free: 1..1000
Sync / transfer / clipboard routes
| Method | Path | Body | Response |
|---|---|---|---|
| POST | /api/retry_transfer | {"file_path":"..."} | queues retry by path |
| POST | /api/copy_to_clipboard | {"text":"..."} | queues local clipboard write + broadcast |
| POST | /api/sync/pause | – | queues PauseSync |
| POST | /api/sync/resume | – | queues ResumeSync |
| POST | /api/sync/toggle | – | queues ToggleSync |
Validation:
RetryTransferRequest.file_path: 1..4096 charsCopyToClipboardRequest.text: 1..1_000_000 chars
Test-only daemon HTTP routes (src/http/handlers_test.rs)
These require CLIPSHOT_TEST_HOOKS=1 or return 403.
| Method | Path | Body | Response |
|---|---|---|---|
| GET | /api/test/clipboard | – | clipboard snapshot: text returns type/data/size, binary returns type/size |
| POST | /api/test/reset | optional {"restore_peer_name"?,"restore_peer_addr"?,"max_peers_free"?} | queues reset-state command |
Tauri hotkey test-hook HTTP server
This is a separate tiny_http server started from src/gui/tauri_app.rs only when CLIPSHOT_TEST_HOOKS=1.
Defaults:
- bind:
127.0.0.1:${CLIPSHOT_TEST_HOOKS_PORT:-18181}
Routes:
| Method | Path | Body | Response |
|---|---|---|---|
| GET / POST | /api/test/health | – | daemon readiness (200 ready or 503 starting) |
| GET / POST | /api/test/sync_enabled | – | current sync_enabled bool |
| GET / POST | /api/test/hotkey_metrics | – | queue/drain latency metrics |
| GET | /api/test/native_listener_health | – | native hotkey listener health |
| GET | /api/test/icon_state | – | { state, state_raw } from global sync atomic |
| GET | /api/test/native_key_events | – | captured native key event snapshot |
| POST | /api/test/native_key_events | – | clears captured native key events |
| POST | /api/test/hotkey_toggle | optional X-Clipshot-Source header | emits hotkey-toggle-sync into app event loop |
Setup sessions (device auth flow)
| Method | Path | Body | Response |
|---|---|---|---|
| POST | /api/setup/sessions | – | { session_id, url, expires_in } |
| GET | /api/setup/sessions/:id | – | { status: "pending"\|"completed"\|"expired", group_token? } |
| POST | /api/setup/sessions/:id/complete | { group_token } (requires JWT) | 200 |
Used by curl | bash install script and GUI “Create Account” button. Session expires in 10 minutes.
OAuth
| Method | Path | Notes |
|---|---|---|
| GET | /api/auth/google | Redirect to Google OAuth. Optional ?state=SESSION_ID for device auth. |
| GET | /api/auth/google/callback | Google callback — creates/finds user, issues JWT. |
| GET | /api/auth/github | Redirect to GitHub OAuth. |
| GET | /api/auth/github/callback | GitHub callback. |
Note: OAuth and setup session routes above are Portal-side endpoints (clipshot.cc), not local daemon HTTP API routes. The local daemon serves routes like /api/status, /api/peers, etc.