---
title: TypeScript SDK
description: "TypeScript SDK: the ten functions as typed async calls in Node.js, with not sure thrown as UnsureError."
goal: A Node.js developer installs the SDK, gets a first answer from decide, learns how not sure and failure arrive, and finds the TypeScript call for each function.
reader: A TypeScript or JavaScript developer who wants answers about text inside a Node.js program
kind: reference
verified: 2026-11-03
---

# TypeScript SDK

The TypeScript SDK gives your Node.js code the ten functions as methods on a client; awaiting a call gives the answer itself, such as `true` or `false` from decide.

```ts
import { ThinkThen } from "thinkthen";

const tt = new ThinkThen();
try {
  // await throws UnsureError when not sure, RecordError on failure.
  const isRefund = await tt.decide(
    "Does the customer request a refund?",
    "Please refund my order.",
  );
  console.log(isRefund);
} finally {
  await tt.close();
}
```

Output

```text
true
```


Install it (Node.js 22.12 or later, in a project with `"type": "module"`):

```sh
npm install thinkthen@0.2.0
```

The client uses the key you set in the [Quickstart](/quickstart/). Close it with `await tt.close()`, or open it with `await using`.

## When decide is not sure

Give decide a low and a high bar, and a text that falls between them is not sure. Awaiting a not-sure answer throws `UnsureError`, so it can't pass for `false`. This message lands between the bars:

```ts
import { ThinkThen, UnsureError } from "thinkthen";

const tt = new ThinkThen();
try {
  const isRefund = await tt.decide(
    "Does the customer request a refund?",
    "I was charged twice for one invoice.",
    { threshold: { low: 0.2, high: 0.8 } },
  );
  console.log(isRefund ? "Send to refunds." : "Leave in the inbox.");
} catch (error) {
  if (!(error instanceof UnsureError)) throw error;
  console.log("Not sure. Ask a person.");
} finally {
  await tt.close();
}
```

Output

```text
Not sure. Ask a person.
```


Send not sure to a person. To read the answer without the throw, call `.row()` on the call, as in `await tt.decide(...).row()`. The result's `status` is `"answered"`, `"unsure"` or `"failed"`, and TypeScript lets you read `answer` only once `status` is `"answered"`. The error's `probability` and `row` hold the details.

## When a call fails

Awaiting a call that failed throws `RecordError`. Blank text fails before anything is sent:

```ts
import { RecordError, ThinkThen } from "thinkthen";

const tt = new ThinkThen();
try {
  await tt.decide("Does the customer request a refund?", "");
} catch (error) {
  if (!(error instanceof RecordError)) throw error;
  console.log(error.code);
} finally {
  await tt.close();
}
```

Output

```text
record.empty
```


The error's `code` names the problem, and `row` holds the failed result. When the service fails for a message, such as when it is down, that message's result fails the same way. A problem that stops the whole call throws instead: `UsageError` for a wrong argument, `BackendError` for the service, and `CancelledError` when an `AbortSignal` passed as `signal` fires. [Troubleshooting](/reference/troubleshooting/) lists each code and its fix.

## The ten functions

Each example asks about the same support-inbox messages as the function pages.

### decide

<details class="example-disclosure">
<summary>decide: yes, no or not sure</summary>

```ts
import { ThinkThen } from "thinkthen";

const tt = new ThinkThen();
try {
  // await throws UnsureError when not sure, RecordError on failure.
  const isRefund = await tt.decide(
    "Does the customer request a refund?",
    "Please refund my order.",
  );
  console.log(isRefund);
} finally {
  await tt.close();
}
```

Output

```text
true
```


The message asks for a refund, so decide says `true`. `threshold: 0.8` moves the bar, and `threshold: { low: 0.2, high: 0.8 }` sets a low and a high bar, as above. `decideStream` judges many messages in one call. Read it with `for await`, and check each result's `status`. `field: "/text"` picks the field to judge in each message:

