---
title: SDK
description: "@kortix/sdk: one typed client for projects, sessions, connectors and the session runtime, for browsers, Node, Bun and edge runtimes."
---

`@kortix/sdk` is the TypeScript client for the Kortix API. It covers projects, sessions, connectors, secrets, accounts and the live runtime of each session. Use it to start and drive sessions from your own app, server, script or CI job.

## Install

```bash
npm install @kortix/sdk
```

| Import | Use it for | Runs in |
| --- | --- | --- |
| `@kortix/sdk` | The client and every framework-free module. | Any runtime with a global `fetch`: browsers, Node 18 or later, Bun, edge workers. |
| `@kortix/sdk/react` | Hooks, `useSession` first. Needs the optional peers `react` (18 or later) and `@tanstack/react-query` (5.75.2 or a later 5.x). | React and React Native. |
| `@kortix/sdk/server` | `createScopedKortix`, `runWithKortix`, `createKortixAuth`, `createKortixAppGuard`, `readAppViewer`, `createAppViewerKortix`. It imports `node:async_hooks`. | Node and Bun. Never bundle it for a browser. |

Import from the root. Two other subpaths are stable. `@kortix/sdk/wire-message-id` holds the wire-id clock in one module with no imports, and the root exports the same names. `@kortix/sdk/workspace-search` adds ranked file search over a session workspace, and the root does not export it. The other subpaths (`@kortix/sdk/turns`, `@kortix/sdk/projects-client` and similar) are deprecated aliases of root names. The full list is in [Entry points and stability](/docs/sdk/reference#entry-points-and-stability).

For a page with no build step, load the browser bundle. It defines `window.Kortix` with the same names as the root, for example `Kortix.createKortix`:

```html
<script src="https://cdn.jsdelivr.net/npm/@kortix/sdk@<version>/dist/kortix.global.js"></script>
```

The package also ships an ES module bundle at `dist/kortix.esm.min.js`. Replace `<version>` with a published version.

A browser page calls the Kortix API directly only from an origin the API allows. Kortix Cloud allows its own origins. From your own site, send browser calls through your server, for example the same-origin proxy of [Sign in with Kortix](/docs/sdk/auth#sign-in-with-kortix). A self-hosted instance adds origins with the API's `CORS_ALLOWED_ORIGINS` setting.

## Create the client

Call `createKortix` once per host, with the API base URL and a function that returns a token.

```ts
import { createKortix } from '@kortix/sdk';

export const kortix = createKortix({
  backendUrl: 'https://api.kortix.com/v1',
  getToken: async () => process.env.KORTIX_API_KEY ?? null,
});
```

| Option | Required | What it does |
| --- | --- | --- |
| `backendUrl` | yes | The API base URL, including `/v1`. Kortix Cloud is `https://api.kortix.com/v1`. A self-hosted instance is its API origin plus `/v1`. |
| `getToken` | yes | Returns the bearer token, or `null` when no one is signed in. The SDK calls it on every request and stores nothing. Your host owns storage and refresh. |
| `fetch` | no | A fetch implementation for tests, edge runtimes and adapters. Default: the global `fetch`. |
| `eventStreamTransport` | no | Delivers the live event stream's bytes when your `fetch` cannot stream a response body (React Native without `expo/fetch`). |
| `clientVersion` | no | Your surface and version as `<surface>/<version>`, for example `my-app/1.2.0`. The SDK sends it as `X-Kortix-Client-Version`. The API writes it to the request log only. |
| `onError` | no | An error sink for a toast or a log. Default: no-op. |
| `onAccessRemedy` | no | Receives `sso_required` or `account_mfa_required` when the API refuses a request until the person signs in through single sign-on or completes multi-factor authentication. It runs for every response of the REST client, not for the session runtime or its event stream. A browser also gets the `kortix:sso-required` and `kortix:mfa-required` window events. Default: no-op. |

The client holds one configuration for the whole process. A second `createKortix()` call with a different `backendUrl` or `getToken` re-points every earlier client and logs one console warning. A server that acts for many people at the same time uses `createScopedKortix()` from `@kortix/sdk/server`. See [Authentication](/docs/sdk/auth).

The client exposes these handles:

| Handle | What it reaches |
| --- | --- |
| `kortix.projects` | List, provision, read and update projects. |
| `kortix.project(projectId)` | Everything inside one project: `sessions`, `secrets`, `connectors`, `apps`, `triggers` and more, with the project id bound. |
| `kortix.session(projectId, sessionId)` | One session: readiness, prompts, the event stream, messages, files and previews. |
| `kortix.connector(slug)`, `kortix.connectors` | Connector calls with a token that already carries its project, such as a session token. |
| `kortix.auth`, `kortix.accounts`, `kortix.iam`, `kortix.billing`, `kortix.notifications` | Sign-in, accounts and members, access control, billing reads, and the caller's notifications. |
| `kortix.validateToken()` | Checks the token `getToken` returns now and gives `{ valid, identity?, error? }`. It never throws. |

The full map is in the [SDK reference](/docs/sdk/reference).

## Get an API key

A script, a CI job or a server uses a personal access key (`kortix_pat_`, **Settings → Personal access keys** or `kortix tokens new`). Unattended automation uses a service account key (`kortix_sa_`, **Account settings → API keys**). Every credential: [Choose a credential](/docs/sdk/auth#choose-a-credential).

Kortix shows the key once. Store it as a secret and return it from `getToken`. Never put a personal access key in a browser bundle: a browser app signs people in with [Sign in with Kortix](/docs/sdk/auth#sign-in-with-kortix) instead.

## Your first session

A session is one unit of agent work, on its own sandbox and branch. From code, it takes six steps.

1. **Pick a project**

    `kortix.projects.list()` returns the projects the token can open. `kortix.projects.provision({ name })` creates a project with a Kortix-managed repository and the starter configuration.

2. **Create the session**

    `kortix.project(projectId).sessions.create(input?)` returns the session with its `session_id`. The sandbox starts to provision now, in the background. All input fields are optional: `name`, `agent_name`, `model` (a bare managed id such as `kimi-k3`, or `provider/model` for your own provider key; see [Models](/docs/project/models#how-a-session-gets-a-model)), `initial_prompt`, `labels`, `metadata`, `base_ref` and more. See [Create a session](/docs/sdk/sessions#create-a-session).

3. **Get the session handle**

    `kortix.session(projectId, sessionId)` binds both ids. The handle has no network cost.

4. **Wait until ready**

    `session.ensureReady()` resolves when the runtime accepts work. It returns `runtimeSessionId`, the id of the root conversation in the runtime. It waits up to 180 seconds by default (`{ readyTimeoutMs }` changes it). It throws an `ApiError` with code `RUNTIME_UNAVAILABLE` when the sandbox fails or stops, or when the time runs out. `send()`, `stream()` and `messages()` call it for you.

5. **Open the stream, then send**

    Open `session.stream({ onEvent })` before `session.send(text)`, so no early event is lost. `send()` puts the prompt in the session's inbox and resolves when the prompt is stored, not when the turn ends. The turn ends on the `session.idle` event whose `sessionID` equals `runtimeSessionId`.

6. **Read the conversation**

    `session.messages()` returns `{ messages, hasMore }`. Each message is `{ info, parts }` in the Kortix transcript format, `kortix.transcript.v1`. `classifyTurn(message).parts` gives each part a `kind` you can switch on: `text`, `reasoning`, `tool`, `file` and more.

The same calls work on both session harnesses, OpenCode and pi. See [Harnesses](/docs/project/agents#harnesses-opencode-and-pi).

## A complete script

This file lists your projects, creates a session, waits for one turn, and prints the transcript.

```ts title="first-session.ts"
import { classifyTurn, createKortix, narrowChatEvent } from '@kortix/sdk';

const kortix = createKortix({
  backendUrl: 'https://api.kortix.com/v1',
  getToken: async () => process.env.KORTIX_API_KEY ?? null,
});

async function main() {
  // 1. Pick a project, or create one.
  const projects = await kortix.projects.list();
  const project = projects[0] ?? (await kortix.projects.provision({ name: 'my-project' }));

  // 2. Create a session. The sandbox starts to provision now.
  const { session_id } = await kortix.project(project.project_id).sessions.create({
    name: 'sdk first session',
  });
  const session = kortix.session(project.project_id, session_id);

  // 3. Wait for the runtime. runtimeSessionId names the root conversation.
  const { runtimeSessionId } = await session.ensureReady();

  // 4. Open the stream before you send, so no event is lost.
  let sent = false;
  let finish!: () => void;
  const turnDone = new Promise<void>((resolve) => (finish = resolve));
  const stream = await session.stream({
    onEvent: (raw) => {
      const event = narrowChatEvent(raw);
      if (!event || !sent) return;
      if (event.type === 'session.error' && (!event.sessionID || event.sessionID === runtimeSessionId)) {
        console.error('session error:', event.error);
        finish();
      }
      if (event.type === 'session.idle' && event.sessionID === runtimeSessionId) finish();
    },
  });

  // 5. Send the prompt and wait for the turn to end.
  sent = true;
  await session.send('List the files in this repository.');
  await turnDone;
  stream.close();

  // 6. Print the transcript.
  const { messages } = await session.messages();
  for (const message of messages) {
    for (const part of classifyTurn(message).parts) {
      if (part.kind === 'text' && !part.synthetic) console.log(`[${message.info.role}] ${part.text}`);
    }
  }
  console.log(`session: ${session_id}`);
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
```

Run it with `tsx` or Bun:

<CodeGroup>
```bash title="Node"
KORTIX_API_KEY=kortix_pat_... npx tsx first-session.ts
```

```bash title="Bun"
KORTIX_API_KEY=kortix_pat_... bun first-session.ts
```
</CodeGroup>

Each run creates a new session, so `ensureReady()` waits while its sandbox boots. The session also appears in the web app, with the same transcript.

- `narrowChatEvent` reduces the raw runtime events to the set a chat needs: message and part updates, session status, idle and error, questions, permissions and todos.
- `send(text, { model, agent })` overrides the model (`{ providerID, modelID }`) or the agent for one prompt. Model ids come from `kortix.projects.modelPicker(projectId)`. Agent names come from `kortix.projects.detail(projectId)`, in `config.agents`.
- To print text while it streams, to answer questions and permission requests, or to handle errors, see [Sessions from code](/docs/sdk/sessions).

## Versions and stability

- `@kortix/sdk` publishes to npm with each Kortix platform release and carries the release's version number.
- Exported names, including types, are the public contract. A renamed export keeps a `@deprecated` alias that works until the next major version.
- The deprecated subpaths resolve until the next major version.
- `@kortix/sdk/internal/*` serves the Kortix web app and is outside semver. It can change in any release.
- Changes per release: the [CHANGELOG](https://github.com/kortix-ai/suna/blob/dev/packages/sdk/CHANGELOG.md).
- Runnable examples, from one `projects.list()` call to a multi-tenant server: [packages/sdk/examples](https://github.com/kortix-ai/suna/tree/dev/packages/sdk/examples) on GitHub. The npm package does not include them.

## Next steps

<CardGroup>
  <Card icon="terminal" title="Sessions from code" href="/docs/sdk/sessions">Create, stream, answer, read files, and handle errors.</Card>
  <Card icon="atom" title="React" href="/docs/sdk/react">Run a whole session in one `useSession` hook.</Card>
  <Card icon="key" title="Authentication" href="/docs/sdk/auth">Credentials, token refresh, Sign in with Kortix, and multi-tenant servers.</Card>
  <Card icon="book-open" title="SDK reference" href="/docs/sdk/reference">Every handle, module and type.</Card>
  <Card icon="plug" title="Connectors from code" href="/docs/connect/connectors#call-connectors-from-code">Call Gmail, Slack, GitHub and MCP servers with typed results.</Card>
  <Card icon="app-window" title="Apps" href="/docs/apps#build-on-apps-from-typescript">Deploy and manage Apps from TypeScript.</Card>
</CardGroup>
