# Node SDK

(Coming soon)

`@koo-io/sdk` is a framework-free, typed Node client for the [Koo API](/docs/developers/api). You create an instance-scoped client with `new KooClient({ token })` — authenticating with a `kc_…` [API token](/docs/developers/api-tokens) — 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](/api), so the SDK surface always matches the API. It has no React dependency and no runtime dependencies at all.

> **Note:**
>
> 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](#until-then)).

```bash
npm install @koo-io/sdk
```

Requires 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.

```ts
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.

```ts
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

```ts
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.

```ts
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 `POST`s 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](/docs/developers/api) covers authentication, errors, and pagination, and the [API reference](/api) documents every endpoint. Calling the API from your own code today takes a `kc_…` [API token](/docs/developers/api-tokens) — so everything the SDK will do is already available over plain HTTPS, and the [CLI](/docs/developers/cli) drives Koo from your terminal in the meantime.

## Related

- 
- 
-
