JavaScript SDK
@unikraft/cloud is the official JavaScript/TypeScript SDK for the Unikraft Cloud API.
Installation
Code
Requires Node.js 18 or later.
Quickstart
quickstart.ts
Authentication
Pass a bearer token to the constructor, or set the UKC_TOKEN environment variable.
Create a token in the Unikraft Cloud dashboard.
Client options
The UnikraftCloud constructor takes one configuration object, and every field is optional:
| Option | Description |
|---|---|
token | Bearer token, which falls back to the UKC_TOKEN environment variable |
metro | Pin the client to one metro (for example, fra), or to a full http(s):// base address, which falls back to UKC_METRO |
metros | The metros operations cover: "all" (the default), one metro, or a list |
baseUrl | Explicit platform API base address, which overrides metro |
controlPlaneUrl | Override the control plane API base address |
fetch | Custom fetch implementation |
headers | Extra headers sent with every request |
userAgent | Override the User-Agent header |
proxyFromEnv | Honour the proxy environment variables on Node, which defaults to true |
metros takes precedence over metro, which stays the target for operations that need exactly one metro.
Set neither, and the scope covers every metro the account can reach, with fra as the target for single-metro operations.
The base address of a metro is https://api.<metro>.unikraft.cloud.
A baseUrl, or a metro that already is an address, pins the client to that one endpoint, as described under self-hosted and staging deployments.
The two layers: porcelain and plumbing
The SDK is explicitly two layers, and you choose per call which one you are in:
| Layer | Where | What you get |
|---|---|---|
| Porcelain | @unikraft/cloud — ukc.instances, ukc.volumes, … | Short verbs, no envelope, auto-pagination, metro fan-out, chainable handles |
| Plumbing | @unikraft/cloud/api/platform, .../api/controlplane | The OpenAPI specification as written: operationId methods, raw envelope, one metro per call |
The porcelain layer holds a plumbing client rather than extending one, so the two never blur together — and the raw client is always one property away:
Code
Plumbing clients are metro-scoped by construction: they talk to whatever baseUrl names, and a single call can be redirected with { baseUrl }.
Fanning out across metros is the porcelain layer's job.
Two APIs: platform and control plane
Both Unikraft Cloud APIs defined by our OpenAPI specification are wrapped:
- Platform — metro-scoped resources (instances, volumes, services, certificates, autoscale, images).
Wrapped idiomatically at the top level; raw under
@unikraft/cloud/api/platformandukc.api.platform. - Control plane — the global (non-metro) API for account, metros, images and self-hosted nodes.
Raw only, under
@unikraft/cloud/api/controlplaneandukc.api.controlplane.
Code
Metros: one account, many regions
The platform API is per-metro, but an account's instances are spread across them.
The porcelain layer treats the metros in scope as one namespace: reads fan out concurrently and are merged, and every result carries the metro it came from.
Code
Set the default scope at construction, too:
Code
UKC_METRO acts like metro: — setting it pins the client to that metro.
| Code | Location |
|---|---|
fra | Frankfurt, DE |
dal | Dallas, TX, USA |
sin | Singapore |
was | Washington, DC, USA |
sfo | San Francisco, USA |
Metro discovery asks the control plane and trusts the endpoint it reports, so new metros work without an SDK upgrade.
KNOWN_METROS lists the ones known when this version was published.
Rules the fan-out follows
- Reads cover the whole scope. Pages are interleaved in arrival order, so a slow metro never holds up a fast one.
- A name identifies a resource within a metro. The same name can exist in several metros at once — usually because you deployed the same thing everywhere — so a name plus a wide scope may match more than one resource. See Names across metros.
createnever fans out. It needs one metro: the client's, or the default metro (metro:/UKC_METRO/fra) when the scope is wider.- Bulk operations are bounded by the scope.
Refs are located first and one call goes to each metro that matched, so
delete([{ name: "web" }])under a wide scope deletes everywebin scope. Narrow the scope or qualify the ref to act on one. - The control plane is global, so it is unaffected by scope.
Names across metros
Names are scoped to a metro, so the same name can name a different resource in every metro. Three ways to say what you mean:
Code
{ uuid } refs never need qualifying: a UUID identifies one resource wherever it lives.
get() insists on exactly one match, because the next thing you write might be a mutation.
When a name matches in several metros it throws an AmbiguousRefError carrying the matches, so recovering costs no further requests:
Code
each(ref) is the deliberate plural.
It resolves the matches once, then runs each operation in the metro that holds it:
Code
Set operations return arrays and follow the same partial-failure rule as reads: successes are returned on the thrown MetroFanoutError as err.results.
each() exists on instances, volumes, services and certificates.
Partial failure
A metro that is unreachable does not throw away the rest of the answer.
Healthy metros are drained first, then a MetroFanoutError naming the failures is thrown:
Code
For a bulk operation, which cannot yield as it goes, the results that did succeed are attached to the thrown error as err.results.
Chainable handles
Single-resource operations return a handle: a lazily-evaluated reference to one resource in one metro. Awaiting a handle gives the resource; calling an operation on it returns another handle:
Code
Nothing is sent until a handle is awaited or chained onto, and each step runs at most once however many times you await it. What that costs depends on the scope:
| Ref and scope | get(ref).suspend() |
|---|---|
{ name, metro }, any scope | 1 request — the suspend. The ref says where. |
{ name }, one metro in scope | 1 request — the scope says where. |
{ name }, many metros | Locate first (one concurrent read per metro), then suspend where it lives — or throw AmbiguousRefError if several match. |
Handles also answer where they landed, and mutating steps return what that endpoint reports (suspend() resolves to { uuid, name, state, previous_state }, not a full instance):
Code
Every idiomatic method is also available in non-chained form (ukc.instances.logs({ name: "web" }, { offset: -4096 })), which is exactly shorthand for get(ref).logs(...).
Updating a resource
The API models an update as a list of { prop, op, value } triples, with value typed unknown.
That is the plumbing.
Idiomatically you write a patch object and the op is worked out for you — a value sets it, null removes it, and an omitted (or undefined) property is left alone, following JSON Merge Patch:
Code
Every property is typed, so memory_mb: "512" and tags: "prod" no longer compile.
image takes the same string shorthand as create.
When set is not what you mean — merging into a property, or removing individual members — stage the operations with edit() and send them as one request:
Code
apply() returns a handle like any other operation, so chaining continues:
Code
Both forms are one request, and both are available with a ref instead of a handle (ukc.instances.update({ name: "web" }, { memory_mb: 512 }), ukc.instances.edit({ name: "web" })).
The raw triples still work as an escape hatch: update({ name: "web" }, [{ prop: "memory_mb", op: "set", value: 512 }]).
Refs
Every operation on an existing resource takes a ref: either { name } or { uuid }, never both.
The API validates each identifier it is given, so a name sent in the uuid filter fails with Invalid uuid '<name>' — the ref makes you state which kind you hold, and only that field is sent.
Code
Idiomatic methods
| Resource | Methods |
|---|---|
ukc.instances | create, get, each, list, update, edit, delete, start, stop, suspend, wait, metrics, history, logs |
ukc.volumes | create, get, each, list, update, edit, delete, attach, detach |
ukc.services | create, get, each, list, update, edit, delete |
ukc.certificates | create, get, each, list, update, delete |
ukc.users | quotas, quotasByUuid, add |
Handles add the per-resource operations: refresh, start, stop, suspend, delete, update, edit, wait, logs, metrics, history on an instance; attach, detach, update, edit, delete on a volume; update, edit and delete on a service group.
Anything not listed — autoscale, the image registry, node information, the whole control plane — is reachable raw via ukc.api.platform.* and ukc.api.controlplane.*:
Code
See examples/ for complete programs.
Errors
Any network failure or non-2xx response throws an UnikraftCloudError:
Code
err.kind is "http", "network", "parse", or "fanout" (a multi-metro operation that partly failed, or an unusable scope — see MetroFanoutError above).
Self-hosted and staging deployments
metro (and UKC_METRO) also accepts a full http(s):// base URL, which is used verbatim instead of being expanded into https://api.<metro>.unikraft.cloud.
A trailing /v1 is dropped, since every operation path already carries it:
Code
A named endpoint is the only endpoint there is: no metro discovery is attempted and no hostnames are invented, whatever the scope says.
Point the control plane at a matching deployment with controlPlaneUrl:
Code
Runtime support
fetch is used from the global scope.
On Node.js 18+ it is built in.
For older runtimes, or to customise transport, pass your own:
Code
Debugging with a proxy (MITM)
On Node, the client honours the standard proxy environment variables so you can route traffic through a man-in-the-middle proxy such as mitmproxy, Charles, or Proxyman without any code change:
Code
HTTP_PROXY, HTTPS_PROXY, ALL_PROXY and NO_PROXY are all recognised, in upper- or lower-case.
Node's global fetch ignores these by itself, so the SDK applies them via undici's EnvHttpProxyAgent, imported lazily only when a proxy variable is set.
If undici is not installed, requests proceed unproxied and a one-time warning is logged.
Opt out per client with new UnikraftCloud({ proxyFromEnv: false }).
Proxy support is Node-only; browsers and Deno ignore these variables.
Source
The SDK source lives at github.com/unikraft-cloud/js-sdk, and the published package at npmjs.com/package/@unikraft/cloud.
Learn more
- Go SDK: the client library for the same platform API in Go.
- Metros: the regions that the SDK fans out across.
- Instances: create and manage the instances that the SDK drives.
- Services: the load-balanced networking that
ukc.servicesconfigures. - Unikraft Cloud's REST API reference, which the plumbing layer mirrors.