Skip to content

API Reference

Traefik Manager exposes a REST API used by the web UI and official mobile app.

Interactive reference

Every TM instance has a built-in API reference with live Try It at /api, or the API button in the top bar. Requests go to your own instance with your session already authenticated.


Authentication

API key (recommended) - generate a key in Settings → Authentication → API Keys and pass it as a header. API keys bypass CSRF checks entirely.

X-Api-Key: your-api-key

Session cookie - log in via the web UI. The browser session cookie is used automatically.

When authentication fails

Every /api/ endpoint except GET /api/health answers an unauthenticated or expired request with 401 and a JSON body. It never redirects.

json
{ "ok": false, "error": "Not authenticated", "auth_required": true }

Treat 401 on any /api/ path as "log in again", not as an empty result. Sessions expire on inactivity (INACTIVITY_TIMEOUT_MINUTES, default 120), so a long-lived script on session auth will start getting 401 even though it authenticated earlier. API keys do not expire.

Page routes (everything outside /api/) still redirect to /login as a browser expects.

Changed in v1.10.1

Before v1.10.1, /api/ paths also redirected to /login, which returned the login page's HTML with status 200. Clients could not distinguish "logged out" from "no data". If you parsed those responses, switch to checking for 401.


Response format

All /api/ endpoints return JSON. The form endpoints POST /save, POST /delete/{id}, POST /save-middleware and POST /delete-middleware/{name} return JSON only when the request sends X-Requested-With: fetch; otherwise they redirect (302) to the UI.

OutcomeShape
Success{ "ok": true } or { "success": true }
Error{ "ok": false, "message": "..." } or { "error": "..." }

Common status codes:

CodeMeaning
400Invalid or missing parameters
401Not authenticated, or the session expired
403CSRF token missing or invalid
404Object not found
429Rate limit exceeded
502An upstream (Traefik, an agent, CrowdSec, a remote repo) could not be reached

State-changing endpoints (POST / PUT / DELETE / PATCH) require an X-CSRF-Token header when using session auth. API key requests skip this.

Caching

Responses are sent with Cache-Control: no-store, no-cache, must-revalidate by default. Two things keep their own caching: anything under /static/, and any endpoint that sets the header itself - GET /api/dashboard/icon/<slug> serves icons with max-age=86400.


Routes & Middlewares

GET /api/routes

All managed routes and middlewares from every loaded config file.

Response

json
{
  "apps": [ /* Route[] */ ],
  "middlewares": [ /* Middleware[] */ ],
  "configErrors": [ { "file": "dynamic.yml", "error": "..." } ],
  "services": { "http": ["app-service"], "tcp": [], "udp": [] }
}

services lists the service names defined in the config files, per protocol - what serviceRef accepts.

When multiple config files are loaded, route id is prefixed as configFile::name. Strip the prefix before using the name as a YAML key.

Route object

FieldTypeDescription
idstringUnique identifier
namestringRouter name
service_namestringService the router points at, as written (may be svc@docker)
enabledboolean
protocolstringhttp, tcp, or udp
rulestringTraefik rule expression
entryPointsstring[]Router entrypoints
targetstringFirst backend. Kept for backwards compatibility; same as servers[0]
serversstring[]All backends. URLs for HTTP, host:port for TCP and UDP
stickyobjectloadBalancer.sticky.cookie, or {} when off. HTTP only
stickyEnabledbooleanWhether a sticky block is present. HTTP only
healthCheckobjectloadBalancer.healthCheck, or {} when unset. HTTP only
priorityinteger | nullRouter priority, null when unset. HTTP and TCP only
middlewaresstring[]Applied middleware names
tlsboolean | object | nullBoolean for HTTP and UDP. For TCP it is the router's tls mapping (e.g. {"passthrough": true}), or null when TLS is not set
certResolverstringACME resolver name, or empty for external certs
configFilestringSource config file
providerstringfile for managed routes, otherwise the Traefik provider it was discovered from

HTTP routes also carry passHostHeader, tlsDomains, tlsOptionsProfile, insecureSkipVerify, streaming and serviceType.


GET /api/routes/all

Same shape as GET /api/routes, minus configErrors, and nothing is filtered out: routes from other Traefik providers (Docker, Kubernetes, and the rest) are enriched from the live Traefik API, and Traefik's own @internal routers are included.

Use this for everything Traefik is serving, and /api/routes for only what this instance manages in its own config files.


POST /save

Create or update a route. Accepts application/x-www-form-urlencoded.

FieldTypeDescription
serviceNamestringRoute name. Required
protocolstringhttp, tcp, or udp (default: http)
subdomainstringHostname. A value containing a dot is used as-is; a bare label is combined with the configured domain(s). Becomes Host() for HTTP and HostSNI() for TCP
domainsstringRepeatable. Domains used to build the rule. Defaults to the first configured domain
httpRule / tcpRulestringRaw Traefik rule expression. Overrides subdomain
targetIpstringBackend host, repeated per protocol - see below. Ignored when the matching backendsJson* field is sent
targetPortstringBackend port, repeated per protocol
serviceRefstringReference an existing service instead of creating <name>-service. Writes only the router; target and load-balancing fields are ignored. A bare name must exist in the file config for that protocol (400 otherwise); a provider-qualified name (svc@docker) is written verbatim
backendsJsonHttpstring (JSON)HTTP service definition - see Multiple backends below
backendsJsonTcpstring (JSON)TCP service definition
backendsJsonUdpstring (JSON)UDP service definition
entryPointsstringComma-separated, repeated per protocol. HTTP defaults to https. UDP uses udpEntryPoint instead
middlewaresstringComma-separated middleware names. HTTP only; TCP uses middlewaresTcp
schemestringhttp or https (default: http). HTTP only
passHostHeaderbooleanSend true to keep the host header. Omitting the field writes passHostHeader: false into the service
insecureSkipVerifybooleantrue writes a <serviceName>-transport serversTransport that skips backend certificate checks
certResolverstringACME resolver name, repeated per protocol. Use none to write tls: {} with no resolver (external certs), or __disabled__ to write no tls block at all
useTls / tlsPassthroughbooleanTCP only. tlsPassthrough wins and writes tls: {passthrough: true}
tlsWildcardMainstringMain domain for tls.domains (e.g. example.com). Use with DNS challenge resolvers for wildcard certs
tlsWildcardSansstringNewline-separated SANs for tls.domains (e.g. *.example.com)
tlsOptionsProfilestringtls.options profile name to attach to the router. HTTP only
configFilestringTarget config file (multi-config only)
agent_idstringWrite to this agent instead of the Host
isEditbooleantrue when updating an existing route
originalIdstringOriginal route ID when renaming

