# API keys in the console

> Create, scope, and revoke the long-lived control-plane credentials that your own integrations authenticate with.

This screen manages the long-lived **API keys** that your backend uses to
call the TeleQuick control plane. It is the only place the key string
is ever shown. Everything else on the platform — relay tokens, browser
tokens, playback tokens — is derived from a key rather than replacing it.

For the wider model (why servers hold keys and clients hold short-lived
tokens), see [Authentication](/concepts/authentication). This page covers
the screen itself.

## What a key on this screen grants

An API key is scoped to one org and to a set of capabilities. It
authenticates **control-plane** calls over HTTPS:

| Surface          | Examples                                 |
| ---------------- | ---------------------------------------- |
| Voice control    | originate, and the rest of voice control |
| Streams          | streams CRUD                             |
| Agents           | agent attach                             |
| Webhooks         | webhook list and management              |
| Analytics        | analytics reads                          |

A key does **not** authenticate the data plane. The relay never sees an
API key. To publish or subscribe over MoQT, a client presents a
short-lived relay token that your server mints using the key. The
relay's `namespace_auth` hook checks that token, not the key.

## Create a key and the one-time secret reveal

The create action on this screen is backed by:

```ts
streams.apiKeys.create({ orgId, label, scopes });
```

The `label` is for humans — it is what identifies the row in this list
later, so name it after the consumer (`dialer-worker`, `ci`,
`analytics-export`) rather than after a person.

The response carries the key string **once, at creation**. The
control-plane API stores only the hash of the key, so neither this screen
nor support can show it again. Copy it out of the reveal panel and put it
straight into your secret store:

```bash
export TELEQUICK_API_KEY=…
```

Treat the string as opaque and store it verbatim. If you lose it, there
is no recovery path — create a replacement key and revoke the old one.

## Scopes and least privilege

Scopes are checked per procedure. The control-plane API rejects a key
that does not carry the capability scope required by the procedure being
called, and it does so regardless of whether the org itself is entitled
to that surface. That makes narrow keys cheap to run:

- Give each deployed component its own key with only the surfaces it
  calls. A dialer worker that only originates calls does not need the
  scopes for webhook management.
- Keep read-only consumers (dashboards, exports) on keys without CRUD
  scopes, so a leak cannot mutate configuration.
- Because scopes are fixed at creation, widening a component's access
  means creating a new key with the larger scope set and retiring the
  old one — see below.

A key that mints relay tokens can only mint them within the namespaces it
is itself scoped to, so narrowing a key also narrows every session token
derived from it.

## Revoking a key and what happens to in-flight traffic

Revoking a key from this list stops it from authenticating further
control-plane calls. Two consequences are worth planning for:

- **Control-plane calls fail immediately.** Every call authenticates on
  the request itself over HTTPS, so there is no session to drain. Any
  process still holding the revoked string starts failing on its next
  call, not at some later renewal point.
- **Relay tokens already minted from that key stay valid until they
  expire.** The relay validates a relay token against the org's signing
  key that it hydrates from Redis — it does not consult the API key the
  token was minted from. A browser or service session that already
  completed the MoQT handshake continues until the token's `exp`.

So revocation is the right tool for "this credential leaked, stop new
work", and it is not sufficient on its own for "cut off every live
session". To do the second, retire the signing key as well, described
next.

Because only the hash is stored, a revoked key cannot be reinstated.
Replacement is always a new key with a new string.

## Rotating without downtime

Rotate an API key by overlapping two keys rather than by editing one:

1. Create a second key on this screen with the same scopes and a label
   that identifies the new generation.
2. Deploy the new string to every consumer and confirm traffic on it.
3. Revoke the old key.

There is no cutover window to coordinate, because keys are validated per
request.

Rotating the **signing key** that relay tokens are signed with is a
separate, ordered sequence, and it is what actually ages out live
data-plane sessions:

1. `streams.signingKeys.create({ orgId, label })` — issues a new active
   key.
2. Wait for outstanding client tokens to expire (1h by default).
3. `streams.signingKeys.retire({ id })` — the old key mints no new
   tokens.

The relay refreshes its set of active public keys from Redis every 30 s,
so a retirement takes effect within that window.

## Relationship to service accounts and relay tokens

Three credential types coexist, and this screen owns only the first:

| Credential            | Lifetime               | Held by         | Checked by                     |
| --------------------- | ---------------------- | --------------- | ------------------------------ |
| API key               | Long-lived             | Your backend    | Control-plane API, per request |
| Relay token           | Short-lived (~1h)      | A client / session | The relay's `namespace_auth` hook |
| Service-account JWT   | Signed per handshake   | Legacy SDK host | The QUIC handshake             |

Relay tokens are minted from an API key, scoped to namespace patterns,
and presented during the MoQT handshake. Browser sessions use the same
mechanism: mint the token server-side and hand the client only the token,
never the key.

The legacy service-account JSON referenced by `TELEQUICK_CREDENTIALS` is
independent of this screen. Keys created here do not replace or revoke a
service account, and revoking a key here has no effect on an SDK host
still authenticating with the RSA service-account file. New integrations
should use API keys from this screen plus relay tokens.

## Related

- [Authentication](/concepts/authentication) — the full key, relay-token, and JWT model
- [Telemetry](/platform/telemetry) — `telequick_admin_requests_total` counts control-plane requests by `method` and `result`
