@koo-io/sdk
v0.2.0
Published
Koo SDK — a framework-free, typed Node client for the Koo API.
Keywords
Readme
@koo-io/sdk
A framework-free, typed Node client for the Koo API. No React, no
global state, zero runtime dependencies — one KooClient instance per credential,
generated from the same OpenAPI spec as the API itself.
npm install @koo-io/sdkRequires Node >= 20.19. Authenticate with a kc_… service-account
API token.
Quickstart
import { KooClient, KooError } from '@koo-io/sdk';
const koo = new KooClient({
baseUrl: 'https://api.koo.io',
token: process.env.KOO_TOKEN, // a kc_… service-account token
});
// Who am I? (works with a kc_ token, unlike the browser /me)
const me = await koo.whoami();
if (me.kind !== 'service_account') throw new Error('expected a service-account token');
// Navigate the resource tree: account → project → environment → service.
const svc = koo.account(me.accountId).project('web').environment('prod').service('api');
// Ship an image and watch the deployment pipeline.
const deployment = await svc.deploy({ image: 'ghcr.io/acme/api:sha' });
// status advances: queued → building → built → applied
// `applied` = the release is on the platform; the service then becomes "online" (running).
try {
await svc.get();
} catch (error) {
if (error instanceof KooError) {
console.error(`[${error.code}] ${error.message} (ref ${error.requestId})`);
}
}The scope chain
new KooClient({ baseUrl, token | getToken })
.whoami() / .me()
.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 }
.variables{ batch, resolved } · .logs() · .metrics()Every call takes a trailing { idempotencyKey?, signal? } and returns the unwrapped
response body. Every request/response shape is exported as a type.
Variables
A service's variables are written with a single merge-shaped batch — { set, unset }
applied atomically, with no read-modify-write and no full-set replace, so untouched
variables stay put. resolved returns the effective environment the service receives
(project ∪ environment ∪ service, nearest wins) with sensitive values withheld.
const vars = svc.variables;
// Merge in two values and drop one, in one atomic write.
await vars.batch({
set: [
{ name: 'LOG_LEVEL', value: 'info' },
{ name: 'API_KEY', value: process.env.API_KEY, sensitive: true },
],
unset: ['LEGACY_FLAG'],
});
// What the service actually receives (sensitive values are withheld).
const { data, dangling } = await vars.resolved();Reveal, blast-radius impact, and the project/environment scopes are deliberately kept off
the fluent client — reach for the console or the raw API for those.
Errors
Every call rejects with a KooError carrying a stable code, a human message,
optional details, the HTTP status, and a requestId (from the x-request-id
response header) to quote to support.
Retries
Idempotent verbs (GET/HEAD/PUT/DELETE) and POSTs that carry an
Idempotency-Key are retried on network errors, 429, and 5xx, with full-jitter
exponential backoff (250ms · 2^n, capped at 4s) that honours Retry-After. A 4xx
other than 429 fails fast on the first request. POSTs get an automatic, stable
Idempotency-Key so they are safe to retry (turn off with autoIdempotency: false).
The exact policy is documented in the repo's docs/arch/client-http-policy.md.
Options
new KooClient({
baseUrl: 'https://api.koo.io',
token: 'kc_…', // or getToken: () => Promise<string>
fetchImpl: customFetch, // defaults to the global fetch
retry: { maxAttempts: 3 }, // override any retry knob
autoIdempotency: true, // auto-stamp Idempotency-Key on POSTs (default)
});Publishing
Publishing @koo-io/sdk to npm is an ops step (it is the first artifact in the developer/agent
surface release, ahead of @koo-io/mcp-server and the CLI). See the consolidated runbook:
docs/runbooks/release-sdk-node.md.
License
MIT