Multiple backends

A service can point at several servers. Send the backendsJson* field matching the protocol; it takes precedence over targetIp/targetPort, which stay supported for single-backend clients.

json
{
  "servers": [
    { "scheme": "http", "host": "192.168.1.10", "port": "8080" },
    { "scheme": "http", "host": "192.168.1.11", "port": "8080" }
  ],
  "sticky":      { "enabled": true, "cookieName": "tm_sticky", "secure": true, "httpOnly": true },
  "healthCheck": { "enabled": true, "path": "/health", "interval": "10s", "timeout": "3s" },
  "priority": 10
}
  • A host already starting with http:// or https:// is used verbatim; otherwise scheme://host:port is built.
  • Rows with an empty host are skipped. Invalid JSON falls back to targetIp/targetPort rather than failing the save.
  • healthCheck needs a path; without one the whole block is dropped. interval and timeout take a Go duration of one unit (10s, 1m, 500ms); a bare number is read as seconds, anything else is dropped.
  • A priority of 0 is treated as unset.
  • sticky, healthCheck, and priority apply to HTTP. TCP accepts servers and priority; UDP accepts servers only.
  • targetIp, targetPort, certResolver and entryPoints are repeated fields indexed by protocol - index 0 for HTTP, 1 for TCP, 2 for UDP - so a TCP save must send two targetIp values (the first may be empty). certResolver and entryPoints only use indexes 0 and 1.
  • For TCP and UDP you may instead send the joined host:port form in targetIp at that index and leave targetPort empty.

Sending backendsJson* replaces the whole service

A save whose backendsJson* yields at least one valid server row is authoritative: servers is replaced outright, and sticky or healthCheck absent from the payload are deleted. A client that edits backends must read the route first and echo sticky, healthCheck and priority back, or it will silently drop them. Omit backendsJson* to get the merge behaviour below instead.

Editing from a single-backend client

A save that omits backendsJson* on an edit replaces only the first backend. Further backends, plus sticky, healthCheck and priority, are preserved, so the mobile app and older cached pages cannot wipe a multi-backend route.

The same protection covers shared services: an edit that omits serviceRef on a router pointing at a shared or cross-provider service keeps the reference and ignores the posted target fields.


POST /delete/{route_id}

Delete a route by ID. Accepts application/x-www-form-urlencoded. Removes the router and, unless another router still uses it, its service. 404 when no config file and no disabled-route record holds that router.

ParamDescription
route_idRoute ID (path)
configFileConfig file basename (body, multi-config only)
agent_idDelete on this agent instead of the Host (body)

POST /api/routes/{route_id}/toggle

Enable or disable a route without deleting it. Config is preserved in manager.yml. Pass agent_id to toggle a route on an agent.

json
{ "enable": true, "agent_id": "" }

GET /api/routes/{route_id}/raw

The YAML for one route and its service, as stored. route_id is either the router name or file.yml::router on a multi-file install. Go template expressions are preserved rather than being expanded.

json
{ "raw": "http:\n  routers:\n    my-app:\n      ...", "configFile": "dynamic.yml", "proto": "http" }

404 if no config file contains that router.


POST /api/routes/{route_id}/raw

Replace that route's YAML. A backup is taken first, and Go templates in your content are preserved.

json
{ "content": "http:\n  routers:\n    my-app:\n      rule: Host(`app.example.com`)" }

Returns 400 for empty content or invalid YAML, 404 if the route cannot be located.


GET /api/configs

List all loaded dynamic config files.

json
{
  "files": [{ "label": "routes.yml", "path": "/config/routes.yml" }],
  "configDirSet": true
}

POST /save-middleware

Create or update a middleware. Config is provided as raw YAML. Accepts application/x-www-form-urlencoded.

FieldDescription
middlewareNameMiddleware name
middlewareContentRaw YAML body
mwProtocolhttp (default) or tcp
configFileTarget config file
agent_idWrite to this agent instead of the Host
isMwEdittrue when updating
originalMwIdOriginal ID when renaming
originalMwProtocolOriginal protocol when moving between http and tcp

POST /delete-middleware/{name}

Delete a middleware by name. Accepts application/x-www-form-urlencoded.

ParamDescription
nameMiddleware name (path)
configFileConfig file basename (body, multi-config only)
agent_idDelete on this agent instead of the Host (body)

Traefik

These endpoints proxy read-only data from the Traefik API. They require a valid Traefik API URL in settings.

GET /api/traefik/overview

Router, service, and middleware counts plus Traefik feature flags. Passes through the Traefik dashboard overview object.


GET /api/traefik/routers

All routers across HTTP, TCP, and UDP, following Traefik's pagination.

json
{ "http": [...], "tcp": [...], "udp": [...], "reachable": true }

reachable is false when no protocol could be fetched, which separates an unreachable Traefik from one with no routers.


GET /api/traefik/services

All services across HTTP, TCP, and UDP. Same shape as GET /api/traefik/routers.


GET /api/traefik/middlewares

All middlewares across HTTP and TCP.

json
{ "http": [...], "tcp": [...] }

