-
Notifications
You must be signed in to change notification settings - Fork 2
REST API Reference
Docker Sentinel exposes a JSON REST API on the same port as the web dashboard (default :8080). All API endpoints return JSON unless stated otherwise.
When authentication is enabled (the default after first-run setup), every API request must include one of:
-
Session cookie, set automatically by the browser after
POST /login. -
API bearer token, created on the My Account page or via
POST /api/auth/tokens.
Authorization: Bearer stk_abc123...
Token-authenticated requests are CSRF-exempt. Session-authenticated requests must include the CSRF token from the sentinel_csrf cookie in the X-CSRF-Token header.
When authentication is disabled, all endpoints are accessible without credentials.
The webhook endpoint (POST /api/webhook) uses its own secret-based auth instead of sessions/tokens.
10 granular permissions grouped into 3 built-in roles:
| Permission | Admin | Operator | Viewer |
|---|---|---|---|
containers.view |
yes | yes | yes |
containers.update |
yes | yes | no |
containers.manage |
yes | yes | no |
containers.approve |
yes | yes | no |
containers.rollback |
yes | yes | no |
history.view |
yes | yes | yes |
logs.view |
yes | yes | yes |
settings.view |
yes | yes | no |
settings.modify |
yes | no | no |
users.manage |
yes | no | no |
All error responses use a consistent JSON envelope:
{"error": "description of the problem"}| Code | Meaning |
|---|---|
400 |
Bad request / validation failure |
401 |
Not authenticated |
403 |
Insufficient permissions or self-protection |
404 |
Resource not found |
409 |
Conflict (e.g. setup already complete, username taken) |
429 |
Rate limited (login attempts) |
500 |
Internal server error |
502 |
Registry check failed (upstream error) |
| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/containers |
containers.view |
List all containers |
GET |
/api/containers/{name} |
containers.view |
Container detail (history, snapshots) |
GET |
/api/containers/{name}/versions |
containers.view |
Available semver versions from registry |
GET |
/api/containers/{name}/tags |
containers.view |
All tags for the container's image |
GET |
/api/containers/{name}/logs |
containers.view |
Container log output |
GET |
/api/containers/{name}/row |
containers.view |
HTML partial + stats (for SSE row refresh) |
GET |
/api/containers/{name}/ghcr |
containers.view |
GHCR alternative image info |
GET |
/api/stats |
containers.view |
Dashboard stat card counts |
GET |
/api/last-scan |
containers.view |
Timestamp of last completed scan |
GET |
/api/ghcr/alternatives |
containers.view |
All known GHCR alternatives |
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/containers[
{
"id": "abc123def456...",
"name": "nginx",
"image": "nginx:1.27.4",
"policy": "auto",
"state": "running",
"maintenance": false,
"stack": "webstack"
}
]curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/containers/nginx{
"id": "abc123def456...",
"name": "nginx",
"image": "nginx:1.27.4",
"policy": "auto",
"state": "running",
"maintenance": false,
"history": [
{
"timestamp": "2025-01-15T14:30:00Z",
"container_name": "nginx",
"type": "update",
"old_image": "nginx:1.27.3",
"new_image": "nginx:1.27.4",
"outcome": "success",
"duration": 12500000000
}
],
"snapshots": [
{
"container_name": "nginx",
"timestamp": "2025-01-15T14:29:58Z"
}
]
}| Parameter | Type | Default | Description |
|---|---|---|---|
lines |
int | 50 | Number of lines (max 500) |
host |
string | Cluster host ID for remote containers |
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/containers/nginx/logs?lines=100"{
"logs": "2025-01-15 10:30:00 ...\n...",
"lines": 100,
"remote": false
}Returns lightweight counts for the dashboard stat cards.
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/stats{
"total": 25,
"running": 23,
"pending": 2
}{"last_scan": "2025-01-15T14:00:00Z"}Returns {"last_scan": null} if no scan has run yet.
All action endpoints accept an optional ?host=<hostID> query parameter to target remote cluster containers. Actions on the Sentinel container itself (identified by sentinel.self=true label) return 403 Forbidden.
| Method | Path | Permission | Description |
|---|---|---|---|
POST |
/api/containers/{name}/restart |
containers.manage |
Restart a container |
POST |
/api/containers/{name}/stop |
containers.manage |
Stop a container |
POST |
/api/containers/{name}/start |
containers.manage |
Start a container |
POST |
/api/update/{name} |
containers.update |
Trigger update (pull + recreate) |
POST |
/api/check/{name} |
containers.update |
Check registry for updates |
POST |
/api/containers/{name}/rollback |
containers.rollback |
Rollback to last snapshot |
POST |
/api/containers/{name}/switch-ghcr |
containers.update |
Migrate from Docker Hub to GHCR |
POST |
/api/containers/{name}/update-to-version |
containers.update |
Update to a specific tag |
POST |
/api/scan |
containers.update |
Trigger a full scan cycle |
POST |
/api/self-update |
settings.modify |
Self-update Sentinel |
Triggers a container update. If a newer version was found during scanning, the container is recreated with the new image. Runs asynchronously.
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/update/nginx{
"status": "started",
"name": "nginx",
"message": "update started for nginx"
}Recreates a container with an explicit image tag. For Sentinel containers, routes through the self-updater helper.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"tag": "1.27.5"}' \
http://localhost:8080/api/containers/nginx/update-to-version{
"status": "started",
"name": "nginx",
"message": "Updating nginx to nginx:1.27.5"
}Runs a synchronous registry check for a single container. If an update is found, it is added to the queue and SSE events are emitted. Ignored versions are filtered out.
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/check/nginxUpdate available:
{
"status": "update_available",
"name": "nginx",
"message": "Update available for nginx",
"newer_versions": ["1.27.5"]
}Up to date:
{
"status": "up_to_date",
"name": "nginx",
"message": "nginx is up to date"
}Rolls back to the most recent pre-update snapshot. If a rollback_policy setting is configured (manual or pinned), the container's policy is changed after rollback to prevent the next scan from retrying the same update.
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/containers/nginx/rollback{
"status": "started",
"name": "nginx",
"message": "rollback started for nginx"
}Triggers an immediate full scan of all containers. Runs asynchronously.
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/scan{"message": "Scan started"}Triggers a self-update via an ephemeral helper container. Sentinel will restart.
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/self-update{
"status": "started",
"message": "Self-update to v2.1.0 initiated -- Sentinel will restart shortly"
}| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/queue |
containers.view |
List pending updates (with release notes URLs) |
GET |
/api/queue/count |
containers.view |
Pending update count |
POST |
/api/approve/{key} |
containers.approve |
Approve and execute a pending update |
POST |
/api/reject/{key} |
containers.approve |
Reject and remove a pending update |
POST |
/api/ignore/{key} |
containers.approve |
Ignore a specific version |
The {key} is the container name for local containers, or hostID::name for remote cluster containers.
Returns pending updates enriched with release notes URLs when available.
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/queue[
{
"container_name": "nginx",
"current_image": "nginx:1.27.4",
"newer_versions": ["1.27.5"],
"resolved_current_version": "1.27.4",
"resolved_target_version": "1.27.5",
"release_notes_url": "https://github.com/nginx/nginx/releases/tag/1.27.5"
}
]Lightweight count without release notes enrichment.
{"count": 3}Approves a pending update and triggers the update asynchronously. For remote containers, dispatches to the cluster agent.
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/approve/nginx{
"status": "approved",
"name": "nginx",
"message": "update started for nginx"
}Ignores the top version in the pending update. That version will be excluded from future queue entries.
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/ignore/nginx{
"status": "ignored",
"name": "nginx",
"version": "1.27.5",
"message": "version 1.27.5 ignored for nginx"
}Removes the pending update from the queue without ignoring the version. It may reappear on the next scan.
{
"status": "rejected",
"name": "nginx",
"message": "update rejected for nginx"
}| Method | Path | Permission | Description |
|---|---|---|---|
POST |
/api/containers/{name}/policy |
containers.manage |
Set policy override |
DELETE |
/api/containers/{name}/policy |
containers.manage |
Remove policy override (revert to label) |
POST |
/api/bulk/policy |
containers.manage |
Bulk policy change |
Valid policies: auto, manual, pinned.
For remote containers, add ?host=<hostID> to scope the override.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"policy": "manual"}' \
http://localhost:8080/api/containers/nginx/policy{
"status": "ok",
"name": "nginx",
"policy": "manual",
"message": "policy set to manual for nginx"
}Supports two modes: preview (default) shows what would change, confirm applies the changes. Self-protected containers are blocked.
Preview:
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"containers": ["nginx", "redis"], "policy": "pinned"}' \
http://localhost:8080/api/bulk/policy{
"mode": "preview",
"changes": [{"name": "nginx", "from": "auto", "to": "pinned"}],
"blocked": [],
"unchanged": [{"name": "redis", "reason": "already pinned"}]
}Confirm:
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"containers": ["nginx", "redis"], "policy": "pinned", "confirm": true}' \
http://localhost:8080/api/bulk/policy{
"mode": "executed",
"applied": 1,
"blocked": 0,
"unchanged": 1
}| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/settings |
settings.view |
All settings (env + runtime overrides) |
GET |
/api/about |
settings.view |
Version, uptime, stats, channels |
GET |
/api/ratelimits |
containers.view |
Registry rate limit status |
GET |
/api/release-sources |
settings.view |
Release note source mappings |
Returns all configuration values merged from environment variables and runtime overrides stored in BoltDB.
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/settings{
"SENTINEL_POLL_INTERVAL": "6h",
"poll_interval": "6h",
"default_policy": "auto",
"paused": "false"
}{
"version": "v2.0.1",
"commit": "a1b2c3d",
"go_version": "go1.24.4",
"data_directory": "/data",
"uptime": "3d 12h 5m",
"started_at": "2025-01-12T08:00:00Z",
"poll_interval": "6h",
"last_scan": "2025-01-15T14:00:00Z",
"containers": 25,
"updates_applied": 42,
"snapshots": 150,
"channels": [{"name": "Gotify", "type": "gotify"}],
"registries": ["docker.io", "ghcr.io"]
}{
"health": "ok",
"registries": [
{
"registry": "docker.io",
"remaining": 95,
"limit": 100,
"reset": "2025-01-15T15:00:00Z"
}
]
}All setting modification endpoints accept JSON request bodies and require settings.modify permission. They return a status message on success.
| Method | Path | Body | Description |
|---|---|---|---|
POST |
/api/settings/poll-interval |
{"interval": "6h"} |
Poll interval (5m to 24h) |
POST |
/api/settings/default-policy |
{"policy": "auto"} |
Default policy (auto/manual/pinned) |
POST |
/api/settings/grace-period |
{"duration": "30s"} |
Grace period (0 to 10m) |
POST |
/api/settings/pause |
{"paused": true} |
Pause/unpause scanning |
POST |
/api/settings/latest-auto-update |
{"enabled": true} |
Auto-update :latest containers |
POST |
/api/settings/filters |
{"patterns": ["test-*"]} |
Scan exclusion filters |
POST |
/api/settings/image-cleanup |
{"enabled": true} |
Remove old images after update |
POST |
/api/settings/image-backup |
{"enabled": true} |
Backup images before update |
POST |
/api/settings/schedule |
{"schedule": "0 3 * * *"} |
Cron schedule (empty to disable) |
POST |
/api/settings/hooks-enabled |
{"enabled": true} |
Toggle lifecycle hooks |
POST |
/api/settings/hooks-write-labels |
{"enabled": true} |
Toggle hook label persistence |
POST |
/api/settings/dependency-aware |
{"enabled": true} |
Dependency-aware update ordering |
POST |
/api/settings/rollback-policy |
{"policy": "manual"} |
Policy after rollback (none/manual/pinned) |
POST |
/api/settings/version-scope |
{"scope": "minor"} |
Default version scope |
POST |
/api/settings/dry-run |
{"enabled": true} |
Dry-run mode |
POST |
/api/settings/pull-only |
{"enabled": true} |
Pull-only mode (no recreate) |
POST |
/api/settings/update-delay |
{"delay": "1h"} |
Delay before applying updates |
POST |
/api/settings/show-stopped |
{"enabled": true} |
Show stopped containers on dashboard |
POST |
/api/settings/remove-volumes |
{"enabled": true} |
Remove volumes on container recreate |
POST |
/api/settings/scan-concurrency |
{"concurrency": 4} |
Concurrent registry checks |
POST |
/api/settings/maintenance-window |
{"window": "..."} |
Maintenance window |
POST |
/api/settings/stack-order |
{"order": ["a", "b"]} |
Dashboard stack display order |
POST |
/api/settings/dashboard-columns |
{"columns": ["image","status"]} |
Visible dashboard columns |
POST |
/api/settings/compose-sync |
{"enabled": true} |
Compose sync |
POST |
/api/settings/ha-discovery |
{"enabled": true, ...} |
HA discovery settings |
POST |
/api/settings/switch-role |
{"role": "server"} |
Switch instance role |
POST |
/api/settings/docker-tls |
{"ca":"..","cert":"..","key":".."} |
Docker TLS cert paths |
POST |
/api/settings/docker-tls-test |
(same as above) | Test Docker TLS connection |
POST |
/api/settings/general |
{"key": "web_port", "value": "9090"} |
General settings (web_port, tls_mode, log_format) |
PUT |
/api/release-sources |
[{"image_pattern":"..","github_repo":".."}] |
Replace release note source mappings |
Example:
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"interval": "4h"}' \
http://localhost:8080/api/settings/poll-interval{
"status": "ok",
"interval": "4h0m0s",
"message": "poll interval updated to 4h0m0s"
}| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/settings/notifications |
settings.view |
List channels (secrets masked) |
PUT |
/api/settings/notifications |
settings.modify |
Save channels |
POST |
/api/settings/notifications/test |
settings.modify |
Send test notification |
GET |
/api/settings/notifications/event-types |
settings.view |
Available event types for filtering |
GET |
/api/settings/notifications/templates |
settings.view |
Custom notification templates |
PUT |
/api/settings/notifications/templates |
settings.modify |
Save a custom template |
DELETE |
/api/settings/notifications/templates/{type} |
settings.modify |
Delete template (revert to default) |
POST |
/api/settings/notifications/templates/preview |
settings.modify |
Preview template with sample data |
Saves the full channel list. Secrets from previously saved channels are restored if the value is masked in the request.
curl -X PUT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '[{
"id": "ch1",
"name": "Gotify",
"type": "gotify",
"enabled": true,
"settings": {"url": "http://gotify:8080", "token": "abc123"}
}]' \
http://localhost:8080/api/settings/notifications{"status": "ok", "message": "notification settings saved"}Test a specific channel by ID, or the entire notification chain if no ID is provided.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"id": "ch1"}' \
http://localhost:8080/api/settings/notifications/test{"status": "ok", "message": "test notification sent to Gotify"}curl -X PUT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"event_type": "update_available", "template": "Update: {{.ContainerName}}"}' \
http://localhost:8080/api/settings/notifications/templatescurl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"event_type": "update_available", "template": "Update: {{.ContainerName}}"}' \
http://localhost:8080/api/settings/notifications/templates/preview{"preview": "Update: sentinel-test"}| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/containers/{name}/notify-pref |
settings.view |
Get notification mode |
POST |
/api/containers/{name}/notify-pref |
settings.modify |
Set notification mode |
GET |
/api/settings/container-notify-prefs |
settings.view |
All per-container preferences |
DELETE |
/api/notify-states |
settings.modify |
Clear all notification dedup states |
Valid modes: default, every_scan, digest_only, muted.
For remote containers, add ?host=<hostID>.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"mode": "muted"}' \
http://localhost:8080/api/containers/nginx/notify-pref{"status": "ok", "mode": "muted"}| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/settings/digest |
settings.view |
Get digest settings |
POST |
/api/settings/digest |
settings.modify |
Save digest settings |
POST |
/api/digest/trigger |
settings.modify |
Trigger immediate digest |
GET |
/api/digest/banner |
containers.view |
Pending digest banner info |
POST |
/api/digest/banner/dismiss |
containers.view |
Dismiss the banner |
All fields are optional; only provided fields are updated.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"digest_enabled": true, "digest_time": "09:00", "digest_interval": "24h"}' \
http://localhost:8080/api/settings/digest| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/settings/registries |
settings.view |
List credentials (masked) with rate limit status |
PUT |
/api/settings/registries |
settings.modify |
Save credentials |
POST |
/api/settings/registries/test |
settings.modify |
Test a credential |
DELETE |
/api/settings/registries/{id} |
settings.modify |
Delete a credential |
Secrets ending in **** are restored from previously saved values.
curl -X PUT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '[{"id":"r1","registry":"docker.io","username":"myuser","secret":"dckr_pat_abc"}]' \
http://localhost:8080/api/settings/registries{"status": "ok", "message": "registry credentials saved"}curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"registry":"docker.io","username":"myuser","secret":"dckr_pat_abc"}' \
http://localhost:8080/api/settings/registries/test{"success": true, "message": "Credentials valid"}| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/hooks/{container} |
settings.view |
List hooks for a container |
POST |
/api/hooks/{container} |
settings.modify |
Create or update a hook |
DELETE |
/api/hooks/{container}/{phase} |
settings.modify |
Delete a hook |
Valid phases: pre-update, post-update. For remote containers, add ?host=<hostID>.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"phase":"pre-update","command":["sh","-c","pg_dump > backup.sql"],"timeout":60}' \
http://localhost:8080/api/hooks/postgres{
"container_name": "postgres",
"phase": "pre-update",
"command": ["sh", "-c", "pg_dump > backup.sql"],
"timeout": 60
}| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/deps |
containers.view |
Full dependency graph |
GET |
/api/deps/{container} |
containers.view |
Dependencies for one container |
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/deps{
"containers": [
{"name": "app", "dependencies": ["db"], "dependents": []},
{"name": "db", "dependencies": [], "dependents": ["app"]}
],
"order": ["db", "app"],
"has_cycles": false
}{
"name": "app",
"dependencies": ["db"],
"dependents": []
}Available when Docker is running in Swarm mode.
| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/services |
containers.view |
List all Swarm services |
GET |
/api/services/{name}/detail |
containers.view |
Service detail with tasks |
POST |
/api/services/{name}/update |
containers.update |
Update service image |
POST |
/api/services/{name}/rollback |
containers.rollback |
Native Swarm rollback |
POST |
/api/services/{name}/scale |
containers.manage |
Scale replicas |
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"replicas": 3}' \
http://localhost:8080/api/services/web/scale{"status": "scaled", "previous_replicas": 1}Maximum 100 replicas. Scaling to 0 saves the previous count so "scale up" can restore it.
All cluster endpoints require settings.modify permission.
| Method | Path | Description |
|---|---|---|
GET |
/api/cluster/hosts |
List enrolled hosts with connection status |
POST |
/api/cluster/enroll-token |
Generate enrolment token |
DELETE |
/api/cluster/hosts/{id} |
Remove a host |
POST |
/api/cluster/hosts/{id}/revoke |
Revoke a host's certificate |
POST |
/api/cluster/hosts/{id}/pause |
Pause a host |
GET |
/api/settings/cluster |
Get cluster settings |
POST |
/api/settings/cluster |
Save cluster settings |
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/cluster/enroll-token{"token": "enroll_abc123...", "id": "host-uuid"}Sentinel supports connecting to multiple Portainer instances simultaneously. Each instance is identified by a server-generated ID.
| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/portainer/instances |
settings.modify |
List all configured instances (tokens redacted) |
POST |
/api/portainer/instances |
settings.modify |
Add a new Portainer instance |
PUT |
/api/portainer/instances/{id} |
settings.modify |
Update an existing instance (partial update) |
DELETE |
/api/portainer/instances/{id} |
settings.modify |
Remove an instance |
POST |
/api/portainer/instances/{id}/test |
settings.modify |
Test connectivity and populate endpoints |
| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/portainer/instances/{id}/endpoints |
containers.view |
List endpoints for an instance |
PUT |
/api/portainer/instances/{id}/endpoints/{epid} |
settings.modify |
Toggle endpoint enabled/blocked state |
GET |
/api/portainer/endpoints/{id}/containers |
containers.view |
Containers for a specific endpoint (pass ?instance_id= query param) |
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Production", "url": "https://portainer.example.com", "token": "ptr_your_api_token"}' \
http://localhost:8080/api/portainer/instancesResponse (201 Created):
{
"id": "1",
"name": "Production",
"url": "https://portainer.example.com",
"token": "***",
"enabled": true
}Supports partial updates - only include fields you want to change.
curl -X PUT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Production (renamed)", "enabled": false}' \
http://localhost:8080/api/portainer/instances/1Tests connectivity and auto-discovers endpoints. Endpoints on the local Docker socket are automatically blocked when the Portainer instance runs on the same host (to avoid duplicating direct monitoring). Endpoints whose Engine ID matches an existing local or cluster source are also auto-blocked.
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/portainer/instances/1/testResponse (200 OK):
{
"success": true,
"endpoints": [
{"ID": 1, "Name": "local", "URL": "unix:///var/run/docker.sock"},
{"ID": 2, "Name": "remote-host", "URL": "tcp://192.168.1.x:2376"}
]
}Toggle an endpoint's enabled state, or force-allow a blocked endpoint.
curl -X PUT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": true, "force_allow": true}' \
http://localhost:8080/api/portainer/instances/1/endpoints/2| Method | Path | Permission | Description |
|---|---|---|---|
POST |
/api/settings/npm-enabled |
settings.modify |
Toggle NPM integration |
POST |
/api/settings/npm-url |
settings.modify |
Set NPM URL |
POST |
/api/settings/npm-credentials |
settings.modify |
Set NPM login credentials |
POST |
/api/settings/npm-test |
settings.modify |
Test connection |
POST |
/api/settings/npm-sync |
settings.modify |
Sync proxy host mappings |
GET |
/api/settings/npm-mappings |
settings.view |
Get cached mappings |
GET /api/settings/npm-mappings supports ?grouped=true for mappings grouped by forward host.
Custom URL overrides per container port. Used for dashboard port link chips.
| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/containers/{name}/port-config |
containers.view |
Get port overrides |
POST |
/api/containers/{name}/port-config/{port} |
settings.modify |
Set a port override |
DELETE |
/api/containers/{name}/port-config/{port} |
settings.modify |
Remove a port override |
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://app.example.com"}' \
http://localhost:8080/api/containers/nginx/port-config/8080| Method | Path | Description |
|---|---|---|
POST |
/login |
Login (form or JSON) |
POST |
/setup |
First-run admin setup |
POST |
/logout |
End session |
POST |
/api/auth/totp/verify |
Complete 2FA login |
POST |
/api/auth/passkeys/login/begin |
Begin passkey login |
POST |
/api/auth/passkeys/login/finish |
Finish passkey login |
GET |
/api/auth/passkeys/available |
Check if passkeys are configured |
GET |
/api/auth/oidc/login |
Initiate OIDC login flow |
GET |
/api/auth/oidc/callback |
OIDC callback handler |
GET |
/api/auth/oidc/available |
Check if OIDC is configured |
| Method | Path | Description |
|---|---|---|
GET |
/api/auth/me |
Current user info and permissions |
POST |
/api/auth/change-password |
Change own password |
GET |
/api/auth/sessions |
List own sessions |
DELETE |
/api/auth/sessions/{token} |
Revoke a session |
DELETE |
/api/auth/sessions |
Revoke all other sessions |
POST |
/api/auth/tokens |
Create API bearer token |
DELETE |
/api/auth/tokens/{id} |
Delete API token |
POST |
/api/auth/passkeys/register/begin |
Begin passkey registration |
POST |
/api/auth/passkeys/register/finish |
Finish passkey registration |
GET |
/api/auth/passkeys |
List own passkeys |
DELETE |
/api/auth/passkeys/{id} |
Delete a passkey |
POST |
/api/auth/totp/setup |
Begin 2FA setup (returns secret + QR) |
POST |
/api/auth/totp/confirm |
Confirm 2FA (returns recovery codes) |
POST |
/api/auth/totp/disable |
Disable 2FA (requires password) |
GET |
/api/auth/totp/status |
2FA status |
| Method | Path | Description |
|---|---|---|
GET |
/api/auth/users |
List all users |
POST |
/api/auth/users |
Create user |
DELETE |
/api/auth/users/{id} |
Delete user |
POST |
/api/auth/settings |
Toggle auth on/off |
GET |
/api/settings/oidc |
Get OIDC settings |
POST |
/api/settings/oidc |
Save OIDC settings |
curl -X POST -H "Content-Type: application/json" \
-d '{"username":"admin","password":"secret"}' \
http://localhost:8080/loginSuccess:
{"redirect": "/"}2FA required:
{"totp_required": true, "totp_token": "pending_abc123"}curl -X POST -H "Content-Type: application/json" \
-d '{"totp_token":"pending_abc123","code":"123456"}' \
http://localhost:8080/api/auth/totp/verify{"redirect": "/"}curl -X POST -H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "CI Token"}' \
http://localhost:8080/api/auth/tokens{
"id": "tok_abc123",
"name": "CI Token",
"token": "stk_full_token_shown_once"
}The plaintext token is returned only once at creation time.
Valid roles: admin, operator, viewer.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"operator1","password":"strongpass123","role_id":"operator"}' \
http://localhost:8080/api/auth/users{"id": "usr_abc123", "username": "operator1"}{
"id": "usr_abc123",
"username": "admin",
"role_id": "admin",
"permissions": ["containers.view", "containers.update", "..."],
"auth_enabled": true
}| Method | Path | Auth | Description |
|---|---|---|---|
POST |
/api/webhook |
Webhook secret | Receive push events from registries or CI |
POST |
/api/settings/webhook-enabled |
Session/token | Toggle webhooks |
POST |
/api/settings/webhook-secret |
Session/token | Regenerate secret |
GET |
/api/settings/webhook-info |
Session/token | Webhook config (secret masked) |
Uses webhook secret authentication (not session/token). The secret must be passed via the X-Webhook-Secret header. Query-string authentication is not supported.
curl -X POST http://localhost:8080/api/webhook \
-H "X-Webhook-Secret: your_secret" \
-H "Content-Type: application/json" \
-d '{"push_data":{"tag":"latest"},"repository":{"repo_name":"myuser/myapp"}}'{
"status": "accepted",
"image": "myuser/myapp",
"tag": "latest",
"source": "dockerhub"
}Supported payload formats: Docker Hub, GHCR, and generic. Unrecognised payloads trigger a full scan as a fallback.
| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/history |
history.view |
Recent update history |
GET |
/api/history/export |
history.view |
Export all history |
GET |
/api/logs |
logs.view |
Activity log entries |
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | 50 | Max records (up to 200) |
before |
string | RFC3339 timestamp for cursor-based pagination |
curl -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/history?limit=10"[
{
"timestamp": "2025-01-15T14:30:00Z",
"container_name": "nginx",
"type": "update",
"old_image": "nginx:1.27.3",
"new_image": "nginx:1.27.4",
"outcome": "success",
"duration": 12500000000,
"host_id": "",
"host_name": ""
}
]| Parameter | Type | Default | Description |
|---|---|---|---|
format |
string | json |
json or csv
|
curl -H "Authorization: Bearer $TOKEN" -o history.csv \
"http://localhost:8080/api/history/export?format=csv"CSV columns: timestamp, container, type, old_image, new_image, outcome, duration_s, error, host_id, host_name.
| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/images |
containers.view |
List all Docker images |
POST |
/api/images/prune |
containers.manage |
Remove dangling images |
DELETE |
/api/images/{id} |
containers.manage |
Remove a specific image |
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:8080/api/images/prune| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/config/export |
settings.modify |
Download full config backup |
POST |
/api/config/import |
settings.modify |
Import config from backup (max 5 MB) |
GET |
/api/grafana-dashboard |
settings.modify |
Download Grafana dashboard JSON |
By default, secrets are replaced with ***REDACTED***. Add ?secrets=true to include them.
curl -H "Authorization: Bearer $TOKEN" -o sentinel-config.json \
http://localhost:8080/api/config/export{
"version": "1",
"exported_at": "2025-01-15T14:00:00Z",
"settings": {"poll_interval": "6h", "default_policy": "auto"},
"notifications": [],
"registries": []
}Redacted values (***REDACTED***) are skipped. Unknown setting keys are rejected.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @sentinel-config.json \
http://localhost:8080/api/config/import{
"message": "Imported 15 settings, 2 notification channels, 1 registry credentials",
"settings_imported": 15,
"notifications_imported": 2,
"registries_imported": 1,
"redacted_skipped": 3,
"warnings": []
}| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/metrics |
None | Prometheus metrics |
Standard Prometheus exposition format. Only available when SENTINEL_METRICS=true is set.
| Method | Path | Permission | Description |
|---|---|---|---|
GET |
/api/events |
containers.view |
Real-time event stream |
The SSE endpoint keeps a long-lived connection open and pushes events as they occur. The web dashboard uses this for live updates without polling.
curl -N -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/eventsOn connect, the server sends an initial event:
event: connected
data: {}
| Event Type | Fired When |
|---|---|
container_update |
Update started, completed, or failed |
container_state |
Container started, stopped, or restarted |
queue_change |
Queue item added or removed |
scan_complete |
Full scan finished |
policy_change |
Policy override changed |
digest_ready |
Digest notification ready |
settings_change |
Settings modified |
rate_limits |
Rate limit status changed |
ghcr_check |
GHCR alternative detected |
service_update |
Swarm service update event |
cluster_host |
Cluster host connected, disconnected, or enrolled |
All events share a common JSON structure:
{
"type": "container_update",
"container_name": "nginx",
"message": "Update available for nginx",
"host_id": "",
"host_name": "",
"timestamp": "2025-01-15T14:30:00Z"
}host_id and host_name are populated for cluster events. They are empty for local events.
event: connected
data: {}
event: scan_complete
data: {"type":"scan_complete","message":"Scan complete","timestamp":"2025-01-15T14:00:00Z"}
event: container_update
data: {"type":"container_update","container_name":"nginx","message":"Update available","timestamp":"2025-01-15T14:00:01Z"}
event: queue_change
data: {"type":"queue_change","container_name":"nginx","message":"Added to queue","timestamp":"2025-01-15T14:00:01Z"}
Getting Started
Using Sentinel
Multi-Host
Security
Reference