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

# Secrets and API keys

> How gtm-sdk resolves provider API keys: explicit arguments, contextvar scopes, environment variables, and Infisical-backed hydration for Modal functions.

Every adapter in `libs/` needs a provider API key, and all of them resolve keys the same
way. This page covers the resolution order, the two ways to supply credentials, and how
keys reach functions running on Modal.

## Two ways to provide keys

<CodeGroup>
  ```bash API-key flags theme={"system"}
  # No Infisical required: pass the provider key directly on the command.
  # The flag rides the call to the deployed Modal function and overrides
  # the environment inside the container for that one invocation.
  uv run gtm exa search "developer-first CRM tools" --exa-api-key <YOUR_EXA_API_KEY>
  ```

  ```bash Infisical theme={"system"}
  # One-time: copy the template and fill in your Infisical credentials.
  cp .env.example .env.local

  # Once per shell: load the bootstrap credentials.
  set -a && source .env.local && set +a

  # Keys are fetched from Infisical at function entry — no per-service flags.
  infisical run --projectId "$INFISICAL_PROJECT_ID" --token "$INFISICAL_TOKEN" --env=prod -- \
    uv run gtm exa search "developer-first CRM tools"
  ```
</CodeGroup>

* **API-key flags** work without any vault. Commands that call a provider accept an
  override flag (`--exa-api-key`, `--attio-api-key`, `--apollo-api-key`, …) that is
  forwarded to the deployed Modal function for that single invocation.
* **Infisical** is the zero-flags path: the CLI ships your Infisical bootstrap
  credentials to the Modal function, which fetches the keys it needs at entry.

## Key resolution inside `libs/`

Each adapter's client resolves its key in a fixed order, documented in the client module
(for example `libs/exa/client.py`):

1. An explicit `api_key=` argument — used by tests and one-off scripts.
2. The contextvar bound by `api_key_scope(...)` — opened by webhook handlers and Modal
   functions after fetching the key from Infisical.
3. The adapter's environment variable (for example `EXA_API_KEY`) — the fallback for
   plain-environment setups.

```python theme={"system"}
from libs.exa.client import api_key_scope
from libs.exa.search import search

with api_key_scope("<YOUR_EXA_API_KEY>"):
    response = search(payload)
```

<Note>
  The contextvar scope exists for concurrency: multiple Modal inputs can run in the same
  container, and a contextvar keeps keys from leaking between requests. Prefer
  `api_key_scope` over mutating `os.environ` in long-lived processes.
</Note>

## The `.env.local` bootstrap (Infisical)

`.env.local` holds only the credentials that get you *into* the vault — every provider
secret lives in Infisical itself, never in the file:

```bash theme={"system"}
cp .env.example .env.local   # then fill in the values
set -a && source .env.local && set +a
```

| Variable               | Purpose                                                                                                                                                                                                                             |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INFISICAL_TOKEN`      | Service token or machine-identity access token for the project.                                                                                                                                                                     |
| `INFISICAL_PROJECT_ID` | The Infisical project the token points at.                                                                                                                                                                                          |
| `INFISICAL_ENV`        | Environment slug to read against. Required at fetch time — there is no default, by design: a silent `dev` default could route production traffic at dev credentials. Match your project's slugs (for example `dev`, `stg`, `prod`). |
| `INFISICAL_HOST`       | Optional. Only for self-hosted Infisical; defaults to `https://app.infisical.com`.                                                                                                                                                  |

There is no `.infisical.json` in the repo, so the `infisical` CLI does not auto-detect
the project — always pass `--projectId`, `--token`, and `--env` explicitly, as shown in
every example on this site.

## Keys at runtime on Modal

Keyed CLI commands do not call providers from your laptop — they invoke functions
deployed on Modal (see the [CLI contract](/concepts/cli-contract)). Keys reach those
functions through a bootstrap pattern in `src/secrets_bootstrap.py`:

1. Each function is declared with `@with_secrets("<X>_API_KEY")` and
   `secrets=[bootstrap_secret()]`.
2. `bootstrap_secret()` captures your `INFISICAL_*` credentials from the deploy-time
   shell and ships them to Modal as a server-side secret — they never appear in image
   layers or build logs.
3. At function entry, the wrapper fetches each declared key from Infisical and opens the
   matching `api_key_scope` for the duration of the call.

The set of keys the bootstrap can bind is `KEY_SCOPES` in `src/secrets_bootstrap.py`:
`APOLLO_API_KEY`, `ATTIO_API_KEY`, `CALCOM_API_KEY`, `EXA_API_KEY`, `LINEAR_API_KEY`,
`PARALLEL_API_KEY`, and `SLACK_BOT_TOKEN`.

<Warning>
  Do not bind named Modal secrets with `modal.Secret.from_name(...)` — the bootstrap
  pattern replaced them. Adding a new key means: wire an `api_key_scope` contextvar in
  `libs/<x>/client.py`, add the `"<X>_API_KEY"` entry to `KEY_SCOPES`, and decorate the
  function with `@with_secrets("<X>_API_KEY")`.
</Warning>

## Environment variable reference

Provider keys, one per adapter:

| Environment variable | Adapter                                  |
| -------------------- | ---------------------------------------- |
| `APOLLO_API_KEY`     | `libs/apollo`                            |
| `ATTIO_API_KEY`      | `libs/attio`                             |
| `CALCOM_API_KEY`     | `libs/caldotcom`                         |
| `EXA_API_KEY`        | `libs/exa`                               |
| `FATHOM_API_KEY`     | `libs/fathom`                            |
| `GRANOLA_API_KEY`    | `libs/granola` (API source mode)         |
| `HARVEST_API_KEY`    | `libs/harvest`                           |
| `HOOKDECK_API_KEY`   | `gtm webhook sync` (registry generation) |
| `LINEAR_API_KEY`     | `libs/linear`                            |
| `OCTOLENS_API_KEY`   | `libs/octolens`                          |
| `PARALLEL_API_KEY`   | `libs/parallel`                          |
| `RESEND_API_KEY`     | `libs/resend`                            |
| `SANITY_API_TOKEN`   | `libs/sanity`                            |
| `SLACK_BOT_TOKEN`    | `libs/slack`                             |

Telemetry has its own set of producer- and collector-side variables — see the
[telemetry overview](/telemetry/overview).


## Related topics

- [CLI contract](/concepts/cli-contract.md)
- [Architecture](/concepts/architecture.md)
- [gtm parallel](/cli/parallel.md)
- [gtm apollo](/cli/apollo.md)
- [Quickstart: install gtm-sdk and run your first command](/quickstart.md)