Returns 502 with {"error": "Traefik API unreachable"} if the Traefik API cannot be reached. Earlier versions returned 200 with empty lists.


GET /api/traefik/entrypoints

All configured entrypoints.

json
[{ "name": "websecure", "address": ":443" }]

Returns 502 with {"error": "Traefik API unreachable"} if the Traefik API cannot be reached. Earlier versions returned 200 with an empty list.


GET /api/traefik/router/{protocol}/{name}

Details for a specific router. protocol is http, tcp, or udp. name is URL-encoded.


GET /api/traefik/version

Traefik version string and codename.


GET /api/traefik/ping

Ping the Traefik API and return latency.

json
{ "ok": true, "latency_ms": 3 }

Returns 503 with { "ok": false, "latency_ms": null } when the ping fails.


GET /api/traefik/plugins

List plugins defined under experimental.plugins in the static config.

json
{ "plugins": [{ "name": "crowdsec", "moduleName": "github.com/org/repo", "version": "v1.4.5", "settings": null }] }

Returns 200 with an error string and an empty list when no static config path is configured or the file is missing.


GET /api/plugins/catalog

Latest known version per plugin module, as { "plugins": { "github.com/org/repo": "vX.Y.Z" } }. Module paths are lowercased. Fetched from the Traefik plugin catalog and cached for 24 hours; returns an empty map when the catalog is unreachable, and retries after 15 minutes.


GET /api/traefik/certs

List TLS certificates from ACME (acme.json) and from tls.certificates entries in the loaded dynamic config files. ACME entries need an acme.json path, from ACME_JSON_PATH or Settings; it can be a comma-separated list or a directory.

json
{ "certs": [ ... ] }
FieldDescription
resolverACME resolver name, or file for file-provider certificates
mainPrimary domain
sansSubject alternative names
not_afterExpiry timestamp (ISO 8601), null if the certificate cannot be parsed
sourceacme.json file the certificate came from (ACME entries)
certFileCertificate path (file-provider entries)

When nothing could be read, the response also carries an error string.


GET /api/traefik/logs

Tail Traefik access logs. Requires an access log path, from ACCESS_LOG_PATH or Settings.

Query paramDefaultMax
lines1001000
json
{ "lines": ["..."] }

GET /api/diagnostics/client-ip

Read-only diagnostic for the current request: the client as seen after ProxyFix, the raw socket peer, the forwarding headers as received, the trusted proxy hop count (PROXY_FIX_HOPS), and a scope class (public, private, cgnat, loopback, link-local or unknown) per observed IP.

json
{
  "effective_ip": "203.0.113.5",
  "effective_class": "public",
  "socket_peer": "172.20.0.1",
  "socket_peer_class": "private",
  "headers": {
    "X-Forwarded-For": "203.0.113.5",
    "X-Real-IP": "",
    "CF-Connecting-IP": "",
    "X-Forwarded-Proto": "https",
    "X-Forwarded-Host": "example.com"
  },
  "forwarded_for_chain": ["203.0.113.5"],
  "proxy_hops": 1,
  "classes": { "203.0.113.5": "public", "172.20.0.1": "private" }
}

Dashboard

GET /api/dashboard/config

Get saved dashboard configuration - custom groups and per-route icon, name, link and hidden overrides.

Pass ?server=<agent-id> to read an agent's configuration. Without it you get the Host's. Each server keeps its own groups and overrides.

json
{
  "custom_groups": [{ "name": "Media" }],
  "route_overrides": {
    "dynamic.yml::jellyfin": {
      "display_name": "Jellyfin",
      "icon_type": "slug",
      "icon_slug": "jellyfin",
      "icon_url": "",
      "group": "Media",
      "url": "https://jellyfin.example.com",
      "hidden": false,
      "link_disabled": false
    }
  },
  "tm_route_name": "traefik-manager"
}

Keys of route_overrides are route ids (<config-file>::<router-name>, or just the router name on a single-file install). tm_route_name is read-only and names the router that points at Traefik Manager itself, so a client can give it TM's own icon.


POST /api/dashboard/config

Save dashboard configuration. Replaces that server's section of dashboard.yml, leaving the other servers untouched.

Pass ?server=<agent-id>, or a server key in the body, to write an agent's configuration. Without it the Host's is written.

json
{
  "custom_groups": [{ "name": "Media" }],
  "route_overrides": {
    "plex": { "display_name": "Plex", "icon_type": "slug", "icon_slug": "plex", "group": "Media",
              "url": "https://plex.example.com", "link_disabled": false }
  }
}

icon_type is auto, slug, or url. url overrides the URL the dashboard card opens and must start with http:// or https:// - anything else is dropped on save. link_disabled: true makes the card non-clickable.


GET /api/dashboard/icon/{slug}

Serve a cached app icon by slug (e.g. plex, grafana). On a cache miss it fetches the selfh.st icon set from jsDelivr and stores the PNG on disk, with Cache-Control: max-age=86400.

Misses are cached too: a slug with no icon returns 404 at once on later requests. Prefer this over the CDN directly - a client hitting the CDN itself makes one request per route on every render and loses the negative cache.

The slug is lowercased and stripped to a-z0-9-; anything else returns 404.

Resolving a route's icon

The dashboard resolves icons client-side. To match it:

  1. icon_type: "url" - use icon_url as-is.
  2. icon_type: "slug" - use icon_slug.
  3. Route name equals tm_route_name - use Traefik Manager's own icon.
  4. Otherwise (icon_type: "auto", or no override) - derive the slug from service_name, falling back to the route name:
    • drop anything from @ onward, then strip a trailing :port
    • strip one trailing -service, -svc, -router, -app, -container or -pod, with an optional s, separated by - or _
    • lowercase, then remove every character that is not a-z, 0-9 or -

So Jellyfin-Service and jellyfin both resolve to jellyfin. Fall back to a monogram of the route's first letters when the request returns 404.


