# Clients and call options

URL: /code/sdk/guides/clients · Id: `page:code/sdk/guides/clients`

> Scope $ to another namespace, key, environment or Customer with $.with, and set timeouts, cancellation and retries per call or per client.

`$` acts for the key in `STARTUP_BUILDER_API_KEY`. `$.with(scope)` binds the same client to another namespace, key, environment or [Customer](/business/nouns/customer); options set timeouts, cancellation and retries.

## Where it runs
One package runs everywhere code calls a [Startup](/business/nouns/startup). What changes is where the key comes from.

| Runtime | Key | How |
| --- | --- | --- |
| A server or a script | `sk_test_` or `sk_live_` | Read from `STARTUP_BUILDER_API_KEY` |
| An edge runtime with no process environment | The same secret key | `$.with({ key: env.STARTUP_BUILDER_API_KEY })` |
| A browser | `pk_test_` or `pk_live_` | [React hooks](/code/sdk/react), or `$.with` with the publishable key |
| A Startup's own [Function](/business/nouns/function) | None | `ctx.$`, acting as the Function's [Worker](/business/nouns/worker) |
| CI | A 15-minute `xt_` token | `npx startup-builder login --oidc` before the script runs |

[Keys](/code/api/guides/keys) covers each kind.

## Scope a client
`$.with` takes any of `namespace`, `key`, `env` and `for`, and returns a client with the same [Nouns](/business/nouns/noun), [Modes](/business/nouns/mode) and [Events](/business/nouns/event). A scope only narrows: a client never reaches a namespace its key cannot.

`leaseline.app` tracks lease renewals for property managers. Its server acts for the signed-in manager, so each [Action](/business/nouns/action) records that Customer as `for` and is checked against what that Customer may do:

```ts
import { $ } from 'startup-builder'

// The signed-in manager's Customer, from your own session.
const customer = 'customer_harbor'
const manager = $.with({ for: customer })

const page = await manager.Invoice.list({ limit: 20 })
```

A script that works on two Startups holds one client per namespace:

```ts
const sibling = $.with({ namespace: 'renewalnotes.com', key: process.env.RENEWALNOTES_KEY })

await sibling.Offer.list({ limit: 5 })
```

`env: 'test'` pins a client to the test environment whatever its key, which keeps a test suite off live data.

## Call options
Every method takes an options object after its input. `$.with` takes the same options as defaults for every call it makes.

| Option | Default | Does |
| --- | --- | --- |
| `idempotencyKey` | Generated per call | Makes a repeat return the first Action |
| `timeout` | 30 seconds | Stops waiting for the response |
| `signal` | None | An `AbortSignal` that stops waiting when it fires |
| `maxRetries` | 3 | How often to retry what time fixes |
| `headers` | None | Extra headers, such as `traceparent` for tracing |

```ts
const controller = new AbortController()

const subscription = await $.Offer.subscribe(
  { offer: 'offer_1', quantity: 1 },
  { idempotencyKey: 'manager-4417-annual', timeout: 10_000, signal: controller.signal },
)
```

A timeout or an abort stops the wait, not the work. A write that reached the platform keeps its Action. A second call with the same `idempotencyKey` returns that Action instead of running the [Verb](/business/nouns/verb) again.

## Retries
The client retries only what time fixes: [rate-limited](/code/api/errors/rate-limited) after `Retry-After` plus jitter, any `5xx`, and a dropped connection. Each retry carries the first call's idempotency key, so a write that landed runs once.

It never retries an error the input or the record caused. [Refused](/code/api/errors/refused), [conflict](/code/api/errors/conflict) and [invalid-input](/code/api/errors/invalid-input) need a changed call, and the problem's `fix` says what to change. A call over a [Limit](/business/nouns/limit) is not an error at all: its Action waits for an [Approval](/business/nouns/approval), and a retry returns that same Action.

To turn retries off for one call, pass `maxRetries: 0`. [Errors and testing](/code/sdk/guides/errors-and-testing) lists every error and whether to retry it.