# Full example

One file that lists projects, starts a session, and streams a reply.

Canonical page: https://kortix.com/docs/sdk/example

This page shows the whole SDK in one file. It uses no framework and needs no
build step beyond TypeScript. The file uses every core concept in order: the
client, the platform REST surface, session readiness, live streaming, sending
a prompt, and turn classification. Each numbered step links to a deep dive.

## The complete script

```ts
import { ApiError, classifyTurn, createKortix, narrowChatEvent } from '@kortix/sdk';
import type { MessageWithParts } from '@kortix/sdk';

async function main() {
  // 1. One client, one auth seam. getToken returns your API key
  //    (kortix_pat_…) or a logged-in user's Supabase JWT — nothing else.
  const kortix = createKortix({
    backendUrl: 'https://api.kortix.com/v1',
    getToken: async () => process.env.KORTIX_API_KEY!,
  });

  // 2. Platform REST: list projects, pick one (or provision your first).
  const projects = await kortix.projects.list();
  const project = projects[0] ?? (await kortix.projects.provision({ name: 'sdk-quickstart' }));
  console.log(`using project ${project.name} (${project.project_id})`);

  // 3. Create a session — a cheap platform call. No sandbox exists yet.
  const created = await kortix.projects.createSession(project.project_id, {
    name: 'sdk full example',
  });
  const session = kortix.session(project.project_id, created.session_id);

  // 4. Ready the session. This provisions (or resumes) the real cloud
  //    sandbox. ensureReady() polls /start (each call long-polls up to 30s)
  //    until the runtime is ready or its deadline (~3 min) elapses, so a
  //    cold boot just takes longer rather than throwing. The
  //    retryUntilReady wrapper below is optional — keep it only if you want
  //    a longer total budget than the default.
  const { opencodeSessionId } = await retryUntilReady(() => session.ensureReady());

  // 5. Connect the event stream before you send, so no early events are
  //    missed. narrowChatEvent() collapses the wire events into a small
  //    typed union you can switch over.
  let resolveIdle!: () => void;
  const idle = new Promise<void>((resolve) => (resolveIdle = resolve));

  const stream = await session.stream({
    onEvent: (event) => {
      const e = narrowChatEvent(event);
      if (!e) return;
      if (e.type === 'message.part.updated') process.stdout.write('.');
      if (e.type === 'session.error') console.error('\nerror:', e.error);
      if (e.type === 'session.idle' && e.sessionID === opencodeSessionId) {
        resolveIdle(); // the turn is finished
      }
    },
  });

  // 6. Send. Per-send overrides pick the model and the agent for this
  //    prompt only (ids come from projects.modelPicker() and
  //    projects.detail().config.agents).
  await session.send('What files are in this repo?', {
    model: { providerID: 'kortix', modelID: 'claude-sonnet-4.6' },
  });

  // 7. Wait for the turn to finish — the session.idle event, not a sleep.
  await idle;
  stream.close();

  // 8. Render the transcript. classifyTurn() turns the wire part variants
  //    into one union, so a renderer can switch on part.kind and
  //    TypeScript proves no case is missed.
  const result = await session.runtime.session.messages({
    sessionID: opencodeSessionId,
  });
  for (const message of (result.data ?? []) as MessageWithParts[]) {
    for (const part of classifyTurn(message).parts) {
      if (part.kind === 'text') console.log(`\n[${message.info.role}] ${part.text}`);
    }
  }
}

/** Optional outer-budget wrapper — ensureReady() already polls internally. */
async function retryUntilReady<T>(ensure: () => Promise<T>): Promise<T> {
  const deadline = Date.now() + 300_000;
  for (;;) {
    try {
      return await ensure();
    } catch (error) {
      const provisioning = error instanceof ApiError && error.code === 'RUNTIME_UNAVAILABLE';
      if (!provisioning || Date.now() > deadline) throw error;
      await new Promise((r) => setTimeout(r, 3_000));
    }
  }
}

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

Run it with Node 18 or later, Bun, or `tsx`:

```sh
KORTIX_API_KEY=kortix_pat_... npx tsx full-example.ts
```

The first `ensureReady()` call on a fresh session provisions a real cloud sandbox, so the ready
step takes a while on the first run. Later runs resume the same sandbox and finish fast.

## What each step teaches

| Step | Concept                                             | Deep dive                          |
| ---- | ---------------------------------------------------- | ----------------------------------- |
| 1    | One client, one token, one auth seam                | [Authentication](/docs/sdk/auth)   |
| 2–3  | The platform REST surface: projects and sessions    | [Reference](/docs/sdk/reference)   |
| 4    | Session readiness, the bridge from platform to runtime | [Sessions](/docs/sdk/sessions)     |
| 5, 7 | Live SSE events, `narrowChatEvent`, `session.idle`  | [Sessions](/docs/sdk/sessions)     |
| 6    | Per-send `{ model, agent }` overrides               | [Sessions](/docs/sdk/sessions)     |
| 8    | `classifyTurn` and the exhaustive part union         | [Reference](/docs/sdk/reference)   |

## Going further from here

The same client reaches the rest of the platform through one facade.

```ts
const project = kortix.project(projectId);

// Workspace files inside the session's live sandbox
const tree = await session.files.list('/workspace');
const readme = await session.files.read('/workspace/README.md');

// Project secrets — env vars every session's agent can read at runtime
await project.secrets.upsert({ name: 'MY_API_KEY', value: 'sk-…' });

// The project's agents and skills (config files in the repo)
const { config } = await kortix.projects.detail(projectId);
console.log(
  config.agents.map((a) => a.name),
  config.skills.length,
);

// LLM gateway observability — cost, latency, per-model breakdown
const overview = await project.gateway.overview(7);
const routing = await project.gateway.routing.get();
```

The package ships runnable examples that cover each of these steps, in
`packages/sdk/examples/`. They include a minimal client, streaming, a server
wrapper, transcript rendering, and files and secrets.