Settings

GET /api/settings

Get current application settings. Every secret is stripped and replaced by a *_set boolean: password hash, OIDC client secret, Traefik API password, CrowdSec key and machine password, webhook password, OTP secret, git token. The agents list is stripped too - use GET /api/agents.

FieldDescription
domainsAllowed domains list
cert_resolverDefault ACME resolver name(s)
traefik_api_urlTraefik API base URL
acme_json_pathPath to acme.json inside the container
access_log_pathPath to Traefik access log
static_config_pathPath to traefik.yml
auth_enabledPassword auth on/off
auth_env_forcedtrue when AUTH_ENABLED disables auth from the environment
oidc_enabledOIDC on/off
no_authtrue when neither password auth nor OIDC is active
has_passwordtrue if a login password is set
visible_tabsTab visibility map
ui_prefsDisplay preferences, see GET /api/settings/ui
webhook_urlNotification webhook URL
traefik_api_userTraefik API username for basic auth
traefik_api_password_settrue if a Traefik API password is saved
crowdsec_lapi_urlCrowdSec LAPI URL
crowdsec_api_key_settrue if a CrowdSec API key is saved
crowdsec_enabledtrue when a LAPI URL is set plus either a bouncer API key or machine credentials

POST /api/settings

Update settings. Full replace, not a patch: domains is required (400 without it) and any omitted field resets to its default, so send the current values you want to keep.

Exceptions: git_backup_*, backup_keep_count and default_theme are updated only when present, and blank traefik_api_password, crowdsec_api_key, crowdsec_machine_password, webhook_password and git_backup_token keep the stored secret.

Returns { "success": true, "settings": { ... } } with secrets stripped. 400 for a missing domain, an invalid traefik_api_url or an unsupported git_backup_repo scheme.


POST /api/settings/webhook-test

Send a test payload to a webhook URL without saving it. Also accepts webhook_type, username and password.

json
{ "url": "https://discord.com/api/webhooks/..." }

Returns 400 for a URL that is not http(s) or that fails the SSRF guard.


GET /api/settings/self-route

Get the saved self-route domain. If none is saved and ?hostname=<host> is supplied, TM scans the config files for an existing route pointing to the TM service. The response always includes default_entry_point.


POST /api/settings/self-route

Save or remove the self-route. An empty domain deletes the self-route file. router_name defaults to traefik-manager and is what tm_route_name reports; entry_point defaults to the best entrypoint TM can find.

json
{ "domain": "manager.example.com", "service_url": "http://traefik-manager:5000",
  "router_name": "traefik-manager", "entry_point": "websecure" }

POST /api/settings/tabs

Show or hide optional UI tabs. Send the tab keys at the top level; anything that is not a known tab key is ignored.

json
{ "dashboard": true, "routemap": true, "docker": false }

Known keys: dashboard, routemap, docker, kubernetes, swarm, nomad, ecs, consulcatalog, redis, etcd, consul, zookeeper, http_provider, file_external, certs, tls, crowdsec, plugins, logs, static.


POST /api/settings/test-connection

Test connectivity to a Traefik API URL before saving. Accepts optional credentials for auth-protected dashboards.

json
{ "url": "http://traefik:8080", "user": "admin", "password": "secret" }

GET /api/settings/ui

Display preferences stored server-side, so they follow the user across browsers and devices.

json
{ "ok": true, "ui_prefs": { "showApiLink": true, "svcViewMode": "list" } }

POST /api/settings/ui

Update one or more preferences. Keys not sent keep their current value. A bare object without the ui_prefs wrapper is also accepted.

json
{ "ui_prefs": { "showDocsLink": false, "mwViewMode": "list" } }
KeyValues
showStatCards, compactStatCards, showEntrypoints, showDocsLink, showApiLink, showShortcutsBtn, showIpDiagBtn, showTraefikBadge, showTmBadge, showRouteIcons, logsAutoRefreshboolean
routeViewMode, mwViewMode, svcViewModegrid or list
statBarScopeall (stat cards on every tab) or dashboard
layoutModefluid or fixed (modern/classic still accepted as aliases)
dashPodDensitylist or icons
staticPlacementoff, settings or tab - where the Static Config editor appears
staticOpenSections, settingsOpenSectionsstring arrays of accordion section names

Anything else is dropped rather than stored. Returns 400 if ui_prefs is not an object.


POST /api/settings/theme

Set the default theme for new browsers. One of dark, light, system.

json
{ "default_theme": "system" }

400 for any other value.


POST /api/settings/geoip

Enable GeoIP and set the database path. Omitted keys keep their current value.

json
{ "geoip_enabled": true, "geoip_db_path": "/app/config/geoip/dbip-city-lite.mmdb" }

Returns { "success": true, "status": { } } carrying the same payload as GET /api/geoip/status.


TLS Options

All three endpoints accept ?server=<agent-id> to act on an agent's config files instead of the Host's.

GET /api/tls-options

List all tls.options profiles from every mounted config file.

Response - array of profiles:

FieldTypeDescription
namestringProfile key (e.g. modern, default)
configFilestringSource config file basename, empty on a single-file install
configFilePathstringFull path of the source file
minVersionstringMinimum TLS version (e.g. VersionTLS12)
maxVersionstringMaximum TLS version
sniStrictbooleanSNI strict mode enabled
cipherSuitesstring[]Cipher suite list
curvePreferencesstring[]ECDH curve list
alpnProtocolsstring[]ALPN protocol list
clientAuthTypestringClient auth type
clientAuthCAsstring[]CA file paths, from clientAuth.caFiles
yamlstringRaw YAML block for display

POST /api/tls-options

Create or update a TLS options profile. JSON body. Empty fields are left out of the written YAML, and clientAuthType: NoClientCert writes no clientAuth block. 400 without a name.

