Docs

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

MethodEndpointDescription
GET/pingHealth check
GET/Web UI (HTML)
GET/portalWeb UI (HTML, alias)
GET/configRead full active config
GET/config/canonicalRead full config in canonical format
PUT/configReplace full config
POST/config/validateValidate JSON without saving
POST/config/resetReset to built-in firmware defaults
GET/api/modesList all modes (summary)
GET/api/mode?id=XRead a single mode
POST/api/modeCreate a new mode
PUT/api/mode?id=XReplace a mode
DELETE/api/mode?id=XDelete a mode
GET/api/wifiRead Wi-Fi config
PUT/api/wifiUpdate Wi-Fi config (merge)
GET/api/defaultsRead defaults (touch, mouse)
PUT/api/defaultsUpdate defaults (touch, mouse; merge)
GET/api/recordingRead recording config
PUT/api/recordingUpdate recording config (merge)
GET/api/recordingsList 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/recordingsRecordings management web UI (HTML)
PUT/api/active-modeSet the active mode
GET/api/boot-modeRead boot-mode definition
GET/api/global-bindingsRead global bindings
GET/downloads/mode-config.schema.jsonDownload JSON schema
GET/downloads/AI_GUIDE.mdDownload AI guide
GET/downloads/USER_GUIDE.mdDownload 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 PUT and POST endpoints expect a Content-Type: application/json body.

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:

  • 400 if trying to delete the currently active mode.
  • 404 if 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.

EndpointContent-TypeDescription
GET /downloads/mode-config.schema.jsonapplication/jsonJSON schema for config validation
GET /downloads/AI_GUIDE.mdtext/markdownAI assistant guide for generating configs
GET /downloads/USER_GUIDE.mdtext/markdownEnd-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