Skip to content

API

All endpoints are JSON over /api/*, authenticated with a JWT bearer token unless noted. Errors follow RFC 9457 (application/problem+json) with a machine-readable reason property on domain errors.

Auth

Method Path Notes
POST /api/auth/login email + password → access token (15 min) + refresh token (7 days). Unauthenticated.
POST /api/auth/refresh refresh token → new token pair. Unauthenticated.
POST /api/auth/logout Revokes the refresh token server-side. Unauthenticated.

The access token's roles claim (USER, OPERATOR, ADMIN) drives authorization; the UI decodes the same claim for display gating.

Jobs

Method Path Notes
POST /api/jobs Create. type, payloadJson required; priority, maxRetries (≤ 20), idempotencyKey optional.
GET /api/jobs Paged list. Filters: status, type, priority, ownerId, sourceScheduleId. Owners see their own; OPERATOR/ADMIN see all.
GET /api/jobs/stats Counts by status (all statuses present, zero-padded).
GET /api/jobs/{id} Single job.
GET /api/jobs/{id}/executions Attempt history (worker, lease, timing, outcome).
POST /api/jobs/{id}/cancel PENDING → immediate; RUNNING → deferred until the attempt completes. 409 reason on invalid state.
POST /api/jobs/{id}/retry DEAD_LETTER → PENDING with a fresh attempt budget. 409 on invalid state.

Schedules

Method Path Notes
POST /api/schedules 6-field Spring cron + IANA timezone. 400 INVALID_CRON / INVALID_TIMEZONE, 409 DUPLICATE_NAME.
GET /api/schedules Paged list. Filters: enabled, type, priority, ownerId.
GET /api/schedules/{id} Single schedule.
PATCH /api/schedules/{id} Partial update — null fields untouched.
POST /api/schedules/{id}/pause Disables; clears nextRunAt.
POST /api/schedules/{id}/resume Re-enables; recomputes nextRunAt.
DELETE /api/schedules/{id} Soft delete. Materialized jobs are unaffected.

Audit & users

Method Path Notes
GET /api/audit/target/{type}/{id} Audit trail for one entity. OPERATOR/ADMIN only.
GET /api/users/me Caller's profile.

User management (ADMIN only)

There is deliberately no self-service registration — accounts exist because an admin created one (or bootstrap seeded it). The gate lives in the service layer; non-admins get 403 on every endpoint below.

Method Path Notes
GET /api/users Paged list with roles and status.
POST /api/users Create. BCrypt-hashed password (min 12 chars), roles validated against the roles table (default USER). 409 DUPLICATE_EMAIL (race-safe via constraint translation), 400 UNKNOWN_ROLE.
PATCH /api/users/{id} Partial update of status and/or roles (null = untouched). Locking/disabling revokes all refresh tokens. 409 SELF_MODIFICATION on your own account.

Status semantics: LOCKED/DISABLED accounts are refused at login and refresh with 403 ACCOUNT_LOCKED/ACCOUNT_DISABLED — distinct from 401 invalid-credentials. Every mutation is audited (USER_CREATED, USER_ROLE_CHANGED, USER_STATUS_CHANGED).

Operational endpoints

Spring Actuator (/actuator/health, /metrics, /prometheus) is served by the API container but not proxied by Caddy — it is reachable only inside the compose network.