FieldTypeDescription
namestringProfile name (required)
configFilestringTarget config file basename (multi-config only)
minVersionstringe.g. VersionTLS12
maxVersionstringOptional upper bound
sniStrictbooleanEnable SNI strict
cipherSuitesstring[]Cipher suite list
curvePreferencesstring[]Curve list
alpnProtocolsstring[]ALPN list
clientAuthTypestringClient auth type
clientAuthCAsstring[]CA file paths

DELETE /api/tls-options/{name}

Delete a TLS options profile by name. 404 if the profile does not exist.

Query paramDescription
configFileConfig file basename (multi-config only)

Backups

GET /api/backups

List all backup files, newest first. kind is static for backups of traefik.yml, routes for everything else.

json
[{ "name": "dynamic.yml.20260324_220000.bak", "size": 1024, "modified": "2026-03-24 22:00:00", "kind": "routes" }]

POST /api/backup/create

Create a manual backup of every loaded config file. Returns { "success": true, "names": ["dynamic.yml.20260324_220000.bak"], "count": 1 }, or 400 when there is nothing to back up.


POST /api/restore/{filename}

Restore configuration from a backup file. Rate-limited to 10/min. The current file is backed up first. 404 if the backup is missing, 400 if its name matches no loaded config file or the static config.


POST /api/backup/delete/{filename}

Delete a backup file.


POST /api/static/backup/create

Create a backup of traefik.yml on demand. POST /api/backup/static/create is an alias for the same handler.

json
{ "success": true, "name": "traefik.yml.20260812_051500.bak" }

Returns 400 if no static config path is configured or the file is missing.


POST /api/settings/backup-retention

Set how many backups to keep per file. 0 keeps all of them.

json
{ "backup_keep_count": 20 }

Git backup

Every endpoint here accepts an optional ?agent_id=<agent-id> to act on an agent's repository instead of the Host's.

GET /api/backup/git/status

json
{ "enabled": true, "configured": true, "last_sha": "a1b2c3d4", "last_push": "2026-08-12 05:15:00 +0000" }

last_sha is the short form. Agent requests also return branch.


POST /api/backup/git/push

Commit and push the current config. An optional message overrides the configured commit template for this push only. Returns 400 with the git error on failure.

json
{ "message": "before the entrypoint change" }

POST /api/backup/git/test

Test repository credentials without pushing, via git ls-remote. Falls back to the saved settings when the body is empty, so it can verify an existing configuration.

json
{ "repo_url": "https://github.com/you/configs", "username": "you", "token": "ghp_..." }

Returns { "ok": true }, or 400 with the git error. Tokens are redacted from the message.


GET /api/backup/git/commits

The 50 most recent commits. Returns [] rather than an error when git backup is not configured.

json
[{ "sha": "a1b2...", "sha_short": "a1b2c3d4", "timestamp": "2026-08-12 05:15:00 +0000", "message": "Update dynamic config" }]

GET /api/backup/git/commit/{sha}/diff

The diffstat plus the old and new content of every file in that commit, which is what the UI's diff viewer renders.

json
{ "stat": " dynamic/app.yml | 4 ++--", "files": [{ "filename": "dynamic/app.yml", "status": "M", "old": "...", "new": "..." }] }

400 if sha is not a hex SHA of 7 to 40 characters.


POST /api/backup/git/restore/{sha}

Restore the config from a commit. On the Host, every config file and the static config are backed up locally first; on an agent the files are pushed back through the agent instead.


DELETE /api/backup/git/repo

Delete the local clone. The next push re-initialises it. Use this when the repository or credentials change and the clone is stale.


Notifications

GET /api/notifications

List stored notifications, newest first. The last 200 are kept.

json
[{ "ts": "2026-04-13 20:25:03", "type": "route_saved", "msg": "Route my-app saved" }]

POST /api/notifications/delete

Delete a single notification by timestamp.

json
{ "ts": "2026-04-13 20:25:03" }

POST /api/notifications/clear

Clear all notifications.

Changed in v1.10.1

add, delete and clear enforced CSRF unconditionally, so an API key request was rejected with 403 even though API keys are meant to skip CSRF. All three now honour the key. On v1.10.0 and earlier, they are session-only.


POST /api/notifications/add

Add a notification. Unlike /log, this also fires the configured webhook.

json
{ "type": "info", "message": "Deployment finished" }

POST /api/notifications/log

Record a UI toast in the notification history without firing a webhook. type is one of info, success, warning, error and falls back to info. The message is truncated to 300 characters.

json
{ "ok": true, "stored": true }

stored is false when the message is identical to one recorded in the last 8 seconds (duplicate suppression). An empty message returns 400.


POST /api/notifications/update

Record an "update available" notification. product is manager for Traefik Manager, anything else means Traefik.

json
{ "version": "1.10.1", "product": "manager" }

Authentication endpoints

POST /api/auth/change-password

Change the login password. Rate-limited to 10/min. The new password must be at least 8 characters. 403 if current_password is wrong.

json
{ "current_password": "...", "new_password": "...", "confirm_password": "..." }

POST /api/auth/toggle

Enable or disable password authentication. The response carries reauth_required when the change means the current session must log in again.

json
{ "auth_enabled": false }

GET /api/auth/otp/status

Check whether TOTP is enabled.


POST /api/auth/otp/setup

Generate a TOTP secret and QR code URI for scanning with an authenticator app. Returns secret and uri.


POST /api/auth/otp/enable

Confirm and activate TOTP using a code from the authenticator app.

json
{ "code": "123456" }

POST /api/auth/otp/disable

Disable TOTP.


GET /api/auth/apikey/status

List active API keys. Full keys are never returned after generation.

json
{
  "enabled": true,
  "count": 2,
  "keys": [{ "name": "My Phone", "preview": "abcd1234...ef56", "created_at": "2026-04-03 12:00" }]
}

POST /api/auth/apikey/generate

