Ofelia serves a JSON API when the web server is enabled (--enable-web, default address :8081).
Every API route lives under the /api/ prefix; the health endpoints live at the server root.
This document describes the endpoints that exist in the code (web/server.go — the routes
table is the source of truth); if you find a mismatch, that is a bug in this file.
Two general rules:
- All mutating endpoints are
POSTand take a JSON body. Any other method returns405 Method Not Allowed. - A malformed JSON body returns
400 Bad Request; an unknown job name returns404 Not Found; internal failures return500 Internal Server Errorwith a short plain-text message.
Authentication is optional and disabled unless the daemon is configured with web auth. When disabled, the auth endpoints below are not registered and every other endpoint is open.
When enabled, all /api/ routes except /api/login, /api/auth/status and
/api/csrf-token require authentication via either:
- the
auth_tokencookie set by login (HttpOnly,SameSite=Strict;Securewhen the request arrived over TLS or withX-Forwarded-Proto: https), or - an
Authorization: Bearer <token>header carrying the token returned by login.
POST /api/login
Content-Type: application/json
{"username": "admin", "password": "secret"}Response 200 OK (and the auth_token cookie):
{"token": "<token>", "csrf_token": "<csrf-token>", "expires_in": 86400}Failed credentials return 401 Unauthorized; repeated failures are rate limited per client IP.
There is no refresh endpoint — log in again when the token expires.
POST /api/logoutClears the auth cookie. Returns 204 No Content.
GET /api/auth/statusReports whether authentication is enabled and whether the caller is authenticated.
GET /api/csrf-tokenReturns the CSRF token for the session; the web UI sends it with the login form.
GET /api/jobs # active jobs
GET /api/jobs/removed # jobs removed from the configuration
GET /api/jobs/disabled # disabled (paused) jobsEach returns a JSON array of job objects:
[
{
"name": "backup",
"type": "run",
"schedule": "@daily",
"command": "backup.sh",
"running": false,
"lastRun": {
"date": "2026-08-26T00:00:00Z",
"duration": 300000000000,
"failed": false,
"skipped": false,
"stdout": "…",
"stderr": ""
},
"nextRuns": ["2026-08-27T00:00:00Z"],
"prevRuns": ["2026-08-26T00:00:00Z"],
"origin": "ini",
"config": {"…": "full job configuration as JSON"}
}
]lastRun is omitted when the job has never run. duration values are Go
time.Duration nanoseconds. origin is one of ini, label, api, web.
GET /api/jobs/{name}/historyReturns a JSON array of executions in the shape of lastRun above (plus an
error string when the execution errored). 404 Not Found for unknown jobs or
job types without history.
POST /api/jobs/run
Content-Type: application/json
{"name": "backup"}Returns 204 No Content on success.
POST /api/jobs/disable
POST /api/jobs/enable
Content-Type: application/json
{"name": "backup"}Returns 204 No Content. Works for jobs of every origin — disabling an
INI/label job from the API is the supported way to suppress it temporarily, and
the disabled state is persisted across restarts.
POST /api/jobs/create
Content-Type: application/json
{
"name": "cleanup",
"type": "run",
"schedule": "0 2 * * *",
"image": "alpine:latest",
"command": "cleanup.sh"
}The request body (jobRequest) accepts:
| Field | Type | Notes |
|---|---|---|
name |
string | required; ≤256 chars, no control characters |
type |
string | run, exec, local, service-run, compose |
schedule |
string | cron expression or @shortcut |
command |
string | |
image |
string | run jobs |
container |
string | exec jobs |
file |
string | compose jobs |
service |
string | service-run jobs |
exec |
bool | compose jobs: exec in a running service |
Returns 201 Created. Validation failures return 400 Bad Request.
API-created jobs are persisted and survive restarts.
POST /api/jobs/update
Content-Type: application/jsonSame body as create. Full replace: omitted optional fields reset to their
defaults. Returns 200 OK when an existing job was updated, 201 Created when
the job did not exist and was created.
POST /api/jobs/delete
Content-Type: application/json
{"name": "cleanup"}Returns 204 No Content. Jobs owned by the INI file or Docker labels return
403 Forbidden — edit the owning configuration to remove them (or use
/api/jobs/disable to suppress them); only API/web-created jobs can be deleted
here.
GET /api/configReturns the running daemon configuration as JSON, with the job collections stripped (use the job endpoints for those).
Registered at the server root (not under /api/, no authentication):
GET /health # aggregate health report
GET /healthz # alias of /health
GET /ready # readiness probe
GET /live # liveness probeErrors are plain-text messages with the appropriate status code:
400 invalid body or validation failure, 401 unauthenticated (auth enabled),
403 config-owned job on delete, 404 unknown job, 405 wrong method,
500 internal failure. Login attempts are rate limited per client IP.