# SDKs

raw.computer has an SDK for TypeScript and one for Python. Both are generated from the API's
[OpenAPI document](https://raw.computer/v1/openapi.json), so every operation of the [HTTP API](/docs/api) is a typed
method of theirs, named by its operationId, and both behave the same way: the version they were made from on every
request, an `Idempotency-Key` on every POST, retries as `Retry-After` says, and one typed error.

> **Early access:** the packages are not on npm or PyPI yet. They will be `npm install rawcomputer` and
> `pip install rawcomputer`. Until then, use the [HTTP API](/docs/api) or the [CLI](/docs/cli), which do the same.

## TypeScript

For Node 20 or later, Bun, Deno, Workers and browsers: anywhere `fetch` runs.

```ts
import { Raw, RawError } from 'rawcomputer';

const raw = new Raw(); // RAW_API_KEY, or RAW_TOKEN with RAW_WORKSPACE

const me = await raw.getMe();
const acting = me.workspace ? `${me.workspace.code} (${me.workspace.id})` : 'no workspace';
console.log(
	`me: ${me.user.email} with a ${me.api_key.kind} key, in ${acting}, API version ${me.raw_version}`
);

for (const workspace of (await raw.listWorkspaces()).data)
	console.log(
		`workspace: ${workspace.code} · ${workspace.name} (${workspace.id}) as ${workspace.relation}`
	);

console.log(`sandboxes: ${(await raw.listSandboxes()).data.length}`);

try {
	await raw.createSandbox({ body: { class: 'std', size: '4x8' } });
} catch (e) {
	if (!(e instanceof RawError)) throw e;
	console.log(`createSandbox: RawError ${e.status} ${e.code}: ${e.message}`);
	console.log(`  next step: ${e.next_step.message} ${e.next_step.url ?? ''}`.trimEnd());
	console.log(`  docs: ${e.docs}`);
}
```

```text
me: ada@example.com with a workspace key, in default (ws_01m6zbey4kf5db2f5qbn7fwppy), API version 2026-11-08
workspace: default · Default (ws_01m6zbey4kf5db2f5qbn7fwppy) as developer
sandboxes: 0
createSandbox: RawError 503 capacity_unavailable: No cell serves std yet: raw.computer is in early access.
  next step: Read the early-access note; sandbox creates open when the first cell does. https://raw.computer/docs/limits#early-access
  docs: https://raw.computer/docs/errors#capacity_unavailable
```

A method takes one object with the parts its operation has: `body`, `path` (`raw.getSandbox({ path: { id } })`),
`query` (`raw.listSandboxes({ query: { limit: 10 } })`) and `headers`. Its answer and its body are typed from the
document; every type is exported (`import type { Sandbox } from 'rawcomputer'`).

## Python

For Python 3.11 or later. `AsyncRaw` is the same client with every method awaited.

```python
from rawcomputer import Raw, RawError
from rawcomputer.models import SandboxCreate

raw = Raw()  # RAW_API_KEY, or RAW_TOKEN with RAW_WORKSPACE

me = raw.get_me()
acting = f"{me.workspace.code} ({me.workspace.id})" if me.workspace else "no workspace"
print(f"me: {me.user.email} with a {me.api_key.kind} key, in {acting}, API version {me.raw_version}")

for workspace in raw.list_workspaces().data:
    print(f"workspace: {workspace.code} · {workspace.name} ({workspace.id}) as {workspace.relation}")

print(f"sandboxes: {len(raw.list_sandboxes().data)}")

try:
    raw.create_sandbox(body=SandboxCreate(class_="std", size="4x8"))
except RawError as e:
    print(f"create_sandbox: RawError {e.status} {e.code}: {e.message}")
    print(f"  next step: {e.next_step.message} {e.next_step.url or ''}".rstrip())
    print(f"  docs: {e.docs}")
```

```text
me: ada@example.com with a personal key, in default (ws_01m6zbey4kf5db2f5qbn7fwppy), API version 2026-11-08
workspace: default · Default (ws_01m6zbey4kf5db2f5qbn7fwppy) as admin
sandboxes: 0
create_sandbox: RawError 503 capacity_unavailable: No cell serves std yet: raw.computer is in early access.
  next step: Read the early-access note; sandbox creates open when the first cell does. https://raw.computer/docs/limits#early-access
  docs: https://raw.computer/docs/errors#capacity_unavailable
```

Methods are the operationIds in snake_case (`get_me`, `list_workspaces`, `create_sandbox`). A path parameter comes
first (`raw.get_sandbox("sbx_…")`), everything else by keyword (`body=`, `limit=`, `raw_workspace=`). Request and answer
classes are in `rawcomputer.models`; a field the API may leave out is `UNSET`.

## Credentials

| What                                            | TypeScript                              | Python                         |
| ----------------------------------------------- | --------------------------------------- | ------------------------------ |
| A workspace API key (`rc_live_…`)               | `new Raw({ apiKey })`                   | `Raw(api_key)`                 |
| A personal token (`rc_pat_…`) and its workspace | `new Raw({ apiKey: token, workspace })` | `Raw(token, workspace="ws_…")` |
| Another origin                                  | `new Raw({ baseUrl })`                  | `Raw(base_url=…)`              |

Left out, they are read from the environment as the [CLI](/docs/cli#sign-in) reads it: `RAW_TOKEN`, else `RAW_API_KEY`;
`RAW_WORKSPACE`; `RAW_API_URL`. Make a key in the console under **API keys**; a personal token is what `raw login`
keeps. A token acts as its person in the workspace it names (sent as `Raw-Workspace`) and in none without it; one call
can name another (`headers: { 'Raw-Workspace': … }` in TypeScript, `raw_workspace=` in Python). A credential of
neither kind is refused before any request is sent.

## Versions

Every request names the API version the SDK was made from in `Raw-Version` (`RAW_VERSION`, today `2026-11-08`), so
an answer has the shape the SDK reads whatever version the key is pinned to. A value the API adds later (a new state,
a new error code) is read as it came, never refused.

## Idempotency and retries

- Every POST carries an `Idempotency-Key`, made by the SDK unless the call names its own, and each retry sends the
  same one: a POST that reached the API is answered as the first time and never done twice
  ([Idempotency](/docs/api#idempotency)).
- 429 and a 5xx with `Retry-After` are sent again after it, when the wait is at most `maxRetryWait` /
  `max_retry_wait` (60 seconds); a longer wait is yours to choose, and the error's `retry_at` says when.
- 500, 502 and 504 without `Retry-After`, and a request that got no answer, are sent again after 0.5, 1, 2… seconds,
  when they are safe to repeat: a GET, or a POST with its key.
- At most `maxRetries` / `max_retries` (3) times. Anything else is yours at once: a 4xx, and a
  [503 `capacity_unavailable`](/docs/errors#capacity_unavailable), which no wait mends.

## Errors

Every answer that is not a success throws (TypeScript) or raises (Python) a `RawError`:

| Field       | Holds                                                                                          |
| ----------- | ---------------------------------------------------------------------------------------------- |
| `status`    | the HTTP status                                                                                |
| `code`      | the stable code to branch on: one of [Errors](/docs/errors)                                    |
| `message`   | what happened, in one sentence                                                                 |
| `next_step` | what to do: `message`, and where one helps, `url`, `api` (`"POST /v1/api_keys"`) and `cli`     |
| `docs`      | the code on the Errors page                                                                    |
| `retry_at`  | when a retry is expected to succeed, on `capacity_full` and `rate_limited`; else null / `None` |

A request that got no answer at all, after its retries, throws what `fetch` or httpx2 threw.
