<!-- Generated by `just docs` from proto/automaton/v1/tenant.proto. Edit the source, not this file. -->

# KeysService

The project's own API keys: mint one, list them, retire one. Minting and retiring take an admin key, because a key that could mint its own reviewer would staff an approval gate rather than satisfy it. Listing carries no key material and is open to any of the project's keys.

Every call is a POST to `https://api.atmon.ai/automaton.v1.KeysService/<Call>` with a JSON body, and authenticates with `Authorization: Bearer <your project key>`. Field names in JSON are lowerCamelCase, so the field written `tool_slug` below is `toolSlug` on the wire. [How to call the API](./index.md) has the whole convention.

## Calls

| Call | Request | Response | Summary |
| --- | --- | --- | --- |
| `CreateKey` | `CreateKeyRequest` | `CreateKeyResponse` | Mints a key for the project and returns its plaintext once. |
| `ListKeys` | `ListKeysRequest` | `ListKeysResponse` | Lists the project's keys, newest first, with no key material. |
| `RevokeKey` | `RevokeKeyRequest` | `RevokeKeyResponse` | Retires one of the project's keys. |

### CreateKey

Mints a key for the project and returns its plaintext once. It requires the
admin role, and an expiry already in the past is refused rather than
stored, because a key born expired looks like a key that stopped working.

Request `CreateKeyRequest`, response `CreateKeyResponse`.

```http
POST /automaton.v1.KeysService/CreateKey HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{
  "name": "...",
  "role": "KEY_ROLE_AGENT",
  "expiresAt": "2026-01-31T09:15:00Z"
}
```

The response:

```json
{
  "key": {
    "id": "...",
    "name": "...",
    "role": "KEY_ROLE_AGENT",
    "createdAt": "2026-01-31T09:15:00Z",
    "revokedAt": "2026-01-31T09:15:00Z",
    "expiresAt": "2026-01-31T09:15:00Z",
    "createdByUserId": "..."
  },
  "plaintext": "..."
}
```

### ListKeys

Lists the project's keys, newest first, with no key material. Open to any of
the project's keys.

Request `ListKeysRequest`, response `ListKeysResponse`.

```http
POST /automaton.v1.KeysService/ListKeys HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{}
```

The response:

```json
{
  "keys": [{
    "id": "...",
    "name": "...",
    "role": "KEY_ROLE_AGENT",
    "createdAt": "2026-01-31T09:15:00Z",
    "revokedAt": "2026-01-31T09:15:00Z",
    "expiresAt": "2026-01-31T09:15:00Z",
    "createdByUserId": "..."
  }]
}
```

### RevokeKey

Retires one of the project's keys. It requires the admin role, and it
refuses the key the request arrived on: revoking that would end the session
doing the administration, so a key retires itself with
`automaton apikey revoke`.

Request `RevokeKeyRequest`, response `RevokeKeyResponse`.

```http
POST /automaton.v1.KeysService/RevokeKey HTTP/1.1
Host: api.atmon.ai
Authorization: Bearer amk_your_project_key
Content-Type: application/json

{
  "id": "..."
}
```

The response:

```json
{}
```

## Messages

### CreateKeyRequest

CreateKeyRequest mints a key for the authenticated project. It names no
project, because a key mints only for its own.

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `name` | `string` | 1 |  |
| `role` | `KeyRole` | 2 |  |
| `expires_at` | `google.protobuf.Timestamp` | 3 | expires_at must be in the future when it is set. Absent means the key never expires. |

### CreateKeyResponse

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `key` | `ProjectKey` | 1 |  |
| `plaintext` | `string` | 2 | plaintext is the only time the key exists outside the caller's hands. It is never stored, never logged, and no later read returns it: a lost key is replaced, not recovered. |

### ListKeysRequest

No fields. The call takes its scope from the authenticated project.

### ListKeysResponse

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `keys` | repeated `ProjectKey` | 1 |  |

### ProjectKey

ProjectKey is one API key as a listing shows it. It carries no key material at
all: the plaintext exists once, in CreateKeyResponse, and the server stores
only its hash.

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `id` | `string` | 1 |  |
| `name` | `string` | 2 | name is the label whoever minted it wrote, for their own bookkeeping. |
| `role` | `KeyRole` | 3 |  |
| `created_at` | `google.protobuf.Timestamp` | 4 |  |
| `revoked_at` | `google.protobuf.Timestamp` | 5 | revoked_at is absent while the key is live. |
| `expires_at` | `google.protobuf.Timestamp` | 6 | expires_at is absent when the key never expires. |
| `created_by_user_id` | `string` | 7 | created_by_user_id is the console user who minted it, empty for every key minted by the CLI and for every key minted before accounts existed. It is bookkeeping, never an authorization: a key minted by a person is a distinct principal from that person, which is what lets separation of duty hold when an agent acts under a key its own reviewer created. The member-removal dialog reads this field, and it is the reason that dialog can list what a departing person's keys are before deciding which of them keep running. |

### RevokeKeyRequest

RevokeKeyRequest retires one of the project's keys. It is idempotent: a key
already revoked keeps its original revocation time.

| Field | Type | # | Notes |
| --- | --- | --- | --- |
| `id` | `string` | 1 |  |

### RevokeKeyResponse

No fields. The call takes its scope from the authenticated project.

## Enums

### KeyRole

KeyRole is what a key may do. The vocabulary is closed, because a role a key
could present that the policy gate has never heard of is not a role, it is a
hole. Adding one is a recorded decision rather than a field value.

| Value | # | Meaning |
| --- | --- | --- |
| `KEY_ROLE_UNSPECIFIED` | 0 | KEY_ROLE_UNSPECIFIED reads as KEY_ROLE_AGENT on a mint, which is what an omitted role means and what every key minted before roles existed reads as. |
| `KEY_ROLE_AGENT` | 1 | KEY_ROLE_AGENT executes tools, submits jobs, and connects accounts for entities. It is the role an AI holds. |
| `KEY_ROLE_APPROVER` | 2 | KEY_ROLE_APPROVER resolves approvals and answers asks addressed to approvers. It is a separation of duty rather than an escalation: it grants review, not execution, and not administration. |
| `KEY_ROLE_ADMIN` | 3 | KEY_ROLE_ADMIN mints and revokes keys, writes the policy document and the principal directory, registers private toolkits, storage backends and telemetry destinations, and resolves approvals. It is the top of the vocabulary, and separation of duty still binds on it: an admin that requested a call may not release it. |
| `KEY_ROLE_VIEWER` | 4 | KEY_ROLE_VIEWER reads what the governance surfaces show and changes nothing. |
