---
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/sdk/overview"
updated: "2026-11-01"
---

# TypeScript SDK

`@lookio/sdk` is the official TypeScript client for the [Lookio API](/docs/api/overview). 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. A key in browser code lets visitors use your workspace and spend its 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 returned by the model spends the selected mode's fixed price, even when the resources do not contain an answer. An assistant with no resources is refused before a model call, at no cost. See [Credits and plans](/docs/platform/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`) and has `abort()`. Lookio does not automatically retry a stream. If the connection closes after the request reached Lookio, the answer may still finish and spend credits. Check the conversation or usage before asking again. See [Streaming answers](/docs/api/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`. Intervals range from 1 to 90 days, 1 to 52 weeks or 1 to 12 months. Recurring schedules require a paid plan.

When you omit `schedule` on creation, Lookio uses `manual` on Free and `weekly` on paid plans. When you omit it from `sites.updateRules`, Lookio keeps the current schedule.

## 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/platform/conversations-and-end-users), [Feedback on answers](/docs/platform/answer-feedback) and [Logs](/docs/platform/logs).

`end_user` is an identifier, not proof of identity. Set it on your server from the person who has already signed in to your product. Check that a conversation belongs to that person before continuing it. Do not trust an `end_user` or `conversation_id` from browser code without checking it.

## Use an assistant in your product

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

```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 verified conversation id to `assistants.ask`. The SDK returns answers and data; it does not render a chat interface. Use the [Widget](/docs/widget/overview) if you want Lookio to provide the chat UI.

## 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/widget/overview).

## What is built in

|                 |                                                                                                                                                                                                                                                                                             |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Idempotency** | Every `POST` except a stream carries an `Idempotency-Key`, the same on every retry: a retried question is never charged twice. Pass `{ idempotencyKey }` to choose 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 landed.                                                                                                                                     |
| **Timeouts**    | `timeoutMs` per client or per call (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/api/overview), 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.
