# JavaScript SDK

> The launchables package for Node: install, ask, handle errors.

Section: Resources. Web page: https://www.launchables.ai/docs/sdk-javascript

The `launchables` package is the JavaScript and TypeScript client for the Decision API. It is ESM, includes TypeScript types, has no runtime dependencies and runs on Node 18 or later (it uses the global `fetch`). It calls the Myles-G models over HTTP; it does not run the CPU models (see [Model types](https://www.launchables.ai/docs/model-types)).

```bash
npm install launchables
```

Keep your API key on a server. The client refuses to start in a browser, because a key in a web page or app is public.

## Authentication

The client reads your key from the `LAUNCHABLES_API_TOKEN` environment variable, or you can pass it as `apiKey`. Get a key in the [console](https://console.launchables.ai).

```bash
export LAUNCHABLES_API_TOKEN=launchables_...
```

## Ask a question

```
import { Launchables, noul } from "launchables";

const client = new Launchables(); // reads LAUNCHABLES_API_TOKEN

const resp = await client.decide(
  "Customer bought the item 12 days ago and has no receipt. Policy: refunds within 30 days with proof of purchase.",
  { refund: noul("Is a refund permitted under the policy?") },
);

const answer = resp.answers.refund;
console.log(answer.probability);                                    // probability of yes, e.g. 0.04
console.log(answer.decide({ thresholdYes: 0.9, thresholdNo: 0.1 })); // "yes", "no" or "person"
console.log(resp.usage.input_tokens, resp.model);
```

`decide(state, questions, model?)` returns `{ model, answers, usage, raw }`. The state can be a string or an object. Answers are typed from the questions you ask, so `resp.answers.refund` above is a yes-or-no answer.

## Question types

```
import { noul, choice, score } from "launchables";

const resp = await client.decide(state, {
  refund: noul("Is a refund permitted?", { true: "Policy clearly allows it", false: "Policy clearly forbids it" }),
  topic: choice("What is this about?", ["billing", "returns", "other"]), // or { label: description }
  urgency: score("How urgent is it?", ["not urgent", "low", "medium", "high"]), // lowest level first, numbered from 0
});

resp.answers.topic.choice;        // "returns"
resp.answers.topic.probabilities; // { billing: 0.06, returns: 0.89, ... }
resp.answers.urgency.level;       // 2
resp.answers.urgency.score;       // 1.92
```

The helpers check your questions before anything is sent and throw `ValidationError` if a request would be refused: at most 16 questions a request, at most 26 options a choice, and instructions that are not empty. See [Question types](https://www.launchables.ai/docs/question-types) for what each type returns.

## Images

From version 0.4.0, the third argument of `decide` can be options, `{ model, assets, imageBudget }`, as well as a model name. Pass images in `assets` under a reference and write the reference where the model should see the image (Myles-G 12B; for Myles-G 31B, also pass `model: "myles-g-31b"` and construct the client with `baseURL` set to `https://gw.launchables.ai`):

```
import { Launchables, imageFromPath, noul } from "launchables";

const resp = await client.decide(
  { receipt: "receipt_photo" },
  { total_matches: noul("Does the total printed on receipt_photo equal 23.40?") },
  { assets: { receipt_photo: await imageFromPath("receipt.jpg") }, imageBudget: 140 }, // budget: 70, 140 or 280
);
```

`imageFromPath` and `imageFromBytes` detect PNG or JPEG from the bytes and throw `ValidationError` for anything else or anything over 16 MiB; `decide` throws it for an `imageBudget` other than 70, 140 or 280. The server's image errors arrive as `InvalidRequestError` with a `type` such as `image_dimensions`. See [Images](https://www.launchables.ai/docs/api/images) for references, limits and billing.

## Other calls

```
await client.models(); // [{ id: "myles-g-12b", object: "model", owned_by: "launchables" }]
await client.health(); // { status: "ok" }
```

`models()` and `health()` need no key.

## Options

| Option | What it does |
|---|---|
| `apiKey` | Your API key. Defaults to `LAUNCHABLES_API_TOKEN`. |
| `baseURL` | Set it for an endpoint you host yourself. Defaults to the hosted API. |
| `timeout` | Milliseconds allowed for each attempt. Default 30000. |
| `maxRetries` | Retries after the first attempt, for 429, 502, 503 and connection errors. Default 3. |

```
const client = new Launchables({ apiKey: "launchables_...", timeout: 10_000, maxRetries: 5 });
```

Retries wait 0.5, 1 and 2 seconds, or as long as a `Retry-After` header asks, up to 60 seconds. Other errors are thrown straight away.

## Errors

Every error extends `LaunchablesError` and carries `status`, `serverMessage`, `body` and `type`, the server's error type such as `"out_of_credit"`.

| Error | When |
|---|---|
| `AuthenticationError` | 401, or no key given or found |
| `OutOfCreditError` | 402: the organisation has run out of credit (`type` `"out_of_credit"`), or the key's workspace has reached its monthly budget (`"budget_exceeded"`) |
| `PermissionDeniedError` | 403: the key's workspace may not use this model (`"model_not_allowed"`) |
| `RateLimitError` | 429, with `retryAfter` in seconds when the server sends it |
| `InvalidRequestError` | 400, 411, 413, 422 and other 4xx; `ValidationError` is the check made before sending |
| `APIError` | 5xx |
| `APIConnectionError` | The endpoint could not be reached, including timeouts |

```
import { OutOfCreditError, RateLimitError, InvalidRequestError, LaunchablesError } from "launchables";

try {
  await client.decide(state, questions);
} catch (e) {
  if (e instanceof OutOfCreditError) console.log(e.type === "budget_exceeded" ? "workspace budget reached" : "top up credit");
  else if (e instanceof RateLimitError) console.log("slow down", e.retryAfter);
  else if (e instanceof InvalidRequestError) console.log(e.status, e.serverMessage);
  else if (e instanceof LaunchablesError) console.log("failed:", e.message);
  else throw e;
}
```

`OutOfCreditError` and `PermissionDeniedError` were added in version 0.1.1, the current release. On 0.1.0, a 402 or 403 is an `InvalidRequestError`; check `status`, or update with `npm install launchables@latest`.

## Python

A Python package is coming. Until then, call the API directly; see [Code samples](https://www.launchables.ai/docs/samples).
