---
title: "TypeScript SDK"
description: "Call Lookio assistants from TypeScript or JavaScript, return answers and citations to your product, stream responses, and continue conversations."
canonical: "https://lookio.app/docs/developers/sdk"
updated: "2026-11-08"
---

# TypeScript SDK

`@lookio/sdk` is the official TypeScript client for the [Lookio API](/docs/developers/api). It has no runtime dependencies and runs on Node 18+, Cloudflare Workers, Deno and Bun, in ESM and CommonJS.

## Install and connect

```bash
npm install @lookio/sdk
```

```ts
import { Lookio } from "@lookio/sdk";

const lookio = new Lookio({ apiKey: process.env.LOOKIO_API_KEY });
```

Create an API key in **Developer tools**. For a product that only asks questions, choose **Ask questions** and limit the key to the assistants it needs. Keep the key on your server: in browser code, visitors could spend your credits.

## Ask an assistant

```ts
const answer = await lookio.assistants.ask("ASSISTANT_ID", {
  question: "How much does the pro plan cost?",
  mode: "flash", // or "eco", "deep"
});

console.log(answer.answer); // Markdown, citations as [1], [2]
for (const c of answer.citations) console.log(c.n, c.title, c.url);
console.log(answer.credit_cost, answer.credits_remaining);
```

Each answer costs the fixed price of its mode, also when the resources hold no answer. See [Credits and plans](/docs/get-started/credits-and-plans).

## Stream an answer

```ts
const stream = lookio.assistants.askStream("ASSISTANT_ID", { question: "Summarise our refund policy." });

for await (const text of stream.textStream()) process.stdout.write(text);

const final = await stream.finalAnswer(); // the same fields as `ask`
```

The stream is also an async iterable of typed events (`status`, `delta`, `done`, `error`), with `abort()`. The SDK never retries a stream. If the connection closes, the answer can still finish and cost credits. Check the conversation or usage before you ask again. See [Streaming answers](/docs/developers/streaming).

## Import a file and wait until it is ready

```ts
import { openAsBlob } from "node:fs";

const file = await openAsBlob("./handbook.pdf");
const resource = await lookio.resources.uploadFile(file, "handbook.pdf", { title: "Employee handbook", tags: ["hr"] });
const ready = await lookio.resources.waitUntilReady(resource.id); // throws ResourceFailedError if reading fails
```

`uploadFile` takes a `Blob`, `File`, `ArrayBuffer`, `Uint8Array` or web `ReadableStream` (with `{ contentLength }`).

## Walk every resource

```ts
for await (const resource of lookio.resources.iterate({ tags: ["hr"], status: "ready" })) {
  console.log(resource.id, resource.title);
}
```

Lists return `{ data, hasMore, nextCursor }`. `iterate()` follows the cursors for you.

## Schedule a site

