Web API Reference

MIA exposes an HTTP REST API served over its own WiFi AP. All endpoints are available at http://192.168.4.1 when connected to the MIA AP.

Important: POST endpoints require the header Content-Type: application/json. Omitting this header causes ESPAsyncWebServer to skip the request body, resulting in a silent empty response.


Radio configuration

GET /api/config

Returns current radio parameters.

Response:

{
  "frequency": 906.875,
  "bandwidth": 250,
  "spreadFactor": 11,
  "codingRate": 5,
  "power": 22
}

POST /api/config

Updates radio parameters. Only include fields you want to change.

Request:

{"spreadFactor": 9, "power": 17}

Response:

{"status": "ok"}

Example:

curl -X POST http://192.168.4.1/api/config \
  -H "Content-Type: application/json" \
  -d '{"spreadFactor": 9}'

PSK (channel key)

GET /api/psk

Returns the current AES key as base64.

Response:

{"psk": "1PG7OiApB1nwvP+rz05pAQ=="}

POST /api/psk

Sets a new AES channel key.

Request:

{"psk": "uUAKMhSSaaZpRNM0ytp6qw=="}

Must decode to exactly 16 bytes (AES-128) or 32 bytes (AES-256).


WiFi / AP settings

GET /api/wifi

Returns current AP configuration. Password is omitted.

Response:

{"enabled": true, "ssid": "Mesh Injection Apparatus"}

POST /api/wifi

Updates AP settings. Include any combination of fields.

Request:

{"enabled": true, "ssid": "MyMIA", "password": "newpassword"}

SSID and password take effect after reboot.


Inject

POST /api/inject/fire

Executes a DuckyScript command locally via USB HID. The MIA device must be plugged into a target machine.

Request:

{"command": "STRINGLN hello world"}

The !mia: prefix is stripped automatically if present.

Response:

{"status": "ok"}

Example:

curl -X POST http://192.168.4.1/api/inject/fire \
  -H "Content-Type: application/json" \
  -d '{"command": "GUI r, DELAY 500, STRINGLN notepad, ENTER"}'

POST /api/inject/transmit

Transmits a !mia: prefixed command over LoRa to other MIA devices on the same channel.

Request:

{"command": "STRINGLN hello from MIA"}

Response:

{"status": "ok"}

GET /api/inject/payloads

Lists payload files stored on the device filesystem.

Response:

[
  {"name": "open_terminal.txt", "size": 42},
  {"name": "reverse_shell.txt", "size": 128}
]

POST /api/inject/upload

Uploads a DuckyScript payload file to the device. Uses multipart form upload.

Example:

curl -X POST http://192.168.4.1/api/inject/upload \
  -F "file=@payload.txt"

DELETE /api/inject/payload

Deletes a stored payload by name.

Request:

{"name": "open_terminal.txt"}

Board info

GET /api/board

Returns device information.

Response:

{
  "version": "0.9.0",
  "mac": "3C:0F:02:ED:BF:8C",
  "uptime": 123456,
  "freeHeap": 245760
}

uptime is in milliseconds since last boot.


Defaults

POST /api/defaults

Saves or resets device configuration.

Save current config as defaults:

{"action": "save"}

Reset to saved defaults (or factory values):

{"action": "reset"}

Reboot

POST /api/reboot

Reboots the device. The connection will drop immediately after the response.

Request: empty body or {}

Response:

{"status": "ok"}

Static files

The web dashboard (index.html, logo.png) is served from LittleFS at the root path. Any file placed in firmware/data/ and uploaded via pio run -t uploadfs will be accessible at http://192.168.4.1/<filename>.