Generate a new API key. device_name is required and truncated to 50 characters. Up to 10 keys can exist. Rate-limited to 5/hour. The full key is returned once - store it securely.

json
{ "device_name": "My Phone" }

Response: { "ok": true, "key": "8Kv2v1s...URL-safe token" }


POST /api/auth/apikey/revoke

Revoke an API key by its preview string.

json
{ "preview": "abcd1234...ef56" }

GET /api/auth/oidc

Get current OIDC configuration. The client secret is replaced by oidc_client_secret_set.


POST /api/auth/oidc

Save OIDC configuration. This is a full replace: omitted fields fall back to their defaults. Leave oidc_client_secret blank to keep the existing secret.

FieldDescription
oidc_enabledEnable or disable OIDC
oidc_provider_urlProvider base URL (without /.well-known/...)
oidc_client_idClient ID
oidc_client_secretClient secret (omit to keep existing)
oidc_display_nameLogin button label (default OIDC)
oidc_allowed_emailsComma-separated allowed emails
oidc_allowed_groupsComma-separated allowed groups
oidc_allow_any_authenticatedAccept any user the provider authenticates
oidc_groups_claimClaim name containing groups (default groups)
oidc_auto_loginSend the login page straight to the provider

POST /api/auth/oidc/test

Test connectivity to an OIDC provider's discovery endpoint. Credentials are not verified.

json
{ "provider_url": "https://accounts.google.com" }

POST /api/auth/external-ack

Acknowledge that an external provider (a Traefik forward-auth middleware, for example) already protects this instance, which hides the "no authentication" banner.

json
{ "auth_external_ack": true }
json
{ "success": true, "auth_external_ack": true }

Returns 400 if a password or OIDC is active, since there is then nothing to acknowledge. This changes only what the UI reports - it never changes what is enforced. Setting and clearing it are both logged.


Static Config

Requires a static config path, from STATIC_CONFIG_PATH or Settings. See Enable Static Config.

GET /api/static/available

Check whether the static config editor is available.

json
{ "available": true }

GET /api/static/config

Read and parse the current static config file. Pass ?server=<agent-id> to read an agent's instead.

json
{ "raw": "...", "parsed": { ... }, "path": "/app/traefik.yml" }

404 when the file is missing or no path is configured.


POST /api/static/config

Validate and write an updated static config. A timestamped backup is created before writing. The key raw is accepted as an alias for content.

json
{ "content": "entryPoints:\n  web:\n    address: ':80'\n" }

Returns 400 with { "error": "..." } for empty content, invalid YAML or no configured path, and 403 if the path resolves outside the allowed directories.


POST /api/static/restart

Trigger a Traefik restart using the configured RESTART_METHOD. Returns 500 with the reason when the restart could not be triggered.


GET /api/static/status

Check whether Traefik is currently up. Used by the reconnect overlay after a restart.

json
{ "up": true }

POST /api/static/section

Edit one named section of the static config without writing raw YAML. Nothing is saved: the endpoint returns the rewritten document and the client persists it with POST /api/static/config, which is why it works the same on the Host and on a remote agent.

json
{
  "action": "add",
  "section": "entrypoints",
  "name": "websecure",
  "data": { "address": ":443" },
  "current_raw": ""
}

current_raw is the document to edit; empty reads the file on disk. old_name renames an entry on action: "edit".

Supported sections and actions

SectionActionsMain data fields
entrypointsadd, edit, removeaddress, redirect_to, http3, as_default, middlewares, tls_enabled, tls_cert_resolver, tls_options, trusted_ips, proxy_trusted_ips, read_timeout, write_timeout, idle_timeout, underscore_headers
resolversadd, edit, removeemail, storage, challenge_type, provider, http_entrypoint, ca_server, key_type, eab_kid, eab_hmac, dns_resolvers, dns_delay, dns_disable_checks
pluginsadd, edit, removemoduleName, version, local
apisetenabled, dashboard, insecure, debug
logsetlevel, log_format, log_file, log_max_size, log_max_backups, log_max_age, log_compress, accessLog, accessLogPath, al_format, al_buffering, al_status_codes, al_min_duration, al_headers_mode
providerssetdocker, dockerEndpoint, dockerExposedByDefault, dockerWatch, file, fileDirectory, fileWatch, providers_throttle
providersadd, edit, removename = provider type key, yaml_config = YAML body
observabilitysetping, prometheus, prom_ep_labels, prom_router_labels, prom_svc_labels
systemsetcheck_new_version, send_usage, rule_syntax, st_insecure, st_root_cas, st_max_idle, st_dial, st_resp_header, st_idle_conn

Returns { "ok": true, "raw": "...", "parsed": { } }. 400 for an unknown section, a missing field, or an invalid duration, CIDR or number.


POST /api/static/trusted-ips/preview

Compute the result of adding forwardedHeaders.trustedIPs to an entrypoint, without writing anything to disk. Backs the Trusted IPs helper in the Static Config editor.

Trusting a proxy's IP makes Traefik believe its X-Forwarded-For, which then feeds the access logs, CrowdSec, ipAllowList, and the login rate-limiter. Only trust proxies you control.

The merge is additive with dedup: existing entries are kept, and ranges already covered are skipped by normalized network (so 10.5.5.5/8 will not re-add 10.0.0.0/8). Sibling keys under forwardedHeaders, other entrypoints and YAML comments are preserved. As with POST /api/static/section, the client persists the returned raw through POST /api/static/config.

Called in two modes.

Inspect (no entrypoint) - lists entrypoints and the presets:

json
{ "current_raw": "entryPoints:\n  websecure:\n    address: ':443'\n" }

Preview (with entrypoint) - also returns the merge:

