TypeScript-first API client for Proxmox VE.
It provides a typed API surface generated from the Proxmox spec, so endpoint paths, query/body fields, and return types are available through autocomplete.
{ data: ... } -> data)npm install pve-client
import { Client } from "pve-client";
import { Agent } from "node:https";
const client = new Client({
baseUrl: "https://pve.example.com:8006",
apiToken: "PVEAPIToken=root@pam!mytoken=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
// only needed for self-signed certs
agent: new Agent({ rejectUnauthorized: false }),
});
const nodes = await client.api.nodes.list();
console.log(nodes);
import { Client } from "pve-client";
import { Agent } from "node:https";
const client = new Client({
baseUrl: "https://pve.example.com:8006",
username: "root",
password: "your-password",
realm: "pam", // optional, defaults to "pam"
agent: new Agent({ rejectUnauthorized: false }),
});
await client.login();
const nodes = await client.api.nodes.list();
console.log(nodes);
.envPVE_BASE_URL=https://pve.example.com:8006
PVE_API_TOKEN=PVEAPIToken=root@pam!mytoken=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
import "dotenv/config";
import { Client } from "pve-client";
const client = new Client({
baseUrl: process.env.PVE_BASE_URL!,
apiToken: process.env.PVE_API_TOKEN!,
});
const nodes = await client.api.nodes.list();
console.log(nodes);
const cluster = await client.api.cluster.index();
const nodes = await client.api.nodes.list();
const nodeStatus = await client.api.nodes.get("pve").status.get();
// no params required
const version = await client.api.version.version();
// optional query/body can still be passed
const pools = await client.api.pools.index({
$query: { type: "qemu" },
});
Methods accept an object with optional special fields depending on endpoint:
$path: path parameters (usually handled by path helper methods like .get("node"))$query: query string parameters$body: request body (sent as application/x-www-form-urlencoded)$headers: additional request headersExample:
await client.api.access.permissions({
$query: { path: "/", userid: "root@pam" },
});
apiToken accepts either:
PVEAPIToken=user@realm!token=secretuser@realm!token=secretawait client.login() before other API calls.https.Agent with rejectUnauthorized: false.There is a runnable example that opens a VM terminal and binds it to your local terminal (stdin/stdout):
npm run example/terminal -- 100
Or set PVE_VMID in .env and run without CLI args:
npm run example/terminal
Example .env (username/password required for terminal helper):
PVE_BASE_URL=https://pve.example.com:8006
PVE_USERNAME=root
PVE_PASSWORD=your-password
PVE_REALM=pam
PVE_VMID=100
Controls:
Ctrl-] disconnects from the local bridge.There is also a terminal example that continuously displays cluster tasks:
npm run example/tasks
Optional .env values:
# required
PVE_BASE_URL=https://pve.example.com:8006
# auth: use token OR username/password
PVE_API_TOKEN=PVEAPIToken=root@pam!tokenid=secret
# or:
PVE_USERNAME=root
PVE_PASSWORD=your-password
PVE_REALM=pam
# optional polling interval (default 2000)
PVE_TASKS_POLL_INTERVAL_MS=2000
Non-2xx responses throw an Error including status and response text:
try {
await client.api.nodes.list();
} catch (error) {
console.error(error);
}
npm run build
npm test
npm run test:coverage
npm run example/auth
npm run example/terminal -- 100
npm run example/tasks
This project uses Vitest in a Node environment.
npm test runs all testsnpm run test:coverage runs tests and generates coverage reports (text, json, html)Current high-value suites cover:
Client authentication, request handling, events, and task pollingnative_fetch request construction, header/body behavior, and abort handlingTimerPulledEventEmitter polling, filtering, dedupe, and error flowsApache-2.0