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

# Secrets

> Store API keys for services without a built-in integration and use them in backend scripts without the script ever seeing the key.

Some services have no ElasticFunnels integration yet: a text-to-speech API, an AI model, your own backend. Save the API key once as a secret, then send it from any backend script as `env.secret.NAME`.

The script never holds the key. `env.secret.NAME` is a placeholder that is only replaced with the real key inside a request header, on its way to a host you allowed.

## Add a secret

1. Open **Settings** and pick **Backend Scripts Secrets** (under Developer).
2. Click **Add secret**.
3. For ElevenLabs, OpenAI, Claude or Gemini, pick it under **Start from**: it fills in the name, the allowed host and a description, and links to the page where you create the key. For any other service, fill in:
   * **Name**: capital letters, digits and `_`, starting with a letter. Example: `ELEVENLABS_API_KEY`.
   * **Value**: the API key, at least 8 characters. After you save it, nobody can read it back, you included. The list shows only its last 4 characters.
   * **Allowed hosts**: the hostnames the key may be sent to, such as `api.elevenlabs.io`. Use `*.example.com` to allow every subdomain of `example.com` (the bare `example.com` is not included).
   * **Description** (optional).
4. Click **Save secret**.

To replace a key, edit the secret and type the new value. Leave the value empty to keep the current one. Adding a host requires typing the value again, so nobody can redirect a saved key to their own server.

## Use it in a script

Click **Example** next to a saved secret for a ready call that uses it. Services with a preset get their real request; any other secret gets a call to its first allowed host.

Put the secret in a request header:

```javascript theme={null}
// /with-timestamps answers JSON with the mp3 as base64 (http.* bodies are text)
var res = http.post(
  'https://api.elevenlabs.io/v1/text-to-speech/' + voiceId + '/with-timestamps',
  JSON.stringify({ text: 'Hello there', model_id: 'eleven_multilingual_v2' }),
  { 'xi-api-key': env.secret.ELEVENLABS_API_KEY, 'Content-Type': 'application/json' }
);
var audioBase64 = JSON.parse(res.body).audio_base64;
```

Prefixes work as usual:

```javascript theme={null}
{ 'Authorization': 'Bearer ' + env.secret.OPENAI_API_KEY }
```

Claude needs its version header too:

```javascript theme={null}
{
  'x-api-key': env.secret.ANTHROPIC_API_KEY,
  'anthropic-version': '2023-06-01',
  'Content-Type': 'application/json'
}
```

Gemini takes the key in the `x-goog-api-key` header (not `?key=` in the URL):

```javascript theme={null}
{ 'x-goog-api-key': env.secret.GEMINI_API_KEY, 'Content-Type': 'application/json' }
```

Requests time out after 10 seconds, so keep AI answers short (a low `max_tokens`).

The real key is added only when all of these hold:

* the secret is in a **request header** (not in the URL or the body),
* the URL uses **https**,
* the URL's host is in the secret's **allowed hosts**.

## What the script sees

`env.secret.NAME` is a random placeholder such as `efsec_3f9c...`, different on every run. Printing it, saving it with `storage.set`, returning it in `response.json` or sending it to another site reveals nothing.

If the service echoes the key back in its response (some error messages do), the key is replaced with `[redacted]` before your script gets the response.

## When a request is refused

A refused request is not sent. `http.*` returns `status: 0` and a `body` that says why:

| Body says | What to do |
| - | - |
| `env.secret.NAME is not set` | Check the name, or add the secret in Settings. |
| `... is not allowed for <host>` | Add that host to the secret's allowed hosts. |
| `... can only be sent in a request header` | Move it from the URL or body into a header. If the service wants the key in the URL, it cannot be used as a secret yet. |
| `... only sent over https` | Use the `https://` URL. |
| `... could not be read` | Save the value again. |

```javascript theme={null}
var res = http.get('https://api.example.com/me', { 'X-Api-Key': env.secret.EXAMPLE_KEY });
if (res.status === 0) {
  console.error(res.body);
}
```

## Good to know

* **Last used** in Settings shows when a script last sent the secret.
* Deleting a secret removes it for good. Scripts using it get `env.secret.NAME is not set`.
* Secrets belong to one brand. Other brands cannot use them.
* Anyone who can edit backend scripts in the brand can use a secret in a request to its allowed hosts, without seeing its value.


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