json
{
  "current_raw": "entryPoints:\n  websecure:\n    address: ':443'\n",
  "entrypoint": "websecure",
  "cloudflare": true,
  "private": false,
  "custom_cidrs": "203.0.113.10, 198.51.100.0/24"
}
FieldTypeDescription
current_rawstringStatic config YAML to operate on. Falls back to the file on disk when empty.
entrypointstringTarget entrypoint. Omit for inspect mode.
cloudflarebooleanInclude the built-in Cloudflare edge ranges.
privatebooleanInclude the private-range preset (10/8, 172.16/12, 192.168/16, fc00::/7).
custom_cidrsstring | string[]Extra CIDRs or IPs, comma/whitespace-separated or an array. Invalid entries are returned in invalid and skipped.

Inspect mode returns ok, entrypoints (each with name, address, trusted_ips), cloudflare_captured, cloudflare_ranges, and private_ranges. Preview mode adds entrypoint, existing, added, invalid, final, the merged raw YAML, and the parsed object.

Returns 400 if the named entrypoint is absent or the config is not a mapping, and 404 if there is no static config on disk and no current_raw was supplied.


Utility

GET /api/manager/version

Get the deployed Traefik Manager version.

json
{ "version": "1.11.0", "repo": "chr0nzz/traefik-manager", "static_config_configured": true }

GET /api/manager/router-names

Router names across every protocol, from the default config file only. Useful for autocomplete.

json
["my-app", "api"]

POST /api/setup/test-connection

Test connectivity to a Traefik API URL during first-time setup. Accepts optional user and password. Requires authentication like every other /api/ endpoint, and returns 403 once setup is complete.

json
{ "url": "http://traefik:8080" }

GET /api/ping

Send a HEAD request to a route's domain from the TM server and return latency. Backs the route health check in the Routes tab.

