REST API Reference
REST API Reference
WalKEY-TalKEY exposes a local HTTP REST API over Wi-Fi. All endpoints are served from the device itself -- no internet or cloud service required.
Base URL: http://walkey-talkey.local (or http://192.168.4.1 when connected to the device's fallback AP)
All request and response bodies are JSON (application/json) unless otherwise noted. The API uses chunked transfer encoding for large responses.
Quick Reference
| Method | Endpoint | Description |
|---|---|---|
| GET | /ping | Health check |
| GET | / | Web UI (HTML) |
| GET | /portal | Web UI (HTML, alias) |
| GET | /config | Read full active config |
| GET | /config/canonical | Read full config in canonical format |
| PUT | /config | Replace full config |
| POST | /config/validate | Validate JSON without saving |
| POST | /config/reset | Reset to built-in firmware defaults |
| GET | /api/modes | List all modes (summary) |
| GET | /api/mode?id=X | Read a single mode |
| POST | /api/mode | Create a new mode |
| PUT | /api/mode?id=X | Replace a mode |
| DELETE | /api/mode?id=X | Delete a mode |
| GET | /api/wifi | Read Wi-Fi config |
| PUT | /api/wifi | Update Wi-Fi config (merge) |
| GET | /api/defaults | Read defaults (touch, mouse) |
| PUT | /api/defaults | Update defaults (touch, mouse; merge) |
| GET | /api/recording | Read recording config |
| PUT | /api/recording | Update recording config (merge) |
| GET | /api/recordings | List all recording WAV files |
| GET | /api/recordings/download?file=... | Download a recording WAV file |
| GET | /api/recordings/delete?file=... | Delete a recording WAV file |
| GET | /recordings | Recordings management web UI (HTML) |
| PUT | /api/active-mode | Set the active mode |
| GET | /api/boot-mode | Read boot-mode definition |
| GET | /api/global-bindings | Read global bindings |
| GET | /downloads/mode-config.schema.json | Download JSON schema |
| GET | /downloads/AI_GUIDE.md | Download AI guide |
| GET | /downloads/USER_GUIDE.md | Download user guide |
General Notes
- The device starts the HTTP server about 8 seconds after boot to let the display and touch stack settle first.
- Responses from config-mutating endpoints (
PUT /config,POST /config/reset,PUT /api/*,POST /api/mode,DELETE /api/mode) automatically save to flash, reload the runtime, and reapply Wi-Fi settings. No manual reboot is needed. - The maximum request body size is 32 KB.
- All
PUTandPOSTendpoints expect aContent-Type: application/jsonbody.
Health & UI
GET /ping
Health check. Returns ok as plain text.
HTTP/1.1 200 OK
Content-Type: text/plain
ok
GET / and GET /portal
Serve the built-in web UI as HTML. Both paths return identical content.
Full Config Endpoints
These endpoints operate on the entire config file at once.
GET /config
Read the full active config. The response wraps the config JSON inside a metadata envelope.
Response:
1{ 2 "ok": true, 3 "source": "external", 4 "config": { 5 "version": 1, 6 "activeMode": "cursor", 7 "defaults": { ... }, 8 "wifi": { ... }, 9 "globalBindings": [ ... ], 10 "bootMode": { ... }, 11 "modes": [ ... ] 12 } 13}
The source field indicates where the config was loaded from: "external" (SPIFFS file), "builtin" (compiled into firmware), or "failsafe" (hardcoded last resort).
GET /config/canonical
Same as GET /config but returns the config in canonical (normalized) JSON format. Includes an additional "format": "canonical" field.
Response:
1{ 2 "ok": true, 3 "source": "external", 4 "format": "canonical", 5 "config": { ... } 6}
PUT /config
Replace the entire config. Validates, saves to SPIFFS, reloads the runtime, and reapplies Wi-Fi.
Request body: Full config JSON object (same shape as what GET /config returns in its config field).
Success response:
1{ 2 "ok": true, 3 "saved": true, 4 "source": "external", 5 "config": { ... } 6}
Error response (validation failure):
1{ 2 "ok": false, 3 "error": "VALIDATION_FAILED", 4 "message": "Description of what went wrong" 5}
Error response (storage failure):
1{ 2 "ok": false, 3 "error": "STORAGE_FAILED", 4 "stage": "write", 5 "formatAttempted": false, 6 "path": "/spiffs/mode-config.json", 7 "partition": "storage", 8 "espError": "...", 9 "errnoValue": 28, 10 "errnoMessage": "No space left on device", 11 "suggestions": ["Reflash the firmware and partition table"] 12}
POST /config/validate
Validate a JSON config without saving. Useful for checking edits before committing.
Request body: Full config JSON object.
Success response:
1{ 2 "ok": true, 3 "valid": true, 4 "config": { ... } 5}
The returned config is the normalized version of what you sent.
Error response:
1{ 2 "ok": false, 3 "error": "VALIDATION_FAILED", 4 "message": "..." 5}
POST /config/reset
Reset the config to the built-in firmware defaults. Writes the default JSON back to SPIFFS, reloads the runtime, and reapplies Wi-Fi.
No request body required.
Success response:
1{ 2 "ok": true, 3 "source": "external", 4 "config": { ... } 5}
Mode Endpoints
Granular endpoints for working with individual modes.
GET /api/modes
List all modes with summary information.
Response:
1[ 2 { 3 "id": "cursor", 4 "label": "Cursor", 5 "cycleOrder": 0, 6 "bindingCount": 8 7 }, 8 { 9 "id": "media", 10 "label": "Media", 11 "cycleOrder": 1, 12 "bindingCount": 5 13 } 14]
GET /api/mode?id=<mode_id>
Read the full definition of a single mode.
Query parameter: id (required) -- the mode id string.
Response:
1{ 2 "id": "cursor", 3 "cycleOrder": 0, 4 "label": "Cursor", 5 "bindings": [ 6 { 7 "input": "touch", 8 "trigger": "tap", 9 "actions": [ 10 { "type": "hid_key_tap", "key": "F14" }, 11 { "type": "ui_hint", "text": "Cursor mode" } 12 ] 13 } 14 ] 15}
Error: 404 if the mode id does not exist.
POST /api/mode
Create a new mode. The request body is a complete mode JSON object.
Request body:
1{ 2 "id": "gaming", 3 "cycleOrder": 4, 4 "label": "Gaming", 5 "bindings": [ 6 { 7 "input": "touch", 8 "trigger": "tap", 9 "actions": [ 10 { "type": "hid_key_tap", "key": "SPACE" } 11 ] 12 } 13 ] 14}
Success response:
1{ "ok": true, "created": true }
PUT /api/mode?id=<mode_id>
Replace an existing mode entirely. The request body is the full updated mode object.
Query parameter: id (required) -- the mode id to replace.
Request body: Same shape as POST /api/mode.
Success response:
1{ "ok": true, "updated": true }
Error: 404 if the mode id does not exist.
DELETE /api/mode?id=<mode_id>
Delete a mode.
Query parameter: id (required) -- the mode id to delete.
Success response:
1{ "ok": true, "deleted": true }
Errors:
400if trying to delete the currently active mode.404if the mode id does not exist.
Wi-Fi Endpoints
GET /api/wifi
Read the current Wi-Fi configuration.
Response:
1{ 2 "sta": { 3 "ssid": "YourNetworkName", 4 "password": "YourPassword" 5 }, 6 "ap": { 7 "ssid": "walkey-talkey", 8 "password": "secretKEY" 9 }, 10 "hostname": "walkey-talkey", 11 "localUrl": "walkey-talkey.local" 12}
PUT /api/wifi
Update Wi-Fi settings. Uses merge semantics -- only the fields you send are overwritten; everything else is preserved.
Request body (example -- update just the STA credentials):
1{ 2 "sta": { 3 "ssid": "NewNetwork", 4 "password": "NewPassword" 5 } 6}
Success response:
1{ "ok": true, "updated": true }
Wi-Fi is reapplied immediately after save. If you change the STA credentials, the device will attempt to join the new network.
Defaults Endpoints
GET /api/defaults
Read the current defaults for touch timing, mouse mode selection, and per-backend mouse configuration.
Response:
1{ 2 "touch": { 3 "holdMs": 400, 4 "doubleTapMs": 350, 5 "swipeMinDistance": 40 6 }, 7 "defaultMouse": "airMouse", 8 "airMouse": { 9 "sensitivity": 1.0, 10 "deadZoneDps": 6.0, 11 "easingExponent": 1.25, 12 "maxDps": 300.0, 13 "emaAlpha": 0.35, 14 "rewindDepth": 12, 15 "rewindDecay": 0.7, 16 "calibrationSamples": 128 17 }, 18 "touchMouse": { 19 "sensitivity": 1.0, 20 "moveThresholdPx": 5, 21 "tapDragWindowMs": 180 22 } 23}
PUT /api/defaults
Update defaults. Uses merge semantics -- only the fields you send are overwritten; everything else is preserved. Works for touch, defaultMouse, airMouse, and touchMouse fields.
Request body (example -- adjust hold threshold):
1{ 2 "touch": { 3 "holdMs": 500 4 } 5}
Request body (example -- switch to touch mouse and tweak air sensitivity):
1{ 2 "defaultMouse": "touchMouse", 3 "airMouse": { 4 "sensitivity": 1.5 5 } 6}
Success response:
1{ "ok": true, "updated": true }
Recording Endpoints
GET /api/recording
Read the current SD card recording settings.
Response:
1{ 2 "enabled": false, 3 "format": "wav" 4}
When enabled is true, every mic_gate activation also writes a WAV file (48kHz, 16-bit, mono) to the SD card at /sdcard/recordings/<modeId>/<sessionId>_<uptimeSec>.wav. Recordings are accessed over HTTP, not via USB drive browsing.
PUT /api/recording
Update recording settings. Uses merge semantics -- only the fields you send are overwritten.
Request body (enable recording):
1{ 2 "enabled": true 3}
Request body (disable recording):
1{ 2 "enabled": false 3}
Success response:
1{ "ok": true, "updated": true }
curl example:
1curl -X PUT http://walkey-talkey.local/api/recording \ 2 -H "Content-Type: application/json" \ 3 -d '{"enabled": true}'
Recording File Endpoints
Recordings are served over Wi-Fi via these endpoints. They are not accessed via USB Mass Storage.
GET /api/recordings
List all recording WAV files on the SD card.
Response:
1{ 2 "recordings": [ 3 { "path": "whisper/ABC123_00042.wav", "size": 344300 }, 4 { "path": "whisper/ABC123_00058.wav", "size": 750380 } 5 ] 6}
GET /api/recordings/download?file=<path>
Stream a recording WAV file. The file parameter is the relative path from the listing.
Example:
1curl -o recording.wav "http://walkey-talkey.local/api/recordings/download?file=whisper/ABC123_00042.wav"
Response: Binary WAV data with Content-Type: audio/wav, streamed in chunks.
GET /api/recordings/delete?file=<path>
Delete a recording from the SD card.
Example:
1curl "http://walkey-talkey.local/api/recordings/delete?file=whisper/ABC123_00042.wav"
Response:
1{ "ok": true }
GET /recordings
Web UI page for browsing, playing, downloading, and deleting recordings. Returns an HTML page.
Active Mode Endpoint
PUT /api/active-mode
Set which mode the device starts in.
Request body:
1{ 2 "activeMode": "media" 3}
Success response:
1{ "ok": true, "updated": true }
Boot Mode & Global Bindings (Read-Only)
GET /api/boot-mode
Read the boot-mode definition (the temporary control layer active while the BOOT button is held).
Response:
1{ 2 "label": "Mode Control", 3 "ui": { 4 "title": "Swipe to switch mode", 5 "subtitle": "Hold BOOT and swipe to change modes", 6 "showModeList": true, 7 "showGestureHints": true, 8 "showCurrentModeCard": true 9 }, 10 "bindings": [ ... ] 11}
GET /api/global-bindings
Read the global bindings (always-active bindings regardless of current mode).
Response:
1[ 2 { 3 "input": "boot_button", 4 "trigger": "press", 5 "actions": [ 6 { "type": "enter_boot_mode" } 7 ] 8 }, 9 { 10 "input": "boot_button", 11 "trigger": "release", 12 "actions": [ 13 { "type": "exit_boot_mode" } 14 ] 15 } 16]
Download Endpoints
These serve documentation and schema files embedded in the firmware.
| Endpoint | Content-Type | Description |
|---|---|---|
GET /downloads/mode-config.schema.json | application/json | JSON schema for config validation |
GET /downloads/AI_GUIDE.md | text/markdown | AI assistant guide for generating configs |
GET /downloads/USER_GUIDE.md | text/markdown | End-user setup and authoring guide |
Error Format
All error responses follow this shape:
1{ 2 "ok": false, 3 "error": "ERROR_CODE", 4 "message": "Human-readable description" 5}
Storage-specific failures include additional diagnostic fields (stage, formatAttempted, path, partition, espError, errnoValue, errnoMessage, suggestions).
Example: Create a Mode with curl
1# Create a new "spotify" mode 2curl -X POST http://walkey-talkey.local/api/mode \ 3 -H "Content-Type: application/json" \ 4 -d '{ 5 "id": "spotify", 6 "cycleOrder": 4, 7 "label": "Spotify", 8 "bindings": [ 9 { 10 "input": "touch", 11 "trigger": "tap", 12 "actions": [ 13 { "type": "hid_usage_tap", "usage": "PLAY_PAUSE" }, 14 { "type": "ui_hint", "text": "Play/Pause" } 15 ] 16 }, 17 { 18 "input": "touch", 19 "trigger": "swipe_right", 20 "actions": [ 21 { "type": "hid_usage_tap", "usage": "MEDIA_NEXT_TRACK" }, 22 { "type": "ui_hint", "text": "Next" } 23 ] 24 }, 25 { 26 "input": "touch", 27 "trigger": "swipe_left", 28 "actions": [ 29 { "type": "hid_usage_tap", "usage": "MEDIA_PREV_TRACK" }, 30 { "type": "ui_hint", "text": "Previous" } 31 ] 32 }, 33 { 34 "input": "touch", 35 "trigger": "swipe_up", 36 "actions": [ 37 { "type": "hid_usage_tap", "usage": "VOLUME_UP" }, 38 { "type": "ui_hint", "text": "Volume up" } 39 ] 40 }, 41 { 42 "input": "touch", 43 "trigger": "swipe_down", 44 "actions": [ 45 { "type": "hid_usage_tap", "usage": "VOLUME_DOWN" }, 46 { "type": "ui_hint", "text": "Volume down" } 47 ] 48 } 49 ] 50 }'
1# Switch to the new mode 2curl -X PUT http://walkey-talkey.local/api/active-mode \ 3 -H "Content-Type: application/json" \ 4 -d '{"activeMode": "spotify"}'
1# Verify it's there 2curl http://walkey-talkey.local/api/modes