---
title: Client
description: Construction, options, resource groups, and the generated contract client underneath.
---

<CodeGroup>

```ts TypeScript
import { Client } from "@raster/sdk";

const client = new Client();
```

```python Python
from raster import Client

client = Client()
```

</CodeGroup>

With no arguments the client reads `RASTER_API_KEY` and `RASTER_BASE_URL` from the
environment. Every one is also a constructor argument.

## Options

<CodeGroup>

```ts TypeScript
const client = new Client({
  baseUrl: "https://api.raster.sh/v1",
  apiKey: process.env.MY_KEY,
  timeoutMs: 30_000,
  maxRetries: 2,
  headers: { "x-team": "research" },
  fetch: myFetch,
});
```

```python Python
client = Client(
    base_url="https://api.raster.sh/v1",
    api_key=os.environ["MY_KEY"],
    timeout=30.0,
    max_retries=2,
    headers={"x-team": "research"},
    http_client=my_httpx_client,
)
```

</CodeGroup>

| Prop | Type | Default | Description |
| - | - | - | - |
| `baseUrl?` | `string` | - | TypeScript `baseUrl` / Python `base_url`. Where the API answers, including the version prefix. Falls back to RASTER_BASE_URL. No built-in default: the constructor refuses rather than guessing a hostname to send your key to. |
| `apiKey?` | `string` | - | TypeScript `apiKey` / Python `api_key`. Falls back to RASTER_API_KEY. |
| `timeout?` | `number` | `30s` | TypeScript `timeoutMs` (milliseconds) / Python `timeout` (seconds). Per attempt. A machine that is still booting is refused, not waited on — waiting is waitForDesktop's job. |
| `maxRetries?` | `number` | `2` | TypeScript `maxRetries` / Python `max_retries`. Only applies to requests that are safe to repeat. |
| `headers?` | `Record<string, string>` | - | Sent on every request. |
| `transport?` | `fetch \| httpx.Client` | - | TypeScript `fetch` / Python `http_client`. Swap the transport, mostly for tests or a corporate proxy. |

## No default base URL

Both constructors throw when they have neither an argument nor `RASTER_BASE_URL`:

```
invalid_request: no api url: pass `baseUrl` or set RASTER_BASE_URL
```

That is deliberate. A published SDK with a plausible hostname compiled into it will eventually
send someone's API key to a host nobody chose, and the failure is silent. Refusing is louder and
cheaper.

## Resource groups

| Property    | What it holds                                              |
| ----------- | ---------------------------------------------------------- |
| `machines`  | [create, get, list](/docs/sdk/machines)                    |
| `sessions`  | list, close                                                |
| `templates` | [list, get, create, delete](/docs/sdk/snapshots#templates) |
| `snapshots` | [list, get, waitUntilReady, delete](/docs/sdk/snapshots)   |
| `plans`     | list, get                                                  |
| `billing`   | get, current, checkout, portal                             |
| `usage`     | list, series, current                                      |
| `api`       | the generated contract client                              |

<CodeGroup>

```ts TypeScript
const plans = await client.plans.list();
const account = await client.billing.get();
const summary = await client.billing.current();

const url = await client.billing.checkout({ planId: "pro", successUrl });
const portal = await client.billing.portal();

for await (const record of client.usage.list({ periodStart, periodEnd })) {
  record.meter;
  record.quantity;
}
const series = await client.usage.series({ periodStart, periodEnd, meter: "egress_bytes" });
```

```python Python
plans = client.plans.list()
account = client.billing.get()
summary = client.billing.current()

url = client.billing.checkout(plan_id="pro", success_url=success_url)
portal = client.billing.portal()

for record in client.usage.list(period_start=start, period_end=end):
    record["meter"], record["quantity"]

# not wrapped in python; the contract client is the same route.
series = client.api.usage.series(period_start=start, period_end=end, meter="egress_bytes")
```

</CodeGroup>

See [plans, usage and billing](/docs/reference/billing) for what the numbers mean.

## `client.api`

The generated contract client, built from the same oRPC contract the API implements and the
OpenAPI document is generated from. Because it is generated, it cannot drift from the API.

It is **public on purpose**. A caller who needs a route the handwritten wrappers do not cover
should reach it directly rather than fork the SDK:

<CodeGroup>

```ts TypeScript
const lease = await client.api.inputLeases.acquire({
  params: { machine_id: machine.id },
  body: { session_id, ttl_seconds: 60, force: true },
});
```

```python Python
lease = client.api.input_leases.acquire(
    machine_id=machine.id,
    body={"session_id": session_id, "ttl_seconds": 60, "force": True},
)
```

</CodeGroup>

Field names on `client.api` are the wire's own `snake_case` and are passed through untouched. The
wrapper layer above it is where they become the host language's style.

## Closing

The Python client owns an HTTP pool and is a context manager:

```python
with Client() as client:
    ...
```

`client.close()` does the same by hand. The TypeScript client holds no pool of its own and needs
no teardown — but a **machine** does, in both languages. See
[machines](/docs/sdk/machines#cleaning-up).
