Skip to content

API Reference

Complete REST API reference for C3PO. All endpoints require authentication unless noted.


Authentication

Session (Browser)

Login via POST /login sets a session cookie.

Bearer Token (API)

Authorization: Bearer <MULTILAT_AUTH_TOKEN>

Most API endpoints accept either session or bearer token.

Rate Limiting

Default: 200 requests/minute per IP. Specific limits noted per endpoint.


Devices

List Devices

GET /api/devices

Returns all connected and recently seen devices.

Response:

{
    "devices": [
        {
            "id": "abc12345",
            "ip": "192.168.1.100",
            "port": 54321,
            "status": "Connected",
            "chip": "esp32",
            "modules": "network,recon,ota",
            "connected_at": 1708700000.5,
            "last_seen": 1708700100.2,
            "connected_for_seconds": 99.7,
            "last_seen_ago_seconds": 0.1
        }
    ]
}
Field Type Description
id string Device identifier
ip string Client IP address
port int Client TCP port
status string "Connected" or "Inactive"
chip string ESP32 chip type (from system_info)
modules string Comma-separated enabled modules
connected_at float Unix timestamp
last_seen float Last activity timestamp
connected_for_seconds float Connection duration
last_seen_ago_seconds float Time since last activity

Device Stream (SSE)

GET /api/devices/stream

Server-Sent Events stream. Pushes device list every 3 seconds.

Event format:

data: {"devices": [...]}

Commands

Send Command

POST /api/commands

Dispatch a command to one or more devices.

Rate limit: 60/minute

Request body:

{
    "device_ids": ["abc12345", "def67890"],
    "command": "ping",
    "argv": ["8.8.8.8"]
}
Field Type Required Description
device_ids array or "all" Yes Target devices (max 100) or "all"
command string Yes Command name (regex: ^[a-zA-Z][a-zA-Z0-9_-]{0,63}$)
argv array No Arguments (max 10 items, 256 chars each)

Response:

{
    "results": [
        {
            "device_id": "abc12345",
            "status": "ok",
            "request_id": "web-cmd-a1b2c3d4"
        }
    ]
}

curl example:

curl -X POST http://localhost:8000/api/commands \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"device_ids": ["abc12345"], "command": "ping", "argv": ["8.8.8.8"]}'

Get Command Result

GET /api/commands/<request_id>

Poll for command response.

Response:

{
    "request_id": "web-cmd-a1b2c3d4",
    "device_id": "abc12345",
    "command": "ping",
    "status": "completed",
    "output": [
        "Reply from 8.8.8.8: time=23ms"
    ]
}
Field Type Description
status string "pending" or "completed"
output array Response lines from the device

Auto-purge

Completed commands are automatically purged after 120 seconds (2 minutes).


List Recent Commands

GET /api/commands

Returns the 50 most recent commands.

Response:

{
    "commands": [
        {
            "request_id": "web-cmd-a1b2c3d4",
            "device_id": "abc12345",
            "command": "ping",
            "status": "completed",
            "output": ["Reply from 8.8.8.8: time=23ms"]
        }
    ]
}

Cameras

List Cameras

GET /api/cameras

Scans IMAGE_DIR for camera frames and returns camera list with recording status.

Response:

{
    "cameras": [
        {
            "id": "camera_01",
            "recording": false,
            "filename": null
        }
    ]
}

Start Recording

POST /api/recording/start/<camera_id>

Start MJPEG video recording for a camera.

Response:

{
    "status": "recording",
    "camera_id": "camera_01"
}

Stop Recording

POST /api/recording/stop/<camera_id>

Stop recording and finalize video file.

Response:

{
    "status": "stopped",
    "camera_id": "camera_01",
    "filename": "camera_01_20240223_143022.avi"
}

Recording Status

GET /api/recording/status?camera_id=camera_01

Check recording state.


List Recordings

GET /api/recordings

List completed video recordings.

Response:

{
    "recordings": [
        {
            "filename": "camera_01_20240223_143022.avi",
            "size": 5242880
        }
    ]
}

MLAT (Multilateration)

Submit Readings

POST /api/mlat/collect

Submit scanner RSSI readings. Accepts raw text body.

Request body (text/plain):

SCANNER_1;(48.8566,2.3522);-45
SCANNER_2;(48.8570,2.3520);-42
SCANNER_3;(48.8568,2.3525);-50

Format: SCANNER_ID;(lat,lon);rssi

