API documentation Web hosting accessible to everyone Visit LWS

Getting started

Introduction

The KVM Panel API lets you manage the KVM VPS of your LWS account programmatically: inventory, monitoring, power, reinstallation, snapshots, backups, firewall, network and SSH keys. It offers what the KVM customer panel can do, for your scripts, your continuous integration or your monitoring.

Quick start

1. Create your API key. In the KVM panel, open the API / MCP page, available to the main account only. Declare the IP address your calls will come from, enable access, generate the key, then choose the VPS it may manage: all of them, or a selection. A key that does not cover any VPS yet works, but its VPS list is empty. The full key is displayed only once: copy it right away.

2. Store it in an environment variable, as all the examples in this documentation do:

export LWS_API_KEY="lws_live_sk_7Yc2Kp9RtNwQ4mZ8Bx3Vf1Hd6Ls0AjE2"

3. Check that it works:

curl https://panelkvm.lwspanel.com/api/v1/me \
  -H "Authorization: Bearer $LWS_API_KEY"

The response shows the account, the key's permission level and the number of VPS it covers.

4. List your VPS:

curl https://panelkvm.lwspanel.com/api/v1/servers \
  -H "Authorization: Bearer $LWS_API_KEY"

Each VPS is identified by its vmid, which you will use in every other call.

5. Start an action and track it. Reboot a VPS:

curl -X POST https://panelkvm.lwspanel.com/api/v1/servers/116807/power \
  -H "Authorization: Bearer $LWS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"action": "reboot"}'

The response contains a task ("id": "tsk_000002e1"). Poll it until its status is succeeded:

curl https://panelkvm.lwspanel.com/api/v1/tasks/tsk_000002e1 \
  -H "Authorization: Bearer $LWS_API_KEY"

Authentication

Every request carries the key in the Authorization header, using the Bearer scheme. Never pass it in a URL: access logs keep URLs.

The key is managed from the API / MCP page of the panel:

  • Authorized addresses (required): IPv4, IPv6 or CIDR ranges. Access cannot be enabled without at least one address. A call from any other address is rejected (403 ip_not_allowed).
  • Activation: a switch turns access off without deleting the key (401 access_disabled).
  • Regeneration: a new key invalidates the previous one immediately, and the account holder is notified by email.

Permissions and scope

Each key is granted a permission level:

Level Value What it allows
Read only read All read operations: inventory, status, metrics, lists, tasks, activity log.
Operations exploitation Read only, plus: power, snapshot and backup creation, ISO images, reverse DNS, SSH keys, creating and editing rule sets, applying a rule set, enabling the firewall, requesting port 25 unblocking.
Full full Operations, plus irreversible operations and those that expose the server: reinstallation, rescue mode, root password, snapshot rollback and deletion, backup restore, disabling the firewall, removing and deleting rule sets.

The required level is shown at the top of each operation. A call beyond the granted level is rejected (403 insufficient_scope), and the response states the required level in required_level.

The level is chosen on the API / MCP page of the panel. Switching to the Full level must be confirmed with a code sent by email to the account holder; returning to a lower level does not require one.

The key's scope covers either all the VPS of the account, including those purchased later, or a selection of VPS. A VPS outside the scope responds exactly like a VPS that does not exist (404 vm_not_found).

Requests and responses

  • Data is exchanged as JSON, encoded in UTF-8. Request bodies are sent with Content-Type: application/json.
  • A single resource is returned under data.
  • A list is returned under data, along with a meta block. Long lists (VPS, tasks, activity log) are paginated with page (default 1) and per_page (default 25, at most 100); short lists are returned in full.
  • Dates use the ISO 8601 format with a time zone offset: 2027-03-14T00:00:00+01:00.
  • Sizes are expressed in bytes, except min_memory_gb in the operating system catalog.
  • Identifiers are strings, including vmid: "116807". Only account_id, in GET /me, is a number.
  • Human-readable texts produced by the API (error title and detail, task and activity log description) are written in French. Base your processing on the machine-readable fields.
  • Every response carries an X-Request-Id header. Give it to LWS support whenever you report an issue: it identifies the exact call.
{
  "data": [
    {
      "id": "tsk_000002e1",
      "status": "succeeded",
      "action": "server.reboot",
      "vmid": "116807",
      "description": "Demande de reboot du serveur VPS-116807",
      "created_at": "2026-08-20T14:31:02+02:00",
      "finished_at": "2026-08-20T14:31:40+02:00"
    }
  ],
  "meta": { "page": 1, "per_page": 25, "total": 1, "total_pages": 1 }
}

Asynchronous operations

Operations marked [Differed] (start, reinstallation, snapshot, backup, firewall…) do not run during the request: they are queued and respond 202 Accepted with the task they created.

