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

> ## Agent Instructions
> Unmute compiles to exactly three targets. Pipecat and LiveKit are code targets: compile writes a Python project you run. SLNG is a hosted target: compile writes a deployment body and SLNG runs the agent, so it has no `unmute dev`. Those three are the only values `provider` accepts in `targets.yaml`. Deepgram and ElevenLabs appear in these docs as model vendors, which is not the same thing as a target, and `slng` is both.
> The Go structs in `internal/spec` and `internal/ir` are the schema truth. Check a field against them, or run `unmute validate`, rather than against what you remember.

# Credentials

> Give the agent an API key without putting one in your package.

Your agent needs keys: one for the model provider, usually one more for each
API a tool calls.

A package never holds the value. It holds the **name** of an environment
variable, and the value arrives at run time from your `.env` file locally, or
your platform's secret store once you deploy.

That means `agent.yaml` is safe to commit, and the same package works on your
machine and in production without editing.

On this page:

* [Three steps](#three-steps) - write the value, declare the name, point at it
* [Where a name goes](#where-a-name-goes) - every key that takes one
* [A secret is not a variable](#a-secret-is-not-a-variable) - two things that look alike
* [What you do not declare](#what-you-do-not-declare) - the names the target owns

## Three steps

<Steps>
  <Step title="Put the value in .env">
    Next to `agent.yaml`, in a file git already ignores:

    ```sh .env theme={null}
    OPENAI_API_KEY=sk-...
    SALON_API_TOKEN=...
    ```

    `unmute dev` reads `.env` and `.env.local` from both the current directory
    and the package directory. Neither is ever generated with values, and
    neither is committed.
  </Step>

  <Step title="Declare the name in secrets:">
    ```yaml agent.yaml theme={null}
    secrets:
      - OPENAI_API_KEY
      - SALON_API_TOKEN
    ```

    A list of names, each one UPPER\_SNAKE. This is the package's inventory of
    what the generated project reads. A lower case or punctuated entry is
    refused, because it is a typo that would otherwise fail at call time.
  </Step>

  <Step title="Point at the name from where it is used">
    Never at the value. Each <Tooltip tip="A place where your agent reaches out to something else: a tool's URL, an API key, a phone account. Each one has its own field that names an environment variable.">seam</Tooltip> has its own `*_env` field:

    ```yaml tools/reschedule_appointment.yaml theme={null}
    webhook:
      url_env: SALON_API_URL
      path: /customers/{{customer_id}}/appointments
      auth:
        type: bearer
        token_env: SALON_API_TOKEN
    ```

    A Python handler reads its own with `os.environ["SALON_API_TOKEN"]`.
  </Step>
</Steps>

Compile, and `build/<target>/.env.example` lists exactly the values you have to
supply for that build, ready to copy to `.env`.

### Every key a secrets block takes

One key, at the top level of `agent.yaml`.

<ParamField path="secrets" type="list of UPPER_SNAKE names">
  The environment variables the generated project reads. Each entry is a
  capital letter, then capitals, digits and underscores; a lower case or
  punctuated entry is refused, and so is the same name twice. Left out, the
  compiler still infers the names it can see and warns that the inventory is
  incomplete.
</ParamField>

## Where a name goes

### Every key a seam takes

Each one holds the name of an environment variable, never a value.

<ParamField path="webhook.url_env" type="UPPER_SNAKE name">
  In `tools/<name>.yaml`. The base URL of an authenticated API. A webhook tool
  needs this or `base_url`, and the code targets read this one.
</ParamField>

<ParamField path="mcp.url_env" type="UPPER_SNAKE name">
  In `tools/<name>.yaml`. The MCP server's address. The code targets dial the
  server themselves and need it; an `slng` package leaves it out.
</ParamField>

<ParamField path="auth.token_env" type="UPPER_SNAKE name">
  Under `auth:` in a `webhook:` or `mcp:` block. The bearer token or API key,
  and required once `auth:` is written at all.
</ParamField>

<ParamField path="endpoint_env" type="UPPER_SNAKE name">
  On a `models:` entry in `agent.yaml`. Points that model at your own gateway
  instead of the provider's.
</ParamField>

<ParamField path="environment" type="map of route fields to UPPER_SNAKE names">
  In `connections/<name>.yaml`. The <Tooltip tip="The phone company your number is with, such as Twilio or Telnyx.">carrier</Tooltip> account behind a phone route, one key per field the route needs.
</ParamField>

<ParamField path="destinations" type="map of names to UPPER_SNAKE names">
  In `agent.yaml`. Each entry is the desk an escalation reaches, mapped onto
  the variable holding its phone number.
</ParamField>

A `local:` handler is the one case with no key: it reads
`os.environ` itself, so the name lives in the Python you wrote rather than in
`agent.yaml`. Declare it in `secrets:` all the same, so the inventory stays
complete.

## A secret is not a variable

They look similar and they are not the same thing.

|                   | [Variables](/build/variables) | Credentials                                    |
| ----------------- | ----------------------------- | ---------------------------------------------- |
| holds             | a value about this call       | a key your service authenticates with          |
| written as        | `{{caller_name}}` in a prompt | a `*_env` field naming an environment variable |
| changes           | during the call               | never, during a call                           |
| reaches the model | when a prompt names it        | never                                          |

<Warning>
  `{{...}}` renders variables only. Naming a secret in a template is a compile
  error, not a value that leaks at run time. A template renders into speech, a
  prompt, a tool argument or a URL, so whatever it holds gets spoken, logged or
  traced. That is right for a customer's name and wrong for a token.
</Warning>

## What you do not declare

Some names the target supplies for you: `LIVEKIT_URL` and its key pair, `REDIS_URL`
for the phone routes that need it, the public URL and token `unmute dev` creates
for a local phone run. Leave those out of `secrets:`.

You may still have to supply some of their values when you deploy. The
generated `README.md` and `compile-report.json` list the complete set and say
who supplies each one.

## Where to go next

<Columns cols={2}>
  <Card title="Secrets reference" icon="file-code" href="/reference/secrets">
    Every seam, every check, and what the generated files do with the inventory.
  </Card>

  <Card title="Going live" icon="rocket" href="/deploy/going-live">
    Moving these values into a platform's secret store.
  </Card>
</Columns>