Response:

{
    "status": "ok",
    "readings_processed": 3
}

Get State

GET /api/mlat/state

Returns all scanners and estimated target position.

Response:

{
    "scanners": [
        {
            "id": "SCANNER_1",
            "position": {"lat": 48.8566, "lon": 2.3522},
            "last_rssi": -45.2,
            "estimated_distance": 12.5,
            "last_seen": 1708700000.0,
            "age_seconds": 5.2
        }
    ],
    "scanners_count": 3,
    "target": {
        "position": {"lat": 48.8567, "lon": 2.3521},
        "calculated_at": 1708700000.0,
        "age_seconds": 2.1
    },
    "config": {
        "rssi_at_1m": -40,
        "path_loss_n": 2.5,
        "smoothing_window": 5
    },
    "coord_mode": "gps"
}

Minimum 3 scanners

Target position is only calculated when ≥3 scanners have recent readings.


Get/Set Config

GET /api/mlat/config
POST /api/mlat/config

POST body:

{
    "rssi_at_1m": -40,
    "path_loss_n": 2.5,
    "smoothing_window": 5
}
Parameter Default Description
rssi_at_1m -40 RSSI at 1 meter reference distance (dBm)
path_loss_n 2.5 Path loss exponent (2.0=free space, 3.0+=indoor)
smoothing_window 5 Number of readings to average

Clear Data

POST /api/mlat/clear

Reset all scanner and target data.


OTA (Firmware Updates)

Deploy Firmware

POST /api/ota/deploy

Push OTA update to one or more devices.

Rate limit: 10/minute

Request body:

{
    "url": "https://example.com/firmware.bin",
    "device_ids": ["abc12345", "def67890"]
}

Response:

{
    "results": [
        {
            "device_id": "abc12345",
            "status": "ok",
            "request_id": "ota-abc12345-1708700000"
        },
        {
            "device_id": "def67890",
            "status": "error",
            "message": "Device not connected"
        }
    ]
}

The server sends ota_update <url> to each device via the C2 channel. The ESP32 then fetches the firmware binary from the URL directly.


Upload Firmware

POST /api/ota/upload

Upload a .bin firmware file to the server.

Rate limit: 5/minute

Request: multipart/form-data

Field Type Required Description
file file Yes .bin firmware binary
name string No Custom filename

Response:

{
    "filename": "custom-name.bin",
    "size": 1523456,
    "url": "/api/ota/firmware/custom-name.bin"
}

curl example:

curl -X POST http://localhost:8000/api/ota/upload \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@firmware.bin" \
  -F "name=v2.1-network"

List Firmware

GET /api/ota/firmware

List uploaded firmware files.

Response:

{
    "firmware": [
        {
            "filename": "v2.1-network.bin",
            "size": 1523456,
            "modified": 1708700000.0
        }
    ]
}

Serve Firmware

GET /api/ota/firmware/<filename>

Download firmware binary (authenticated).

GET /api/ota/fw/<filename>?token=<MULTILAT_AUTH_TOKEN>

Short URL for ESP32 OTA fetch. Accepts session, bearer, or ?token= parameter.


Delete Firmware

DELETE /api/ota/firmware/<filename>

Remove uploaded firmware file.


Build (Firmware Builder)

Get Defaults

GET /api/ota/build/defaults

Returns default build configuration from deploy.json.

Response:

{
    "server": {
        "ip": "192.168.1.100",
        "port": 2626
    },
    "network": {
        "mode": "wifi",
        "wifi_ssid": "MyNetwork",
        "wifi_pass": "secret"
    },
    "modules": {
        "network": true,
        "fakeap": false,
        "honeypot": false,
        "recon": false,
        "canbus": false
    },
    "ota": {
        "enabled": true,
        "allow_http": false
    }
}

Start Build

POST /api/ota/build/start

Trigger a firmware build in background.

Rate limit: 3/minute

Request body:

{
    "device_id": "abc12345",
    "hostname": "esp32-01",
    "modules": {
        "network": true,
        "fakeap": false,
        "honeypot": true,
        "recon": false,
        "recon_camera": false,
        "recon_ble_trilat": false,
        "redteam": false,
        "ota": true,
        "canbus": false
    },
    "server": {
        "ip": "192.168.1.100",
        "port": 2626
    },
    "network": {
        "mode": "wifi",
        "wifi_ssid": "MyNetwork",
        "wifi_pass": "secret"
    },
    "ota": {
        "enabled": true,
        "allow_http": false
    }
}
Field Type Required Description
device_id string Yes ^[a-zA-Z0-9_-]{1,32}$
hostname string No ^[a-zA-Z0-9_-]{1,63}$
modules object No Module toggles (booleans)
server object No Server IP/port overrides
network object No WiFi/GPRS settings
ota object No OTA configuration