{
  "data": {
    "tasks": [
      {
        "id": "tsk_000002e1",
        "status": "pending",
        "action": "server.reboot",
        "vmid": "116807",
        "description": "Demande de reboot du serveur VPS-116807",
        "created_at": "2026-08-20T14:31:02+02:00",
        "finished_at": null
      }
    ]
  }
}

Then track the task with Retrieve a task every few seconds, until status is succeeded or failed. Polling more often does not speed anything up and uses up your rate limit.

Only one operation of a given type can run at a time on a VPS: a second request is rejected (409 task_already_running) and the response identifies the running task in task_id.

A VPS locked by the hypervisor also rejects write operations (409 server_locked). The reason field tells you what to do: operation_in_progress, an operation is running, retry after the delay given in the Retry-After header; suspended, the VPS is hibernating, start it first; support_notified, the lock is abnormal, the LWS team has been notified and is taking care of it, there is no point in retrying in a loop.

During an intervention announced by LWS on the host server of a VPS (update, reboot…), if that server stops responding, write operations on this VPS and reads that query the hypervisor are rejected (503 server_maintenance). The response's maintenance field gives the nature of the intervention and its expected end, and the Retry-After header the recommended delay before trying again. The VPS inventory and record keep responding, with status: maintenance.

All other write operations (ISO images, reverse DNS, root password, SSH keys, rule sets) take effect immediately and return their result directly.

Errors

Errors are returned in the application/problem+json format:

{
  "type": "https://docs.lws.fr/kvm/errors/vm-not-found",
  "title": "VPS introuvable",
  "status": 404,
  "code": "vm_not_found",
  "detail": "Aucun VPS 999999 sur ce compte.",
  "request_id": "3f9a1c07b2e45d18"
}

Base your error handling on code, which is stable. title and detail are written in French for a human reader and may change. Depending on the error, the response also includes errors (details per parameter), required_level, task_id, lock and reason, or retry_after.

HTTP Code Cause
400 bad_request Unreadable body (malformed JSON).
400 validation_failed Invalid parameter; errors details each faulty field.
401 invalid_token Authorization header missing or malformed, or key unknown or since regenerated.
401 access_disabled API access turned off for this account (switch on the "API / MCP" page), or API service temporarily unavailable.
403 ip_not_allowed Source address not among the authorized addresses.
403 insufficient_scope Key level too low; required_level gives the level required.
404 vm_not_found VPS does not exist or is outside the key's scope (the two cases are deliberately indistinguishable).
404 snapshot_not_found, backup_not_found, set_not_found, ssh_key_not_found, task_not_found Resource unknown on this VPS or account.
404 not_found Route does not exist.
405 method_not_allowed HTTP method not supported on an existing path; the Allow header lists the accepted methods.
409 task_already_running An operation of the same type is already running on this VPS; task_id identifies it.
409 server_locked VPS locked by the hypervisor; reason tells you whether to retry, start it, or wait for the LWS team.
409 service_suspended VPS disabled or expired: read operations remain possible, write operations do not.
409 invalid_state Action incompatible with the current state.
409 quota_exceeded Functional limit reached (3 snapshots, 10 rule sets per VPS).
422 unsupported_operation Feature unavailable on this VPS (QEMU guest agent missing, in particular).
429 rate_limited Rate limit exceeded: honor Retry-After.
500 internal_error Unexpected error: give the request_id to support.
502 hypervisor_unreachable Hypervisor temporarily unreachable: try again.
502 upstream_unavailable Internal dependency unavailable (inventory, backups, catalog…): try again.
503 server_maintenance Host server under an intervention announced by LWS and unreachable: write operations and hypervisor reads are rejected until the expected end (maintenance); honor Retry-After.

Rate limits

Calls are limited per account, over a sliding one-hour window, in three independent families:

Family Limit Operations covered
read 3,600 calls per hour All read operations (GET)
mutation 300 calls per hour Regular write operations
heavy 6 calls per hour per VPS, 60 per account Snapshots (create, roll back, delete), backups (start, restore), reinstallation

A heavy operation is subject to two caps: that of the targeted VPS and that of the account. The per-VPS cap prevents a single server from being hit in a loop, without penalising accounts with many VPS; the refusal message states which of the two caps has been reached.

To spare the hypervisors, the state and instant metrics of a VPS are cached for 15 seconds, and the metrics history for 1 minute to 1 hour depending on the period. Polling more often returns the same value and uses quota for nothing.

Responses to authenticated calls show the rate limit status in the X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers. An authenticated call that is refused (invalid parameter, insufficient level, conflict…) also counts against the limit; a call rejected at authentication, sent to a non-existent route, or refused for exceeding the limit (429) does not. Once the limit is reached, the API responds 429 rate_limited: wait for the number of seconds given in the Retry-After header before trying again.