API Reference
LWS provides a comprehensive REST API for programmatic access to all functionality.
Base URL
http://localhost:8080/api/v1
Authentication
Most endpoints require API key authentication via the X-API-Key header.
curl -H "X-API-Key: your-api-key" \
http://localhost:8080/api/v1/lxc/instances
Configure your API key in config.yaml:
# The server refuses to start if this is empty or one of a few recognized
# placeholders. Generate a real one:
# python3 -c 'import secrets; print(secrets.token_urlsafe(32))'
api_key: "REPLACE_ME_WITH_32_PLUS_RANDOM_CHARACTERS"
Response Format
All responses are in JSON format:
Success Response
{
"output": "command output here"
}
If the underlying command’s stdout starts with { or [ (e.g. commands run with a JSON output option), the API returns that parsed JSON directly instead of wrapping it in "output".
Error Response
{
"error": "Error description",
"details": "Detailed error message",
"return_code": 1
}
Known limitation affecting several endpoints below (marked ⚠️):
run_lws_commandbuilds the underlying CLI call by appending every key still present in the request body as a--key valueflag (api.py:200-215), even for keys the route handler already consumed to build a positional argument. When the correspondinglws.pycommand takes that value as a positional argument rather than an option — which is true for most of the endpoints marked below — the extra flag doesn’t exist and the underlying CLI call fails with a Click “no such option” error. This is a bug inapi.py, not something you can work around from the request body itself.
API Endpoints
Health & Status
GET /health
Check API health status (no authentication required).
curl http://localhost:8080/api/v1/health
Response:
{
"status": "ok"
}
Configuration Endpoints
GET /conf
Show current configuration (masked).
curl -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/conf
POST /conf/validate
Validate configuration.
curl -X POST \
-H "X-API-Key: your-key" \
http://localhost:8080/api/v1/conf/validate
POST /conf/backup
Back up the current configuration to a file. Unlike most POST handlers, this one builds --timestamp/--compress flags manually instead of forwarding the whole body, so it isn’t affected by the known limitation below.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"destination_path": "/backup/lws-config.yaml",
"timestamp": true,
"compress": true
}' \
http://localhost:8080/api/v1/conf/backup
Proxmox Host Endpoints
GET /px/hosts
List all Proxmox hosts.
curl -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/px/hosts
Query Parameters:
region- Filter by region
GET /px/status
Get Proxmox host resource usage.
curl -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/px/status?region=eu-south-1&az=az1"
POST /px/reboot
Reboot a Proxmox host.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"region": "eu-south-1", "az": "az1", "confirm": true}' \
http://localhost:8080/api/v1/px/reboot
GET /px/templates
List available templates.
curl -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/px/templates?region=eu-south-1&az=az1"
GET /px/clusters
List all clusters in the Proxmox environment.
curl -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/px/clusters?region=eu-south-1&az=az1"
POST /px/update
Run apt-get update on the machine running the API server — not on any Proxmox host. No request body needed.
curl -X POST -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/px/update
POST /px/cluster/start / /px/cluster/stop / /px/cluster/restart
Start, stop, or restart cluster services (pve-cluster, corosync) on a Proxmox host.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"region": "eu-south-1", "az": "az1"}' \
http://localhost:8080/api/v1/px/cluster/start
POST /px/backup-lxc ⚠️
Back up a single LXC container via vzdump on the Proxmox host. Affected by the known limitation above: vmid is positional in px backup-lxc and gets duplicated.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"vmid": "100", "storage": "local-lvm", "mode": "snapshot"}' \
http://localhost:8080/api/v1/px/backup-lxc
POST /px/backup ⚠️
Back up Proxmox host configuration (/etc/pve) to a local .tar.gz. Affected by the known limitation above: backup_dir is positional and gets duplicated.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"backup_dir": "/backups/px", "region": "eu-south-1", "az": "az1"}' \
http://localhost:8080/api/v1/px/backup
POST /px/image ⚠️
Create a template image from an LXC container. Affected by the known limitation above: instance_id and template_name are both positional and get duplicated.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"instance_id": "100", "template_name": "my-template"}' \
http://localhost:8080/api/v1/px/image
DELETE /px/image/{template_name}
Delete a template image from the Proxmox template cache. template_name comes from the URL, so it isn’t affected by the known limitation.
curl -X DELETE -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/px/image/my-template?region=eu-south-1&az=az1"
GET /px/security-groups
List all security groups and their rules in the cluster firewall.
curl -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/px/security-groups?region=eu-south-1&az=az1"
POST /px/security-groups ⚠️
Create a security group. Affected by the known limitation above: group_name is positional and gets duplicated.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"group_name": "web", "description": "Web tier"}' \
http://localhost:8080/api/v1/px/security-groups
DELETE /px/security-groups/{group_name}
Delete a security group. group_name comes from the URL, so it isn’t affected by the known limitation.
curl -X DELETE -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/px/security-groups/web?region=eu-south-1&az=az1"
POST /px/security-groups/{group_name}/rules / DELETE .../rules
Add or remove a firewall rule within an existing security group. group_name comes from the URL; the rule fields (direction, action, protocol, source_ip, source_port, destination_ip, destination_port) are all real options on px security-group-rule-add/rule-rm, so this pair isn’t affected by the known limitation.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"direction": "IN", "protocol": "tcp", "destination_port": "443"}' \
http://localhost:8080/api/v1/px/security-groups/web/rules
POST /px/security-groups/attach / /px/security-groups/detach ⚠️
Attach or detach a security group from a container’s firewall config. Affected by the known limitation above: group_name and vmid are both positional and get duplicated.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"group_name": "web", "vmid": "100"}' \
http://localhost:8080/api/v1/px/security-groups/attach
POST /px/upload ⚠️
Upload an LXC template to a Proxmox host. Affected by the known limitation above: local_path and remote_template_name are both positional and get duplicated.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"local_path": "./ubuntu-22.04.tar.gz", "remote_template_name": "ubuntu-22.04"}' \
http://localhost:8080/api/v1/px/upload
POST /px/exec
Execute an arbitrary command on a Proxmox host over SSH. The handler filters the request body down to region/az before forwarding it, so — unlike most of the endpoints above — it isn’t affected by the known limitation.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"command": "df -h /var/lib/vz"}' \
http://localhost:8080/api/v1/px/exec
LXC Container Endpoints
POST /lxc/instances
Create and start LXC containers.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"image_id": "local:vztmpl/ubuntu-22.04.tar.gz",
"size": "medium",
"count": 1,
"hostname": "web-server",
"region": "eu-south-1",
"az": "az1"
}' \
http://localhost:8080/api/v1/lxc/instances
Request Body:
{
"image_id": "string (required)",
"size": "string (default: small)",
"count": "integer (default: 1)",
"hostname": "string",
"password": "string",
"ip": "string",
"netmask": "string (default: 24)",
"gateway": "string",
"dns": "string",
"dhcp": "boolean",
"region": "string",
"az": "string"
}
GET /lxc/instances
List all LXC containers.
curl -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/lxc/instances?region=eu-south-1&az=az1"
GET /lxc/instances/{instance_id}
Get details of a specific container.
curl -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/lxc/instances/100
POST /lxc/instances/start ⚠️
Start containers. Affected by the known limitation above: instance_ids stays in the request body and gets appended a second time as --instance-ids "['100', ...]" — a stringified Python list, not a real flag lxc start accepts.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"instance_ids": ["100", "101", "102"],
"region": "eu-south-1",
"az": "az1"
}' \
http://localhost:8080/api/v1/lxc/instances/start
POST /lxc/instances/stop ⚠️
Stop containers. Affected by the same instance_ids duplication described above.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"instance_ids": ["100", "101"],
"region": "eu-south-1",
"az": "az1"
}' \
http://localhost:8080/api/v1/lxc/instances/stop
POST /lxc/instances/terminate ⚠️
Terminate (destroy) containers. Affected by the same instance_ids duplication described above.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"instance_ids": ["100"],
"region": "eu-south-1",
"az": "az1"
}' \
http://localhost:8080/api/v1/lxc/instances/terminate
POST /lxc/instances/reboot ⚠️
Reboot running containers. Affected by the same instance_ids duplication described above.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"instance_ids": ["100", "101"], "region": "eu-south-1", "az": "az1"}' \
http://localhost:8080/api/v1/lxc/instances/reboot
POST /lxc/instances/status ⚠️
A one-shot resource snapshot (load average, memory, disk, swap) for one or more containers. Affected by the same instance_ids duplication described above.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"instance_ids": ["100", "101"], "region": "eu-south-1", "az": "az1"}' \
http://localhost:8080/api/v1/lxc/instances/status
POST /lxc/instances/scale ⚠️
Scale container resources. Affected by the same instance_ids duplication described above (the memory/cpulimit/storage_size options themselves are unaffected — they’re real options, not positional).
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"instance_ids": ["100"],
"memory": 4096,
"cpulimit": 4,
"storage_size": "64G",
"region": "eu-south-1",
"az": "az1"
}' \
http://localhost:8080/api/v1/lxc/instances/scale
POST /lxc/instances/clone ⚠️
Clone an LXC container. Affected by the known limitation above: source_instance_id and target_instance_id are both positional in lxc clone and get duplicated.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"source_instance_id": "100", "target_instance_id": "200", "full": true}' \
http://localhost:8080/api/v1/lxc/instances/clone
POST /lxc/instances/{instance_id}/exec
Execute command in container.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"command": "apt update && apt upgrade -y",
"region": "eu-south-1",
"az": "az1"
}' \
http://localhost:8080/api/v1/lxc/instances/100/exec
POST /lxc/instances/{instance_id}/snapshots ⚠️
Create a snapshot. Affected by the known limitation above: snapshot_name is a positional argument in lxc snapshot-add, so it gets sent twice (once correctly, once as a nonexistent --snapshot-name flag) and the call fails.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"snapshot_name": "before-update",
"region": "eu-south-1",
"az": "az1"
}' \
http://localhost:8080/api/v1/lxc/instances/100/snapshots
DELETE /lxc/instances/{instance_id}/snapshots/{snapshot_name}
Delete a snapshot.
curl -X DELETE \
-H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/lxc/instances/100/snapshots/before-update?region=eu-south-1&az=az1"
GET /lxc/instances/{instance_id}/snapshots
List all snapshots of a container.
curl -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/lxc/instances/100/snapshots?region=eu-south-1&az=az1"
POST /lxc/instances/{instance_id}/volumes/attach ⚠️
Attach a storage volume. Affected by the known limitation above: volume_name and volume_size are both positional in lxc volume-attach and get duplicated.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"volume_name": "local-lvm", "volume_size": "16G", "mount_point": "/mnt/data"}' \
http://localhost:8080/api/v1/lxc/instances/100/volumes/attach
POST /lxc/instances/{instance_id}/volumes/detach ⚠️
Detach a storage volume — this always removes the container’s mp0 mount, regardless of volume_name. Affected by the known limitation above: volume_name is positional and gets duplicated.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"volume_name": "local-lvm"}' \
http://localhost:8080/api/v1/lxc/instances/100/volumes/detach
POST /lxc/instances/{instance_id}/service ⚠️
Run a systemctl action against a service inside a container. Affected by the known limitation above: action and service_name are both positional in lxc service and get duplicated.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"action": "restart", "service_name": "nginx"}' \
http://localhost:8080/api/v1/lxc/instances/100/service
POST /lxc/instances/{instance_id}/migrate
Migrate a container to another Proxmox host. target_host is a real option (not positional), so this one isn’t affected by the known limitation.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"target_host": "proxmox2.example.com"}' \
http://localhost:8080/api/v1/lxc/instances/100/migrate
GET /lxc/instances/{instance_id}/storage
List storage usage inside a container.
curl -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/lxc/instances/100/storage
GET /lxc/instances/{instance_id}/scale-check
Read-only scaling recommendation based on the scaling thresholds in config.yaml.
curl -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/lxc/instances/100/scale-check
GET /lxc/instances/{instance_id}/net-check ⚠️
Check whether a TCP/UDP port is open on a container. Affected by the known limitation above: protocol and port are both positional in lxc net and get duplicated as query params turned back into flags.
curl -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/lxc/instances/100/net-check?protocol=tcp&port=443"
GET /lxc/instances/{instance_id}/info
Retrieve IP address(es), in-container hostname, DNS servers, and the container’s Proxmox-side hostname.
curl -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/lxc/instances/100/info
GET /lxc/instances/{instance_id}/public-ip
Retrieve the public IP address(es) of a container.
curl -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/lxc/instances/100/public-ip
GET /lxc/instances/{instance_id}/health-check
Run health checks on a container.
curl -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/lxc/instances/100/health-check?fix=true"
POST /lxc/instances/{instance_id}/restore
Restore a container from a backup file. backup_file is a real option (not positional), so this one isn’t affected by the known limitation.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"backup_file": "/var/lib/vz/dump/vzdump-lxc-100.tar.gz"}' \
http://localhost:8080/api/v1/lxc/instances/100/restore
POST /lxc/instances/{instance_id}/backup
Create a backup of a container. All fields (destination, download, compress_level) are real options, so this one isn’t affected by the known limitation. An empty body is fine — it uses lxc backup-create’s defaults.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"download": true}' \
http://localhost:8080/api/v1/lxc/instances/100/backup
GET /lxc/instances/{instance_id}/report
Generate a comprehensive report about a container.
curl -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/lxc/instances/100/report
GET /lxc/instances/{instance_id}/resources
Monitor container resources.
curl -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/lxc/instances/100/resources
Docker/App Endpoints
POST /lxc/instances/{instance_id}/app/setup ⚠️
Install Docker in a container. Affected by the known limitation above: package_name is positional in app setup and gets duplicated.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"package_name": "docker",
"region": "eu-south-1",
"az": "az1"
}' \
http://localhost:8080/api/v1/lxc/instances/100/app/setup
POST /lxc/instances/{instance_id}/app/run
Run Docker container.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"docker_command": ["-d", "-p", "80:80", "nginx"],
"region": "eu-south-1",
"az": "az1"
}' \
http://localhost:8080/api/v1/lxc/instances/100/app/run
POST /lxc/instances/{instance_id}/app/deploy ⚠️
Deploy Docker Compose app. Affected by the known limitation above (action is positional and gets duplicated) and a second, separate issue: the request body’s compose_file/auto_start get turned into --compose-file/--auto-start (hyphens), while the actual CLI command only accepts --compose_file/--auto_start (underscores). Either issue alone breaks the call.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"action": "install",
"compose_file": "/path/to/docker-compose.yml",
"auto_start": true,
"region": "eu-south-1",
"az": "az1"
}' \
http://localhost:8080/api/v1/lxc/instances/100/app/deploy
POST /lxc/instances/{instance_id}/app/update ⚠️
Update a Compose app by re-deploying a new Compose file. This endpoint cannot currently work: compose_file is a required positional argument on app update, but the handler never adds it to the command at all — it only ever ends up as a --compose_file/--compose-file flag, which doesn’t exist as an option on this command either. The call fails regardless of what you send.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"compose_file": "/path/to/docker-compose.yml"}' \
http://localhost:8080/api/v1/lxc/instances/100/app/update
GET /lxc/instances/{instance_id}/app/logs/{container_name}
Get Docker logs.
curl -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/lxc/instances/100/app/logs/nginx?follow=false&lines=100"
GET /lxc/instances/{instance_id}/app/containers
List Docker containers running inside an LXC container.
curl -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/lxc/instances/100/app/containers
POST /lxc/instances/app/remove ⚠️
Uninstall Docker and Compose from one or more containers. This endpoint always returns a 500: the view function is declared as app_remove(instance_ids), but its route has no <instance_ids> path segment, so Flask calls it with no arguments and Python raises TypeError: app_remove() missing 1 required positional argument. This is unrelated to the request body shown below — no request would succeed against this endpoint as currently written.
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"instance_ids": ["100", "101"]}' \
http://localhost:8080/api/v1/lxc/instances/app/remove
Security Endpoints
GET /sec/discovery
Discover reachable hosts. Called with no query parameters, this works fine (sec discovery’s lxc_id argument is optional). ⚠️ Called with ?lxc_id=... as shown below, it hits the known limitation above — lxc_id is positional and gets duplicated as a nonexistent --lxc-id flag, so the call fails.
curl -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/sec/discovery?lxc_id=100"
GET /lxc/instances/{instance_id}/sec/scan
Security scan a container.
curl -H "X-API-Key: your-key" \
"http://localhost:8080/api/v1/lxc/instances/100/sec/scan?scan_type=full"
Web UI
The API includes a simple web interface at the root URL:
http://localhost:8080/
The Web UI provides:
- API key configuration
- Quick access to common operations
- Response visualization
Swagger Documentation
Interactive API documentation is available at:
http://localhost:8080/api/v1/docs
Features:
- Interactive endpoint testing
- Request/response schemas
- Authentication testing
Error Codes
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Invalid or missing API key |
| 404 | Not Found - Resource doesn’t exist |
| 500 | Internal Server Error - Command execution failed |
Rate Limiting
Currently, there are no rate limits. For production use, consider implementing rate limiting via a reverse proxy (nginx, Caddy).
CORS Configuration
Configure allowed origins in config.yaml:
api:
allowed_origins:
- "http://localhost:8080"
- "https://yourdomain.com"
Example: Complete Workflow
1. Check API Health
curl http://localhost:8080/api/v1/health
2. List Proxmox Hosts
curl -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/px/hosts
3. Create Container
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{
"image_id": "local:vztmpl/ubuntu-22.04.tar.gz",
"size": "medium",
"hostname": "api-test"
}' \
http://localhost:8080/api/v1/lxc/instances
4. Monitor Container
curl -H "X-API-Key: your-key" \
http://localhost:8080/api/v1/lxc/instances/100/resources
5. Execute Command
curl -X POST \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"command": "uptime"}' \
http://localhost:8080/api/v1/lxc/instances/100/exec
Python Client Example
import requests
class LWSClient:
def __init__(self, base_url, api_key):
self.base_url = base_url
self.headers = {'X-API-Key': api_key}
def list_containers(self):
url = f"{self.base_url}/api/v1/lxc/instances"
response = requests.get(url, headers=self.headers)
return response.json()
def create_container(self, image_id, size='medium'):
url = f"{self.base_url}/api/v1/lxc/instances"
data = {'image_id': image_id, 'size': size}
response = requests.post(url, json=data, headers=self.headers)
return response.json()
# Usage
client = LWSClient('http://localhost:8080', 'your-api-key')
containers = client.list_containers()
print(containers)
| ← CLI Reference | Next: Configuration → |