API Overview
The dockmesh API is a plain REST + JSON interface served at /api/v1/. The same API powers the SvelteKit UI — everything you can do in the browser, you can do over HTTP.
Base URL
Section titled “Base URL”https://dockmesh.example.com/api/v1/Behind the scenes, Caddy (or your own reverse proxy) terminates TLS and proxies to the Go server. In dev, http://localhost:8080/api/v1/ works.
Authentication
Section titled “Authentication”Three authentication options:
JWT (for users)
Section titled “JWT (for users)”Exchange username+password (or an SSO callback) for a short-lived access token (15-minute JWT) and a refresh token. Refresh-token lifetime comes from the server’s session settings (defaults: 24h absolute, 60-min idle, 14-day remember-me — configurable under Authentication → Sessions & sign-in flow). Send the access token as Authorization: Bearer <token> on every request.
# Logincurl -X POST https://dockmesh.example.com/api/v1/auth/login \ -H 'Content-Type: application/json' \ -d '{"username":"admin","password":"••••"}'
# Response{ "access_token": "eyJhbGci...", "refresh_token": "eyJhbGci...", "user": { "id": "…", "username": "admin", "role": "admin" }}
# Use itcurl https://dockmesh.example.com/api/v1/stacks \ -H 'Authorization: Bearer eyJhbGci...'Refresh with POST /api/v1/auth/refresh { "refresh_token": "..." } before the access token expires. The refresh path rotates the refresh token (token-family rotation): capture and store the new pair every time.
API tokens (for CI and scripts)
Section titled “API tokens (for CI and scripts)”User Profile → API tokens → New token creates a token tied to your account. Choose an expiry at creation; revoke from the same UI when no longer needed. Send it like any JWT: Authorization: Bearer <token>. API tokens inherit your role + scope tags — issue tokens from a scoped service-account user so the token can’t reach more than the job needs.
mTLS (for agents)
Section titled “mTLS (for agents)”Agents authenticate with their client certificate on the dedicated mTLS listener (:8443). Not used for general REST traffic — REST goes through the HTTP listener on :8080 with JWT or API-token auth as above.
Request format
Section titled “Request format”All endpoints accept and return JSON. Set Content-Type: application/json on POST/PUT/PATCH.
Response shapes
Section titled “Response shapes”dockmesh uses bare arrays / objects, not a wrapping envelope. A list endpoint returns the array directly:
[ { "name": "monitoring", ... }, { "name": "gitea", ... }]Multi-host list endpoints (containers, images, etc.) return a FanOutResponse wrapper when called with ?host=all:
{ "items": [ ... ], "unreachable_hosts": [ { "host_id": "prod-eu-2", "error": "agent offline" } ]}Use ?host=<id> for single-host (bare-array) responses. List endpoints don’t implement page/per_page/sort/filter today — fetches are unpaginated up to a backend-side cap of 200–1000 entries depending on the resource.
Errors
Section titled “Errors”Errors use a flat shape and an appropriate HTTP status:
{ "error": "pull access denied for private/image" }| Status | Meaning |
|---|---|
| 400 | Validation error (bad input) |
| 401 | No / invalid auth |
| 403 | Authenticated but forbidden by RBAC |
| 404 | Resource not found |
| 409 | Conflict (e.g. stack already exists) |
| 422 | Unprocessable (e.g. compose.yaml failed to parse) |
| 423 | Locked — used when an account is in cooldown after failed logins |
| 500 | Server error — see req_id from journalctl -u dockmesh for correlation |
Every response carries an X-Request-Id header (the chi middleware’s id). journalctl -u dockmesh | grep <id> lines up the server-side log entry with the client-side error.
Rate limits
Section titled “Rate limits”There is no general per-route rate limiter today. The only rate limit in place is brute-force protection on /auth/login (configurable via the auth.lockout_max_attempts and auth.lockout_duration_minutes policy settings — defaults 5 attempts / 15-minute lockout per account). Other endpoints have no limiter and don’t return 429.
If you need a global rate limit (untrusted clients, public-internet exposure), put dockmesh behind a reverse proxy with rate-limiting (Caddy, Traefik, nginx) and tune there.
WebSockets
Section titled “WebSockets”dockmesh streams several live feeds over WebSocket. Browser clients can’t set custom headers on a WebSocket upgrade, so dockmesh uses a ticket flow: POST /api/v1/ws/ticket with your Bearer token, receive a single-use ticket, append it to the WebSocket URL as ?ticket=<ticket>.
| Endpoint | Streams |
|---|---|
/api/v1/ws/logs/{container-id} | Live container logs |
/api/v1/ws/stats/{container-id} | Live CPU / memory / network / I/O |
/api/v1/ws/exec/{container-id} | Interactive shell |
/api/v1/ws/events | Docker daemon events + dockmesh lifecycle events |
# Get a ticketTICKET=$(curl -s -X POST https://dockmesh.example.com/api/v1/ws/ticket \ -H "Authorization: Bearer $TOKEN" | jq -r .ticket)# Dial the WebSocketwebsocat "wss://dockmesh.example.com/api/v1/ws/events?ticket=$TICKET"CLI / server-side clients can skip the ticket and set Authorization: Bearer … on the upgrade if their HTTP client supports it (dmctl uses the ticket path for parity with the browser).
OpenAPI spec + Swagger UI
Section titled “OpenAPI spec + Swagger UI”The machine-readable OpenAPI 3.1 spec is served at three paths — all public (no Bearer token needed) so consumers can integrate without first getting credentials:
GET /api/v1/openapi.json— JSON, 5-minute cache, ready foropenapi-typescript,oapi-codegen, Swagger Codegen,openapi-generator-cli, etc.GET /api/v1/openapi.yaml— raw YAML — whatspectral/redoclylinters preferGET /api/v1/docs— rendered Swagger UI with “Try it out” wired up against the live server. Log in separately in Swagger UI’s Authorize button (paste any Bearer token you already have).
Point any generator at openapi.json:
# Generate a TypeScript clientnpx openapi-typescript https://dockmesh.example.com/api/v1/openapi.json \ -o ./dockmesh-api.d.ts
# Or a Go clientoapi-codegen -generate client \ -o dockmesh_client.go -package dockmesh \ https://dockmesh.example.com/api/v1/openapi.jsonThe spec is hand-maintained at internal/api/openapi/openapi.yaml in the dockmesh repo. A drift test (TestOpenAPIDriftAgainstRoutes) fails CI if a route is registered without a matching spec entry (or vice versa), so the documentation and the running server never diverge — that’s the mechanical guarantee, not a process promise.
See also
Section titled “See also”- Quick Start — if you’d rather click than curl
- RBAC & Roles — how tokens inherit permissions