> ## 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

> One site from .env, or its key kept encrypted in your database.

A single site needs only `.env`. The install writes it for you:

```bash Terminal theme={"dark"}
php artisan attract:install
```

It adds these lines:

```ini .env theme={"dark"}
FETCHSKI_ATTRACT_SITE=acme
FETCHSKI_ATTRACT_URL=https://acme.example
FETCHSKI_ATTRACT_API_KEY=fsk_acme_…
FETCHSKI_ATTRACT_SIGNING_SECRET=fss_…   # only when the key has one
```

**FETCHSKI\_ATTRACT\_SITE** `string` *required*

The site's slug: the `acme` in its key, `fsk_acme_…`. A key filed under another slug is refused,
naming the right one.

**FETCHSKI\_ATTRACT\_URL** `string` *required*

The site's public origin: `https://` and the host it answers on, with no path, query or credentials.
A redirect is never followed; plain `http://` reaches only localhost or a private service name.

**FETCHSKI\_ATTRACT\_API\_KEY** `string` *required*

`fsk_<site>_<key id>_<secret>`, as the site showed it. An empty value is refused, never taken as the
public.

**FETCHSKI\_ATTRACT\_SIGNING\_SECRET** `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.

> [card] [Or keep the key in your database](https://docs.fetchski-attract.com/laravel/key-in-database.md)
>
> The key and signing secret encrypted in a table the package ships, replaced without touching `.env`.

## The default site

**1. Attract::x() reaches the default**

The site `FETCHSKI_ATTRACT_SITE` names, or, when that is unset, the one site configured (with the
key in the database, the table's one row).

**2. Attract::site('slug') reaches any other**

Its client is made once per slug.

**3. No default, no slug: a clear error**

`No default site: set FETCHSKI_ATTRACT_SITE, or call Attract::site('<slug>').`

## More sites

> [tab] By hand
>
> List them in `config/fetchski-attract.php`, beside the env one:
>
> ```php config/fetchski-attract.php theme={"dark"}
> 'sites' => [
>     // …the env site, as published…
>     'globex' => [
>         'url' => 'https://globex.example',
>         'api_key' => env('GLOBEX_API_KEY'),
>         'api_signing_secret' => env('GLOBEX_API_SIGNING_SECRET'),
>     ],
> ],
> ```
>
> Leave `api_key` out for a public site (the env site always needs its key). `api_signing`
> (`none`, `writes` or `all`) is optional: it only has the client refuse what the key can't send.

> [tab] From the attract\_api\_keys table
>
> Keep every site's key in the package's own `attract_api_keys` table, encrypted (the table, model and
> prompts are described in [Key in the database](https://docs.fetchski-attract.com/laravel/key-in-database.md)), and have the SDK read
> them all from there.
>
> **1. Publish the migration**
>
> ```bash Terminal theme={"dark"}
> php artisan vendor:publish --tag=attract-migrations
> ```
>
> **2. Run it**
>
> ```bash Terminal theme={"dark"}
> php artisan migrate
> ```
>
> **3. Register the table's resolver**
>
> In your app's existing `app/Providers/AppServiceProvider.php`, add this binding to its
> `register()` method, beside whatever is already there:
>
> ```php app/Providers/AppServiceProvider.php theme={"dark"}
> use Fetchski\AttractCmsLaravel\Contracts\SiteResolver;
> use Fetchski\AttractCmsLaravel\EloquentSiteResolver;
>
> public function register(): void
> {
>     // …your existing bindings…
>
>     $this->app->singleton(SiteResolver::class, EloquentSiteResolver::class);
> }
> ```
>
> The SDK now finds every site's key in the table. It reads the rows through the config's
> `model`, so your own model ([Add your own columns](https://docs.fetchski-attract.com/laravel/key-in-database.md#add-your-own-columns))
> applies here too.
>
> **4. Store each site's key**
>
> Run this once per site, with that site's key:
>
> ```bash Terminal theme={"dark"}
> php artisan attract:store-key
> ```
>
> Each key is filed under its own site's slug, the `globex` in `fsk_globex_…`.
>
> **5. Call a site**
>
> Name the site on each call:
>
> ```php theme={"dark"}
> use Fetchski\AttractCmsLaravel\Facades\Attract;
>
> $posts = Attract::site('globex')->blogPosts()->listBlogs();
> ```
>
> While the table holds a single row, `Attract::blogPosts()` with no site reaches it.

> [!WARNING]
>
> Read keys in a config file, never with `env()` in app code: once your app's config is cached, `env()`
> answers `null` outside the config files.

## Checked at boot, and on demand

**At boot**, every entry must build and `default` must name a configured site. The first mistake throws
one `SiteConfigurationError` saying what to fix. (Skipped in unit tests, and with no sites.)

**On demand**, run the same check, then a call to the site: reachable, the key accepted, and the
clocks within a minute (signatures allow five). It exits 1 on any failure:

```bash Terminal theme={"dark"}
php artisan attract:check
```

Name a site to check another one:

```bash Terminal theme={"dark"}
php artisan attract:check --site=globex
```

## The package's settings

| Key | Env | Default |
| - | - | - |
| `timeout` | `FETCHSKI_ATTRACT_TIMEOUT` | 15 s per attempt |
| `connect_timeout` | `FETCHSKI_ATTRACT_CONNECT_TIMEOUT` | 5 s |
| `retries` | `FETCHSKI_ATTRACT_RETRIES` | 2 (0 to 10) |
| `user_agent` | `FETCHSKI_ATTRACT_USER_AGENT` | `Fetchski-Attract-CMS-Laravel` |
| `sites_from` | `FETCHSKI_ATTRACT_SITES_FROM` | `config` (`.env`); `database` for [the key in the database](https://docs.fetchski-attract.com/laravel/key-in-database.md) |
| `model` | none | `Fetchski\AttractCmsLaravel\Models\AttractApiKey`, the `attract_api_keys` table's model |
| `log_channel` | `FETCHSKI_ATTRACT_LOG_CHANNEL` | none |
| `module` | `FETCHSKI_ATTRACT_MODULE` | none: new Actions are scaffolded into the app ([Testing](https://docs.fetchski-attract.com/laravel/testing.md)) |
| `modules_path` | none | `modules` |
| `modules_namespace` | none | `Modules` |

**details**

A timeout is seconds; unset, empty or `0` is the default, and anything else (`-3`, `abc`) is refused
naming the setting. `timeout` caps each attempt, connecting included. The app's
`Http::globalOptions()` transport settings (`proxy`, `verify`, `cert`, `ssl_key`, `crypto_method`,
`version`, `force_ip_resolve`, `curl`) apply; its headers, redirects, errors and timeouts don't.

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


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