# Give an app its own key

> The credential an app you host holds, the separate authority that creates it, where the secret lives, and how to stop the app.

Canonical page: https://anectico.com/docs/apps/credentials/


Each backend app has a dedicated management key bound to one project, with only the permissions it
needs, kept in your server's secret store. An approved browser aggregate uses a temporary token minted
by that exact backend parent, as described below. A deliberately public
[static aggregate page](/docs/apps/static-aggregate-view) instead publishes one
finite projection credential and needs no backend; its management parent stays secret. The complete
table of which credential each kind of host holds is in
[Authentication and API keys](/docs/reference/authentication#which-credential-each-host-holds); this
page is the checklist your agent follows for each worked example.

## Two credentials, two jobs

| Credential | Who holds it | What it may do |
| --- | --- | --- |
| The builder's credential | Your agent, while it builds and provisions the app | Read the contract and the docs, preview and confirm the app's key (`api_key:read`, `api_key:write`, `mcp:write`, and every permission it grants). |
| The app's runtime key | The app's server, scheduler or bot backend, and nothing else | Only the reads the app makes. It never creates, lists, edits or revokes keys, and every route that does refuses it. |

Do not give the runtime key `mcp:read`, `docs:read` or a bundle such as `investigate`. Discovery is the
builder's job; the running app reads only the operations it calls.

## Provision the runtime key

Your agent previews the key and you confirm it. Use the app's name, the one project it reads and a
fixed expiry:

```bash
anectico --project PROJECT_UUID apikey create --name status-board --scope services:read \
  --expires-at <fixed-future-RFC3339-expiry> --idempotency-key status-board-1
# Review the complete server preview, then repeat the identical arguments:
anectico --project PROJECT_UUID apikey create --name status-board --scope services:read \
  --expires-at <fixed-future-RFC3339-expiry> --idempotency-key status-board-1 --confirm-token <server-token>
```

The secret appears once. Put it straight into the host's secret store (a secrets manager, the
scheduler's secret variables, a file readable only by the service user) and hand it to the app as
`ANECTICO_API_KEY`. If the response was lost, repeat the same command with the same
`--idempotency-key`: you get the key's details and no secret, and no second key is created. If you
never saw the secret, revoke that key and create a new one.

The permissions each worked example needs:

| Example | Runtime permissions |
| --- | --- |
| [Status board](/docs/apps/status-board) | `services:read` |
| [Support page](/docs/apps/support-page) | `persons:read` |
| [Slack bot](/docs/apps/slack-bot) | `oncall:read`, `projects:read`, `members:read` |
| [Nightly report](/docs/apps/nightly-script) | `errors:read`; with a saved result, also `insights:read`, `analytics:read`, `persons:read` and the result's own source permissions |

Add a permission that releases customer content (`persons:profile:read`, `replay:content:read`,
`agents:content:read`) only when the app must show that content, and say so in the app's own
documentation.

## Where the secret may appear

Only in the server process's environment or secret store, and in the `X-API-Key` header of requests
the server sends to Anectico. Never in browser code, a mobile binary, a source map, browser storage, a
URL, a query string, a log line, an error page, a repository or a screenshot. Redact it from crash
reports. A program that serves pages to people renders them on the server and sends no key, not even a
masked one.

## Watch and limit the key

The key has its own request rate and operation budget beside it on **Agents and keys**, and every call
it makes is in the agent activity record under the key's name:

```bash
anectico budget get
anectico activity list --limit 20
```

A limit of zero answers `429` and keeps the key valid; it is a throttle, not a stop. See
[Manage agent budgets](/docs/manage/agent-budgets) and [Limits](/docs/reference/limits#api-request-rate).

## Stop the app

Revoke the key. It is the one action that stops the app's next request:

```bash
anectico apikey revoke <key-id>
```

Revoking ends the app's access, not the app: the process keeps running and should show "access
ended" (each worked example does) rather than retrying or switching to another key. It does not
retract data the app already read. The app's caches, exports, logs and messages are yours to delete;
Anectico's erasure cannot reach them. Freezing a key leaves a read-only key reading. See
[Revoke an agent's access](/docs/agents/revoke-agent-access#stop-an-app-that-reads).

## Related

- [Build an app on the REST API](/docs/apps/architecture)
- [Pin the contract your app is built on](/docs/apps/contract)
- [Polling, retries and refusals](/docs/apps/polling-and-streaming)

## Browser aggregate sharing

For browser aggregate sharing, provisioning additionally needs `app_views:share`, key read and write and
all base and source scopes. Runtime mint uses the grant's exact project parent key with `app_views:mint`,
`analytics:read`, `persons:read` and every result source scope. A mint-only key fails. The parent retains
ordinary project reads; only the issued `app_read` token is restricted to one approved result and projection,
at most five minutes without refresh. CLI mint requires a new protected `--out`; no secret goes to stdout.

See [Share a saved aggregate with a browser page](/docs/apps/browser-aggregate-view) for the full
credential, theft, lifetime, privacy and external-copy contract.
