An API key used to be an API key. It could ingest telemetry for its app, and that was the whole story.
Then the places a key lives multiplied. The backend plugin's key sits in a server's environment. The Flutter SDK's key ships inside an app bundle, which anyone can unzip. The Postgres collector's key sits on the database host. And as of last week, an agent's key sits in a config file on a laptop.
Each of those leaks differently. A bundle leaks to anyone who downloads the app; a laptop leaks to whoever borrows it; a database host leaks to whoever gets a shell there. When every key can do everything, any one of those leaks is all of them: the key that fell out of your Android APK can now post fake database statistics, a session replay, or โ with reads โ your customers' error logs.
So a key now says what it is for, and can do only that.
The kinds
Chosen once, when the key is issued โ API Keys โ Generate โ What is this key for?
| Kind | Give it to | Can reach |
|---|---|---|
| Server | the backend plugin | every ingest surface โ what every key could always do |
| Mobile app | the Flutter, Kotlin or Swift SDK | requests, logs, errors, events, sessions, metrics, traces |
| Database collector | pg-collector |
/api/ingest/db/*, nothing else |
| OpenTelemetry exporter | an OTLP exporter's headers | /v1/traces, nothing else |
| AI agent โ read only | the MCP server and CLI | reads one app; writes nothing |
| AI agent โ may resolve issues | the same, when you want it to close bugs | reads, and an issue's status |
The rule is enforced by the path, on every surface that accepts a key. A mobile key posting a replay gets a 403. A collector key posting requests gets a 403. An agent key posting anything gets a 403. Each one says which kind the key is and which kind that surface wants, so the fix is one sentence rather than a hunt.
What did not change
Every key that existed before today is a server key, and a server key reaches every ingest surface โ the plugin's, the collector's, and OTLP's โ because that is what every key could do yesterday and deployments depend on it. A collector that was set up on an ordinary key keeps sending. The migration adds one column with one default; nothing running in production noticed.
The narrowing is in the new kinds. When you register a database or set up a phone, the dashboard now hands you the narrow key for that job, and a leak of it exposes that job alone.
New server keys keep the prefixes every doc already shows: snt_live_ and snt_dev_. The new kinds get their own: snt_mobile_, snt_db_, snt_otlp_, snt_mcp_, snt_mcprw_. A leaked key is now self-describing. Someone who finds snt_mobile_โฆ in a log knows what it can do without asking.
Two things this quietly fixes
You can now tell where telemetry came from. Every key records when it was last used, and now every key belongs to one integration. "Is the collector on the new replica actually sending?" is a glance at the keys page, not a query.
OpenTelemetry exporters can use issued keys. Until now the OTLP endpoint only accepted an app's original key, not ones issued from the dashboard. It accepts both now, and refuses a mobile or collector key that wanders in โ a valid key is not the same thing as the right key.
The prefix is a claim, not a proof
A key's kind is stored in the database, and the prefix on the key says the same thing. Both have to agree. An agent key is checked against its stored kind and its prefix, so a row edited by hand โ kind bumped to "may resolve", key left as read-only โ is rejected rather than escalated. Neither half is trusted alone.
Choosing one
If the key will exist inside something you ship โ an app, a browser bundle, a container image other people pull โ it is a mobile key or, for the web, no key at all (the browser SDK proxies through your backend precisely so nothing public holds a credential). If it runs next to a database, it is a collector key. If a person or an agent will read with it, it is an agent key, and read-only unless you specifically want the agent closing issues.
If it is the plugin in your own backend, it is a server key, and that is the default, so the common case is still one click.