= API keys :nav-title: API Keys :description: Long-lived, revocable credentials a user issues for an AI agent, a script or an app, with roles you allow. Turn it on and decide who may issue what. :keywords: API keys, agent credentials, long-lived token, revocable credentials, MCP authentication, service account :group: Your users :order: 40 NOTE: API keys for a service's users are available from RESTHeart Cloud's 9.9 release. Something acting on a user's behalf — an AI agent, a script, a mobile app, another backend — connects to your service and stays connected. There is no sign-in dialog in which to renew a token, and a fifteen-minute JWT is no use to it. What it needs is a credential that lives long and can be revoked one at a time: an **API key**. AI agents over MCP are the case that made this urgent; the key is the same for any of them. The key belongs to a **user of your service** — one of the people who sign in to *your* application — and is issued by that user, with roles you allow *for that user's role*. Your application asks the service for it on the user's behalf. You, the owner, decide whether this is possible at all, which roles a key may carry for whom, and you see and revoke everything that was issued. Not to be confused with the xref:tokens.adoc[personal access tokens] of your *cloud account*, which `rhc` signs in with. Those reach your services' configuration; these reach one service's data, as one of its users, and nothing else. image::/assets/docs-images/api-keys.png[The API Keys page: the plugin enabled, the default limits, and who may issue which keys] == How it works . **You install the API Keys plugin** on the service, as you would any other, and set its default limits. . **You write a permission on `/keys`** for each role that may issue, naming in it the roles a key may be given. The API Keys page does this for you. . **A user issues a key** with `POST /keys`, signed in with their own credentials, naming the roles they want among those their permission allows. . **The client sends the key** as `Authorization: Bearer rhak_…`, and does what those roles may do. . **Anyone revokes**: the user their own, you any. Two things have to agree before a key can be issued, the plugin being enabled and a permission with an `apiKeys` block. Each on its own does nothing, and the error says which one is missing. == Who may issue which keys This is the part to get right, and it is written where every other grant is: in the service's ACL. A permission that lets a role reach `/keys` carries an `apiKeys` block naming the roles a key may be given, and optionally its own limits: [source,json] ---- { "_id": "adminsIssueKeys", "predicate": "path-prefix('/keys')", "roles": ["admin"], "priority": 10, "apiKeys": { "roles": ["cli-rw", "api-admin"], "max-expires-in-days": 365 } } { "_id": "usersIssueKeys", "predicate": "path-prefix('/keys')", "roles": ["user"], "priority": 1, "apiKeys": { "roles": ["cli-ro"] } } ---- With these, an `admin` may issue a `cli-rw` or an `api-admin` key and a `user` only a `cli-ro` one. The key roles need not be roles the user holds — usually they are not: a key's role is deliberately narrower than its owner's, and it can do only what *other* permissions grant to it. `cli-ro`, `cli-rw` and `api-admin` here are ordinary roles you define like any other. Two rules follow from how RESTHeart evaluates permissions: * **One permission decides.** When several match — a user who is both `admin` and `user` — the one with the highest priority is the one that applies, and only its `apiKeys.roles` are available. Give the wider grant the higher priority. * **A permission without the block grants nothing.** Its roles reach `/keys` and get a `403` that says so. Reaching the endpoint and being allowed to issue are two different things. * **A malformed block is refused where it is written.** `roles` must be a non-empty array of distinct role names, the two limits whole numbers within their bounds, and no other field is accepted. A typo — `role` for `roles` — would otherwise be a permission that reaches `/keys` and grants nothing, found out only by a refused user; so the service answers the write with a `400` that names the field, whoever writes it: the console, `rhc`, `curl`. The **API Keys** page lists the permissions on `/keys` with the key roles each one grants, lets you edit them, and adds a new one from three fields: the role that may issue, the roles a key may carry, and, optionally, its limits. They are ordinary permissions, also visible on the xref:managing-permissions.adoc[Permissions] page, and like any ACL change they apply within twenty seconds. == Turn it on API keys are a plugin, with the lifecycle every plugin has: **Install**, **Enable**, **Disable**, **Uninstall**, and a badge that says where it stands. The **API Keys** page opens with it. Disabled or uninstalled, `POST /keys` answers `403` whatever the ACL says, and no key authenticates; nothing is deleted, and installing again finds the keys, the permissions and the limits as they were. It is free on every tier. Below the plugin, the default limits: [cols="1,3"] |=== | Setting | What it decides | Longest a key may live | The default, in days, up to ten years. A permission may set a lower one for its roles. A user may ask for less, never for more. | Keys per user, at most | The default. A permission may set its own. A user at the limit has to revoke one before issuing another. |=== == Issue a key Your application calls the service, with the user's own credentials: [source,bash] ---- curl -u alice@example.com:her-password \ -X POST https://f3a9c1.eu-central-1-free-1.restheart.com/keys \ -H 'Content-Type: application/json' \ -d '{ "name": "my agent", "roles": ["cli-rw"], "expiresIn": 90 }' ---- [source,json] ---- { "_id": "68c7…", "name": "my agent", "roles": ["cli-rw"], "expiresAt": { "$date": 1765555200000 }, "key": "rhak_Kx7Rz9…" } ---- **The key is shown once.** Only its SHA-256 is stored, so it cannot be recovered afterwards, not by you, not by the user, not through any endpoint. An application that loses one issues another. `roles` is the part to understand. A key's roles must lie within what the permission that authorised the call names in its `apiKeys.roles` — nothing else, whatever the user holds. With the permissions above, `alice` as an `admin` may write `["cli-rw"]` or `["api-admin"]` and nothing else; `bob` as a `user` only `["cli-ro"]`. Left out, `roles` is empty, and an empty key can do nothing. There is no field in which to name another user. The principal is the one who signed in, always; anything else in the body is not read. `expiresIn` is in days, defaulting to 90 or the maximum that applies, whichever is lower: the permission's, when it sets one, otherwise the service's. == Use a key [source,bash] ---- curl https://f3a9c1.eu-central-1-free-1.restheart.com/things \ -H 'Authorization: Bearer rhak_Kx7Rz9…' ---- The request runs as the user who issued the key, with the key's roles and no others. The ACL applies as it does to a password sign-in — `readFilter`, `projectResponse`, everything. An MCP client uses the same header on `/mcp`. A key works on the service it was issued on and nowhere else, not even on another of your services that happens to know the same role names. == List and revoke A user sees their own keys, without the key or its hash: [source,bash] ---- curl -u alice@example.com:her-password https://f3a9c1.eu-central-1-free-1.restheart.com/keys curl -u alice@example.com:her-password -X DELETE https://f3a9c1.eu-central-1-free-1.restheart.com/keys/68c7… ---- `lastUsedAt` is absent until the key is first used. That absence is the useful part: it is how you tell the key somebody forgot about from the one in use. You see every key issued on the service on the **API Keys** page, with who holds it and when it was last used, and revoke any with the bin icon. Revocation takes effect within twenty seconds. == Try the MCP catalogue with a key The **What an agent sees** panel on the MCP Server page accepts an API key. Use it. The catalogue an agent gets is filtered by what its credential may read, so a test made with a user's password tells you what the *user* sees, not necessarily what their *key* does: the key carries only the roles it was issued with. == Related pages * xref:mcp.adoc[MCP Server] — where an agent uses the key * xref:tokens.adoc[Tokens] — the other kind of credential, for your cloud account and `rhc` * xref:managing-permissions.adoc[Managing Permissions (ACL)] — where the `apiKeys` block lives, and what a key's roles may do