> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fetchski-attract.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Zero-config from the environment, or a client with options.

One client per site. Build it once, and reuse it for every call to that site.

> [tab] From the environment
>
> ```ts theme={"dark"}
> import { createSiteClient } from '@fetchski/attract-cms-sdk';
>
> const site = createSiteClient();
> ```
>
> ```ini .env theme={"dark"}
> FETCHSKI_ATTRACT_URL=https://acme.example
> FETCHSKI_ATTRACT_API_KEY=fsk_acme_…            # unset: the public
> FETCHSKI_ATTRACT_SIGNING_SECRET=fss_…          # only when the key has one
> ```
>
> An unset `FETCHSKI_ATTRACT_URL` throws `SiteConfigurationError` naming it; a key that is set but
> empty throws too, never quietly the public.

> [tab] With options
>
> ```ts theme={"dark"}
> import { createSiteClient } from '@fetchski/attract-cms-sdk';
>
> const site = createSiteClient({
>   baseUrl: 'https://acme.example',
>   apiKey: process.env.ACME_API_KEY, // leave out for the public
>   signingSecret: process.env.ACME_API_SIGNING_SECRET // when the key signs
> });
> ```

## Options

**baseUrl** `string` *required*

The site's public origin, `https://` and the host the site answers on. A trailing `/api/v1` is fine;
any other path, a query or credentials throw. A redirect is never followed. Plain `http://` reaches
only localhost or a private service name, so a key never crosses the internet in the clear.

**apiKey** `string`

`fsk_<site>_<key id>_<secret>`, as the site showed it. Leave it out to read as the public. Given but
empty (an unset environment variable) throws: never quietly the public.

**signingSecret** `string`

`fss_…`, when the key signs. With a secret, the client signs every request it can, uploads by their
SHA-256. [Signing requests](https://docs.fetchski-attract.com/signing.md) lists which requests must be signed.

**signing** `'none' | 'writes' | 'all'`

The key's signing mode, from its **Security** tab. Name it only to have the client refuse what the
key can't send before it leaves.

**timeout** `number` (default: 15000)

Milliseconds per attempt. A route the site marks as slow carries its own (the larger wins).

**retries** `number` (default: 2)

Extra attempts for a call that failed in a way that is safe to repeat. `0` for none, at most `10`.

**onResponse** `(call) =\> void`

Called once per attempt with `{ site, method, path, status, ms }`: never a header, query or body. A
hook that throws or rejects never changes the call.

**more options**

**fetch** `typeof fetch`

A proxy's `fetch`.

**userAgent** `string` (default: Fetchski-Attract-CMS-TypeScript)

Browsers ignore it.

**slug** `string`

Checks the slug read from the key: a key filed under another site throws.

> [!WARNING]
>
> A bad option throws `SiteConfigurationError` when the client is built, saying which option and how to
> fix it. No request is made.

## Many sites, by slug

When your app talks to several sites, keep them in one directory and ask for each by the slug in its key.

```ts theme={"dark"}
import { createSiteClients } from '@fetchski/attract-cms-sdk';

const sites = createSiteClients({
  // a list, or a function that finds one: (slug) => db.site(slug)
  sites: {
    acme: { baseUrl: 'https://acme.example', apiKey: process.env.ACME_API_KEY }
  },
  timeout: 10_000
});

const site = await sites.forSite('acme'); // a fresh client each call
```

> [!NOTE]
>
> An entry's name must be its key's own slug, the `acme` in `fsk_acme_…`. Anything else throws before
> any request, naming the right one. `forSite()` with a slug no site has throws `UnknownSite`.

> [card] [Next: calling the API](https://docs.fetchski-attract.com/typescript/calling-the-api.md)
>
> Arguments, models, lists, ids and uploads.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.