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)¶
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¶
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)¶
Server-Sent Events stream. Pushes device list every 3 seconds.
Event format:
Commands¶
Send Command¶
Dispatch a command to one or more devices.
Rate limit: 60/minute
Request body:
| 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:
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¶
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¶
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¶
Scans IMAGE_DIR for camera frames and returns camera list with recording status.
Response:
Start Recording¶
Start MJPEG video recording for a camera.
Response:
Stop Recording¶
Stop recording and finalize video file.
Response:
Recording Status¶
Check recording state.
List Recordings¶
List completed video recordings.
Response:
MLAT (Multilateration)¶
Submit Readings¶
Submit scanner RSSI readings. Accepts raw text body.
Request body (text/plain):
Format: SCANNER_ID;(lat,lon);rssi
Response:
Get 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¶
POST body:
| 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¶
Reset all scanner and target data.
OTA (Firmware Updates)¶
Deploy Firmware¶
Push OTA update to one or more devices.
Rate limit: 10/minute
Request body:
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¶
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:
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¶
List uploaded firmware files.
Response:
Serve Firmware¶
Download firmware binary (authenticated).
Short URL for ESP32 OTA fetch. Accepts session, bearer, or ?token= parameter.
Delete Firmware¶
Remove uploaded firmware file.
Build (Firmware Builder)¶
Get 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¶
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):
Response (already building):
(HTTP 409)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¶
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¶
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¶
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¶
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¶
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¶
Map serial ports to device IDs.
Request body:
Stream Serial Output (SSE)¶
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¶
Stop serial monitoring for a port.
Honeypot¶
Optional Module
Honeypot API is only available if hp_dashboard is configured.
List Events¶
Paginated honeypot event list.
List Sessions¶
Tracked attacker sessions.
Captured Credentials¶
Username/password pairs captured by honeypot services.
Alert State¶
Current alert rules and triggered alerts.
Statistics¶
Server 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:
| 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 |