```ts
import { ThinkThen } from "thinkthen";

const messages = [
  { id: "a1", text: "Please refund my order." },
  { id: "a2", text: "Where is my parcel?" },
  { id: "a3", text: "I was charged twice for one invoice." },
];

const question = "Does the customer request a refund?";
const client = new ThinkThen();
try {
  // One stream for every message, so the engine can batch them.
  const refunds = client.decideStream(question, messages, {
    field: "/text",
    threshold: { low: 0.3, high: 0.7 },
  });
  for await (const refund of refunds) {
    if (refund.status === "answered") {
      const queue = refund.answer ? "refund queue" : "inbox";
      console.log(refund.id, queue, refund.probability);
    } else if (refund.status === "unsure") {
      console.log(refund.id, "a person reads it", refund.probability);
    } else {
      console.error(refund.id, refund.error.code, refund.error.message);
    }
  }
} finally {
  await client.close();
}
```

Output

```text
a1 refund queue 0.99
a2 inbox 0.03
a3 refund queue 0.7
```


[decide](/functions/decide/) shows every answer.

</details>

### choose

<details class="example-disclosure">
<summary>choose: one answer from your list</summary>

```ts
import { ThinkThen } from "thinkthen";

const tt = new ThinkThen();
try {
  // await throws UnsureError when not sure, RecordError on failure.
  const topic = await tt.choose(
    "What is the message about?",
    "Please refund my order.",
    { choices: ["billing", "shipping", "other"] },
  );
  console.log(topic);
} finally {
  await tt.close();
}
```

Output

```text
billing
```


A refund request is about billing. The answer's type is the union of your choices. With `threshold`, choose is not sure when no choice reaches the bar, and awaiting it throws `UnsureError`. [choose](/functions/choose/) shows every answer.

</details>

### filter

<details class="example-disclosure">
<summary>filter: keep the messages that answer yes</summary>

```ts
import { ThinkThen } from "thinkthen";

const messages = [
  "Please refund my order.",
  "Where is my parcel?",
  "I was charged twice for one invoice.",
];
const tt = new ThinkThen();
try {
  // Only the messages answered yes. Not sure is left out.
  const refundRequests = await tt.filter(
    "Does the customer request a refund?",
    messages,
  ).toArray();
  for (const refundRequest of refundRequests) {
    console.log(refundRequest);
  }
} finally {
  await tt.close();
}
```

Output

```text
Please refund my order.
I was charged twice for one invoice.
```


filter yields your own messages that answer yes: the refund request and the double charge. Not-sure messages are left out. If a message fails, filter first yields every kept message, then throws one `RecordError` whose `rows` lists each failed result. `filterStream` yields every message instead, as `{ item, row, kept }`. [filter](/functions/filter/) shows every answer.

</details>

### tag

<details class="example-disclosure">
<summary>tag: every label that fits</summary>

```ts
import { ThinkThen } from "thinkthen";

const tt = new ThinkThen();
try {
  // await throws RecordError on failure.
  const topics = await tt.tag(
    "Which topics appear?",
    "I was charged twice and my parcel is late again.",
    { labels: ["billing", "shipping"] },
  );
  console.log(topics);
} finally {
  await tt.close();
}
```

Output

```text
[ 'billing', 'shipping' ]
```


The message names a double charge and a late parcel, so both labels fit. The array is empty when none fits. [tag](/functions/tag/) shows every answer.

</details>

### score

<details class="example-disclosure">
<summary>score: a number from 0 to 1</summary>

```ts
import { ThinkThen } from "thinkthen";

const tt = new ThinkThen();
try {
  // await throws RecordError on failure.
  const urgency = await tt.score(
    "How urgent is the request?",
    "Please refund my order.",
    { levels: ["routine", "urgent"] },
  );
  console.log(urgency);
} finally {
  await tt.close();
}
```

Output

```text
0.05
```


A refund request is routine, so it scores near 0. Levels run lowest first. [score](/functions/score/) shows every answer.

</details>

### rank

<details class="example-disclosure">
<summary>rank: put messages in order</summary>

```ts
import { ThinkThen, answer } from "thinkthen";

const messages = [
  "Please refund my order.",
  "Where is my parcel?",
  "I was charged twice for one invoice.",
];
const tt = new ThinkThen();
try {
  // Most urgent first. answer() gives the rank, from 1.
  const byUrgency = tt.rankStream(
    "How urgent is the request?",
    messages,
    { levels: ["routine", "urgent"] },
  );
  for await (const ranked of byUrgency) {
    const message = messages[ranked.position - 1];
    console.log(answer(ranked), message);
  }
} finally {
  await tt.close();
}
```

