A publishable key answers one question: which agent replies. Your page sends it with the first request, and Teaflask uses it to pick the agent and check where the request came from. The key is meant to sit in page source and client bundles. It is not a secret, and it says nothing about who the user is. That's [Identity](/identity).

## How a key is used

When the assistant mounts, it sends the key to Teaflask and gets back a short-lived visitor token. That token authenticates everything else in the conversation, and the assistant renews it before it expires. Teaflask hands out a visitor token when both of these are true:

1. The key exists and hasn't been revoked.
2. The request comes from one of the key's allowed origins.

## Create a key

Open the agent in the dashboard and go to its **Install** section. Choose **New key**, pick **Live** or **Test**, and enter the allowed origins, one per line. The key belongs to that agent.

Every key has a row in the **Install** section. Opening a row shows the full key with a copy button, and the key's allowed origins, at any time. Keys created before Teaflask started storing them in full show only a prefix. They keep working, and rotating one gives you a successor that's stored in full.

## Live and test keys

| Mode | Use it for                    |
| ---- | ----------------------------- |
| Live | Production                    |
| Test | Local development and staging |

The mode labels the key's traffic and is fixed when you create the key. A test key isn't a sandbox for your application. If its actions adapter points at your production API with a real user's session, the actions are real. Pair a test key with a test environment of your API.

## Allowed origins

An origin is a scheme, a host and an optional port, with no path, query or trailing slash:

```text
https://app.example.com
https://staging.example.com
http://localhost:5173
```

The match is exact. Case is ignored, and a stray trailing slash is ignored, but nothing else is:

| These are different origins                            | Because                                                  |
| ------------------------------------------------------ | -------------------------------------------------------- |
| `http://localhost:3000` and `http://localhost:5173`    | The ports differ                                         |
| `http://localhost:3000` and `http://127.0.0.1:3000`    | The hosts differ                                         |
| `http://app.example.com` and `https://app.example.com` | The schemes differ                                       |
| `https://example.com` and `https://app.example.com`    | Subdomains are listed one by one; there are no wildcards |

Origins are fixed when you create a key, and rotating keeps them. To serve a new origin, create a new key. A key's reach should never widen quietly.

A request from an origin that isn't on the list gets `403 ORIGIN_NOT_ALLOWED`, and the error message names the origin it saw. Compare that with the key's list in the dashboard.

The origin check limits which websites can use your key from a browser. It doesn't prove who is calling: a script outside a browser can send any `Origin` header it likes. Your own API still has to authenticate users and decide what they may do.

## Rotate, replace and revoke

| To                          | Do this                                                                                | What happens                                                                        |
| --------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| Swap keys with no downtime  | Create a second key with the same mode and origins, deploy it, then revoke the old one | Both keys work until you revoke the old one                                         |
| Get a fresh key immediately | Rotate                                                                                 | The old key stops working at once. The new one has the same agent, mode and origins |
| Stop a key for good         | Revoke                                                                                 | The key stops working at once. There's no undo                                      |

Rotate and revoke take effect immediately, so pages still sending the old key stop working until you deploy the new one. A key appearing in public code is expected and isn't a reason to rotate.

## More than one key

Several keys can select the same agent. That's the normal way to keep environments apart: a test key for local and staging origins, a live key for production. Because each key has its own origins, a key copied from a staging page can't be used from production.

Surfaces that should share a conversation must use the same key, because the key is part of what identifies the conversation. See [Surfaces](/surfaces).

## More resources

- [Install](/install) – where the key goes in your app
- [Identity](/identity) – the end-user JWT secret, which unlike this key never reaches the browser
- [Surfaces](/surfaces) – why surfaces that share a conversation need the same key
- [Revisions](/revisions) – publishing the agent, without which no key gets an answer
