SDKs and CLI
TypeScript SDK
Reference for @vmyard/sdk, the TypeScript client for Node.js 20+ with no runtime dependencies.
@vmyard/sdk is the TypeScript client for the VMYard API. It runs on Node.js 20 or newer, ships ESM and CommonJS builds, and has no runtime dependencies (it uses the global fetch).
git clone https://github.com/erphq/vmyard-sdk.git
cd vmyard-sdk && npm ci && npm run build
cd your-project && npm install ../vmyard-sdk/typescriptCreate a client
import { VMYard } from "@vmyard/sdk";
const vy = new VMYard({
apiKey: process.env.VMYARD_API_KEY, // default: VMYARD_API_KEY
baseUrl: "https://api.example.com", // default: VMYARD_API_URL
});| Option | Default | Meaning |
|---|---|---|
apiKey | VMYARD_API_KEY | A vmy_… key. Required. |
baseUrl | VMYARD_API_URL | Your API origin. Required: there is no default host. |
organizationId | VMYARD_ORGANIZATION_ID | Sent as X-Organization-ID. Optional with an API key; must match the key. |
maxRetries | 2 | Retries for retryable failures. |
timeoutMs | 60000 | Timeout per HTTP attempt. |
fetch | globalThis.fetch | A custom fetch implementation. |
userAgent | — | Extra User-Agent text. |
The client never follows redirects, so the key is never sent to another origin.
Machines
const machine = await vy.machines.create({ name: "dev", plan: "small", autoStopAfterSeconds: 7200 });
const same = await vy.machines.find("dev"); // id, tenant alias, or unique display name
const page = await vy.machines.list({ limit: 20 }); // { items, nextCursor }
const all = await vy.machines.listAll();
await machine.update({ name: "api" });
await machine.stop();
await machine.start();
await machine.restart();
await machine.refresh(); // reload machine.data
await machine.delete();| Call | Route | Notes |
|---|---|---|
vy.machines.create(params) | POST /v1/machines | Resolves plan, image, and region names or IDs to exact versions; waits for the operation; returns a Machine. |
vy.machines.beginCreate(params) | POST /v1/machines | Submits without waiting; returns the accepted operation. |
vy.machines.buildCreateRequest(params) | — | The exact request create would send. |
vy.machines.list({ limit, cursor }) | GET /v1/machines | One page. listAll() and iterate() follow every page. |
vy.machines.get(id) / find(idOrName) | GET /v1/machines/{id} | find accepts an ID, tenant alias, or unique display name. |
vy.machines.update(id, params) / machine.update(params) | PATCH /v1/machines/{id} | name, autoStopAfterSeconds (null = never), neverAutoStop. |
vy.machines.start/stop/restart(id, { wait }) | POST …/{action} | Returns the refreshed Machine. |
vy.machines.delete(id, { wait }) | POST …/delete | Returns the operation. |
vy.machines.usage(id, { since, until }) / machine.usage() | GET …/usage | Seconds and micros; defaults to the machine's lifetime. |
create params: name, alias (the immutable tenant alias; defaults to name, else vy-xxxxxxxx), plan, image, region (name, ID, or {id, version}; default the first in the catalog), resources, backend, autoStopAfterSeconds, neverAutoStop, and setupScript.
machine.data is the wire MachineDto. Pass { wait: false } to any lifecycle call to get the accepted operation instead of waiting.
Commands
const result = await machine.exec("uname -a", { timeoutMs: 30_000 });
// { operationId, exitCode, stdout, stderr, stdoutBytes, stderrBytes, timedOut, cancelled, truncated }
const stream = machine.exec(["npm", "test"], { stream: true });
for await (const frame of stream) {
// started | stdout | stderr | exit | error; stdout/stderr carry decoded bytes in `data`
}A string runs under /bin/sh -c; an array runs as argv. A non-zero exit, timeout, or cancellation raises OperationError (see Running commands). Streams skip frame types they don't know.
Background commands
const { command_id } = await machine.commands.start("npm run dev");
const status = await machine.commands.status(command_id, { logOffset: 0, logLimit: 65536 });
const finished = await machine.commands.wait(command_id); // exited, killed, or failed_to_start
await machine.commands.kill(command_id);Files
await machine.files.write("app/config.json", JSON.stringify({ debug: true })); // string or bytes
const text = await machine.files.read("app/config.json");
const bytes = await machine.files.readBytes("out/image.png");Reads follow a queued 202 answer and reassemble file_ranges for you.
Operations
const operation = await vy.operations.get(operationId);
const done = await vy.operations.wait(operationId); // throws OperationError unless it succeeds
for await (const event of vy.operations.events(operationId)) console.log(event);Waiting polls every 250 ms, growing 1.5× up to 2 s, for at most 10 minutes; past that it throws OperationTimeoutError and the operation keeps going. events streams server-sent events and resumes with Last-Event-ID.
Catalog, identity, billing, webhooks
| Call | Route |
|---|---|
vy.me() | GET /v1/me |
vy.catalog.plans() / images() / regions() | GET /v1/plans, /v1/images, /v1/regions |
vy.billing.get() / limits() / invoicePreview() | GET /v1/billing, /v1/limits, /v1/billing/invoice-preview |
vy.billing.usage({ since, until }) | GET /v1/usage |
vy.webhooks.list() / get(id) | GET /v1/webhooks[/{id}] |
vy.webhooks.create({ name, url, events, enabled }) | POST /v1/webhooks; returns { webhook, secret } |
vy.webhooks.update(id, params) / delete(id) / rotateSecret(id) | PATCH/DELETE /v1/webhooks/{id}, POST …/rotate-secret |
vy.webhooks.deliveries(id, { limit }) | GET /v1/webhooks/{id}/deliveries |
The SDK reads billing and can change it too: spend caps, checkout, the payment portal, the trial, the plan, and automatic refill (billing.update, billing.checkout, billing.portal, and others). vy.apiKeys.list() exists, but the server refuses API keys on key routes (403 session_required).
Device sign-in
import { DeviceAuthorization } from "@vmyard/sdk";
const device = new DeviceAuthorization({ baseUrl: process.env.VMYARD_API_URL });
const code = await device.requestCode({ clientName: "my-tool on build-host" });
console.log(`Open ${code.verification_uri} and enter ${code.user_code}`);
const token = await device.waitForToken(code); // token.access_token is the new API keywaitForToken waits interval seconds between polls, adds 5 seconds on slow_down, and raises DeviceAuthorizationError on access_denied, expired_token, or invalid_grant. See CLI device login.
Idempotency and retries
Every mutation gets a fresh UUID Idempotency-Key unless you pass idempotencyKey. Reads retry on connection errors and 408, 429, 502, 503, and 504; mutations retry only on 429 and 503, with the same key. Retries back off exponentially with jitter and honour Retry-After. A Retry-After longer than 60 seconds is raised at once with retryAfterSeconds set, so you can schedule it, and a 429 limit_reached for concurrent machines is raised without retrying.
Errors
| Class | When |
|---|---|
APIError | Any problem-details answer. Subclasses: BadRequestError 400, AuthenticationError 401, PaymentRequiredError 402, PermissionDeniedError 403, NotFoundError 404, ConflictError 409, PayloadTooLargeError 413, UnprocessableEntityError 422, RateLimitError 429, ServerError 5xx. |
BillingRequiredError, SpendCapReachedError | 402 billing admission. |
TrialMachineClassNotAllowedError | 403 trial size refusal. |
LimitReachedError | 429 limit_reached, with limit and retryAfter. |
OperationError | An operation ended failed, uncertain, or correlation_miss; has operation, status, errorCode. |
OperationTimeoutError | Waiting ran out of time; the operation continues. |
ExecStreamError | A streamed exec ended with an error frame or broke off. |
DeviceAuthorizationError | Device sign-in was denied, expired, or invalid. |
ConnectionError, ConfigurationError, ResolutionError | Network failure, missing key or URL, or a catalog name that didn't resolve. |
All of them extend VMYardError. APIError carries status, code, title, message, remediation, correlationId, fieldErrors, retryAfterSeconds, problem, and the raw body.
More of the SDK
The SDK also covers ports and port tokens (machine.ports), SSH keys (machine.setSshKey), internet access (machine.setNetwork), snapshots, forks and restores, templates, environments, billing changes, metrics, command history, and organizations; this page shows the common calls. For anything else, call the HTTP API directly; wire types such as MachineDto and OperationDto are exported for that.