Node SDK
Coming soon@koo-io/sdk is a framework-free, typed Node client for the Koo API. You create an instance-scoped client with new KooClient({ token }) — authenticating with a kc_… API token — and navigate a typed scope chain from account down to a single service. It ships ESM and CJS builds with full TypeScript types, generated from the same OpenAPI spec as the API reference, so the SDK surface always matches the API. It has no React dependency and no runtime dependencies at all.
The SDK is code-complete and verified in the Koo monorepo, but the @koo-io/sdk package has not been published to npm yet — the install command below will work once it is. Until then, everything the SDK does is available over plain HTTPS with a kc_… token (see Until then).
npm install @koo-io/sdkRequires Node 20.19 or newer.
Authenticate
The SDK authenticates with a kc_… service-account token — read it from the environment, never hard-code it. Use whoami() to confirm who you're acting as; unlike the browser's /me, it works with a service-account token and tells you the single account the token is confined to.
import { KooClient } from '@koo-io/sdk';
const koo = new KooClient({ baseUrl: 'https://api.koo.io', token: process.env.KOO_TOKEN, // a kc_… token (or pass getToken: () => Promise<string>)});
const me = await koo.whoami();if (me.kind !== 'service_account') throw new Error('Use a kc_ service-account token.');console.log(`Acting as ${me.name} (${me.role}) on account ${me.accountId}`);The scope chain
The client mirrors the API's shape: an account holds projects, a project holds environments, and an environment holds services. Descend with .account(id).project(id).environment(id).service(name); each hop returns a typed scope.
const account = koo.account(me.accountId);
const project = await account.projects.create({ name: 'web' });const environment = await account.project(project.id).environments.create({ name: 'prod' });const service = await account .project(project.id) .environment(environment.id) .services.create({ name: 'api', type: 'web', cpu: 250, memory: 256, exposed: true, source: { type: 'image', image: { ref: 'ghcr.io/acme/api:latest' } }, });Every call takes a trailing { idempotencyKey?, signal? } and returns the unwrapped response body. The full surface:
koo.whoami() / koo.me()koo.account(accountId) .get() · .tokens{ list, create, revoke } · .projects{ list, create } .project(projectId) .get() · .environments{ list, create } .environment(environmentId) .get() · .services{ list, create } .service(name) .get() · .deploy() · .rollback() · .deployFromArchive() .deployments{ list, buildLogs } · .uploads{ create } .logs() · .metrics()Deploy and watch it roll out
const svc = koo.account(me.accountId).project(project.id).environment(environment.id).service('api');
const deployment = await svc.deploy({ image: 'ghcr.io/acme/api:sha' });// The deployment status advances through a fixed pipeline:// queued → building → built → applied (image deploys skip the build phases)Poll svc.deployments.list() until your deployment's status is applied — that means the release is on the platform. It is not a health verdict: read svc.get() and watch status.health reach online (the service is running).
Errors
Every call rejects with a KooError carrying a stable code, a human message, optional details, the HTTP status, and a requestId (from the response's x-request-id header) to quote to support.
import { KooError } from '@koo-io/sdk';
try { await svc.deploy({ image: 'ghcr.io/acme/api:sha' });} catch (error) { if (error instanceof KooError) { console.error(`[${error.code}] ${error.message} — reference ${error.requestId}`); }}Retries and idempotency
The SDK retries transient failures for you. Idempotent verbs (GET/HEAD/PUT/DELETE) and any POST carrying an Idempotency-Key are retried on network errors, 429, and 5xx, with full-jitter exponential backoff that honours Retry-After. A 4xx other than 429 — including 401/403 — fails fast on the first request, with no loop.
By default the SDK stamps a stable Idempotency-Key on POSTs so they are safe to retry (the server dedups the write). Pass your own with { idempotencyKey }, or disable auto-stamping with autoIdempotency: false.
Until then
The REST API the SDK wraps is already live and documented: the API overview covers authentication, errors, and pagination, and the API reference documents every endpoint. Calling the API from your own code today takes a kc_… API token — so everything the SDK will do is already available over plain HTTPS, and the CLI drives Koo from your terminal in the meantime.
Related
- API tokens — mint the
kc_…token the SDK authenticates with. - CLI — deploy from your terminal with
koo up. - API overview — the conventions the SDK is generated from.