From 9a81098b9db67f541790e6a03f9f5281411a3425 Mon Sep 17 00:00:00 2001 From: josh Date: Sat, 4 Jul 2026 20:11:41 -0400 Subject: [PATCH] docs: move API reference to docs/api.md and add Jobs endpoints Extracts the REST API section from README.md into a dedicated docs/api.md. Adds documentation for the four Jobs endpoints (GET/PUT /api/jobs, GET /api/jobs/:id, POST /api/jobs/:id/run) which existed in code but were not yet documented. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 171 +------------------------------ docs/api.md | 283 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+), 170 deletions(-) create mode 100644 docs/api.md diff --git a/README.md b/README.md index 3f1ddd4..dded93a 100644 --- a/README.md +++ b/README.md @@ -37,176 +37,7 @@ Open [http://localhost:3000](http://localhost:3000). ## REST API -All endpoints are under `/api`. Request and response bodies are JSON. - -### Instances - -#### `GET /api/instances` - -Returns all instances sorted by name. All query parameters are optional. - -| Parameter | Type | Description | -|---|---|---| -| `search` | string | Partial match on `name`, `vmid`, or `stack` | -| `state` | string | Exact match: `deployed`, `testing`, `degraded` | -| `stack` | string | Exact match: `production`, `development` | - -``` -GET /api/instances?search=plex&state=deployed -``` - -```json -[ - { - "vmid": 117, - "name": "plex", - "state": "deployed", - "stack": "production", - "tailscale_ip": "100.64.0.1", - "atlas": 1, "argus": 1, "semaphore": 0, - "patchmon": 1, "tailscale": 1, "andromeda": 0, - "hardware_acceleration": 1, - "created_at": "2024-01-15T10:30:00", - "updated_at": "2024-03-10T14:22:00" - } -] -``` - ---- - -#### `GET /api/instances/stacks` - -Returns a sorted array of distinct stack names present in the registry. - -``` -GET /api/instances/stacks -→ ["development", "production"] -``` - ---- - -#### `GET /api/instances/:vmid` - -Returns a single instance by VMID. - -| Status | Condition | -|---|---| -| `200` | Instance found | -| `400` | VMID is not a valid integer | -| `404` | No instance with that VMID | - ---- - -#### `GET /api/instances/:vmid/history` - -Returns the audit log for an instance — newest events first. - -| Status | Condition | -|---|---| -| `200` | History returned (may be empty array) | -| `400` | VMID is not a valid integer | -| `404` | No instance with that VMID | - -```json -[ - { - "id": 3, - "vmid": 117, - "field": "state", - "old_value": "testing", - "new_value": "deployed", - "changed_at": "2024-03-10T14:22:00" - }, - { - "id": 1, - "vmid": 117, - "field": "created", - "old_value": null, - "new_value": null, - "changed_at": "2024-01-15T10:30:00" - } -] -``` - ---- - -#### `POST /api/instances` - -Creates a new instance. Returns the created record. - -| Status | Condition | -|---|---| -| `201` | Created successfully | -| `400` | Validation error — see `errors` array in response | -| `409` | VMID already exists | - -**Request body:** - -| Field | Type | Required | Notes | -|---|---|---|---| -| `name` | string | yes | | -| `vmid` | integer | yes | Must be > 0 and unique | -| `state` | string | yes | `deployed`, `testing`, or `degraded` | -| `stack` | string | yes | `production` or `development` | -| `tailscale_ip` | string | no | Valid IPv4 or empty string | -| `atlas` | 0\|1 | no | | -| `argus` | 0\|1 | no | | -| `semaphore` | 0\|1 | no | | -| `patchmon` | 0\|1 | no | | -| `tailscale` | 0\|1 | no | | -| `andromeda` | 0\|1 | no | | -| `hardware_acceleration` | 0\|1 | no | | - ---- - -#### `PUT /api/instances/:vmid` - -Replaces all fields on an existing instance. Accepts the same body shape as `POST`. The `vmid` in the body may differ from the URL — this is how you change a VMID. - -| Status | Condition | -|---|---| -| `200` | Updated successfully | -| `400` | Validation error | -| `404` | No instance with that VMID | -| `409` | New VMID conflicts with an existing instance | - ---- - -#### `DELETE /api/instances/:vmid` - -Deletes an instance. Only instances on the `development` stack may be deleted. - -| Status | Condition | -|---|---| -| `204` | Deleted successfully | -| `400` | VMID is not a valid integer | -| `404` | No instance with that VMID | -| `422` | Instance is on the `production` stack | - ---- - -### Backup - -#### `GET /api/export` - -Downloads a JSON backup of all instances as a file attachment. - -```json -{ - "version": 1, - "exported_at": "2024-03-10T14:22:00.000Z", - "instances": [ ... ] -} -``` - -#### `POST /api/import` - -Replaces all instances from a JSON backup. Validates every row before committing — if any row is invalid the entire import is rejected. - -| Status | Condition | -|---|---| -| `200` | Import successful — returns `{ "imported": N }` | -| `400` | Body missing `instances` array, or validation errors | +See **[docs/api.md](docs/api.md)** for the full API reference. --- diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..5076cd9 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,283 @@ +# Catalyst API Reference + +All endpoints are under `/api`. Request and response bodies are JSON. + +--- + +## Instances + +#### `GET /api/instances` + +Returns all instances sorted by name. All query parameters are optional. + +| Parameter | Type | Description | +|---|---|---| +| `search` | string | Partial match on `name`, `vmid`, or `stack` | +| `state` | string | Exact match: `deployed`, `testing`, `degraded` | +| `stack` | string | Exact match: `production`, `development` | + +``` +GET /api/instances?search=plex&state=deployed +``` + +```json +[ + { + "vmid": 117, + "name": "plex", + "state": "deployed", + "stack": "production", + "tailscale_ip": "100.64.0.1", + "atlas": 1, "argus": 1, "semaphore": 0, + "patchmon": 1, "tailscale": 1, "andromeda": 0, + "hardware_acceleration": 1, + "created_at": "2024-01-15T10:30:00", + "updated_at": "2024-03-10T14:22:00" + } +] +``` + +--- + +#### `GET /api/instances/stacks` + +Returns a sorted array of distinct stack names present in the registry. + +``` +GET /api/instances/stacks +→ ["development", "production"] +``` + +--- + +#### `GET /api/instances/:vmid` + +Returns a single instance by VMID. + +| Status | Condition | +|---|---| +| `200` | Instance found | +| `400` | VMID is not a valid integer | +| `404` | No instance with that VMID | + +--- + +#### `GET /api/instances/:vmid/history` + +Returns the audit log for an instance — newest events first. + +| Status | Condition | +|---|---| +| `200` | History returned (may be empty array) | +| `400` | VMID is not a valid integer | +| `404` | No instance with that VMID | + +```json +[ + { + "id": 3, + "vmid": 117, + "field": "state", + "old_value": "testing", + "new_value": "deployed", + "changed_at": "2024-03-10T14:22:00" + }, + { + "id": 1, + "vmid": 117, + "field": "created", + "old_value": null, + "new_value": null, + "changed_at": "2024-01-15T10:30:00" + } +] +``` + +--- + +#### `POST /api/instances` + +Creates a new instance. Returns the created record. + +| Status | Condition | +|---|---| +| `201` | Created successfully | +| `400` | Validation error — see `errors` array in response | +| `409` | VMID already exists | + +**Request body:** + +| Field | Type | Required | Notes | +|---|---|---|---| +| `name` | string | yes | | +| `vmid` | integer | yes | Must be > 0 and unique | +| `state` | string | yes | `deployed`, `testing`, or `degraded` | +| `stack` | string | yes | `production` or `development` | +| `tailscale_ip` | string | no | Valid IPv4 or empty string | +| `atlas` | 0\|1 | no | | +| `argus` | 0\|1 | no | | +| `semaphore` | 0\|1 | no | | +| `patchmon` | 0\|1 | no | | +| `tailscale` | 0\|1 | no | | +| `andromeda` | 0\|1 | no | | +| `hardware_acceleration` | 0\|1 | no | | + +--- + +#### `PUT /api/instances/:vmid` + +Replaces all fields on an existing instance. Accepts the same body shape as `POST`. The `vmid` in the body may differ from the URL — this is how you change a VMID. + +| Status | Condition | +|---|---| +| `200` | Updated successfully | +| `400` | Validation error | +| `404` | No instance with that VMID | +| `409` | New VMID conflicts with an existing instance | + +--- + +#### `DELETE /api/instances/:vmid` + +Deletes an instance. Only instances on the `development` stack may be deleted. + +| Status | Condition | +|---|---| +| `204` | Deleted successfully | +| `400` | VMID is not a valid integer | +| `404` | No instance with that VMID | +| `422` | Instance is on the `production` stack | + +--- + +## Jobs + +Jobs are background sync tasks that keep instance data up to date by pulling from external services (Tailscale, Patchmon, Semaphore). Each job runs on a configurable interval and can also be triggered on demand. + +Sensitive config fields (`api_key`, `api_token`) are always returned as `**REDACTED**`. Send `**REDACTED**` back in a `PUT` to preserve the stored value unchanged. + +#### `GET /api/jobs` + +Returns all jobs. + +```json +[ + { + "id": 1, + "key": "tailscale_sync", + "name": "Tailscale Sync", + "description": "Syncs Tailscale device status and IPs", + "enabled": 1, + "schedule": 15, + "config": { "api_key": "**REDACTED**", "tailnet": "example.com" }, + "last_run_at": "2024-03-10T14:22:00", + "last_status": "success" + } +] +``` + +--- + +#### `GET /api/jobs/:id` + +Returns a single job and its last 10 run records, newest first. + +| Status | Condition | +|---|---| +| `200` | Job found | +| `400` | ID is not a valid integer | +| `404` | No job with that ID | + +```json +{ + "id": 1, + "key": "tailscale_sync", + "name": "Tailscale Sync", + "enabled": 1, + "schedule": 15, + "config": { "api_key": "**REDACTED**", "tailnet": "example.com" }, + "runs": [ + { + "id": 42, + "started_at": "2024-03-10T14:22:00", + "ended_at": "2024-03-10T14:22:01", + "status": "success", + "result": "3 updated of 12" + } + ] +} +``` + +--- + +#### `PUT /api/jobs/:id` + +Updates a job's enabled state, schedule interval, or configuration. All fields are optional — omitted fields are left unchanged. Config is merged with the existing config (partial updates are safe). + +| Status | Condition | +|---|---| +| `200` | Updated successfully — returns the updated job | +| `400` | ID is not a valid integer | +| `404` | No job with that ID | + +**Request body:** + +| Field | Type | Notes | +|---|---|---| +| `enabled` | boolean | `true` to enable, `false` to disable | +| `schedule` | integer | Interval in minutes (default: 15) | +| `config` | object | Merged with existing config — see per-job fields below | + +**Config fields by job key:** + +| Key | Field | Notes | +|---|---|---| +| `tailscale_sync` | `api_key` | Tailscale API key | +| `tailscale_sync` | `tailnet` | Tailnet name (e.g. `example.com`) | +| `tailscale_sync` | `run_on_create` | `true` to also run when a new instance is created | +| `patchmon_sync` | `api_url` | Full URL to the Patchmon hosts endpoint | +| `patchmon_sync` | `api_token` | `key:secret` credential or pre-encoded base64 | +| `semaphore_sync` | `api_url` | Full URL to the Semaphore inventory endpoint | +| `semaphore_sync` | `api_token` | Semaphore API token | + +--- + +#### `POST /api/jobs/:id/run` + +Triggers a job to run immediately. Blocks until the job completes and returns the result. The run is recorded in the job's history regardless of outcome. + +| Status | Condition | +|---|---| +| `200` | Job completed successfully | +| `400` | ID is not a valid integer | +| `404` | No job with that ID | +| `500` | Job handler threw an error (run is logged with `status: "error"`) | + +```json +{ "summary": "3 updated of 12" } +``` + +--- + +## Backup + +#### `GET /api/export` + +Downloads a JSON backup of all instances as a file attachment. + +```json +{ + "version": 1, + "exported_at": "2024-03-10T14:22:00.000Z", + "instances": [ ... ] +} +``` + +#### `POST /api/import` + +Replaces all instances from a JSON backup. Validates every row before committing — if any row is invalid the entire import is rejected. + +| Status | Condition | +|---|---| +| `200` | Import successful — returns `{ "imported": N }` | +| `400` | Body missing `instances` array, or validation errors | -- 2.47.3