{
"openapi": "3.1.0",
"info": {
"title": "Moadim Server API",
"description": "REST API for managing cron jobs",
"license": {
"name": "MIT",
"identifier": "MIT"
},
"version": "0.1.0"
},
"servers": [
{
"url": "http://127.0.0.1:5784",
"description": "Local development"
}
],
"paths": {
"/": {
"get": {
"tags": [
"crate::routes::http"
],
"summary": "`GET /` — liveness check.",
"operationId": "index",
"responses": {
"200": {
"description": "Server is running",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
}
}
}
},
"/agents": {
"get": {
"tags": [
"crate::routines"
],
"summary": "`GET /agents` — list the agent registry keys a routine may target.",
"operationId": "list_agents",
"responses": {
"200": {
"description": "Available agent names",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
},
"/cron-jobs": {
"get": {
"tags": [
"crate::cron_jobs"
],
"summary": "`GET /cron-jobs` — list all cron jobs sorted by creation time.",
"operationId": "list",
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CronJobResponse"
}
}
}
}
}
}
},
"post": {
"tags": [
"crate::cron_jobs"
],
"summary": "`POST /cron-jobs` — create a new cron job.",
"operationId": "create",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateRequest"
}
}
},
"required": true
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CronJobResponse"
}
}
}
},
"400": {
"description": "Invalid cron expression"
}
}
}
},
"/cron-jobs/{id}": {
"get": {
"tags": [
"crate::cron_jobs"
],
"summary": "`GET /cron-jobs/{id}` — retrieve a single cron job by UUID.",
"operationId": "get",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Cron job UUID",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CronJobResponse"
}
}
}
},
"404": {
"description": "Not found"
}
}
},
"put": {
"tags": [
"crate::cron_jobs"
],
"summary": "`PUT /cron-jobs/{id}` — fully replace a cron job (behaves identically to PATCH).",
"operationId": "replace",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Cron job UUID",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CronJobResponse"
}
}
}
},
"400": {
"description": "Invalid"
},
"404": {
"description": "Not found"
}
}
},
"delete": {
"tags": [
"crate::cron_jobs"
],
"summary": "`DELETE /cron-jobs/{id}` — delete a cron job by UUID.",
"operationId": "delete",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Cron job UUID",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CronJobResponse"
}
}
}
},
"404": {
"description": "Not found"
}
}
},
"patch": {
"tags": [
"crate::cron_jobs"
],
"summary": "`PATCH /cron-jobs/{id}` — partially update a cron job.",
"operationId": "update",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Cron job UUID",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CronJobResponse"
}
}
}
},
"400": {
"description": "Invalid"
},
"404": {
"description": "Not found"
}
}
}
},
"/cron-jobs/{id}/logs": {
"get": {
"tags": [
"crate::cron_jobs"
],
"summary": "`GET /cron-jobs/{id}/logs` — return the contents of the job's log file as plain text.",
"operationId": "get_logs",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Cron job UUID",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Log file contents as plain text"
},
"404": {
"description": "Not found"
}
}
}
},
"/cron-jobs/{id}/trigger": {
"post": {
"tags": [
"crate::cron_jobs"
],
"summary": "`POST /cron-jobs/{id}/trigger` — manually trigger a cron job outside its schedule.",
"operationId": "trigger",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Cron job UUID",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CronJob"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
},
"/echo": {
"post": {
"tags": [
"crate::routes::http"
],
"summary": "`POST /echo` — parse a JSON body and return the message with a server timestamp.",
"operationId": "echo",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EchoRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EchoResponse"
}
}
}
},
"400": {
"description": "Invalid body"
}
}
}
},
"/health": {
"get": {
"tags": [
"crate::routes::http"
],
"summary": "`GET /health` — health check with uptime.",
"operationId": "health",
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HealthResponse"
}
}
}
}
}
}
},
"/routines": {
"get": {
"tags": [
"crate::routines"
],
"summary": "`GET /routines` — list all routines sorted by creation time.",
"operationId": "list",
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/RoutineResponse"
}
}
}
}
}
}
},
"post": {
"tags": [
"crate::routines"
],
"summary": "`POST /routines` — create a new routine.",
"operationId": "create",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateRoutineRequest"
}
}
},
"required": true
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RoutineResponse"
}
}
}
},
"400": {
"description": "Invalid cron expression"
}
}
}
},
"/routines/{id}": {
"get": {
"tags": [
"crate::routines"
],
"summary": "`GET /routines/{id}` — retrieve a single routine by UUID.",
"operationId": "get",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Routine UUID",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RoutineResponse"
}
}
}
},
"404": {
"description": "Not found"
}
}
},
"put": {
"tags": [
"crate::routines"
],
"summary": "`PUT /routines/{id}` — fully replace a routine (behaves identically to PATCH).",
"operationId": "replace",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Routine UUID",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateRoutineRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RoutineResponse"
}
}
}
},
"400": {
"description": "Invalid"
},
"404": {
"description": "Not found"
}
}
},
"delete": {
"tags": [
"crate::routines"
],
"summary": "`DELETE /routines/{id}` — delete a routine by UUID.",
"operationId": "delete",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Routine UUID",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RoutineResponse"
}
}
}
},
"404": {
"description": "Not found"
}
}
},
"patch": {
"tags": [
"crate::routines"
],
"summary": "`PATCH /routines/{id}` — partially update a routine.",
"operationId": "update",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Routine UUID",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateRoutineRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RoutineResponse"
}
}
}
},
"400": {
"description": "Invalid"
},
"404": {
"description": "Not found"
}
}
}
},
"/routines/{id}/logs": {
"get": {
"tags": [
"crate::routines"
],
"summary": "`GET /routines/{id}/logs` — return the newest workbench `agent.log` as plain text.",
"operationId": "get_logs",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Routine UUID",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Log file contents as plain text"
},
"404": {
"description": "Not found"
}
}
}
},
"/routines/{id}/trigger": {
"post": {
"tags": [
"crate::routines"
],
"summary": "`POST /routines/{id}/trigger` — manually run a routine outside its schedule.",
"operationId": "trigger",
"parameters": [
{
"name": "id",
"in": "path",
"description": "Routine UUID",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Routine"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
},
"/shutdown": {
"post": {
"tags": [
"crate::routes::http"
],
"summary": "`POST /shutdown` — ask the server to stop gracefully.",
"description": "Used by the UI \"STOP\" button (and the `moadim stop` command) to kill a backgrounded server that\nhas no controlling terminal. The response is sent before the graceful shutdown completes.",
"operationId": "shutdown",
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShutdownResponse"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"CreateRequest": {
"type": "object",
"description": "Request body for creating a new cron job.",
"required": [
"schedule",
"handler"
],
"properties": {
"enabled": {
"type": "boolean",
"description": "Whether to create the job in an enabled state (defaults to `true`)."
},
"handler": {
"type": "string",
"description": "Handler identifier to invoke when the schedule fires."
},
"metadata": {
"description": "Optional metadata (defaults to null)."
},
"schedule": {
"type": "string",
"description": "Cron expression for the new job."
}
}
},
"CreateRoutineRequest": {
"type": "object",
"description": "Request body for creating a new routine.",
"required": [
"schedule",
"title",
"agent",
"prompt"
],
"properties": {
"agent": {
"type": "string",
"description": "Agent registry key to launch."
},
"enabled": {
"type": "boolean",
"description": "Whether to create the routine enabled (defaults to `true`)."
},
"prompt": {
"type": "string",
"description": "Task prompt."
},
"repositories": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Repository"
},
"description": "Repositories to list as context (defaults to empty)."
},
"schedule": {
"type": "string",
"description": "Cron expression for the new routine."
},
"title": {
"type": "string",
"description": "Human name for the routine."
}
}
},
"CronJob": {
"type": "object",
"description": "A persisted cron job with scheduling and metadata.",
"required": [
"id",
"schedule",
"handler",
"metadata",
"enabled",
"source",
"created_at",
"updated_at"
],
"properties": {
"created_at": {
"type": "integer",
"format": "int64",
"description": "Unix timestamp (seconds) when the job was created.",
"minimum": 0
},
"enabled": {
"type": "boolean",
"description": "Whether the job is active."
},
"handler": {
"type": "string",
"description": "Identifier for the handler that processes the job."
},
"id": {
"type": "string",
"description": "Unique identifier (UUID v4)."
},
"last_triggered_at": {
"type": [
"integer",
"null"
],
"format": "int64",
"description": "Unix timestamp (seconds) when the job was last manually triggered, if ever.",
"minimum": 0
},
"metadata": {
"description": "Arbitrary JSON metadata attached to the job."
},
"schedule": {
"type": "string",
"description": "Cron expression defining when the job runs."
},
"source": {
"type": "string",
"description": "`\"managed\"` for jobs owned by this server; `\"system:*\"` for read-only system cron entries."
},
"updated_at": {
"type": "integer",
"format": "int64",
"description": "Unix timestamp (seconds) when the job was last updated.",
"minimum": 0
}
}
},
"CronJobResponse": {
"allOf": [
{
"$ref": "#/components/schemas/CronJob",
"description": "The underlying cron job."
},
{
"type": "object",
"required": [
"source_type",
"handler_registered",
"file_path"
],
"properties": {
"file_path": {
"type": "string",
"description": "Absolute path to the job's `job.toml` file on disk."
},
"handler_registered": {
"type": "boolean",
"description": "`true` if the job's handler appears in the server's handler registry."
},
"schedule_description": {
"type": [
"string",
"null"
],
"description": "Human-readable description of the schedule (e.g. `\"At 09:30, Monday through Friday\"`).\n`null` for expressions that cannot be parsed into a description (e.g. `@reboot`)."
},
"source_type": {
"$ref": "#/components/schemas/CronJobSourceType",
"description": "Whether the job is owned by this server or the host OS."
}
}
}
],
"description": "A [`CronJob`] enriched with a flag indicating whether its handler is registered."
},
"CronJobSourceType": {
"type": "string",
"description": "Whether a cron job is owned by this server or discovered from the OS.",
"enum": [
"managed",
"system"
]
},
"EchoRequest": {
"type": "object",
"description": "Request body for `POST /echo`.",
"required": [
"message"
],
"properties": {
"message": {
"type": "string",
"description": "Message to echo back."
}
}
},
"EchoResponse": {
"type": "object",
"description": "Response body for `POST /echo`.",
"required": [
"message",
"timestamp"
],
"properties": {
"message": {
"type": "string",
"description": "The echoed message."
},
"timestamp": {
"type": "integer",
"format": "int64",
"description": "Server timestamp (Unix seconds) when the echo was produced.",
"minimum": 0
}
}
},
"HealthResponse": {
"type": "object",
"description": "Response body for `GET /health`.",
"required": [
"status",
"uptime_secs",
"running"
],
"properties": {
"running": {
"type": "boolean",
"description": "Whether the server is running."
},
"status": {
"type": "string",
"description": "Health status string (always `\"ok\"` when reachable)."
},
"uptime_secs": {
"type": "integer",
"format": "int64",
"description": "Seconds elapsed since the server started.",
"minimum": 0
}
}
},
"Repository": {
"type": "object",
"description": "A git repository made available to a routine's agent as prompt context (not cloned by moadim).",
"required": [
"repository"
],
"properties": {
"branch": {
"type": [
"string",
"null"
],
"description": "Branch to use, or `None` for the remote default branch."
},
"repository": {
"type": "string",
"description": "Git remote URL."
}
}
},
"Routine": {
"type": "object",
"description": "A persisted routine: a scheduled AI-agent task.",
"required": [
"id",
"schedule",
"title",
"agent",
"prompt",
"enabled",
"source",
"created_at",
"updated_at"
],
"properties": {
"agent": {
"type": "string",
"description": "Agent registry key (e.g. `\"claude\"`) resolved from `~/.config/moadim/agents/`."
},
"created_at": {
"type": "integer",
"format": "int64",
"description": "Unix timestamp (seconds) when the routine was created.",
"minimum": 0
},
"enabled": {
"type": "boolean",
"description": "Whether the routine is active."
},
"id": {
"type": "string",
"description": "Unique identifier (UUID v4)."
},
"last_triggered_at": {
"type": [
"integer",
"null"
],
"format": "int64",
"description": "Unix timestamp (seconds) when the routine was last manually triggered, if ever.",
"minimum": 0
},
"prompt": {
"type": "string",
"description": "The task prompt handed to the agent."
},
"repositories": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Repository"
},
"description": "Repositories listed in the prompt as context."
},
"schedule": {
"type": "string",
"description": "Cron expression defining when the routine runs."
},
"source": {
"type": "string",
"description": "`\"managed\"` for routines owned by this server."
},
"title": {
"type": "string",
"description": "Human name; slugified to name the workbench and tmux session."
},
"updated_at": {
"type": "integer",
"format": "int64",
"description": "Unix timestamp (seconds) when the routine was last updated.",
"minimum": 0
}
}
},
"RoutineResponse": {
"allOf": [
{
"$ref": "#/components/schemas/Routine",
"description": "The underlying routine."
},
{
"type": "object",
"required": [
"agent_registered",
"file_path"
],
"properties": {
"agent_registered": {
"type": "boolean",
"description": "`true` if an agent config exists at `~/.config/moadim/agents/<agent>.toml`."
},
"file_path": {
"type": "string",
"description": "Absolute path to the routine's `routine.toml` file on disk."
},
"schedule_description": {
"type": [
"string",
"null"
],
"description": "Human-readable description of the schedule, or `null` if it cannot be parsed."
}
}
}
],
"description": "A [`Routine`] enriched with derived, non-persisted fields for API responses."
},
"ShutdownResponse": {
"type": "object",
"description": "Response body for `POST /shutdown`.",
"required": [
"status"
],
"properties": {
"status": {
"type": "string",
"description": "Acknowledgement status (always `\"shutting down\"`)."
}
}
},
"UpdateRequest": {
"type": "object",
"description": "Request body for partially updating an existing cron job.",
"properties": {
"enabled": {
"type": [
"boolean",
"null"
],
"description": "New enabled state, or `None` to keep the existing value."
},
"handler": {
"type": [
"string",
"null"
],
"description": "New handler identifier, or `None` to keep the existing value."
},
"metadata": {
"description": "New metadata, or `None` to keep the existing value."
},
"schedule": {
"type": [
"string",
"null"
],
"description": "New cron expression, or `None` to keep the existing value."
}
}
},
"UpdateRoutineRequest": {
"type": "object",
"description": "Request body for partially updating an existing routine.",
"properties": {
"agent": {
"type": [
"string",
"null"
],
"description": "New agent key, or `None` to keep the existing value."
},
"enabled": {
"type": [
"boolean",
"null"
],
"description": "New enabled state, or `None` to keep the existing value."
},
"prompt": {
"type": [
"string",
"null"
],
"description": "New prompt, or `None` to keep the existing value."
},
"repositories": {
"type": [
"array",
"null"
],
"items": {
"$ref": "#/components/schemas/Repository"
},
"description": "New repositories list, or `None` to keep the existing value."
},
"schedule": {
"type": [
"string",
"null"
],
"description": "New cron expression, or `None` to keep the existing value."
},
"title": {
"type": [
"string",
"null"
],
"description": "New title, or `None` to keep the existing value."
}
}
}
}
}
}