Skip to content

API Reference

Daniel Heinen edited this page May 9, 2026 · 1 revision

API Reference

ankerctl exposes a REST API and WebSocket endpoints from the Flask web server. The default address is 127.0.0.1:4470.

All endpoints are served by web/__init__.py. For implementation details (which service handles each route, how state is mutated) see Architecture.

Authentication

When no API key is set, all endpoints are public (default, backwards compatible). When an API key is set:

  • GET requests are unauthenticated by default
  • POST / DELETE requests always require auth
  • A small allow-list of GET paths also requires auth (see Protected GET paths below)
  • Setup paths (/api/ankerctl/config/upload, /api/ankerctl/config/login) are exempt when no printer is configured yet
  • All /api/debug/* paths require auth (prefix match)

Three ways to authenticate:

Method Use it for
X-Api-Key: <key> header Slicers (PrusaSlicer, OrcaSlicer fill this from the API Key field)
?apikey=<key> URL parameter Browser bootstrap; sets a session cookie automatically
Session cookie Browser, after first parameter-based auth

Protected GET paths

These GET endpoints require auth even though they are reads:

  • /api/ankerctl/server/reload
  • /api/debug/state, /api/debug/logs, /api/debug/services (and all other /api/debug/*)
  • /api/settings/mqtt
  • /api/notifications/settings

REST endpoints

General

Method Path Auth Description
GET / No Render main UI page
GET /api/health No Liveness probe — always returns {"status": "ok"}
GET /api/version No OctoPrint-compatible version info
GET /video No Raw H.264 video stream (use ?for_timelapse=1 for timelapse client)

Configuration

Method Path Auth Description
POST /api/ankerctl/config/upload Setup-exempt Upload login.json file
POST /api/ankerctl/config/login Setup-exempt Email/password login
GET /api/ankerctl/server/reload Yes Reload config + restart all services
POST /api/ankerctl/config/upload-rate Yes Set upload rate; form field upload_rate_mbps (5/10/25/50/100)

Printer control

Method Path Auth Description
POST /api/printer/gcode Yes Send G-code; JSON {"gcode": "G28"}. Motion blocked while printing (returns 409).
POST /api/printer/control Yes Print state control; JSON {"value": N} — see table below
POST /api/printer/autolevel Yes Start auto-leveling (G29); blocked while printing
GET /api/printer/bed-leveling No Read 7×7 bed level grid via M420 V (~15 s)
GET /api/printer/bed-leveling/last No Most recently saved bed level grid from log dir
GET /api/snapshot No Capture a JPEG snapshot via ffmpeg from /video; returns file download

Print Control values (POST /api/printer/control, JSON body {"value": N}):

N Action
2 Pause
3 Resume
4 Stop / cancel
0 Restart from beginning

Notifications

Method Path Auth Description
GET /api/notifications/settings Yes Return current Apprise config (contains API keys)
POST /api/notifications/settings Yes Update Apprise config; JSON {"apprise": {...}}
POST /api/notifications/test Yes Send test notification; optionally pass {"apprise": {...}} to override settings

Settings

Method Path Auth Description
GET /api/settings/timelapse No Return current timelapse config
POST /api/settings/timelapse Yes Update timelapse config; JSON {"timelapse": {...}}; auto-reloads service
GET /api/settings/mqtt Yes Return current Home Assistant MQTT config (contains broker password)
POST /api/settings/mqtt Yes Update HA MQTT config; JSON {"home_assistant": {...}}; auto-reloads service

Print history

Method Path Auth Description
GET /api/history No List entries; query ?limit= (default 50), ?offset= (default 0). Returns {"entries": [...], "total": N}
DELETE /api/history Yes Clear all history entries

Timelapse

Method Path Auth Description
GET /api/timelapses No List videos; returns {"videos": [{filename, size_bytes, created_at}], "enabled": bool}
GET /api/timelapse/<filename> No Download a video file (MP4)
DELETE /api/timelapse/<filename> Yes Delete a video

Filament profiles

Method Path Auth Description
GET /api/filaments No List all filament profiles
POST /api/filaments Yes Create a profile; JSON body with profile fields
PUT /api/filaments/<id> Yes Update a profile; JSON body with fields to change
DELETE /api/filaments/<id> Yes Delete a profile
POST /api/filaments/<id>/apply Yes Send M104 / M140 to printer with profile temperatures
POST /api/filaments/<id>/duplicate Yes Duplicate profile (appends (copy) to name)

Printer selector

Method Path Auth Description
GET /api/printers No List configured printers + active_index + locked flag
POST /api/printers/active Yes Switch active printer; JSON {"index": N}. Blocked during print (409) or when PRINTER_INDEX env var is set (403).

Slicer integration

Method Path Auth Description
POST /api/files/local Yes OctoPrint-compatible file upload; form fields: file (G-code), print (bool)

This endpoint is the one PrusaSlicer / OrcaSlicer / SuperSlicer / Bambu Studio use when configured as a print host.

WebSocket endpoints

Path Direction Description
/ws/mqtt Server → Client Raw MQTT message stream (JSON objects from MqttQueue)
/ws/video Server → Client Raw H.264 video frames
/ws/pppp-state Server → Client PPPP connection status ({"status": "connected"|"disconnected"})
/ws/upload Server → Client File upload progress events from FileTransferService
/ws/ctrl Bidirectional Light, video quality, video enable/disable. Inline auth — see below.

/ws/ctrl inline auth

before_request does not fire for WebSocket routes, so /ws/ctrl enforces auth inline: when ANKERCTL_API_KEY is set, the first message must include the key. The endpoint accepts JSON commands like:

{"light": true}
{"light": false}
{"video_profile": "sd"}
{"video_profile": "hd"}
{"video_enabled": true}
{"video_enabled": false}

Available video profiles:

ID Resolution
sd 848 × 480
hd 1280 × 720 (default)
fhd 1920 × 1080 (snapshot only — too high for live stream)

Debug endpoints (ANKERCTL_DEV_MODE=true only)

Method Path Auth Description
GET /api/debug/state Yes MqttQueue internal state JSON
POST /api/debug/config Yes Set debug logging; JSON {"debug_logging": bool}
POST /api/debug/simulate Yes Fire simulated event; JSON {"type": "...", "payload": {...}}
GET /api/debug/services Yes Service health summary (state, refs, type)
POST /api/debug/services/<name>/restart Yes Restart a named service asynchronously
GET /api/debug/logs Yes List log files in ANKERCTL_LOG_DIR
GET /api/debug/logs/<filename> Yes Tail log file; query ?lines=N (default 500). Path-traversal protected.
GET /api/debug/bed-leveling Yes Same as /api/printer/bed-leveling (debug alias)

Simulation event types (POST /api/debug/simulate)

type Payload fields Description
start filename Simulate print start
finish filename Simulate print finish
fail filename Simulate print failure
progress progress (0–100), filename, elapsed, remaining Emit fake progress
temperature temp_type (nozzle/bed), current, target (1/100 °C) Emit fake temperature
speed speed (mm/s) Emit fake speed
layer current_layer, total_layers Emit fake layer notification

Examples

Send a G-code

curl -X POST http://localhost:4470/api/printer/gcode \
     -H "X-Api-Key: my-secret-key" \
     -H "Content-Type: application/json" \
     -d '{"gcode": "G28"}'

Pause the active print

curl -X POST http://localhost:4470/api/printer/control \
     -H "X-Api-Key: my-secret-key" \
     -H "Content-Type: application/json" \
     -d '{"value": 2}'

Switch active printer

curl -X POST http://localhost:4470/api/printers/active \
     -H "X-Api-Key: my-secret-key" \
     -H "Content-Type: application/json" \
     -d '{"index": 1}'

Returns 409 if a print is active, 403 if PRINTER_INDEX env var is set.

Upload G-code from a slicer

curl -X POST http://localhost:4470/api/files/local \
     -H "X-Api-Key: my-secret-key" \
     -F "file=@my.gcode" \
     -F "print=true"

List timelapse videos

curl http://localhost:4470/api/timelapses
{
  "enabled": true,
  "videos": [
    {"filename": "my-print_20260509_120000.mp4", "size_bytes": 1234567, "created_at": "2026-05-09T12:00:00Z"}
  ]
}

Subscribe to live MQTT events (browser console)

const ws = new WebSocket("ws://localhost:4470/ws/mqtt");
ws.onmessage = (e) => console.log(JSON.parse(e.data));

Status codes

ankerctl follows standard HTTP semantics with a few project-specific cases:

Code When
200 Success
400 Bad request body / invalid JSON
401 Auth required and missing/invalid
403 Operation forbidden — typically POST /api/printers/active while PRINTER_INDEX is set
404 Unknown route or unknown filament/timelapse ID
409 Conflict — e.g. switching printers or sending motion G-code while printing
413 Upload exceeds UPLOAD_MAX_MB
500 Server error (check logs)

CORS

CORS is not enabled by default. The web UI and the Flask backend are served from the same origin, so cross-origin XHR is not needed. To embed the API in another web app, run a reverse proxy (nginx / Caddy) that handles CORS headers.

Clone this wiki locally