Key points
- Runner install generation is separately authorized because it hands out service credentials, and the one-time token expires in 10 minutes and is consumed by the first download.
- Ejecting a runner blocklists its id; an ordinary disconnect keeps the registration so it can reconnect.
- Disabling dispatch is the drain switch: new work stops, in-flight runs finish.
- Log sources are allow-listed, and lines are redacted before they leave the platform.
- Live log streaming is concurrency-capped, so a forgotten stream answers 429 for the next one.
- The platform takes and stores backups. It does not upload or restore them.
- Cleanup is permanent. Preview first — the counts are easy to misread by an order of magnitude.
Operations
GET/v1/system/configAuthorized
Reads effective runtime configuration.
Notes
This is what the platform resolved, not what a file says: environment, config file, and runtime overrides are already merged here.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config" | jqResponses
Effective configuration.
{
"dispatcher_grpc_address": "dispatcher:9091",
"runner_capacity": 1,
"default_pipeline_timeout": "60m"
}Side effects
- None.
Proven by
services/nopsai/system_services_test.goservices/nopsai/runtime_settings_store.go
PUT/v1/system/configAuthorized
Updates runtime configuration.
Notes
Changing the dispatcher address here affects newly generated runner installs, not runners that are already registered.
Call it
curl -sX PUT "$NOPSAI_URL/v1/system/config" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"default_pipeline_timeout":"90m"}' | jqResponses
Configuration stored.
{ "default_pipeline_timeout": "90m" }When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An unknown setting or an unusable value. | The message names the field. |
| 500 | The configuration could not be stored. | Retry. |
Side effects
- Platform services pick the change up through their runtime-config watch.
- Writes an audit record.
Proven by
services/nopsai/system_services_test.goservices/nopsai/runtime_settings_store.go
GET/v1/system/dispatcherAuthorized
Reads dispatcher and runner fleet status.
Notes
A run that stays queued is answered here: no reachable, dispatch-enabled runner with a matching scope means nothing can pick it up.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/dispatcher" | jq '.runners'Responses
Fleet status.
{
"runners": [
{ "runner_id": "runner-local-1", "scopes": ["prod"], "capacity": 2, "reachable": true, "dispatch_enabled": true }
]
}Side effects
- None.
Proven by
services/nopsai/config_sync_test.goservices/nopsai/system_handlers.go
GET/v1/system/dispatcher/scopesAuthorized
Lists the runtime scopes runners currently advertise.
Notes
This is the live view derived from registered runners, not the configured route map. A scope missing here has no capacity behind it.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/dispatcher/scopes" | jqResponses
Advertised scopes.
["prod", "ci"]When it fails
| Status | Cause | What to do |
|---|---|---|
| 500 | The scope query failed. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/config_sync_test.goservices/nopsai/system_handlers.go
POST/v1/system/dispatcher/runners/{runnerID}/dispatchAuthorized
Enables or disables dispatch to one runner.
Notes
This is the drain switch: disable dispatch, wait for the runner to go idle, then take the host away.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runnerID | path | string | Required | Runner identifier as registered with the dispatcher. |
Call it
curl -sX POST "$NOPSAI_URL/v1/system/dispatcher/runners/$RUNNER_ID/dispatch" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled":false}' -w "%{http_code}\n"Responses
Dispatch flag updated.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An unknown runner or a malformed body. | Send {"enabled": true|false}. |
Side effects
- Stops or resumes new assignments. In-flight runs are unaffected.
- Writes an audit record.
Proven by
services/nopsai/config_sync_test.goservices/nopsai/system_handlers.go
DELETE/v1/system/dispatcher/runners/{runnerID}Authorized
Ejects a runner from the fleet.
Notes
Ejection is deliberate removal, not a disconnect. An ordinary network drop keeps the registration so the runner can reconnect; ejection blocklists it.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runnerID | path | string | Required | Runner identifier as registered with the dispatcher. |
Call it
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/dispatcher/runners/$RUNNER_ID" -w "%{http_code}\n"Responses
Runner ejected.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The runner id is missing or unknown. | Take the id from the fleet view. |
| 500 | The ejection could not be recorded. | Retry; the runner may still be connected. |
Side effects
- Disconnects the runner and clears its dispatcher status.
- Adds its id to
ejected_runner_ids, which is what stops it reconnecting. - Writes an audit record.
Proven by
services/nopsai/config_sync_test.goservices/nopsai/system_handlers.go
GET/v1/system/dispatcher/runner-bootstrap-commandAuthorized
Generates a one-time Docker runner install command.
Notes
The response warnings are worth reading: they say when the dispatcher address had to be derived from the request, which is the usual cause of a runner that installs and never connects.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runner_id | query | string | Optional | Runner identity to install.Default: runner-prod-1 |
runner_scopes | query | string | Optional | Scopes the runner advertises. An explicitly empty value means all scopes.Default: configured default, then `prod` |
runner_capacity | query | integer | Optional | Concurrent runs the runner accepts. Must be a positive integer.Default: 1 |
runner_network_mode | query | string | Optional | Docker network mode for the generated container.Allowed: bridge, host, autoDefault: auto |
runner_image | query | string | Optional | Override the runner image tag.Default: the platform runner image |
registry_credential_ref | query | string | Optional | Credential whose docker config is delivered for image pulls. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" \
"$NOPSAI_URL/v1/system/dispatcher/runner-bootstrap-command?runner_id=runner-local-1&runner_scopes=prod&runner_capacity=2" \
| jq -r .bootstrap_commandResponses
The install command plus the identity and placement it encodes.
{
"runner_id": "runner-local-1",
"runner_scopes": "prod",
"runner_capacity": 2,
"dispatcher_address": "dispatcher:9091",
"network_mode": "bridge",
"runner_image": "ghcr.io/nopsai/nopsai-docker-runner:dev",
"bootstrap_command": "tmp=$(mktemp) && curl -fsSL -H 'Authorization: Bearer ...' ... && sh \"$tmp\"",
"expires_at": "2026-08-19T13:22:04Z",
"warnings": ["This one-time install command expires in 10 minutes and is consumed by the first successful download."]
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | A non-positive capacity, an unknown network mode, or an unusable runner id. | runner_network_mode must be bridge, host, or auto. |
| 403 | The caller may not generate runner installs. | Install generation hands out service credentials, so it is separately authorized. |
| 500 | Required dispatcher TLS or service material is not configured. | The message names what is missing, for example DISPATCHER_TLS_SECRET. |
Side effects
- Issues a one-time token that carries the install script.
- Writes an audit record.
Proven by
services/nopsai/config_sync_test.goservices/nopsai/internal/runnerinstall/docker.go
GET/v1/system/dispatcher/runner-bootstrapPublic
Downloads the install script named by a one-time bootstrap token.
Notes
Public by necessity — the runner host has no platform credentials yet — but bounded by a single-use, short-lived token rather than being open.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
Authorization | header | string | Required | The one-time bearer token embedded in the generated command. |
Call it
tmp=$(mktemp) && curl -fsSL -H "Authorization: Bearer $ONE_TIME_TOKEN" \
"$NOPSAI_URL/v1/system/dispatcher/runner-bootstrap" -o "$tmp" && sh "$tmp"Responses
The install script.
#!/bin/sh
set -eu
if ! command -v docker >/dev/null 2>&1; then
echo "docker is required on this runner host" >&2
exit 1
fi
echo "Installing NopsAI runner runner-local-1"
...
container_id=$(docker run -d \
--name runner-local-1-abc123 \
--restart always \
--network nopsai-net \
-v /var/run/docker.sock:/var/run/docker.sock ...)When it fails
| Status | Cause | What to do |
|---|---|---|
| 404 | The token is unknown, expired, or already used. | Generate a new install command. A token works exactly once. |
| 500 | The script could not be produced. | Retry the generation step. |
Side effects
- Consumes the one-time token.
Proven by
services/nopsai/config_sync_test.goservices/nopsai/internal/runnerinstall/docker.goservices/nopsai/http_middleware.go
GET/v1/system/dispatcher/runner-composeAuthorized
Generates a Docker Compose service for a runner.
Notes
Use this when the runner belongs in a Compose file you keep. It embeds long-lived material directly, so treat the output as a secret.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runner_id | query | string | Optional | Runner identity.Default: runner-prod-1 |
runner_scopes | query | string | Optional | Scopes the runner advertises. |
runner_capacity | query | integer | Optional | Concurrent runs accepted. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" \
"$NOPSAI_URL/v1/system/dispatcher/runner-compose?runner_id=runner-local-1" | jq -r .composeResponses
The Compose fragment and its up command.
{
"runner_id": "runner-local-1",
"resource_name": "runner-local-1-abc123",
"compose": "runner-local-1-abc123:\n image: ghcr.io/nopsai/nopsai-docker-runner:dev\n ...",
"command": "docker compose -f docker-compose.yaml up -d runner-local-1-abc123"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An invalid capacity or runner identity. | The message names the parameter. |
| 500 | Required dispatcher material is not configured. | The message names what is missing. |
Side effects
- None. Unlike the bootstrap command, this issues no token.
Proven by
services/nopsai/config_sync_test.goservices/nopsai/internal/runnerinstall/docker.go
GET/v1/system/dispatcher/kubernetes-runner-bootstrap-commandAuthorized
Generates a one-time Kubernetes runner install command.
Notes
A cross-namespace runner needs the fully qualified dispatcher service name; check the address in the response before running the command.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runner_id | query | string | Optional | Runner identity. |
runner_scopes | query | string | Optional | Scopes the runner advertises. |
namespace | query | string | Optional | Namespace the runner is installed into. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" \
"$NOPSAI_URL/v1/system/dispatcher/kubernetes-runner-bootstrap-command?runner_id=runner-k8s-1" \
| jq -r .bootstrap_commandResponses
The install command and the identity it encodes.
{
"runner_id": "runner-k8s-1",
"runner_scopes": "prod",
"bootstrap_command": "tmp=$(mktemp) && curl -fsSL -H 'Authorization: Bearer ...' ... && sh \"$tmp\"",
"expires_at": "2026-08-19T13:22:04Z"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An invalid runner identity or namespace. | The message names the parameter. |
| 403 | The caller may not generate runner installs. | Separately authorized because it hands out service credentials. |
| 500 | Required dispatcher material is not configured. | The message names what is missing. |
Side effects
- Issues a one-time token.
- Writes an audit record.
Proven by
services/nopsai/config_sync_test.goservices/nopsai/internal/runnerinstall/docker.godoc/kubernetes-runner.md
GET/v1/system/dispatcher/kubernetes-runner-manifestAuthorized
Generates a Kubernetes runner manifest.
Notes
Committing this manifest commits runner credentials. Prefer the one-time bootstrap command unless the manifest is going into a sealed-secret workflow.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
runner_id | query | string | Optional | Runner identity. |
namespace | query | string | Optional | Namespace to render into. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" \
"$NOPSAI_URL/v1/system/dispatcher/kubernetes-runner-manifest?runner_id=runner-k8s-1" | jq -r .manifestResponses
The rendered manifest.
{
"runner_id": "runner-k8s-1",
"namespace": "nopsai-runners",
"manifest": "apiVersion: apps/v1\nkind: Deployment\n..."
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An invalid runner identity or namespace. | The message names the parameter. |
| 500 | Required dispatcher material is not configured. | The message names what is missing. |
Side effects
- None. No token is issued.
Proven by
services/nopsai/config_sync_test.godoc/kubernetes-runner.md
GET/v1/system/logs/sourcesAuthenticated
Lists allow-listed platform log sources.
Notes
The allow list is the point: the platform reads its own services through a socket proxy rather than being handed a writable Docker socket.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/logs/sources" | jq '.[] | {id, name}'Responses
Available log sources.
[
{ "id": "nopsai", "name": "nopsai" },
{ "id": "dispatcher", "name": "dispatcher" }
]When it fails
| Status | Cause | What to do |
|---|---|---|
| 502 | The log provider could not be reached. | On Docker installs this is usually the socket proxy; on Kubernetes it is the API server. |
| 503 | System logs are not configured for this install. | Configure a Docker or Kubernetes log provider. |
Side effects
- None.
Proven by
services/nopsai/system_services_test.godoc/system-logs.md
GET/v1/system/logs/sources/{sourceID}/tailAuthenticated
Returns the recent log buffer for one source.
Notes
Secret values are masked before the lines leave the platform, so a tail is safe to paste into an incident channel.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
sourceID | path | string | Required | Source id from the source list. |
limit | query | integer | Optional | How many lines to return. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/logs/sources/dispatcher/tail?limit=200"Responses
Recent log lines.
[
{ "timestamp": "2026-08-19T08:11:04Z", "line": "dispatcher listening on :9090" }
]When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An unknown source or an invalid limit. | Take the id from the source list. |
| 503 | System logs are not configured. | Configure a log provider. |
Side effects
- None.
Proven by
services/nopsai/system_services_test.godoc/system-logs.md
GET/v1/system/logs/sources/{sourceID}/streamAuthenticated
Streams live logs for one source.
Notes
The 429 is a concurrency cap rather than a rate limit: streams are expensive, so a forgotten open stream blocks the next one.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
sourceID | path | string | Required | Source id from the source list. |
Call it
curl -N -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/logs/sources/dispatcher/stream"Responses
A server-sent event stream of log lines.
data: {"timestamp":"2026-08-19T08:11:04Z","line":"dispatcher listening on :9090"}
data: {"timestamp":"2026-08-19T08:11:07Z","line":"runner runner-local-1 registered"}
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An unknown source. | Take the id from the source list. |
| 429 | Too many concurrent streams. | Close an existing stream; live streaming is deliberately bounded. |
| 503 | System logs are not configured. | Configure a log provider. |
Streaming
text/event-stream - Server-sent events; recent lines are replayed before live output begins.
Side effects
- Holds an open connection against the stream limit until closed.
Proven by
services/nopsai/system_services_test.godoc/system-logs.md
GET/v1/system/notifications/mailAuthorized
Reads platform mail settings.
Notes
Delivery is configured once here; which events a team hears about is configured per team.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/notifications/mail" | jqResponses
Mail settings.
{
"host": "smtp.example.com",
"port": 587,
"sender": "[email protected]",
"credential_ref": "platform/smtp"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 500 | Settings could not be loaded. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/system_services_test.goservices/nopsai/system_handlers.go
PUT/v1/system/notifications/mailAuthorized
Updates platform mail settings.
Call it
curl -sX PUT "$NOPSAI_URL/v1/system/notifications/mail" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"host":"smtp.example.com","port":587,"sender":"[email protected]","credential_ref":"platform/smtp"}' | jqResponses
Settings stored.
{
"host": "smtp.example.com",
"port": 587,
"sender": "[email protected]"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | A missing host, an invalid port, or an unresolvable credential reference. | The message names the field. |
| 500 | Settings could not be stored. | Retry. |
Side effects
- Changes how every notification is delivered.
- Writes an audit record.
Proven by
services/nopsai/system_services_test.goservices/nopsai/system_handlers.go
POST/v1/system/notifications/mail/testAuthorized
Sends a test message through the configured mail settings.
Notes
A team subscription with broken delivery looks exactly like no subscription. Test after every mail change.
Call it
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/notifications/mail/test" | jqResponses
The message was accepted by the server.
{ "ok": true }When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | Mail settings are incomplete. | Configure the host, port, and sender first. |
| 502 | The mail server rejected the message or could not be reached. | The message carries the server response — usually authentication or a sender the domain will not accept. |
| 500 | The test could not run. | Platform fault. |
Side effects
- Sends a real email.
Proven by
services/nopsai/system_services_test.goservices/nopsai/system_handlers.go
GET/v1/system/data/backupsAuthorized
Lists database backups the platform has taken.
Notes
The platform takes and stores backups. It does not upload them anywhere or restore them — that stays an operator responsibility.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/data/backups" | jq '.[] | {id, status, size_bytes, created_at}'Responses
Backups.
[{
"id": "b7f2c910-8d43-4a26-9c05-31e7a4b0d986",
"backup_type": "database",
"status": "complete",
"file_name": "nopsai-2026-08-19.dump",
"content_type": "application/octet-stream",
"size_bytes": 48213004,
"checksum_sha256": "9f2c1b...",
"created_at": "2026-08-19T02:00:11Z"
}]When it fails
| Status | Cause | What to do |
|---|---|---|
| 500 | The query failed. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
POST/v1/system/data/backupsAuthorized
Takes a backup.
Notes
A backup that never leaves the platform volume does not survive losing the platform. Download it.
Call it
curl -sX POST "$NOPSAI_URL/v1/system/data/backups" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"backup_type":"database"}' | jq -r .idResponses
Backup started.
{
"id": "b7f2c910-8d43-4a26-9c05-31e7a4b0d986",
"backup_type": "database",
"status": "complete",
"file_name": "nopsai-2026-08-19.dump",
"content_type": "application/octet-stream",
"size_bytes": 48213004,
"checksum_sha256": "9f2c1b...",
"created_at": "2026-08-19T02:00:11Z"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An unknown backup type. | The message names the accepted values. |
| 500 | The backup could not be taken. | Check disk space on the platform volume. |
Side effects
- Writes a dump to the platform volume, consuming disk.
- Writes an audit record.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
GET/v1/system/data/backups/{backupID}/downloadAuthorized
Downloads a backup file.
Notes
Verify the checksum after downloading. An unverified backup is a hope, not a restore path.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
backupID | path | uuid | Required | Backup identifier. |
Call it
curl -s -OJ -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/data/backups/$BACKUP_ID/download"
shasum -a 256 nopsai-2026-08-19.dumpResponses
The backup file.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 404 | No backup with that id. | List the backups. |
| 409 | The backup is still running or failed. | Wait for status: complete. |
| 500 | The file could not be read. | The dump may have been removed from the volume. |
Side effects
- None.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
DELETE/v1/system/data/backups/{backupID}Authorized
Deletes a backup.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
backupID | path | uuid | Required | Backup to delete. |
Call it
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/data/backups/$BACKUP_ID" -w "%{http_code}\n"Responses
Backup deleted.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 404 | No backup with that id. | It may already be deleted. |
| 500 | The delete failed. | Retry. |
Side effects
- Removes the dump from the platform volume permanently.
- Writes an audit record.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
POST/v1/system/data/cleanup/previewAuthorized
Reports what a cleanup would remove, without removing it.
Notes
Always preview first. Cleanup is permanent and has no undo beyond a restore.
Call it
curl -sX POST "$NOPSAI_URL/v1/system/data/cleanup/preview" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"retention_days":90}' | jqResponses
What would be removed.
{
"retention_days": 90,
"would_remove": { "pipeline_runs": 1240, "logs": 98211 }
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An invalid retention window. | Retention is in whole days. |
| 500 | The preview query failed. | Platform fault. |
Side effects
- None. A preview never deletes.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
POST/v1/system/data/cleanup/runAuthorized
Runs a cleanup now.
Notes
Take a backup before the first cleanup on an install. The counts in a preview are easy to misread by an order of magnitude.
Call it
curl -sX POST "$NOPSAI_URL/v1/system/data/cleanup/run" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"retention_days":90}' | jq -r .idResponses
Cleanup job created.
{
"id": "3a19d740-6c82-4f15-b0e3-8d5c2a9f4711",
"status": "complete",
"removed": { "pipeline_runs": 1240, "logs": 98211 },
"started_at": "2026-08-19T03:00:00Z",
"finished_at": "2026-08-19T03:04:22Z"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An invalid retention window. | Preview the same window first. |
| 500 | The cleanup could not be started. | Retry. |
Side effects
- Permanently deletes run records, logs, and outputs older than the window.
- Writes an audit record.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
GET/v1/system/data/cleanup/jobsAuthorized
Lists cleanup jobs and what they removed.
Notes
This is the record of what a retention policy has actually cost you in history.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | Optional | How many jobs to return. |
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/data/cleanup/jobs" | jqResponses
Cleanup jobs.
[{
"id": "3a19d740-6c82-4f15-b0e3-8d5c2a9f4711",
"status": "complete",
"removed": { "pipeline_runs": 1240, "logs": 98211 },
"started_at": "2026-08-19T03:00:00Z",
"finished_at": "2026-08-19T03:04:22Z"
}]When it fails
| Status | Cause | What to do |
|---|---|---|
| 500 | The query failed. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
GET/v1/system/data/cleanup/schedulesAuthorized
Lists cleanup schedules.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/data/cleanup/schedules" | jqResponses
Cleanup schedules.
[{
"id": "5e8c31a0-9b47-4d12-8f60-2a7d4c1e9b03",
"cron_expression": "0 3 * * *",
"timezone": "Europe/Berlin",
"enabled": true,
"retention_days": 90
}]When it fails
| Status | Cause | What to do |
|---|---|---|
| 500 | The query failed. | Platform fault. |
Side effects
- None.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
POST/v1/system/data/cleanup/schedulesAuthorized
Creates a cleanup schedule.
Notes
Preview the same retention window by hand before scheduling it. A schedule turns a one-off mistake into a nightly one.
Call it
curl -sX POST "$NOPSAI_URL/v1/system/data/cleanup/schedules" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"cron_expression":"0 3 * * *","timezone":"Europe/Berlin","retention_days":90}' | jq -r .idResponses
Schedule created.
{
"id": "5e8c31a0-9b47-4d12-8f60-2a7d4c1e9b03",
"cron_expression": "0 3 * * *",
"timezone": "Europe/Berlin",
"enabled": true,
"retention_days": 90
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An invalid cron expression, timezone, or retention window. | The message names the field. |
| 500 | The schedule could not be created. | Retry. |
Side effects
- Every firing permanently deletes records older than the window.
- Writes an audit record.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
PUT/v1/system/data/cleanup/schedules/{scheduleID}Authorized
Replaces a cleanup schedule.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
scheduleID | path | string | Required | Cleanup schedule identifier. |
Call it
curl -sX PUT "$NOPSAI_URL/v1/system/data/cleanup/schedules/$SCHEDULE_ID" \
-H "Authorization: Bearer $NOPSAI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"cron_expression":"0 3 * * *","timezone":"Europe/Berlin","retention_days":180}' | jqResponses
Schedule replaced.
{
"id": "5e8c31a0-9b47-4d12-8f60-2a7d4c1e9b03",
"cron_expression": "0 3 * * *",
"timezone": "Europe/Berlin",
"enabled": true,
"retention_days": 90
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | An invalid expression or window. | The message names the field. |
| 404 | No schedule with that id. | List the schedules. |
| 500 | The update failed. | Retry. |
Side effects
- Shortening the window means the next firing deletes more.
- Writes an audit record.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
POST/v1/system/data/cleanup/schedules/{scheduleID}/runAuthorized
Runs a cleanup schedule now.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
scheduleID | path | string | Required | Cleanup schedule identifier. |
Call it
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/data/cleanup/schedules/$SCHEDULE_ID/run" | jqResponses
Cleanup started from the schedule.
{
"id": "3a19d740-6c82-4f15-b0e3-8d5c2a9f4711",
"status": "complete",
"removed": { "pipeline_runs": 1240, "logs": 98211 },
"started_at": "2026-08-19T03:00:00Z",
"finished_at": "2026-08-19T03:04:22Z"
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 404 | No schedule with that id. | List the schedules. |
| 500 | The cleanup could not be started. | Retry. |
Side effects
- Permanently deletes records older than the schedule window.
- Writes an audit record.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
POST/v1/system/data/cleanup/schedules/{scheduleID}/enableAdministrator
Enables a cleanup schedule.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
scheduleID | path | string | Required | Cleanup schedule identifier. |
Call it
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/data/cleanup/schedules/$SCHEDULE_ID/enable" | jq '{id, enabled}'Responses
Schedule enabled.
{
"id": "5e8c31a0-9b47-4d12-8f60-2a7d4c1e9b03",
"cron_expression": "0 3 * * *",
"timezone": "Europe/Berlin",
"enabled": true,
"retention_days": 90
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 404 | No schedule with that id. | List the schedules. |
| 500 | The change failed. | Retry. |
Side effects
- Resumes automatic deletion on the schedule.
- Writes an audit record.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
POST/v1/system/data/cleanup/schedules/{scheduleID}/disableAdministrator
Disables a cleanup schedule.
Notes
The first thing to disable when an incident needs history that retention is about to delete.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
scheduleID | path | string | Required | Cleanup schedule identifier. |
Call it
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/data/cleanup/schedules/$SCHEDULE_ID/disable" | jq '{id, enabled}'Responses
Schedule disabled.
{
"id": "5e8c31a0-9b47-4d12-8f60-2a7d4c1e9b03",
"cron_expression": "0 3 * * *",
"timezone": "Europe/Berlin",
"enabled": true,
"retention_days": 90
}When it fails
| Status | Cause | What to do |
|---|---|---|
| 404 | No schedule with that id. | List the schedules. |
| 500 | The change failed. | Retry. |
Side effects
- Stops automatic deletion. Disk use grows until it is re-enabled.
- Writes an audit record.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
DELETE/v1/system/data/cleanup/schedules/{scheduleID}Authorized
Deletes a cleanup schedule.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
scheduleID | path | string | Required | Cleanup schedule identifier. |
Call it
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/data/cleanup/schedules/$SCHEDULE_ID" -w "%{http_code}\n"Responses
Schedule deleted.
When it fails
| Status | Cause | What to do |
|---|---|---|
| 400 | The id is malformed. | Use an id from the list. |
| 404 | No schedule with that id. | It may already be deleted. |
| 500 | The delete failed. | Retry. |
Side effects
- Stops automatic deletion permanently. Cleanup job history remains.
- Writes an audit record.
Proven by
services/nopsai/data_management_gitops_test.goservices/nopsai/data_management.go
GET/v1/system/licenseAuthenticated
What this installation is entitled to run, and why.
Notes
Reports no key material, only what the installation may run. A limit of 0 means unlimited, so an omitted limit never reads as forbidding everything. Verification is a local Ed25519 check: NopsAI never calls home.
Call it
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/license" | jq '{licensed, tier, limits, usage, reason}'Responses
Entitlement, limits, current usage against them, and the reason for any non-licensed state.
{
"licensed": false,
"tier": "noncommercial",
"reason": "No commercial licence key is configured. Running under the non-commercial licence, which is free and uncapped for any non-commercial purpose.",
"limits": { "max_users": 0, "max_teams": 0, "max_concurrent_runs": 0 },
"usage": { "users": 1, "teams": 0 }
}Side effects
- None. The entitlement is resolved from configuration on each call and cached nowhere.
Proven by
pkg/license/license_test.goservices/nopsai/system_license.gopkg/license/entitlement.go
How it works
The runner routes come in two shapes for a reason. The one-time bootstrap command hands the host a short-lived token and nothing else, while the Compose fragment and Kubernetes manifest embed long-lived material directly — which makes them convenient for GitOps and dangerous to commit unsealed. Prefer the bootstrap command unless the output is going somewhere encrypted.
Backups and cleanup pull in opposite directions and are easy to get wrong together. A backup that never leaves the platform volume does not survive losing the platform, and a cleanup schedule quietly deletes the history an incident may need. Verify a downloaded backup against its checksum, and disable the cleanup schedule before it removes evidence you are still reading.
Runtime configuration is what the platform resolved rather than what a file says: environment, config file, and runtime overrides are already merged. Changing the dispatcher address affects newly generated installs, not runners that are already registered.
Implementation evidence
services/nopsai/system_handlers.goConfiguration, dispatcher, and notification handlers.
services/nopsai/internal/runnerinstall/docker.goRunner install generation and one-time token behaviour.
services/nopsai/data_management.goBackups, cleanup jobs, and cleanup schedules.

