> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.milliseconds.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.milliseconds.ai/_mcp/server.

# SDKs

Three clients call `decision-machine-1`. They share one idea: the names you pass in come back as the types you read.

> **Note**
>
> The SDKs cover `decision-machine-1` only. Call `rakeaudio` with any HTTP client. See [Transcribe](/rakeaudio/transcribe).

**`TypeScript`**

```bash title="TypeScript"
npm i @cloudraker/milliseconds     # pnpm add, bun add, yarn add
export MS_API_KEY=test_sk-...
```

**`Python`**

```bash title="Python"
pip install cloudraker-milliseconds
export MS_API_KEY=test_sk-...
```

**`CLI`**

```bash title="CLI"
npm i -g @cloudraker/milliseconds
export MS_API_KEY=test_sk-...
dm1 check
```

Both packages are at version 0.1.0. Get a key in the [console](https://console.milliseconds.ai/signup). Both clients read `MS_API_KEY` from the environment.

## Your labels become types

You describe a decision in your own vocabulary. You pass label names, statements, scale levels, entity types or a JSON Schema. The SDK carries those names into the result type. In TypeScript, `r.label` is the union of the label names you passed, so a typo is a compile error. In Python, one annotation on your constant solves the same `TypeVar`, so `r.label` is your `Literal` and a `match` over it is exhaustive. No code generator can do this, because the names live in your call, not in the API spec. Both packages are therefore hand written.

#### [TypeScript](/sdks/typescript)

Inferred unions, tuple batching, extraction from a schema literal, zod and arktype.

#### [Python](/sdks/python)

Sync and async clients, annotated constants, TypedDict and pydantic extraction.

#### [dm1 CLI](/sdks/dm1)

Every capability from a shell. Tables, JSON, JSONL and exit codes.

## What the SDK changes

The SDK keeps the wire. It unwraps three envelopes, merges two mutually exclusive body keys into one argument, and moves the headers off the result.

| Wire                       | SDK                                             | Why                                                 |
| -------------------------- | ----------------------------------------------- | --------------------------------------------------- |
| `{ "results": [...] }`     | a plain array or list                           | one envelope less. The order is already guaranteed. |
| `{ "entities": [...] }`    | a plain array or list                           | the same                                            |
| `{ "data": {...} }`        | the object itself                               | the same                                            |
| `text` / `texts`           | one positional input                            | the mutual exclusion becomes impossible             |
| `statement` / `statements` | one positional argument                         | the same                                            |
| `question` / `questions`   | one positional argument                         | the same                                            |
| `x-*` headers              | `withUsage()` in TypeScript, `.usage` in Python | the headers stay reachable, the results stay clean  |

Every other field keeps its exact wire name, `snake_case` included: `when_true`, `input_chars`, `inference_ms`, `probability`, `scores`, `start`, `end`.

## What the SDK adds

* **Argument checks before the call.** Label, statement, scale and text limits fail locally. Nothing is sent, so nothing is billed.
* **Retries on the right failures.** `429 rate_limit_exceeded`, `502 runner_error`, `529 overloaded`, and transport failures and timeouts. Every capability is a pure function, so a retry is always safe.
* **One error type.** Catch by class in Python, switch on `code` in TypeScript.
* **An escape hatch.** `post()` sends an untouched body to any path, including a path shipped after the package.

## What the SDK never does

It never chunks a batch for you. Chunking costs money and changes failure modes, so the caller decides. The limits stay the API limits: 32 texts per call, 20,000 characters per text. See [Batching](/capabilities/batching) and [Limits](/reference/limits). The `dm1 --lines` flag chunks, and says so.

It never estimates a price. It reports `x-input-tokens`. See [Pricing](/reference/pricing).