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

# Authentication

> Make an API key in the app, send it as a bearer token, and choose between read and write access.

Every request to the Signal API carries an API key in the `Authorization` header. You make keys in the web app. A key belongs to one workspace, is either read or write, and can be tied to a single agent.

## Make a key

<Steps>
  <Step title="Open API keys">
    In the app, open **API keys** under **Settings** in the sidebar. The page is titled **API keys**.
  </Step>

  <Step title="Choose New key">
    In the **Keys** section, select **New key**. If you have none yet, select **Make your first key**.
  </Step>

  <Step title="Fill in the form">
    * **Name**: what the key is for, such as `GitHub release check`. It is required and can be up to 100 characters.
    * **Access**: **Read** or **Write**. See [Scopes](#scopes). In an organization, only an admin can make a **Write** key. Any member can make a **Read** key.
    * **Agent**: **All agents**, or one agent. A key tied to one agent cannot see any other.
    * **Monthly budget, USD**: shown only for **Write**. It starts at 50 and can be any amount from 0 to 100,000. It is the most this key's runs may reserve at Retell each month, inside the agent's own budget.
  </Step>

  <Step title="Make the key and copy it">
    Select **Make key**. The **Key made** dialog shows the key once, with the text "Copy the key now. It is not shown again." Copy it, then select **Done**. Under **Connect it**, the dialog also has ready-made snippets for Claude Code, Cursor, Claude Desktop and curl with your key filled in. The same snippets, with `sig_your_key` in place of a key, are in the **Connect** section further down the page, next to the API and MCP addresses.
  </Step>
</Steps>

<Warning>
  Signal stores a one-way hash of the key, not the key. If you lose it, nobody can show it to you again. Make a new key and revoke the old one.
</Warning>

Only a signed-in person can make or revoke keys, and a key cannot make or revoke keys. In an organization, making or revoking a **Write** key takes an admin. Any member can make or revoke a **Read** key.

## What a key looks like

A key starts with `sig_` and is followed by 32 random characters (letters, digits, `-` and `_`).

```text Example theme={null}
sig_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

The keys table shows only the first ten characters, such as `sig_a1b2c3…`, so you can tell keys apart. It also shows each key's access, agent, spend this month, when it was last used and when it was made. **Last used** updates at most once a minute.

## Send the key

Send the key as a bearer token in the `Authorization` header on every request.

```text Header theme={null}
Authorization: Bearer sig_your_key
```

<CodeGroup>
  ```bash curl theme={null}
  curl https://first-signal-api.fly.dev/v1/me \
    -H "Authorization: Bearer $SIGNAL_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const res = await fetch("https://first-signal-api.fly.dev/v1/me", {
    headers: { Authorization: `Bearer ${process.env.SIGNAL_API_KEY}` },
  });
  console.log(await res.json());
  ```

  ```python Python theme={null}
  import os
  import requests

  res = requests.get(
      "https://first-signal-api.fly.dev/v1/me",
      headers={"Authorization": f"Bearer {os.environ['SIGNAL_API_KEY']}"},
  )
  print(res.json())
  ```
</CodeGroup>

The same key works for the [MCP server](/mcp/overview), with the same header, and it carries the same scope there. [Connect an MCP client](/mcp/clients) has the configuration for each client.

A request the API cannot authenticate returns `401` with the header `WWW-Authenticate: Bearer realm="signal"` and one of these codes.

| `code` | Cause |
| - | - |
| `missing_authorization` | No `Authorization: Bearer` header |
| `invalid_api_key` | The key is unknown or has been revoked |
| `invalid_token` | A session token is invalid or has expired |

Check a key before you rely on it. `GET /v1/me` returns its scope, the agent it is tied to, its monthly budget and its rate limits.

## Scopes

Each operation needs one scope: `read`, `write` or `person`. The scope is declared in the `x-signal-scope` field of the OpenAPI document at `GET /v1/openapi.json`, and shown on each page of the reference.

| Key | `read` operations | `write` operations | `person` operations |
| - | - | - | - |
| Read | Allowed | `403` with code `key_read_only` | `403` with code `people_only` |
| Write | Allowed | Allowed, within the key's monthly budget | `403` with code `people_only` |

Four operations need `write`: [Create a test](/api-reference/tests/create-a-test), [Replace a test's spec](/api-reference/tests/replace-a-tests-spec), [Start a run](/api-reference/runs/start-a-run) and [Rescore a run](/api-reference/runs/rescore-a-run). One needs `person`: [Calibrate tests](/api-reference/runs/calibrate-tests). It decides whether a test may gate a release, so only a signed-in person can call it, and no API key can, whatever its scope. Every other operation is `read`, including [Validate a test spec](/api-reference/tests/validate-a-test-spec): it is a `POST`, but it stores nothing and spends nothing.

* A **read** key can read everything the API exposes. It stores nothing and spends nothing.
* A **write** key can also create and edit tests and start runs. Runs cost money at Retell, so a write key always has a monthly budget, and it can spend only up to that budget. The budget sits inside the agent's own budget and never raises it. The keys table shows each write key's spend as, for example, `$12.40 of $50`.
* Writes have their own, tighter [rate limit](/limits#api-rate-limits): 60 a minute with a burst of 20.

No key, read or write, can change settings, connect or change a provider, import calls, change an issue's status, delete anything, publish to the live agent, approve a run, calibrate tests, or manage keys and webhooks. The API has no operations for these.

A key also works inside the workspace's plan. When the plan's simulated test calls for the period are used up, starting a run answers `402` with code `quota_exceeded`. See [Limits and quotas](/limits#plan-limit-errors).

## Workspace scoping

A key belongs to the workspace you were in when you made it: a team organization, or your personal workspace. It reaches only that workspace's agents. The `organizationId` in the `GET /v1/me` response says which workspace that is.

Within the workspace, a key made for **All agents** reaches every agent. A key made for one agent reaches that agent only: `GET /v1/agents` lists only that agent.

An agent your key cannot reach returns `404` with code `agent_not_found`, never `403`. The same answer covers an agent that does not exist, one in another workspace, and one outside the key's agent. So ids cannot be probed. A call, issue, test or run that belongs to a different agent is also a `404`.

When the shared demo agent is on, every workspace can read it. Every write to it returns `403` with code `demo_read_only`. It is listed with `readOnly: true`. When the shared demo is off, **Load demo** copies the demo into your workspace instead; that copy is your own agent, listed with `readOnly: false`, and takes writes like any other.

## Rotate a key

Keys do not expire, and Signal does not rotate them for you. To replace one without downtime:

<Steps>
  <Step title="Make a new key">
    Use the same access, agent and budget as the old one. Give it a name that tells the two apart, such as `GitHub release check 2`.
  </Step>

  <Step title="Switch your tools to the new key">
    Update the secret in your CI settings or wherever the old key was stored.
  </Step>

  <Step title="Wait for the old key to go quiet">
    On the **API keys** page, watch **Last used** on the old key. When it stops changing, nothing is using it.
  </Step>

  <Step title="Revoke the old key">
    Select **Revoke** on its row and confirm.
  </Step>
</Steps>

## Revoke a key

Select **Revoke** on the key's row. The app asks `Revoke "<name>"? Anything using it stops working at once.` Confirm, and the key stops working immediately. Requests that use it return `401` with code `invalid_api_key`.

The row stays in the table, dimmed and marked **Revoked**. A revoked key cannot be turned back on. Make a new one.

Revoke a key at once if it appears in a repository, a log, a screenshot or a chat.

## Keep keys secret

* Store keys in your CI system's secrets or a secret manager, and pass them as environment variables.
* Never put a key in code that runs in a browser or a mobile app, in a repository, or in a prompt.
* Make one key per pipeline or tool, with a name that says which. A leaked key then costs you one revocation, and **Last used** tells you what is still in use.
* Start with **Read**. Use **Write** only for the tool that creates tests or starts runs, with the smallest monthly budget that works.
* Tie a key to one agent when the tool only needs that agent.

## Session tokens

The web app signs in with a session token and sends it in the same header. It lasts about a minute, so it does not suit a script. Use an API key.

<CardGroup cols={2}>
  <Card title="API introduction" icon="terminal" href="/api/introduction">
    Base URL, resources, errors and pagination.
  </Card>

  <Card title="MCP server" icon="plug" href="/mcp/overview">
    Connect an AI agent with the same key.
  </Card>
</CardGroup>


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