`resources.createSite` and `sites.updateRules` accept `manual`, `daily`, `weekly`, `monthly`, or an interval such as `every_3_days`, `every_2_weeks` or `every_1_month`: 1 to 90 days, 1 to 52 weeks or 1 to 12 months. Recurring schedules need a paid plan. See [Websites](/docs/guides/websites#keep-it-in-sync) for the defaults.

## Keep a conversation, name your users

```ts
const first = await lookio.assistants.ask("ASSISTANT_ID", {
  question: "How much does the Pro plan cost?",
  end_user: "user_42", // your own id for the person asking
});
const followUp = await lookio.assistants.ask("ASSISTANT_ID", {
  question: "And for the annual plan?",
  conversationId: first.conversation_id!, // the history is read server-side
  end_user: "user_42",
});

// Forward your user's thumb, then read where the assistant fails.
await lookio.conversations.feedback(first.conversation_id!, first.message_id!, { rating: "down", comment: "Out of date" });
for await (const chat of lookio.logs.conversations.iterate({ rating: "down" })) console.log(chat.title);
```

`store: false` keeps nothing. See [Conversations and end users](/docs/guides/conversations-and-end-users), [Feedback on answers](/docs/guides/answer-feedback) and [Logs](/docs/guides/logs).

`end_user` is an id, not a proof of identity. Set it on your server, from the person signed in to your product. Do not trust an `end_user` or a `conversation_id` that comes from browser code.

## Use an assistant in your product

Call the SDK from your backend, such as a Node.js API route or a Cloudflare Worker. Send to the browser only the fields that your interface needs:

```ts
const answer = await lookio.assistants.ask("ASSISTANT_ID", {
  question,
  end_user: authenticatedUser.id,
});

return Response.json({
  answer: answer.answer,
  citations: answer.citations,
  conversation_id: answer.conversation_id,
});
```

For a follow-up, pass the checked conversation id to `assistants.ask`. The SDK returns data, not a chat interface. For a ready chat interface, use the [Widget](/docs/guides/widget/overview).

## Put an assistant on a website

```ts
const widget = await lookio.widgets.create({
  assistant_id: "ASSISTANT_ID",
  allowed_domains: ["docs.acme.com"],
  settings: { accent: "#0F766E", sources: "numbered_no_links" },
});
console.log(widget.snippets.bubble); // paste on your site
```

See [Widget](/docs/guides/widget/overview).

## What is built in

|                 |                                                                                                                                                                                                                                                                                             |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Idempotency** | Every `POST` except a stream sends an `Idempotency-Key`, the same on each retry. So a retried question is never charged twice. Pass `{ idempotencyKey }` to set it.                                                                                                                         |
| **Retries**     | Up to `maxRetries` (2) on 429, 408, 5xx and network errors, with backoff and `Retry-After`. Never on another 4xx. Never a `PATCH` that may have arrived.                                                                                                                                    |
| **Timeouts**    | `timeoutMs` per client or per call (default 60 seconds). Pass a `signal` to cancel.                                                                                                                                                                                                         |
| **Errors**      | Every failure is a `LookioError` with `status`, `code`, `param`, `requestId`, `actionUrl`. Subclasses: `AuthenticationError`, `PermissionError`, `NotFoundError`, `ConflictError`, `RateLimitError`, `InsufficientCreditsError`, `ConnectionError`. Branch on `code`, never on the message. |
| **Types**       | Generated from the [OpenAPI document](/docs/developers/api), in the API's own snake\_case.                                                                                                                                                                                                  |

## Methods

| Namespace                     | Methods                                                                                                                                                                                 |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `account`, `credits`, `usage` | `account.get()`, `credits.get()`, `usage.get()`                                                                                                                                         |
| `resources`                   | `list`, `iterate`, `get`, `createText`, `createUrl`, `createSite`, `uploadFile`, `update`, `delete`, `content`, `reindex`, `waitUntilReady`, `pages`, `iteratePages`                    |
| `sites`                       | `preview`, `pages`, `iteratePages`, `updateRules`, `retryFailed`                                                                                                                        |
| `tags`                        | `list`, `rename`, `delete`                                                                                                                                                              |
| `assistants`                  | `list`, `get`, `create`, `update`, `delete`, `ask`, `askStream`                                                                                                                         |
| `conversations`               | `list`, `iterate`, `get`, `rename`, `delete`, `feedback`: your own conversations (an API key's, with `end_user`)                                                                        |
| `logs`                        | `conversations.list`, `.iterate`, `.get`, `.stats`; `answers.list`, `.iterate`, `.stats`; `indexing.list`, `.iterate`, `.stats`; `activity.list`, `.iterate`: the workspace's four logs |
| `widgets`                     | `list`, `get`, `create`, `update`, `duplicate`, `setImage`, `clearImage`, `delete`, `conversations.list`, `conversations.iterate`, `conversations.get`                                  |
| `members`                     | `list`, `updatePermissions`                                                                                                                                                             |
| `feedback`                    | `send` to report a problem with Lookio                                                                                                                                                  |

`lookio.request({ method, path, body })` reaches any endpoint with the same authentication, retries and idempotency.