Valid modules: network, fakeap, honeypot, recon, recon_camera, recon_ble_trilat, redteam, ota, canbus

Response (success):

{
    "status": "started",
    "device_id": "abc12345"
}

Response (already building):

{
    "error": "A build is already in progress"
}
(HTTP 409)


Build Status

GET /api/ota/build/status

Current build state.

Response:

{
    "status": "building",
    "device_id": "abc12345",
    "started_at": 1708700000.0,
    "finished_at": 0.0,
    "elapsed": 45.3,
    "output_filename": "",
    "error": "",
    "progress_hint": "Compiling network module...",
    "log_line_count": 234
}
Status Description
idle No build
building In progress
success Completed — output_filename set
failed Error — error field has details

Build Log

GET /api/ota/build/log?offset=0

Incremental build log retrieval.

Parameters:

Param Type Default Description
offset int 0 Start from line N

Response:

{
    "lines": [
        "[build] Checking ESP-IDF environment",
        "[build] ESP-IDF: /home/user/esp-idf",
        "[build] Device: abc12345",
        "[build] Modules: network, honeypot",
        "[build] sdkconfig.defaults written",
        "[build] Starting idf.py build..."
    ],
    "offset": 0,
    "total": 6
}

Polling pattern:

let offset = 0;
const poll = setInterval(async () => {
    const log = await fetch(`/api/ota/build/log?offset=${offset}`);
    const data = await log.json();
    offset = data.total;  // Next fetch starts where we left off
    // Append data.lines to display
}, 2000);

Build Flow (End-to-End)

# 1. Get defaults for form pre-population
curl http://localhost:8000/api/ota/build/defaults \
  -H "Authorization: Bearer $TOKEN"

# 2. Start build
curl -X POST http://localhost:8000/api/ota/build/start \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "abc12345",
    "modules": {"network": true, "honeypot": true},
    "ota": {"enabled": true}
  }'

# 3. Poll status
while true; do
  STATUS=$(curl -s http://localhost:8000/api/ota/build/status \
    -H "Authorization: Bearer $TOKEN" | jq -r .status)
  echo "Status: $STATUS"
  [ "$STATUS" != "building" ] && break
  sleep 5
done

# 4. Get log
curl http://localhost:8000/api/ota/build/log \
  -H "Authorization: Bearer $TOKEN"

# 5. Deploy built firmware to devices
curl -X POST http://localhost:8000/api/ota/deploy \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "http://192.168.1.100:8000/api/ota/fw/abc12345-1708700000.bin",
    "device_ids": ["abc12345"]
  }'

CAN Bus

Get Frames

GET /api/can/frames?device_id=abc12345&can_id=291&limit=100&offset=0

Paginated CAN frame retrieval (most recent first).

Parameters:

Param Type Default Description
device_id string (all) Filter by device
can_id int (all) Filter by CAN ID (decimal)
limit int 100 Max frames (1-1000)
offset int 0 Skip N frames

Response:

{
    "frames": [
        {
            "device_id": "abc12345",
            "timestamp_ms": 1708700000500,
            "can_id": "0x123",
            "dlc": 8,
            "data": "0102030405060708",
            "received_at": 1708700001.0
        }
    ],
    "count": 1,
    "offset": 0,
    "limit": 100
}

Get Stats

GET /api/can/stats?device_id=abc12345

CAN frame statistics.

Response:

{
    "total_stored": 5432,
    "total_received": 12000,
    "unique_can_ids": 45,
    "can_ids": ["0x100", "0x123", "0x7DF", "0x7E8"]
}

Ring Buffer

CAN store keeps the last 10,000 frames in memory. total_received tracks the all-time count, while total_stored reflects the current buffer.


Export CSV

GET /api/can/frames/export?device_id=abc12345

Download all stored frames as CSV.

Response: Content-Disposition: attachment; filename=can_frames_abc12345.csv

device_id,timestamp_ms,can_id,dlc,data
abc12345,1708700000500,0x123,8,0102030405060708
abc12345,1708700000510,0x7DF,8,0201050000000000

