API Reference¶
BamDude exposes a versioned REST API at /api/v1 plus a WebSocket channel for realtime printer events. Everything the web UI does is also available to your scripts and integrations.
Quick start¶
- Base URL:
https://<your-bamdude-host>/api/v1 - Interactive docs:
/docs(Swagger UI) and/redoc(ReDoc) - OpenAPI schema:
/openapi.json— drop into Postman, Insomnia, or any OpenAPI-compatible client - Realtime channel:
wss://<your-bamdude-host>/api/v1/ws
All endpoints return JSON unless explicitly noted (camera streams, 3MF downloads, and thumbnails return binary). Errors follow the FastAPI shape:
422 validation errors return arrays of field-level issues:
{
"detail": [
{ "loc": ["body", "name"], "msg": "field required", "type": "value_error.missing" }
]
}
Use /docs first
The interactive Swagger UI is generated from the live server, so it always reflects the routes, schemas, and required permissions of the version you're running. Treat this page as orientation; treat /docs as ground truth.
Authentication methods¶
BamDude supports two authentication mechanisms. Both enforce the same permission checks. API keys are matched first; if neither header is present, the request falls through to JWT.
Generate keys in Settings → System → API Keys. They look like bb_<random_token> and never auto-expire — revoke them per-key when no longer needed.
Send via either header:
# Preferred: dedicated header
curl -H "X-API-Key: bb_abc123..." \
https://bamdude.example.com/api/v1/printers/
# Equivalent: bearer scheme
curl -H "Authorization: Bearer bb_abc123..." \
https://bamdude.example.com/api/v1/printers/
Each key carries its own permission set — a subset of the issuing user's permissions. Revoking a user revokes their keys.
The browser flow:
curl -X POST -H "Content-Type: application/json" \
-d '{"username": "admin", "password": "...", "remember_me": true}' \
https://bamdude.example.com/api/v1/auth/login
Response:
{
"access_token": "eyJhbGc...",
"token_type": "bearer",
"requires_2fa": false,
"user": { "id": 1, "username": "admin", "...": "..." }
}
Send the access token via Authorization: Bearer <jwt>. Access tokens live 1 hour. A refresh token is set as an HttpOnly cookie (bamdude_refresh) on the path /api/v1/auth — call POST /api/v1/auth/refresh to mint a new access token transparently. Cookie attributes:
| Attribute | Value |
|---|---|
| Path | /api/v1/auth (must be preserved by your client) |
| HttpOnly | yes — never exposed to JavaScript |
| SameSite | Lax |
| Secure | auto-detected from request scheme; honors X-Forwarded-Proto behind a trusted proxy. Force with the AUTH_REFRESH_COOKIE_SECURE env var. |
| Max-Age | 30 days when remember_me=true; otherwise session cookie + 12 h DB lifetime |
2FA flow
When requires_2fa: true, the login response also includes pre_auth_token and a 2FA challenge cookie. POST it to /api/v1/auth/2fa/verify together with the user's TOTP code (or a backup code) to obtain the access + refresh tokens.
OIDC SSO follows the same pattern via /api/v1/auth/oidc/exchange (PKCE S256 + state + nonce).
Rate limiting¶
BamDude rate-limits authentication endpoints to slow down credential stuffing. Other endpoints are not rate-limited at the API layer — front them with a reverse proxy or firewall if you need a hard ceiling.
| Endpoint | Per-user / email | Per-IP |
|---|---|---|
POST /auth/login |
10 / 15 min per username | 20 / 15 min |
POST /auth/forgot-password |
3 / 15 min per email | 10 / 15 min |
When the limit trips you receive 429 {"detail": "..."} and a Retry-After header.
Behind a reverse proxy
Set TRUSTED_PROXY_IPS (comma-separated trusted hops) so rate-limiting reads the real client IP from X-Forwarded-For instead of the proxy's IP. See Reverse Proxy & HTTPS for the full nginx / Caddy / Traefik recipes.
Setup gate¶
On first boot the server only accepts three endpoints. Every other request returns 503 {"detail": "setup_required"} until an admin is created.
| Endpoint | Purpose |
|---|---|
GET /api/v1/auth/status |
Returns {is_setup, requires_setup, ...} so installers can detect the empty-DB state. |
POST /api/v1/auth/setup |
One-shot: creates the initial admin and returns the access + refresh tokens. |
GET /api/v1/system/health |
Liveness probe (always whitelisted). |
Once setup completes the gate disables itself in-process; you don't need to restart.
Lost all admins?
Run python -m backend.app.cli reset_admin on the server to clear the setup flags, then visit the UI to re-enter the setup flow. See Authentication recovery for the full recovery protocol.
Permissions¶
Every endpoint is gated by RequirePermission(Permission.X) where X follows the resource:action pattern. There are 80+ permissions defined in backend/app/core/permissions.py. Common ones:
| Resource | Examples |
|---|---|
| Printers | printers:read, printers:control, printers:create, printers:delete, printers:files, printers:clear_plate |
| Archives | archives:read, archives:create, archives:update_own, archives:update_all, archives:delete_own, archives:delete_all, archives:reprint_own, archives:reprint_all |
| Library | library:read, library:upload, library:update_own, library:delete_all, library:notes_write |
| Queue | queue:read, queue:create, queue:update_all, queue:delete_all, queue:reorder |
| Users | users:read, users:create, users:update, users:delete |
| Settings | settings:read, settings:update, settings:backup, settings:restore |
| Camera | camera:view |
Three default groups cover most setups:
- Administrators — every permission.
- Operators — full control of printers, queue, archives, library; no settings/users administration.
- Viewers — read-only.
Create custom groups for granular control. The interactive /docs browser shows the required permission for each endpoint.
Endpoint groups¶
The 43 route modules under backend/app/api/routes/ are registered under the /api/v1 prefix. Major groups at a glance:
| Prefix | What it does | Notable endpoints |
|---|---|---|
/auth/* |
Login, refresh, setup, OIDC | login, refresh, logout, setup, 2fa/verify, oidc/exchange, forgot-password |
/users/*, /groups/*, /api-keys/*, /mfa/* |
Users, groups, API keys, MFA enrollment | CRUD, group assignment, MFA reset, backup codes |
/printers/*, /printer-queues/*, /cloud/*, /discovery/* |
Printer + AMS + Bambu Cloud | status, control, AMS RFID, snapshot, stream-token, network discovery |
/archives/* |
Print history | list, get, reprint, delete, retry-download, cleanup/preview, cleanup/run, cleanup/status |
/queue/*, /background-dispatch/* |
Queue management + dispatch | add, reorder, cancel, set-status, dispatch state |
/library/*, /library-notes/* |
File manager | upload, list, delete, tags — also the inbox for virtual-printer uploads |
/projects/* |
Project grouping | CRUD, print plan, archives by project |
/macros/* |
G-code + MQTT-action macros | CRUD, execute |
/notifications/*, /notification-templates/*, /user-notifications/*, /telegram/* |
Outbound channels | provider CRUD, template overrides, test send, Telegram bot config |
/spoolman/*, /inventory/* |
Spool tracking | sync, slot mapping, color/spool catalog |
/smart-plugs/* |
Smart plug config | CRUD, energy snapshots, manual on/off |
/system/*, /support/*, /updates/*, /firmware/* |
Health + diagnostics + updates | health, settings, backup, restore, debug bundle, firmware check |
/local-backup/*, /git-backup/* |
Backup providers | run, restore, schedule |
/maintenance/*, /kprofiles/* |
Service tracking + K-profiles | log, due, CRUD |
/external-links/*, /ams-history/* |
Misc UX | dashboard links, AMS slot change history |
/metrics, /webhook/*, /obico/*, /virtual-printers/* |
Integrations | Prometheus metrics, inbound webhooks, Obico AI, virtual printer (slicer target) |
The full enumerated list is in /docs — this table just shows where to look.
Camera streams and binary endpoints¶
Some endpoints can't accept Authorization headers because they're consumed by <img> / <video> tags. They use a short-lived stream-token (60 min TTL) passed as a query parameter.
# 1. Mint a token (auth required)
TOKEN=$(curl -s -H "X-API-Key: bb_..." \
-X POST https://bamdude.example.com/api/v1/printers/camera/stream-token \
| jq -r .token)
# 2. Use it on binary endpoints
curl "https://bamdude.example.com/api/v1/printers/2/camera/snapshot?token=$TOKEN" -o snap.jpg
Endpoints behind the stream-token gate:
| Endpoint | Returns |
|---|---|
GET /printers/{id}/camera/stream?token=... |
MJPEG stream |
GET /printers/{id}/camera/snapshot?token=... |
JPEG snapshot |
GET /printers/{id}/cover?token=... |
Current print cover thumbnail (served from local archive — never triggers an FTP fetch) |
GET /printers/{id}/camera/plate-detection/references/{index}/thumbnail?token=... |
Calibration-reference thumbnail used by plate-clear detection ({index} selects which stored reference). |
GET /obico/cached-frame/{nonce} |
Frame URL handed to the Obico ML API. Whitelisted in the auth middleware because Obico's GET can't carry a bearer header — the nonce itself is the capability. |
The web UI keeps the stream token cached per session and refreshes it before expiry.
Webhooks and realtime events¶
BamDude does not expose outbound webhooks for application events. Use a notification provider (Telegram, Discord, ntfy, Pushover, Email, Home Assistant) when you need a one-way push.
The realtime channel is the WebSocket at wss://<host>/api/v1/ws. It carries:
- Printer status updates (temps, progress, AMS state)
- Dispatch and queue progress
- Archive create / update events
- Smart-plug state and energy ticks
WebSocket is currently unauthenticated
/api/v1/ws is in the auth middleware's public-route allowlist (backend/app/main.py::PUBLIC_API_ROUTES) and the handler performs no token check. Anyone able to reach the host on the WebSocket port can subscribe to realtime events. Treat the realtime channel as read-only and intra-network — front BamDude with a reverse proxy (see Reverse Proxy & HTTPS) and don't expose /ws directly to the public internet. Tightening this is tracked work; do not assume Authorization: Bearer will block subscribers today.
Inbound automation endpoints¶
The /webhook/* group is the other direction: endpoints your automation calls, authenticated with an API key. They are the five listed in Settings → API keys.
| Endpoint | Scope needed | What it does |
|---|---|---|
GET /api/v1/webhook/queue |
can_read_status |
Queue status for every printer, or one via ?printer_id= |
GET /api/v1/webhook/printer/{id}/status |
can_read_status |
Connection, state, current print, progress, remaining time |
POST /api/v1/webhook/printer/{id}/start |
can_control_printer |
Start the next queued print on that printer |
POST /api/v1/webhook/printer/{id}/stop |
can_control_printer |
Stop the running print |
POST /api/v1/webhook/printer/{id}/cancel |
can_control_printer |
Cancel a running or paused print |
A key scoped to specific printers (printer_ids) is refused with 403 on any other printer. A printer that is not connected answers 503, not an error — retry when it comes back. start answers 404 when the queue is empty.
curl -H "X-API-Key: $KEY" https://<host>/api/v1/webhook/printer/3/status
curl -X POST -H "X-API-Key: $KEY" https://<host>/api/v1/webhook/printer/3/start
There is no add-to-queue endpoint here
Queueing goes through POST /api/v1/queue/ with an archive_id, a library_file_id, or a readable existing queue row as source_queue_item_id when copying a saved job. The webhook group used to carry its own version; it built the queue row by hand and so skipped the paused-queue check, the printer-model compatibility gate and the queue counters. One way in is the point.
Common operations — quick recipes¶
List the last 50 archives for a printer¶
curl -H "X-API-Key: bb_..." \
"https://bamdude.example.com/api/v1/archives/?printer_id=2&page=1&per_page=50"
Add a library file to the queue (3 copies, fixed AMS mapping)¶
curl -X POST \
-H "X-API-Key: bb_..." \
-H "Content-Type: application/json" \
-d '{
"library_file_id": 42,
"queue_id": 2,
"ams_mapping": [0, 1, 2, 3],
"quantity": 3
}' \
"https://bamdude.example.com/api/v1/queue/"
Get current printer status¶
Skip specific objects mid-print¶
The body is a JSON array of object IDs reported by the slicer.
curl -X POST \
-H "X-API-Key: bb_..." \
-H "Content-Type: application/json" \
-d '[100, 200]' \
"https://bamdude.example.com/api/v1/printers/2/print/skip-objects"
Recover a missing 3MF for an archive¶
When on_print_start couldn't FTP the 3MF (printer unreachable, FTP timeout) the archive row is created with extra_data.no_3mf_available = true. Background sweeps retry automatically; you can also trigger it manually:
curl -X POST \
-H "X-API-Key: bb_..." \
"https://bamdude.example.com/api/v1/archives/123/retry-download"
Trigger archive 3MF cleanup (preview, then run)¶
The cleanup job removes 3MF binaries for archives older than the retention window (the metadata row is kept). The daily cron runs automatically; for an ad-hoc sweep:
# Dry-run preview — what would be deleted, total bytes
curl -H "X-API-Key: bb_..." \
"https://bamdude.example.com/api/v1/archives/cleanup/preview"
# Run it
curl -X POST -H "X-API-Key: bb_..." \
"https://bamdude.example.com/api/v1/archives/cleanup/run"
# Inspect the daily cron's last run + next run
curl -H "X-API-Key: bb_..." \
"https://bamdude.example.com/api/v1/archives/cleanup/status"
Refresh a JWT session from your own client¶
# /auth/refresh reads the HttpOnly bamdude_refresh cookie set during login.
# --cookie-jar / --cookie persists it across calls.
curl -c cookies.txt -X POST \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "..."}' \
https://bamdude.example.com/api/v1/auth/login
curl -b cookies.txt -c cookies.txt -X POST \
https://bamdude.example.com/api/v1/auth/refresh
The refresh response returns a new access token and rotates the refresh cookie in place. Replaying an already-used refresh token revokes the entire token family across devices (OWASP reuse detection).
Versioning and stability¶
- The API version prefix is
/api/v1. Breaking changes will ship under/api/v2rather than mutating v1. - Additive changes (new endpoints, new optional fields) land in patch / minor releases without notice. Pin the BamDude container version if you depend on response shape stability.
- Deprecations are announced in the changelog at least one minor release before removal.
Originally based on Bambuddy.