Output

```text
1 I was charged twice for one invoice.
2 Where is my parcel?
3 Please refund my order.
```


The double charge is the most urgent, so it ranks 1. rank works only as a stream, `rankStream`. `answer(ranked)` gives the rank and throws `RecordError` for a failed message. `ranked.position` is the message's place in your list, counted from 1. `top: 2` ranks only the first two. [rank](/functions/rank/) shows every answer.

</details>

### find

<details class="example-disclosure">
<summary>find: the line that answers</summary>

```ts
import { ThinkThen } from "thinkthen";

const message =
  "Morgan works for Example Group.\nPlease refund my order.";
const tt = new ThinkThen();
try {
  // The line that answers, or null when none does.
  const action = await tt.find(
    "Which line states the requested action?",
    message,
  );
  console.log(action ? action.text : "no line answers");
} finally {
  await tt.close();
}
```

Output

```text
Please refund my order.
```


The second line holds the request. The answer is `null` when no line answers. `start` and `end` count UTF-16 code units, so `message.slice(action.start, action.end)` gives the same text. [find](/functions/find/) shows every answer.

</details>

### recognize

<details class="example-disclosure">
<summary>recognize: names and their kinds</summary>

```ts
import { ThinkThen } from "thinkthen";

const tt = new ThinkThen();
try {
  // await throws RecordError on failure.
  const names = await tt.recognize(
    ["person", "organization"],
    "Morgan works for Example Group.",
  );
  for (const name of names) console.log(name.kind, name.text);
} finally {
  await tt.close();
}
```

Output

```text
person Morgan
organization Example Group
```


Morgan is a person and Example Group an organization. The kinds come first, then the text. Each name also has `start`, `end` and `probability`. [recognize](/functions/recognize/) shows every answer.

</details>

### relate

<details class="example-disclosure">
<summary>relate: how names connect</summary>

```ts
import { ThinkThen } from "thinkthen";

const message =
  "Morgan works for Example Group.\nPlease refund my order.";
const rules = [
  {
    name: "works_for",
    source: "person",
    target: "organization",
  },
] as const;
const tt = new ThinkThen();
try {
  const relations = await tt.relate(rules, message);
  for (const { source, relation, target } of relations) {
    console.log(source.text, relation, target.text);
  }
} finally {
  await tt.close();
}
```

Output

```text
Morgan works_for Example Group
```


Morgan works for Example Group, so relate links the two by the rule. relate finds the names itself, so it needs no recognize call first. Each relation also has `probability`. [relate](/functions/relate/) shows every answer.

</details>

### annotate

<details class="example-disclosure">
<summary>annotate: several questions at once</summary>

```ts
import { ThinkThen, answer, defineToolbox } from "thinkthen";

const messages = [
  "Please refund my order.",
  "Where is my parcel?",
  "I was charged twice for one invoice.",
];
const questions = defineToolbox({
  refund: {
    function: "decide",
    question: "Does the customer request a refund?",
  },
  topic: {
    function: "choose",
    question: "What is the message about?",
    choices: ["billing", "shipping", "other"],
  },
  urgency: {
    function: "score",
    question: "How urgent is the request?",
    levels: ["routine", "urgent"],
  },
});
const tt = new ThinkThen();
try {
  const forms = tt.annotateStream(questions, messages);
  for await (const form of forms) {
    // answer() throws UnsureError or RecordError.
    const triage = answer(form);
    const isRefund = answer(triage.refund);
    const topic = answer(triage.topic);
    const urgency = answer(triage.urgency);
    console.log(isRefund, topic, urgency);
  }
} finally {
  await tt.close();
}
```

Output

```text
true billing 0.05
false shipping 0.14
true billing 0.45
```


Each message gets all three answers: refund or not, the topic, and its urgency. `answer()` on each entry follows the rules of its function, so a not-sure refund throws `UnsureError`. `tt.annotate(questions, message)` answers one message. [annotate](/functions/annotate/) shows every answer.

</details>

## The full API

Each call is also exported on its own, such as `decide(...)`. It opens and closes a client for that call, or uses one you set with `setDefaultClient`. The [TypeScript declarations](https://github.com/botassembly/thinkthen/blob/v0.2.0/sdk/typescript/index.d.ts) list every name.
