---
title: TypeScript SDK
path: sdks/typescript
status: published
---

# TypeScript SDK — `@scailabs/scaigrid`

Official TypeScript / JavaScript client for ScaiGrid. Promise-based, with
async iterators for streaming. Same ergonomics as the Python and .NET
SDKs, idiomatic where it matters.

## Install

```bash
npm install @scailabs/scaigrid
```

Node 18+ (uses native `fetch`). Both ESM (`import`) and CJS (`require`)
bundles ship in the package.

## Quickstart

```ts
import { ScaiGrid } from "@scailabs/scaigrid";

const client = new ScaiGrid({ apiKey: "sgk_..." });

// List models
const models = await client.models.list({ modality: "chat" });
console.log(models.map((m) => m.slug));

// Streaming chat
for await (const chunk of client.inference.chatStream({
  model: "anthropic/claude-haiku-4-5",
  messages: [{ role: "user", content: "Hello!" }],
  metadata: { agentId: "..." },           // flows into accounting
})) {
  process.stdout.write(chunk.delta?.content ?? "");
}
```

## Authentication

Exactly one of three modes per client:

```ts
// 1. Static API key (most common)
const client = new ScaiGrid({ apiKey: "sgk_..." });

// 2. Pre-minted JWT
const client = new ScaiGrid({ accessToken: jwt });

// 3. OAuth client_credentials against ScaiKey — cached + auto-refresh
const client = ScaiGrid.fromClientCredentials({
  clientId: "...",
  clientSecret: "...",
  tokenUrl: "https://scaikey.scailabs.ai/api/v1/platform/oauth/token",
});
```

For full control, implement the `AuthProvider` interface and pass it as
`auth`:

```ts
import { ScaiGrid, type AuthProvider } from "@scailabs/scaigrid";

class MyAuth implements AuthProvider {
  async header() { return "Bearer …"; }
}

const client = new ScaiGrid({ auth: new MyAuth() });
```

## Resources

```ts
// Models
await client.models.list({ modality: "chat" });
await client.models.get("anthropic/claude-haiku-4-5");

// Inference — chat (Promise), chatStream (AsyncIterable), embed
await client.inference.chat({ model, messages });
for await (const chunk of client.inference.chatStream({ model, messages })) { … }
await client.inference.embed({ model: "text-embedding-3-small", input: ["…"] });

// Modules — generic proxy
await client.modules.proxy("scaimatrix", "POST", "/collections", {
  jsonBody: { name: "…", embedding_model: "…" },
});
```

## Module sub-clients

### ScaiBot

```ts
const bot = await client.scaibot.bots.create({
  name: "support",
  model: "anthropic/claude-haiku-4-5",
  displayName: "Support Bot",
});

const calls = await client.scaibot.voice.listCalls({ limit: 20 });
const transcript = await client.scaibot.voice.getTranscript(callId);
const cost = await client.scaibot.voice.getCost(callId);
```

### ScaiSpeak

```ts
const { audio } = await client.scaispeak.synthesize({
  voiceId: "voice_...",
  text: "Hello, world!",
});
// `audio` is a Uint8Array of decoded audio bytes.

const voices = await client.scaispeak.voices.list({ language: "nl" });
```

### ScaiDial

```ts
const extensions = await client.scaidial.extensions.list();
await client.scaidial.extensions.create({
  number: "1042", type: "bot", targetRef: bot.id,
});

await client.scaidial.dialplans.addRule(dialplanId, {
  actionType: "goto_extension",
  actionParams: { extension_number: "1042" },
});
```

## Streaming

`chatStream` returns an `AsyncIterable<ChatChunk>`. Iterate with
`for await`, abort via `AbortSignal`:

```ts
const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000);

try {
  for await (const chunk of client.inference.chatStream({
    model, messages,
  }, { signal: controller.signal })) {
    process.stdout.write(chunk.delta?.content ?? "");
  }
} catch (err) {
  if (err instanceof TimeoutError) {
    // Stream took too long.
  } else {
    throw err;
  }
}
```

## Errors

Every exception extends `ScaiGridError` with a `.code` that matches the
server's `ScaiGridError.code`:

```ts
import {
  NotFoundError,
  RateLimitedError,
  BudgetExceededError,
  ScaiGridError,
} from "@scailabs/scaigrid";

try {
  await client.inference.chat({ model, messages });
} catch (err) {
  if (err instanceof RateLimitedError) {
    console.warn(`retry after ${err.retryAfter}s`);
  } else if (err instanceof BudgetExceededError) {
    // Tenant or user budget cap hit.
  } else if (err instanceof ScaiGridError) {
    // Catch-all; err.code, err.httpStatus, err.requestId all populated.
  } else {
    throw err;
  }
}
```

Full taxonomy: `AuthError`, `AuthTokenInvalidError`,
`AuthTokenExpiredError`, `PermissionDeniedError`, `NotFoundError`,
`ConflictError`, `ValidationError`, `RateLimitedError`,
`BudgetExceededError`, `BackendError`, `BackendTimeoutError`,
`InternalServerError`, `TimeoutError`, `TransportError`.

## Request IDs + observability

Every method accepts `requestId`. Forwarded as `X-Request-ID`,
propagated through server logs and audit:

```ts
await client.inference.chat(
  { model, messages },
  { requestId: "req-batch-job-7" },
);
```

## Browser usage — caveats

The SDK uses native `fetch` and works in modern browsers. **Never ship
an API key to the browser** — keys grant tenant-scoped access. The
recommended browser pattern is a small server-side proxy that
authenticates the end-user, then mints a short-lived ScaiGrid JWT to
forward via `accessToken`.

For local development against a real ScaiGrid, set the `baseUrl`:

```ts
const client = new ScaiGrid({
  apiKey: "sgk_dev_...",
  baseUrl: "http://localhost:8000",
});
```

## Download

The current release is on
[`/downloads`](https://www.scailabs.ai/downloads) under **ScaiGrid →
TypeScript**, with SHA-256 verification.

## See also

- [Authentication](/docs/scaigrid/getting-started/authentication)
- [Chat completions](/docs/scaigrid/api-guides/chat-completions)
- [SDKs overview](/docs/scaigrid/sdks)