Monitor (Serial Ports)

List Ports

GET /api/monitor/ports

Scan for available serial ports and their monitoring status.

Response:

{
    "ports": [
        {
            "port": "/dev/ttyUSB0",
            "device_id": "abc12345",
            "monitoring": true
        },
        {
            "port": "/dev/ttyUSB1",
            "device_id": null,
            "monitoring": false
        }
    ]
}

Scans /dev/ttyUSB* and /dev/ttyACM*.


Set Port Mapping

POST /api/monitor/ports/map

Map serial ports to device IDs.

Request body:

{
    "mapping": {
        "/dev/ttyUSB0": "abc12345",
        "/dev/ttyUSB1": "def67890"
    }
}

Stream Serial Output (SSE)

GET /api/monitor/stream/<port_path>

Auth: Session only (not Bearer).

Server-Sent Events stream of serial output at 115200 baud.

SSE events:

data: {"line": "I (1234) main: Boot complete", "timestamp": 1708700000.5}

data: {"line": "I (1235) wifi: Connected", "timestamp": 1708700001.0}
  • Keep-alive ping every 30 seconds
  • Max 500 buffered lines per subscriber
  • Slow subscribers are dropped

Stop Monitoring

POST /api/monitor/stop/<port_path>

Stop serial monitoring for a port.


Honeypot

Optional Module

Honeypot API is only available if hp_dashboard is configured.

List Events

GET /api/hp/events?limit=100&offset=0&source_ip=1.2.3.4&severity=high

Paginated honeypot event list.


List Sessions

GET /api/hp/sessions?limit=50

Tracked attacker sessions.


Captured Credentials

GET /api/hp/credentials

Username/password pairs captured by honeypot services.


Alert State

GET /api/hp/alerts

Current alert rules and triggered alerts.


Statistics

Server Stats

GET /api/stats

Global server statistics.

Response:

{
    "active_cameras": 2,
    "connected_devices": 5,
    "multilateration_scanners": 3,
    "server_running": true
}

Error Responses

All errors follow a consistent JSON format:

{
    "error": "Description of what went wrong"
}
HTTP Code Meaning
400 Bad request (validation error)
401 Authentication required
404 Resource not found
409 Conflict (e.g., build already in progress)
429 Rate limit exceeded
500 Internal server error

Endpoint Summary

Method URL Rate Description
Devices
GET /api/devices 200/min List all devices
GET /api/devices/stream 200/min SSE device stream
Commands
POST /api/commands 60/min Send command
GET /api/commands/<id> 200/min Get command result
GET /api/commands 200/min List recent commands
Cameras
GET /api/cameras 200/min List cameras
POST /api/recording/start/<cam> 200/min Start recording
POST /api/recording/stop/<cam> 200/min Stop recording
GET /api/recording/status 200/min Recording state
GET /api/recordings 200/min List recordings
MLAT
POST /api/mlat/collect 200/min Submit readings
GET /api/mlat/state 200/min Get scanner/target state
GET/POST /api/mlat/config 200/min Get/set MLAT config
POST /api/mlat/clear 200/min Clear all data
OTA
POST /api/ota/deploy 10/min Push OTA update
POST /api/ota/upload 5/min Upload firmware
GET /api/ota/firmware 200/min List firmware
GET /api/ota/firmware/<file> 200/min Download firmware
GET /api/ota/fw/<file> Short URL (ESP32)
DELETE /api/ota/firmware/<file> 200/min Delete firmware
Build
GET /api/ota/build/defaults 200/min Build defaults
POST /api/ota/build/start 3/min Start build
GET /api/ota/build/status 200/min Build state
GET /api/ota/build/log 200/min Build log
CAN
GET /api/can/frames 200/min Get CAN frames
GET /api/can/stats 200/min CAN statistics
GET /api/can/frames/export 200/min Export CSV
Monitor
GET /api/monitor/ports 200/min List serial ports
POST /api/monitor/ports/map 200/min Set port mapping
GET /api/monitor/stream/<port> SSE serial stream
POST /api/monitor/stop/<port> 200/min Stop monitoring
Honeypot
GET /api/hp/events 200/min Event list
GET /api/hp/sessions 200/min Sessions
GET /api/hp/credentials 200/min Credentials
GET /api/hp/alerts 200/min Alerts
Stats
GET /api/stats 200/min Server statistics