Files
dashcaddy/API-SURFACE.md
T
Hermes Agent f405186eb8
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
Add API-SURFACE.md — full route inventory with auth + rate-limit classification
Generated from static analysis of router.*() registrations across
47 route files. Covers 285 routes grouped into 29 feature areas.
Each entry includes method, full path, auth classification
(public/protected per PUBLIC_ROUTES allowlist), rate-limit bucket
(GENERAL/STRICT/TOTP), and source file:line.

Also cross-checks against openapi.yaml: 142 routes undocumented,
18 stale paths in spec. This is a real gap that should be fixed
before v1.0 public release.
2026-07-13 00:59:22 -07:00

707 lines
38 KiB
Markdown

# DashCaddy API Surface
> **Generated:** 2026-07-13
> **Total routes:** 285
> **Files scanned:** 47
> **Source of truth:** router.* registrations in `dashcaddy-api/routes/` + root paths in `src/app.js`
## Auth & Rate Limit Model
**Auth classification:**
- `public` = in `PUBLIC_ROUTES` allowlist (`src/utilities/middleware.js:310-364`), bypasses TOTP
- `protected` = requires valid TOTP session cookie (`dashcaddy_session`) OR API key/JWT token
**Rate limits** (from `RATE_LIMITS` in `src/utilities/constants.js:69`):
- `GENERAL` = 1000 req / 15 min / IP — default for all `/api/v1/*`
- `STRICT` = 20 req / 15 min / IP — auth key endpoints (`/auth/keys`, `/auth/jwt`, `/auth/gate`, `/auth/app-token`)
- `TOTP` = 10 req / 15 min / IP — TOTP verify/setup
**CSRF:** TOTP session uses double-submit cookie pattern. State-changing requests (POST/PUT/DELETE/PATCH) require `X-CSRF-Token` header matching the `csrf_token` cookie.
---
## Summary by Area
| Area | Routes | Public | Protected |
|---|---:|---:|---:|
| App catalog | 28 | 0 | 28 |
| Tailscale | 20 | 20 | 0 |
| Backups | 19 | 0 | 19 |
| DNS | 19 | 0 | 19 |
| Monitoring | 19 | 3 | 16 |
| Updates | 16 | 6 | 10 |
| Authentication | 15 | 10 | 5 |
| Logs | 15 | 0 | 15 |
| Configuration | 13 | 9 | 4 |
| Health | 12 | 2 | 10 |
| Services | 12 | 4 | 8 |
| Containers (lifecycle) | 10 | 0 | 10 |
| Core / system | 9 | 6 | 3 |
| Dependencies | 8 | 0 | 8 |
| Notifications | 8 | 0 | 8 |
| App recipes | 8 | 0 | 8 |
| Docker resources | 7 | 0 | 7 |
| Caddy / sites | 7 | 0 | 7 |
| Updates / workflows | 6 | 0 | 6 |
| Auto-restart | 5 | 0 | 5 |
| Certificate authority | 5 | 5 | 0 |
| OpenClaw integration | 5 | 0 | 5 |
| Config drift | 4 | 0 | 4 |
| Licensing | 4 | 2 | 2 |
| File browser | 3 | 0 | 3 |
| Theming | 3 | 1 | 2 |
| Service credentials | 2 | 0 | 2 |
| Events | 2 | 0 | 2 |
| Internal helpers | 1 | 0 | 1 |
| **TOTAL** | **285** | **68** | **217** |
## App catalog
_28 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| DELETE | `/api/v1/:appId` | protected | GENERAL (1000/15m) | `routes/apps/removal.js:39` |
| GET | `/api/v1/:appId/backup-points` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:131` |
| POST | `/api/v1/:appId/restore` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:38` |
| POST | `/api/v1/:appId/revert/:filename` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:185` |
| POST | `/api/v1/arr/auto-setup` | protected | GENERAL (1000/15m) | `routes/arr/config.js:282` |
| POST | `/api/v1/arr/configure-overseerr` | protected | GENERAL (1000/15m) | `routes/arr/config.js:27` |
| GET | `/api/v1/arr/credentials` | protected | GENERAL (1000/15m) | `routes/arr/credentials.js:109` |
| POST | `/api/v1/arr/credentials` | protected | GENERAL (1000/15m) | `routes/arr/credentials.js:21` |
| DELETE | `/api/v1/arr/credentials/:service` | protected | GENERAL (1000/15m) | `routes/arr/credentials.js:134` |
| GET | `/api/v1/arr/detect` | protected | GENERAL (1000/15m) | `routes/arr/detect.js:20` |
| GET | `/api/v1/arr/quality-profiles` | protected | GENERAL (1000/15m) | `routes/arr/config.js:497` |
| POST | `/api/v1/arr/quality-profiles` | protected | GENERAL (1000/15m) | `routes/arr/config.js:566` |
| POST | `/api/v1/arr/smart-connect` | protected | GENERAL (1000/15m) | `routes/arr/smart-connect.js:26` |
| GET | `/api/v1/arr/smart-detect` | protected | GENERAL (1000/15m) | `routes/arr/detect.js:78` |
| POST | `/api/v1/arr/test-connection` | protected | GENERAL (1000/15m) | `routes/arr/config.js:208` |
| POST | `/api/v1/check-existing` | protected | GENERAL (1000/15m) | `routes/apps/deploy.js:241` |
| DELETE | `/api/v1/compose-stack/:stackName` | protected | GENERAL (1000/15m) | `routes/apps/compose.js:308` |
| POST | `/api/v1/deploy` | protected | GENERAL (1000/15m) | `routes/apps/deploy.js:254` |
| POST | `/api/v1/deploy-compose` | protected | GENERAL (1000/15m) | `routes/apps/compose.js:170` |
| POST | `/api/v1/import-compose` | protected | GENERAL (1000/15m) | `routes/apps/compose.js:159` |
| GET | `/api/v1/plex/libraries` | protected | GENERAL (1000/15m) | `routes/arr/plex.js:26` |
| GET | `/api/v1/ports/:basePort/suggest` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:77` |
| GET | `/api/v1/ports/:port/check` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:65` |
| POST | `/api/v1/restore-all` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:58` |
| GET | `/api/v1/restore-status` | protected | GENERAL (1000/15m) | `routes/apps/restore.js:97` |
| GET | `/api/v1/templates` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:45` |
| GET | `/api/v1/templates/:appId` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:54` |
| POST | `/api/v1/update-subdomain` | protected | GENERAL (1000/15m) | `routes/apps/templates.js:91` |
## Tailscale
_20 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/tailscale/acl` | public | GENERAL (1000/15m) | `routes/tailscale.js:301` |
| GET | `/api/v1/tailscale/admin/devices` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:154` |
| DELETE | `/api/v1/tailscale/admin/devices/:id` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:170` |
| GET | `/api/v1/tailscale/admin/keys` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:202` |
| POST | `/api/v1/tailscale/admin/keys` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:211` |
| DELETE | `/api/v1/tailscale/admin/keys/:id` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:237` |
| GET | `/api/v1/tailscale/admin/users` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:191` |
| GET | `/api/v1/tailscale/api-devices` | public | GENERAL (1000/15m) | `routes/tailscale.js:274` |
| GET | `/api/v1/tailscale/check-connection` | public | GENERAL (1000/15m) | `routes/tailscale.js:96` |
| POST | `/api/v1/tailscale/config` | public | GENERAL (1000/15m) | `routes/tailscale.js:80` |
| GET | `/api/v1/tailscale/devices` | public | GENERAL (1000/15m) | `routes/tailscale.js:113` |
| DELETE | `/api/v1/tailscale/oauth-config` | public | GENERAL (1000/15m) | `routes/tailscale.js:259` |
| POST | `/api/v1/tailscale/oauth-config` | public | GENERAL (1000/15m) | `routes/tailscale.js:201` |
| POST | `/api/v1/tailscale/protect-service` | public | GENERAL (1000/15m) | `routes/tailscale.js:147` |
| DELETE | `/api/v1/tailscale/settings` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:124` |
| GET | `/api/v1/tailscale/settings` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:60` |
| PUT | `/api/v1/tailscale/settings` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:76` |
| POST | `/api/v1/tailscale/settings/test` | public | GENERAL (1000/15m) | `routes/tailscale-admin.js:131` |
| GET | `/api/v1/tailscale/status` | public | GENERAL (1000/15m) | `routes/tailscale.js:36` |
| POST | `/api/v1/tailscale/sync` | public | GENERAL (1000/15m) | `routes/tailscale.js:287` |
## Backups
_19 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| POST | `/api/v1/backups/backup/:appId` | protected | GENERAL (1000/15m) | `routes/backups.js:161` |
| POST | `/api/v1/backups/compare/:filename` | protected | GENERAL (1000/15m) | `routes/backups.js:373` |
| GET | `/api/v1/backups/config` | protected | GENERAL (1000/15m) | `routes/backups.js:480` |
| POST | `/api/v1/backups/config` | protected | GENERAL (1000/15m) | `routes/backups.js:486` |
| DELETE | `/api/v1/backups/credentials/:provider` | protected | GENERAL (1000/15m) | `routes/backups.js:631` |
| GET | `/api/v1/backups/credentials/:provider` | protected | GENERAL (1000/15m) | `routes/backups.js:558` |
| POST | `/api/v1/backups/credentials/:provider` | protected | GENERAL (1000/15m) | `routes/backups.js:590` |
| POST | `/api/v1/backups/execute` | protected | GENERAL (1000/15m) | `routes/backups.js:492` |
| GET | `/api/v1/backups/files` | protected | GENERAL (1000/15m) | `routes/backups.js:118` |
| GET | `/api/v1/backups/files/:appId` | protected | GENERAL (1000/15m) | `routes/backups.js:189` |
| GET | `/api/v1/backups/history` | protected | GENERAL (1000/15m) | `routes/backups.js:498` |
| POST | `/api/v1/backups/restore-file/:filename` | protected | GENERAL (1000/15m) | `routes/backups.js:237` |
| POST | `/api/v1/backups/restore/:backupId` | protected | GENERAL (1000/15m) | `routes/backups.js:538` |
| GET | `/api/v1/backups/schedule` | protected | GENERAL (1000/15m) | `routes/backups.js:29` |
| POST | `/api/v1/backups/schedule` | protected | GENERAL (1000/15m) | `routes/backups.js:59` |
| POST | `/api/v1/backups/schedule` | protected | GENERAL (1000/15m) | `routes/backups.js:511` |
| DELETE | `/api/v1/backups/schedule/:appId` | protected | GENERAL (1000/15m) | `routes/backups.js:102` |
| GET | `/api/v1/backups/storage-info` | protected | GENERAL (1000/15m) | `routes/backups.js:505` |
| POST | `/api/v1/backups/test-destination` | protected | GENERAL (1000/15m) | `routes/backups.js:546` |
## DNS
_19 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/dns/check-update` | protected | GENERAL (1000/15m) | `routes/dns.js:669` |
| DELETE | `/api/v1/dns/credentials` | protected | GENERAL (1000/15m) | `routes/dns.js:597` |
| POST | `/api/v1/dns/credentials` | protected | GENERAL (1000/15m) | `routes/dns.js:490` |
| GET | `/api/v1/dns/logs` | protected | GENERAL (1000/15m) | `routes/dns.js:337` |
| GET | `/api/v1/dns/propagation` | protected | GENERAL (1000/15m) | `routes/dns.js:802` |
| GET | `/api/v1/dns/propagation/:domain` | protected | GENERAL (1000/15m) | `routes/dns.js:847` |
| POST | `/api/v1/dns/propagation/verify` | protected | GENERAL (1000/15m) | `routes/dns.js:815` |
| GET | `/api/v1/dns/provider/status` | protected | GENERAL (1000/15m) | `routes/dns.js:55` |
| GET | `/api/v1/dns/providers` | protected | GENERAL (1000/15m) | `routes/dns.js:48` |
| DELETE | `/api/v1/dns/record` | protected | GENERAL (1000/15m) | `routes/dns.js:176` |
| POST | `/api/v1/dns/record` | protected | GENERAL (1000/15m) | `routes/dns.js:225` |
| POST | `/api/v1/dns/refresh-token` | protected | GENERAL (1000/15m) | `routes/dns.js:655` |
| GET | `/api/v1/dns/resolve` | protected | GENERAL (1000/15m) | `routes/dns.js:292` |
| POST | `/api/v1/dns/restart/:dnsId` | protected | GENERAL (1000/15m) | `routes/dns.js:621` |
| GET | `/api/v1/dns/token-status` | protected | GENERAL (1000/15m) | `routes/dns.js:474` |
| DELETE | `/api/v1/dns/universal/record` | protected | GENERAL (1000/15m) | `routes/dns.js:119` |
| POST | `/api/v1/dns/universal/record` | protected | GENERAL (1000/15m) | `routes/dns.js:71` |
| GET | `/api/v1/dns/universal/resolve` | protected | GENERAL (1000/15m) | `routes/dns.js:145` |
| POST | `/api/v1/dns/update` | protected | GENERAL (1000/15m) | `routes/dns.js:732` |
## Monitoring
_19 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/certificates` | protected | GENERAL (1000/15m) | `routes/ssl-monitor.js:26` |
| GET | `/api/v1/certificates/:serviceId` | protected | GENERAL (1000/15m) | `routes/ssl-monitor.js:35` |
| POST | `/api/v1/check` | protected | GENERAL (1000/15m) | `routes/ssl-monitor.js:50` |
| POST | `/api/v1/check/:serviceId` | protected | GENERAL (1000/15m) | `routes/ssl-monitor.js:59` |
| GET | `/api/v1/config` | public | GENERAL (1000/15m) | `routes/ssl-monitor.js:80` |
| POST | `/api/v1/config` | public | GENERAL (1000/15m) | `routes/ssl-monitor.js:90` |
| GET | `/api/v1/monitoring/aggregated/:containerId` | protected | GENERAL (1000/15m) | `routes/monitoring.js:72` |
| GET | `/api/v1/monitoring/alerts` | protected | GENERAL (1000/15m) | `routes/monitoring.js:104` |
| DELETE | `/api/v1/monitoring/alerts/:containerId` | protected | GENERAL (1000/15m) | `routes/monitoring.js:174` |
| GET | `/api/v1/monitoring/alerts/:containerId` | protected | GENERAL (1000/15m) | `routes/monitoring.js:168` |
| POST | `/api/v1/monitoring/alerts/:containerId` | protected | GENERAL (1000/15m) | `routes/monitoring.js:162` |
| POST | `/api/v1/monitoring/alerts/:containerId/test` | protected | GENERAL (1000/15m) | `routes/monitoring.js:111` |
| GET | `/api/v1/monitoring/alerts/config` | protected | GENERAL (1000/15m) | `routes/monitoring.js:85` |
| POST | `/api/v1/monitoring/alerts/config` | protected | GENERAL (1000/15m) | `routes/monitoring.js:91` |
| GET | `/api/v1/monitoring/history/:containerId` | protected | GENERAL (1000/15m) | `routes/monitoring.js:49` |
| GET | `/api/v1/monitoring/stats` | public | GENERAL (1000/15m) | `routes/monitoring.js:20` |
| GET | `/api/v1/monitoring/stats/:containerId` | protected | GENERAL (1000/15m) | `routes/monitoring.js:38` |
| GET | `/api/v1/stats/container/:id` | protected | GENERAL (1000/15m) | `routes/monitoring.js:240` |
| GET | `/api/v1/stats/containers` | protected | GENERAL (1000/15m) | `routes/monitoring.js:182` |
## Updates
_16 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| POST | `/api/v1/system/rollback` | protected | GENERAL (1000/15m) | `routes/updates.js:164` |
| GET | `/api/v1/system/rollback-versions` | protected | GENERAL (1000/15m) | `routes/updates.js:158` |
| POST | `/api/v1/system/update-apply` | protected | GENERAL (1000/15m) | `routes/updates.js:95` |
| GET | `/api/v1/system/update-check` | public | GENERAL (1000/15m) | `routes/updates.js:89` |
| GET | `/api/v1/system/update-history` | public | GENERAL (1000/15m) | `routes/updates.js:152` |
| POST | `/api/v1/system/update-notify` | public | GENERAL (1000/15m) | `routes/updates.js:126` |
| GET | `/api/v1/system/update-status` | public | GENERAL (1000/15m) | `routes/updates.js:143` |
| GET | `/api/v1/system/version` | public | GENERAL (1000/15m) | `routes/updates.js:83` |
| GET | `/api/v1/updates/auto-update` | protected | GENERAL (1000/15m) | `routes/updates.js:65` |
| POST | `/api/v1/updates/auto-update/:containerId` | protected | GENERAL (1000/15m) | `routes/updates.js:59` |
| GET | `/api/v1/updates/available` | public | GENERAL (1000/15m) | `routes/updates.js:29` |
| POST | `/api/v1/updates/check` | protected | GENERAL (1000/15m) | `routes/updates.js:22` |
| GET | `/api/v1/updates/history` | protected | GENERAL (1000/15m) | `routes/updates.js:49` |
| POST | `/api/v1/updates/rollback/:containerId` | protected | GENERAL (1000/15m) | `routes/updates.js:43` |
| POST | `/api/v1/updates/schedule/:containerId` | protected | GENERAL (1000/15m) | `routes/updates.js:71` |
| POST | `/api/v1/updates/update/:containerId` | protected | GENERAL (1000/15m) | `routes/updates.js:37` |
## Authentication
_15 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/auth/app-token/:serviceId` | public | STRICT (20/15m) | `routes/auth/sso-gate.js:104` |
| GET | `/api/v1/auth/gate/:serviceId` | public | STRICT (20/15m) | `routes/auth/sso-gate.js:26` |
| POST | `/api/v1/auth/jwt` | protected | STRICT (20/15m) | `routes/auth/keys.js:103` |
| GET | `/api/v1/auth/keys` | protected | STRICT (20/15m) | `routes/auth/keys.js:36` |
| POST | `/api/v1/auth/keys` | protected | STRICT (20/15m) | `routes/auth/keys.js:47` |
| DELETE | `/api/v1/auth/keys/:keyId` | protected | STRICT (20/15m) | `routes/auth/keys.js:81` |
| GET | `/api/v1/auth/login-page` | public | GENERAL (1000/15m) | `routes/auth/sso-gate.js:206` |
| GET | `/api/v1/totp/check-session` | public | TOTP (10/15m) | `routes/auth/totp.js:228` |
| GET | `/api/v1/totp/config` | public | TOTP (10/15m) | `routes/auth/totp.js:30` |
| POST | `/api/v1/totp/config` | public | TOTP (10/15m) | `routes/auth/totp.js:286` |
| POST | `/api/v1/totp/disable` | protected | TOTP (10/15m) | `routes/auth/totp.js:253` |
| GET | `/api/v1/totp/recovery-info` | public | TOTP (10/15m) | `routes/auth/totp.js:56` |
| POST | `/api/v1/totp/setup` | public | TOTP (10/15m) | `routes/auth/totp.js:116` |
| POST | `/api/v1/totp/verify` | public | TOTP (10/15m) | `routes/auth/totp.js:194` |
| POST | `/api/v1/totp/verify-setup` | public | TOTP (10/15m) | `routes/auth/totp.js:157` |
## Logs
_15 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| DELETE | `/api/v1/audit-logs` | protected | GENERAL (1000/15m) | `routes/errorlogs.js:71` |
| GET | `/api/v1/audit-logs` | protected | GENERAL (1000/15m) | `routes/errorlogs.js:55` |
| DELETE | `/api/v1/error-logs` | protected | GENERAL (1000/15m) | `routes/errorlogs.js:47` |
| GET | `/api/v1/error-logs` | protected | GENERAL (1000/15m) | `routes/errorlogs.js:20` |
| GET | `/api/v1/logs/container/:id` | protected | GENERAL (1000/15m) | `routes/logs.js:39` |
| GET | `/api/v1/logs/containers` | protected | GENERAL (1000/15m) | `routes/logs.js:23` |
| GET | `/api/v1/logs/digest/:date` | protected | GENERAL (1000/15m) | `routes/logs.js:184` |
| POST | `/api/v1/logs/digest/generate` | protected | GENERAL (1000/15m) | `routes/logs.js:176` |
| GET | `/api/v1/logs/digest/history` | protected | GENERAL (1000/15m) | `routes/logs.js:169` |
| GET | `/api/v1/logs/digest/latest` | protected | GENERAL (1000/15m) | `routes/logs.js:152` |
| GET | `/api/v1/logs/digest/live` | protected | GENERAL (1000/15m) | `routes/logs.js:162` |
| GET | `/api/v1/logs/docker-disk` | protected | GENERAL (1000/15m) | `routes/logs.js:203` |
| POST | `/api/v1/logs/docker-maintenance` | protected | GENERAL (1000/15m) | `routes/logs.js:211` |
| GET | `/api/v1/logs/file` | protected | GENERAL (1000/15m) | `routes/logs.js:218` |
| GET | `/api/v1/logs/stream/:id` | protected | GENERAL (1000/15m) | `routes/logs.js:93` |
## Configuration
_13 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| POST | `/api/v1/assets/upload` | protected | GENERAL (1000/15m) | `routes/config/assets.js:33` |
| GET | `/api/v1/backup/export` | protected | GENERAL (1000/15m) | `routes/config/backup.js:51` |
| POST | `/api/v1/backup/preview` | protected | GENERAL (1000/15m) | `routes/config/backup.js:153` |
| POST | `/api/v1/backup/restore` | protected | GENERAL (1000/15m) | `routes/config/backup.js:218` |
| DELETE | `/api/v1/config` | public | GENERAL (1000/15m) | `routes/config/settings.js:78` |
| GET | `/api/v1/config` | public | GENERAL (1000/15m) | `routes/config/settings.js:26` |
| POST | `/api/v1/config` | public | GENERAL (1000/15m) | `routes/config/settings.js:35` |
| DELETE | `/api/v1/favicon` | public | GENERAL (1000/15m) | `routes/config/assets.js:272` |
| GET | `/api/v1/favicon` | public | GENERAL (1000/15m) | `routes/config/assets.js:203` |
| POST | `/api/v1/favicon` | public | GENERAL (1000/15m) | `routes/config/assets.js:212` |
| DELETE | `/api/v1/logo` | public | GENERAL (1000/15m) | `routes/config/assets.js:170` |
| GET | `/api/v1/logo` | public | GENERAL (1000/15m) | `routes/config/assets.js:77` |
| POST | `/api/v1/logo` | public | GENERAL (1000/15m) | `routes/config/assets.js:112` |
## Health
_12 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| DELETE | `/api/v1/health-checks/:serviceId/configure` | protected | GENERAL (1000/15m) | `routes/health.js:357` |
| POST | `/api/v1/health-checks/:serviceId/configure` | protected | GENERAL (1000/15m) | `routes/health.js:351` |
| GET | `/api/v1/health-checks/:serviceId/stats` | protected | GENERAL (1000/15m) | `routes/health.js:340` |
| GET | `/api/v1/health-checks/incidents` | protected | GENERAL (1000/15m) | `routes/health.js:363` |
| GET | `/api/v1/health-checks/incidents/history` | protected | GENERAL (1000/15m) | `routes/health.js:371` |
| GET | `/api/v1/health-checks/status` | public | GENERAL (1000/15m) | `routes/health.js:319` |
| GET | `/api/v1/health/ca` | public | GENERAL (1000/15m) | `routes/health.js:267` |
| GET | `/api/v1/health/cached` | protected | GENERAL (1000/15m) | `routes/health.js:179` |
| GET | `/api/v1/health/probe` | protected | GENERAL (1000/15m) | `routes/health.js:230` |
| GET | `/api/v1/health/pylon` | protected | GENERAL (1000/15m) | `routes/health.js:245` |
| GET | `/api/v1/health/service/:id` | protected | GENERAL (1000/15m) | `routes/health.js:188` |
| GET | `/api/v1/health/services` | protected | GENERAL (1000/15m) | `routes/health.js:109` |
## Services
_12 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| DELETE | `/api/v1/seedhost-creds` | protected | GENERAL (1000/15m) | `routes/services.js:310` |
| GET | `/api/v1/seedhost-creds` | protected | GENERAL (1000/15m) | `routes/services.js:289` |
| POST | `/api/v1/seedhost-creds` | protected | GENERAL (1000/15m) | `routes/services.js:272` |
| GET | `/api/v1/services` | public | GENERAL (1000/15m) | `routes/services.js:374` |
| POST | `/api/v1/services` | public | GENERAL (1000/15m) | `routes/services.js:389` |
| PUT | `/api/v1/services` | public | GENERAL (1000/15m) | `routes/services.js:434` |
| DELETE | `/api/v1/services/:id` | protected | GENERAL (1000/15m) | `routes/services.js:462` |
| DELETE | `/api/v1/services/:serviceId/credentials` | protected | GENERAL (1000/15m) | `routes/services.js:237` |
| GET | `/api/v1/services/:serviceId/credentials` | protected | GENERAL (1000/15m) | `routes/services.js:252` |
| POST | `/api/v1/services/:serviceId/credentials` | protected | GENERAL (1000/15m) | `routes/services.js:213` |
| GET | `/api/v1/services/status` | public | GENERAL (1000/15m) | `routes/services.js:327` |
| POST | `/api/v1/services/update` | protected | GENERAL (1000/15m) | `routes/services.js:486` |
## Containers (lifecycle)
_10 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| DELETE | `/api/v1/containers/:id` | protected | GENERAL (1000/15m) | `routes/containers.js:235` |
| GET | `/api/v1/containers/:id/check-update` | protected | GENERAL (1000/15m) | `routes/containers.js:155` |
| GET | `/api/v1/containers/:id/logs` | protected | GENERAL (1000/15m) | `routes/containers.js:193` |
| GET | `/api/v1/containers/:id/resources` | protected | GENERAL (1000/15m) | `routes/containers.js:223` |
| PUT | `/api/v1/containers/:id/resources` | protected | GENERAL (1000/15m) | `routes/containers.js:205` |
| POST | `/api/v1/containers/:id/restart` | protected | GENERAL (1000/15m) | `routes/containers.js:48` |
| POST | `/api/v1/containers/:id/start` | protected | GENERAL (1000/15m) | `routes/containers.js:34` |
| POST | `/api/v1/containers/:id/stop` | protected | GENERAL (1000/15m) | `routes/containers.js:41` |
| POST | `/api/v1/containers/:id/update` | protected | GENERAL (1000/15m) | `routes/containers.js:55` |
| GET | `/api/v1/containers/discover` | protected | GENERAL (1000/15m) | `routes/containers.js:242` |
## Core / system
_9 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/docs` | protected | GENERAL (1000/15m) | `src/app.js:925` |
| GET | `/api/v1/docs/spec` | protected | GENERAL (1000/15m) | `src/app.js:943` |
| GET | `/api/v1/network/ips` | protected | GENERAL (1000/15m) | `src/app.js:899` |
| GET | `/health` | public | GENERAL (1000/15m) | `src/app.js:777` |
| GET | `/health/live` | public | GENERAL (1000/15m) | `src/app.js:778` |
| GET | `/health/ready` | public | GENERAL (1000/15m) | `src/app.js:782` |
| GET | `/healthz` | public | GENERAL (1000/15m) | `src/app.js:779` |
| GET | `/probe/:id` | public | GENERAL (1000/15m) | `src/app.js:786` |
| GET | `/readyz` | public | GENERAL (1000/15m) | `src/app.js:783` |
## Dependencies
_8 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| DELETE | `/api/v1/dependencies/:serviceId` | protected | GENERAL (1000/15m) | `routes/dependencies.js:166` |
| GET | `/api/v1/dependencies/:serviceId` | protected | GENERAL (1000/15m) | `routes/dependencies.js:80` |
| POST | `/api/v1/dependencies/:serviceId` | protected | GENERAL (1000/15m) | `routes/dependencies.js:123` |
| GET | `/api/v1/dependencies/:serviceId/chain` | protected | GENERAL (1000/15m) | `routes/dependencies.js:105` |
| POST | `/api/v1/dependencies/:serviceId/restart` | protected | GENERAL (1000/15m) | `routes/dependencies.js:198` |
| GET | `/api/v1/dependencies/:serviceId/status` | protected | GENERAL (1000/15m) | `routes/dependencies.js:114` |
| GET | `/api/v1/dependencies/graph` | protected | GENERAL (1000/15m) | `routes/dependencies.js:48` |
| GET | `/api/v1/dependencies/validate` | protected | GENERAL (1000/15m) | `routes/dependencies.js:56` |
## Notifications
_8 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/notifications/config` | protected | GENERAL (1000/15m) | `routes/notifications.js:20` |
| POST | `/api/v1/notifications/config` | protected | GENERAL (1000/15m) | `routes/notifications.js:53` |
| POST | `/api/v1/notifications/health-check` | protected | GENERAL (1000/15m) | `routes/notifications.js:214` |
| DELETE | `/api/v1/notifications/history` | protected | GENERAL (1000/15m) | `routes/notifications.js:208` |
| GET | `/api/v1/notifications/history` | protected | GENERAL (1000/15m) | `routes/notifications.js:192` |
| POST | `/api/v1/notifications/send` | protected | GENERAL (1000/15m) | `routes/notifications.js:246` |
| GET | `/api/v1/notifications/status` | protected | GENERAL (1000/15m) | `routes/notifications.js:224` |
| POST | `/api/v1/notifications/test` | protected | GENERAL (1000/15m) | `routes/notifications.js:159` |
## App recipes
_8 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| DELETE | `/api/v1/:recipeId` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:197` |
| POST | `/api/v1/:recipeId/restart` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:171` |
| POST | `/api/v1/:recipeId/start` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:108` |
| POST | `/api/v1/:recipeId/stop` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:139` |
| POST | `/api/v1/deploy` | protected | GENERAL (1000/15m) | `routes/recipes/deploy.js:29` |
| GET | `/api/v1/deployed` | protected | GENERAL (1000/15m) | `routes/recipes/manage.js:24` |
| GET | `/api/v1/templates` | protected | GENERAL (1000/15m) | `routes/recipes/index.js:34` |
| GET | `/api/v1/templates/:recipeId` | protected | GENERAL (1000/15m) | `routes/recipes/index.js:63` |
## Docker resources
_7 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/docker/disk-usage` | protected | GENERAL (1000/15m) | `routes/docker-resources.js:84` |
| GET | `/api/v1/docker/networks` | protected | GENERAL (1000/15m) | `routes/docker-resources.js:50` |
| POST | `/api/v1/docker/networks` | protected | GENERAL (1000/15m) | `routes/docker-resources.js:64` |
| DELETE | `/api/v1/docker/networks/:id` | protected | GENERAL (1000/15m) | `routes/docker-resources.js:76` |
| GET | `/api/v1/docker/volumes` | protected | GENERAL (1000/15m) | `routes/docker-resources.js:17` |
| POST | `/api/v1/docker/volumes` | protected | GENERAL (1000/15m) | `routes/docker-resources.js:30` |
| DELETE | `/api/v1/docker/volumes/:name` | protected | GENERAL (1000/15m) | `routes/docker-resources.js:42` |
## Caddy / sites
_7 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/caddy/cas` | protected | GENERAL (1000/15m) | `routes/sites.js:57` |
| GET | `/api/v1/caddy/config` | protected | GENERAL (1000/15m) | `routes/sites.js:31` |
| POST | `/api/v1/caddy/reload` | protected | GENERAL (1000/15m) | `routes/sites.js:38` |
| GET | `/api/v1/caddyfile` | protected | GENERAL (1000/15m) | `routes/sites.js:25` |
| POST | `/api/v1/site` | protected | GENERAL (1000/15m) | `routes/sites.js:160` |
| DELETE | `/api/v1/site/:domain` | protected | GENERAL (1000/15m) | `routes/sites.js:135` |
| POST | `/api/v1/site/external` | protected | GENERAL (1000/15m) | `routes/sites.js:188` |
## Updates / workflows
_6 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/workflows/workflows` | protected | GENERAL (1000/15m) | `routes/workflows.js:22` |
| POST | `/api/v1/workflows/workflows/:workflowId/disable` | protected | GENERAL (1000/15m) | `routes/workflows.js:35` |
| POST | `/api/v1/workflows/workflows/:workflowId/enable` | protected | GENERAL (1000/15m) | `routes/workflows.js:28` |
| GET | `/api/v1/workflows/workflows/:workflowId/history` | protected | GENERAL (1000/15m) | `routes/workflows.js:52` |
| POST | `/api/v1/workflows/workflows/:workflowId/run` | protected | GENERAL (1000/15m) | `routes/workflows.js:42` |
| GET | `/api/v1/workflows/workflows/history` | protected | GENERAL (1000/15m) | `routes/workflows.js:60` |
## Auto-restart
_5 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/policies` | protected | GENERAL (1000/15m) | `routes/auto-restart.js:30` |
| DELETE | `/api/v1/policies/:serviceId` | protected | GENERAL (1000/15m) | `routes/auto-restart.js:103` |
| GET | `/api/v1/policies/:serviceId` | protected | GENERAL (1000/15m) | `routes/auto-restart.js:39` |
| POST | `/api/v1/policies/:serviceId` | protected | GENERAL (1000/15m) | `routes/auto-restart.js:60` |
| POST | `/api/v1/policies/:serviceId/test` | protected | GENERAL (1000/15m) | `routes/auto-restart.js:123` |
## Certificate authority
_5 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/ca/cert/:domain` | public | GENERAL (1000/15m) | `routes/ca.js:127` |
| GET | `/api/v1/ca/certs` | public | GENERAL (1000/15m) | `routes/ca.js:242` |
| GET | `/api/v1/ca/info` | public | GENERAL (1000/15m) | `routes/ca.js:15` |
| GET | `/api/v1/ca/install-script` | public | GENERAL (1000/15m) | `routes/ca.js:63` |
| GET | `/api/v1/ca/root.crt` | public | GENERAL (1000/15m) | `routes/ca.js:45` |
## OpenClaw integration
_5 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| DELETE | `/api/v1/openclaw/` | protected | GENERAL (1000/15m) | `routes/openclaw.js:244` |
| POST | `/api/v1/openclaw/deploy` | protected | GENERAL (1000/15m) | `routes/openclaw.js:150` |
| GET | `/api/v1/openclaw/proxy/*` | protected | GENERAL (1000/15m) | `routes/openclaw.js:216` |
| POST | `/api/v1/openclaw/proxy/*` | protected | GENERAL (1000/15m) | `routes/openclaw.js:230` |
| GET | `/api/v1/openclaw/status` | protected | GENERAL (1000/15m) | `routes/openclaw.js:116` |
## Config drift
_4 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| POST | `/api/v1/fix` | protected | GENERAL (1000/15m) | `routes/config-drift.js:51` |
| GET | `/api/v1/last` | protected | GENERAL (1000/15m) | `routes/config-drift.js:39` |
| POST | `/api/v1/polling` | protected | GENERAL (1000/15m) | `routes/config-drift.js:66` |
| GET | `/api/v1/report` | protected | GENERAL (1000/15m) | `routes/config-drift.js:30` |
## Licensing
_4 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| POST | `/api/v1/license/activate` | protected | GENERAL (1000/15m) | `routes/license.js:16` |
| POST | `/api/v1/license/deactivate` | protected | GENERAL (1000/15m) | `routes/license.js:41` |
| GET | `/api/v1/license/feature/:feature` | public | GENERAL (1000/15m) | `routes/license.js:52` |
| GET | `/api/v1/license/status` | public | GENERAL (1000/15m) | `routes/license.js:35` |
## File browser
_3 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/browse/directories` | protected | GENERAL (1000/15m) | `routes/browse.js:52` |
| GET | `/api/v1/browse/roots` | protected | GENERAL (1000/15m) | `routes/browse.js:34` |
| GET | `/api/v1/media/detected-mounts` | protected | GENERAL (1000/15m) | `routes/browse.js:137` |
## Theming
_3 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/themes` | public | GENERAL (1000/15m) | `routes/themes.js:40` |
| DELETE | `/api/v1/themes/:slug` | protected | GENERAL (1000/15m) | `routes/themes.js:65` |
| POST | `/api/v1/themes/:slug` | protected | GENERAL (1000/15m) | `routes/themes.js:45` |
## Service credentials
_2 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/credentials/list` | protected | GENERAL (1000/15m) | `routes/credentials.js:15` |
| POST | `/api/v1/credentials/rotate-key` | protected | GENERAL (1000/15m) | `routes/credentials.js:21` |
## Events
_2 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/events/clients` | protected | GENERAL (1000/15m) | `routes/events.js:154` |
| GET | `/api/v1/events/stream` | protected | GENERAL (1000/15m) | `routes/events.js:126` |
## Internal helpers
_1 routes_
| Method | Full path | Auth | Rate limit | Defined in |
|---|---|---|---|---|
| GET | `/api/v1/status` | protected | GENERAL (1000/15m) | `routes/context.js:8` |
## Mount Point Map
How `src/app.js` wires route files to URL prefixes (via `apiRouter.use`):
| Route file(s) | Mounted at |
|---|---|
| `routes/ca` | `/api/v1/ca` |
| `routes/containers` | `/api/v1/containers` |
| `routes/dependencies` | `/api/v1/dependencies` |
| `routes/dns` | `/api/v1/dns` |
| `routes/docker-resources` | `/api/v1/docker` |
| `routes/events` | `/api/v1/events` |
| `routes/license` | `/api/v1/license` |
| `routes/notifications` | `/api/v1/notifications` |
| `routes/openclaw` | `/api/v1/openclaw` |
| `routes/recipes/` | `/api/v1/recipes` |
| `routes/tailscale` | `/api/v1/tailscale` |
| `routes/tailscale-admin` | `/api/v1/tailscale` |
| `routes/workflows` | `/api/v1/workflows` |
| `routes/auth/` | `/api/v1 (root)` |
| `routes/config/` | `/api/v1 (root)` |
| `routes/services` | `/api/v1 (root)` |
| `routes/health` | `/api/v1 (root)` |
| `routes/monitoring` | `/api/v1 (root)` |
| `routes/updates` | `/api/v1 (root)` |
| `routes/sites` | `/api/v1 (root)` |
| `routes/credentials` | `/api/v1 (root)` |
| `routes/arr/` | `/api/v1 (root)` |
| `routes/apps/` | `/api/v1 (root)` |
| `routes/logs` | `/api/v1 (root)` |
| `routes/backups` | `/api/v1 (root)` |
| `routes/browse` | `/api/v1 (root)` |
| `routes/errorlogs` | `/api/v1 (root)` |
| `routes/themes` | `/api/v1 (root)` |
| `routes/auto-restart` | `/api/v1 (root)` |
| `routes/config-drift` | `/api/v1 (root)` |
| `routes/ssl-monitor` | `/api/v1 (root)` |
## PUBLIC_ROUTES Allowlist
Source: `src/utilities/middleware.js:310-364` (42 entries)
| Method | Path | Match |
|---|---|---|
| ANY | `/health` | exact |
| ANY | `/health/live` | exact |
| ANY | `/health/ready` | exact |
| ANY | `/healthz` | exact |
| ANY | `/readyz` | exact |
| ANY | `/probe/` | prefix |
| ANY | `/api/v1/tailscale/` | prefix |
| ANY | `/api/v1/totp/config` | exact |
| ANY | `/api/v1/totp/recovery-info` | exact |
| ANY | `/api/v1/totp/verify` | exact |
| ANY | `/api/v1/totp/setup` | exact |
| ANY | `/api/v1/totp/verify-setup` | exact |
| ANY | `/api/v1/totp/check-session` | exact |
| ANY | `/api/v1/auth/gate/` | prefix |
| ANY | `/api/v1/auth/app-token/` | prefix |
| ANY | `/api/v1/auth/login-page` | exact |
| ANY | `/api/v1/services` | exact |
| ANY | `/api/v1/ca/info` | exact |
| ANY | `/api/v1/ca/root.crt` | exact |
| ANY | `/api/v1/ca/install-script` | exact |
| ANY | `/api/v1/health/ca` | exact |
| GET | `/api/v1/ca/cert/` | prefix |
| ANY | `/api/v1/ca/certs` | exact |
| ANY | `/api/v1/csrf-token` | exact |
| ANY | `/api/v1/logo` | exact |
| ANY | `/api/v1/favicon` | exact |
| ANY | `/api/v1/themes` | exact |
| ANY | `/api/v1/license/status` | exact |
| GET | `/api/v1/license/feature/` | prefix |
| ANY | `/api/v1/config` | exact |
| ANY | `/api/v1/services/status` | exact |
| ANY | `/api/v1/health-checks/status` | exact |
| ANY | `/api/v1/monitoring/stats` | exact |
| ANY | `/api/v1/system/version` | exact |
| ANY | `/api/v1/system/update-status` | exact |
| ANY | `/api/v1/system/update-history` | exact |
| ANY | `/api/v1/system/update-check` | exact |
| ANY | `/api/v1/updates/available` | exact |
| ANY | `/api/v1/system/update-notify` | exact |
| ANY | `/api/v1/monitoring/stats` | exact |
| ANY | `/api/v1/health-checks/status` | exact |
| ANY | `/api/v1/version` | exact |
## Root-Level Endpoints (defined directly in src/app.js)
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | `/health` | public | Liveness (alias for `/health/live`) |
| GET | `/health/live` | public | Process-only check, no I/O |
| GET | `/health/ready` | public | Checks config + services + Docker + Caddy-admin (3s timeout each) |
| GET | `/healthz` | public | k8s alias for `/health/live` |
| GET | `/readyz` | public | k8s alias for `/health/ready` |
| GET | `/probe/:id` | public | Per-service health probe, sets `X-DashCaddy-HealthCheck: 1` |
| GET | `/api/v1/network/ips` | protected | Detected network interfaces + IPs (cached) |
| GET | `/api/v1/docs` | protected | Interactive Swagger UI |
| GET | `/api/v1/docs/spec` | protected | Raw OpenAPI 3.0.3 spec |
| GET | `/api/v1/version` | public (per PUBLIC_ROUTES) | API version |
## OpenAPI Spec Cross-Check
- Routes defined in code: **236**
- Paths in `openapi.yaml`: **112**
### In code but NOT documented in OpenAPI (142)
- `/api/v1/:appId`
- `/api/v1/:appId/backup-points`
- `/api/v1/:appId/restore`
- `/api/v1/:appId/revert/:filename`
- `/api/v1/:recipeId`
- `/api/v1/:recipeId/restart`
- `/api/v1/:recipeId/start`
- `/api/v1/:recipeId/stop`
- `/api/v1/arr/quality-profiles`
- `/api/v1/audit-logs`
- `/api/v1/auth/jwt`
- `/api/v1/auth/keys`
- `/api/v1/auth/keys/:keyId`
- `/api/v1/auth/login-page`
- `/api/v1/backups/backup/:appId`
- `/api/v1/backups/compare/:filename`
- `/api/v1/backups/credentials/:provider`
- `/api/v1/backups/files`
- `/api/v1/backups/files/:appId`
- `/api/v1/backups/restore-file/:filename`
- `/api/v1/backups/schedule`
- `/api/v1/backups/schedule/:appId`
- `/api/v1/backups/storage-info`
- `/api/v1/backups/test-destination`
- `/api/v1/browse/directories`
- `/api/v1/ca/cert/:domain`
- `/api/v1/ca/certs`
- `/api/v1/ca/info`
- `/api/v1/ca/install-script`
- `/api/v1/ca/root.crt`
- ... and 112 more
### Documented but NOT in code (18)
- `/api/v1/apps/:appId`
- `/api/v1/apps/check-existing`
- `/api/v1/apps/check-port/:port`
- `/api/v1/apps/deploy`
- `/api/v1/apps/suggest-port/:basePort`
- `/api/v1/apps/templates`
- `/api/v1/apps/templates/:appId`
- `/api/v1/apps/update-subdomain`
- `/api/v1/audit-log`
- `/api/v1/browse/dir`
- `/api/v1/caddy/get-cas`
- `/api/v1/health`
- `/api/v1/health-check/configure/:serviceId`
- `/api/v1/health-check/incidents`
- `/api/v1/health-check/incidents/history`
- `/api/v1/health-check/stats/:serviceId`
- `/api/v1/health-check/status`
- `/api/v1/service-creds/:serviceId`