Query paramDescription
urlFull URL to ping (must start with http:// or https://)
fallbackOptional second URL, tried when the first attempt fails
json
{ "ok": true, "latency_ms": 42, "status_code": 200 }

On failure: { "ok": false, "error": "Timeout", "latency_ms": null }

A URL pointing at TM's own hostname, or at the configured self-route domain, short-circuits to { "ok": true, "latency_ms": 0, "status_code": 200, "self": true } without a request. A successful fallback adds "via_target": true. Targets that fail the SSRF guard return 400.


GET /api/health

Liveness probe. The only /api/ endpoint that needs no authentication, so it can be used as a container healthcheck.

json
{ "ok": true }

GET /api/traefik/runtime

How Traefik Manager expects to reach Traefik in order to restart it. The Static Config editor uses it to tailor its "new entrypoints need a port mapping" guidance.

json
{ "method": "proxy", "runtime": "docker", "container": "traefik" }

runtime is docker, native or unknown. With RESTART_METHOD=poison-pill it probes the Docker API and reports native when the container cannot be seen.


POST /api/tools/htpasswd

Generate an APR1 hash for a basicauth middleware.

json
{ "username": "admin", "password": "secret" }
json
{ "ok": true, "hash": "admin:$apr1$..." }

POST /api/tools/digestauth

Generate an MD5 hash for a digestauth middleware. realm is required as well.

json
{ "username": "admin", "realm": "traefik", "password": "secret" }
json
{ "ok": true, "hash": "admin:traefik:5f4dcc3b..." }

GET /api/geoip/status

Whether GeoIP is enabled, whether a database is readable, and its vintage.


POST /api/geoip/lookup

Resolve a batch of IPs. Set aggregate to get per-country counts instead of per-IP detail, which is what the Logs and CrowdSec maps use.

json
{ "ips": ["1.2.3.4", "5.6.7.8"], "aggregate": false }

Per-IP: { "enabled": true, "available": true, "results": { "1.2.3.4": { "country": "...", "country_code": "..." } } }

Aggregated: { "enabled": true, "available": true, "counts": { "US": { "count": 2, "country": "United States" } }, "codes": { "1.2.3.4": "US" } }

Duplicate and unresolvable addresses are skipped. When GeoIP is off, returns { "enabled": false, "available": false, "results": {} } rather than an error.


POST /api/geoip/update

Download the current DB-IP city-lite database. Rate-limited to 6/hour.

json
{ "success": true, "db_month": "2026-08", "status": { } }

Returns 502 if the download fails.


POST /api/plugins/install

Install a Traefik plugin by pasting the YAML from its plugin page. static_yaml must contain an experimental.plugins (or top-level plugins) block and is merged into the static config. middleware_yaml optionally creates the middleware that uses it.

FieldDescription
static_yamlRequired. The experimental.plugins snippet
middleware_yamlOptional middleware to create alongside it
middleware_fileDynamic config file for the middleware (default plugin-middlewares.yml)
serverAgent id, or empty for the Host
json
{ "static_yaml": "experimental:\n  plugins:\n    ...", "middleware_yaml": "...", "middleware_file": "dynamic.yml", "server": "" }

Returns { "ok": true, "plugins": ["name"] }, plus middleware_file when one was written and warning when the plugin saved but the middleware did not. 400 for invalid YAML, no plugins block, or a middleware snippet that still contains placeholders; 404 for an unknown agent or a missing static config.


Middleware templates

Templates are reusable middleware snippets shown in the middlewares toolbar. They are stored on the Host and are not per-server.

GET /api/mw/templates

json
{ "templates": [{ "id": "uuid", "name": "Secure headers", "yaml": "headers:\n  ..." }] }

POST /api/mw/templates

Create a template. name is required and truncated to 100 characters.

json
{ "name": "Secure headers", "yaml": "headers:\n  sslRedirect: true" }

Returns { "ok": true, "template": { "id": "uuid", "name": "...", "yaml": "..." } }.


PUT /api/mw/templates/{template_id}

Update a template. name and yaml are both optional; only what you send is changed. 404 if the id is unknown.


DELETE /api/mw/templates/{template_id}

Delete a template. Succeeds even if the id does not exist.


CrowdSec

GET /api/crowdsec/decisions

List active CrowdSec decisions (bans, captchas, bypasses). Expired ones are filtered out. Pass ?full=1 to force a full stream refresh instead of an incremental one.

503 when no LAPI URL is configured, or when there is no bouncer API key and no client certificate - /v1/decisions refuses the machine token. 502 when the LAPI cannot be reached.

Response

json
[
  {
    "id": 1,
    "value": "1.2.3.4",
    "type": "ban",
    "duration": "3h59m",
    "scenario": "crowdsecurity/http-bf",
    "origin": "CAPI"
  }
]

GET /api/crowdsec/alerts

List recent CrowdSec alerts. The default cap is 500, configurable with the crowdsec_alert_limit setting or CROWDSEC_ALERT_LIMIT; the applied cap is returned in the X-CS-Alert-Limit header, and X-CS-Alert-Capped is 1 when the result hit it.

Response

json
[
  {
    "startAt": "2026-05-28T10:00:00Z",
    "source": { "ip": "1.2.3.4" },
    "scenario": "crowdsecurity/http-bf",
    "decisions": [{ "type": "ban", "duration": "4h" }]
  }
]

POST /api/crowdsec/decisions

Add a decision - ban, captcha or bypass an address or range. Written to the LAPI as an alert, using the machine credentials when configured, otherwise the bouncer API key.

FieldTypeNotes
valuestringRequired. IP or CIDR range
typestringban (default), captcha or bypass
durationstringGo duration, default 24h
reasonstringDefaults to manual ban from Traefik Manager
json
{ "value": "203.0.113.10", "type": "ban", "duration": "24h", "reason": "brute force" }

Returns { "ok": true }. Errors: 400 when value is missing or type is not one of the three, 503 when CrowdSec is not configured, 502 when the LAPI call fails (commonly missing write permission).

DELETE /api/crowdsec/decisions/{id}

Unban / remove a decision by ID.

Response

json
{ "ok": true }

Returns 503 with {"error": "CrowdSec not configured"} when CrowdSec is not configured, and 500 with {"error": "Failed to delete decision"} when the LAPI call fails.


Agents

Manage remote TMA agents registered in TM.

GET /api/agents

List all registered agents. The agent API key, CrowdSec secrets and git token are redacted to ***.

Response

json
{
  "agents": [
    {
      "id": "uuid",
      "name": "Server 2",
      "url": "https://server2.example.com:8090",
      "api_key": "***",
      "created_at": "2026-01-01T00:00:00+00:00",
      "traefik_api_url": "http://traefik:8080",
      "config_path": "/app/config"
    }
  ]
}

POST /api/agents

Register a new agent. name and url are required; every other agent field can be set here and otherwise takes its default. TM generates the API key and returns it once as api_key_raw - store it immediately, it is never returned again.

Request body

json
{
  "name": "Server 2",
  "url": "https://server2.example.com:8090"
}

Response

json
{
  "ok": true,
  "agent": {
    "id": "uuid",
    "name": "Server 2",
    "url": "https://server2.example.com:8090",
    "api_key_raw": "the-plaintext-key-shown-once",
    "api_key": "***"
  }
}

PUT /api/agents/{id}

Update an agent's config fields (name, URL, paths, restart method, CrowdSec, git backup, visible tabs). Send only the fields you want to change. Secrets sent as "" or "***" keep their stored value. 404 for an unknown id, 400 if the git branch collides with the Host's or another agent's.


DELETE /api/agents/{id}

Remove an agent from TM. Does not stop the agent service on the remote server.


GET /api/agents/{id}/health

Check connectivity to an agent by calling its /health.

Response

json
{ "ok": true, "latency_ms": 12, "version": "1.5.1", "status": 200 }

If the agent is unreachable, ok is false and latency_ms is -1.


POST /api/agents/{id}/rotate-key

Generate a new API key for an agent. The new key is returned once as api_key_raw.

json
{ "ok": true, "agent": { "id": "uuid", "api_key": "***", "api_key_raw": "the-new-key" } }

The old key stops working immediately, so the agent is unreachable until its TMA_API_KEY is updated and it is restarted.


GET /api/agents/{id}/routes

Routes and middlewares on that agent, in the same shape as GET /api/routes - built from the agent's config files and enriched from its Traefik API. Route objects are identical to the Host's, so a client can render either without special-casing.

json
{
  "apps": [ /* Route[] */ ],
  "middlewares": [ /* Middleware[] */ ],
  "configErrors": [ { "file": "Agent Traefik API", "error": "..." } ],
  "services": { "http": [], "tcp": [], "udp": [] }
}

If the agent's Traefik API is unreachable, routes from its config files are still returned and the failure appears in configErrors.


GET /api/agents/{id}/cert-resolvers

Cert resolver names to offer in the route form for that server.

json
{ "resolvers": ["letsencrypt", "cloudflare"] }

Collected from the resolvers already used by that server's routers (via its Traefik API), anything in its static config when mounted, and the agent's optional cert_resolver field. An agent therefore does not need its static config mounted to offer resolvers.


/api/agents/proxy/{id}/{path}

Proxy a request to the agent's API. TM injects the X-Api-Key header, so a browser or the mobile app reaches an agent without ever holding its key.

Accepts GET, POST, PUT, DELETE and PATCH. Method, query string and body are forwarded; the agent's status code and body come back as-is.

For example, GET /api/agents/proxy/abc123/traefik/routers proxies to GET https://agent-host:8090/api/traefik/routers.

A successful write to the agent's config, route, middleware, static or backup paths also triggers that agent's git backup push, when configured.

Returns 404 for an unknown agent, 502 if the agent refuses the connection, 504 if it times out, and 500 for any other proxy error.

See the Agent API Reference for every endpoint an agent exposes.


OpenAPI spec

The raw OpenAPI 3.1 spec is available from your instance at:

https://your-tm-url/openapi.yaml