cmind/apikey/v2/api_key_service.proto
Package: cmind.apikey.v2
Enums
KeyType
| Value | Number | Description |
|---|---|---|
KEY_TYPE_UNSPECIFIED | 0 | |
KEY_TYPE_PERSONAL | 1 | |
KEY_TYPE_SERVICE_ACCOUNT | 2 |
GrantTargetType
GrantTargetType is the kind of resource a KeyGrant targets. The set is intentionally closed: each target type has a fixed set of supported actions (see GrantAction) and a private mapping to the OpenFGA relation used for the check + the tuple write.
| Value | Number | Description |
|---|---|---|
GRANT_TARGET_TYPE_UNSPECIFIED | 0 | |
GRANT_TARGET_TYPE_MODEL_DEPLOYMENT | 1 | |
GRANT_TARGET_TYPE_EXTERNAL_MODEL | 2 | |
GRANT_TARGET_TYPE_RAG_DEPLOYMENT | 3 | |
GRANT_TARGET_TYPE_GROUP | 4 | |
GRANT_TARGET_TYPE_PRESET_AGENT | 5 | target_id is the PresetAgent CRD name. The agent must belong to the key's tenant. |
GRANT_TARGET_TYPE_MODEL_ALIAS | 6 |
GrantAction
GrantAction names the kind of access a key gets on the target. Not every combination of (target_type, action) is valid — the service rejects unsupported pairs with InvalidArgument.
| Value | Number | Description |
|---|---|---|
GRANT_ACTION_UNSPECIFIED | 0 | |
GRANT_ACTION_MODEL_INVOKE | 1 | model_deployment, external_model, model_alias |
GRANT_ACTION_RAG_READ | 2 | rag_deployment |
GRANT_ACTION_RAG_WRITE | 3 | rag_deployment |
GRANT_ACTION_GROUP_MEMBERSHIP | 5 | group |
GRANT_ACTION_AGENT_INVOKE | 6 | preset agent |
KeyAccess
KeyAccess is the access a key has on top of its grants.
| Value | Number | Description |
|---|---|---|
KEY_ACCESS_UNSPECIFIED | 0 | Treated as KEY_ACCESS_GRANTS_ONLY. |
KEY_ACCESS_GRANTS_ONLY | 1 | The key reaches only what its grants name. |
KEY_ACCESS_OWNER_READ | 2 | Personal keys only. The key can read whatever its owner can read in the key's tenant, following the owner's permissions as they change. It administers nothing beyond what its grants allow. |
Messages
KeyGrant
KeyGrant describes one capability attached to a key. The service maps (target_type, action) to the underlying OpenFGA relation and verifies the caller has the corresponding permission before writing the tuple.
| Field | Type | Number | Description |
|---|---|---|---|
targetType | GrantTargetType | 1 | |
targetId | string | 2 | |
action | GrantAction | 3 |
ApiKey
| Field | Type | Number | Description |
|---|---|---|---|
id | string | 1 | |
type | KeyType | 2 | |
tenantId | string | 3 | |
userId | optional string | 4 | |
displayName | string | 5 | |
description | string | 6 | |
secretPrefix | string | 7 | |
createdBy | string | 8 | |
createdAt | google.protobuf.Timestamp | 9 | |
currentExpiresAt | optional google.protobuf.Timestamp | 10 | |
revokedAt | optional google.protobuf.Timestamp | 11 | |
revokedBy | optional string | 12 | |
disabledAt | optional google.protobuf.Timestamp | 13 | Disabling blocks every secret without changing grants or expiry. |
disabledBy | optional string | 14 | |
access | KeyAccess | 15 |
CreateApiKeyRequest
| Field | Type | Number | Description |
|---|---|---|---|
tenantId | string | 1 | |
displayName | string | 2 | |
grants | repeated KeyGrant | 3 | |
validFor | optional google.protobuf.Duration | 4 | Must be positive. Mutually exclusive with permanent. |
permanent | bool | 5 | |
description | string | 6 | Free-form longer description. Optional. |
access | KeyAccess | 7 | A key with KEY_ACCESS_OWNER_READ may have no grants; any other key needs at least one. |
GetApiKeyRequest
| Field | Type | Number | Description |
|---|---|---|---|
tenantId | string | 1 | |
keyId | string | 2 |
GetApiKeyResponse
| Field | Type | Number | Description |
|---|---|---|---|
apiKey | ApiKey | 1 | |
grants | repeated KeyGrant | 2 |
CreateApiKeyResponse
| Field | Type | Number | Description |
|---|---|---|---|
apiKey | ApiKey | 1 | |
plaintextKey | string | 2 | |
grants | repeated KeyGrant | 3 |
RotateApiKeyRequest
| Field | Type | Number | Description |
|---|---|---|---|
tenantId | string | 1 | |
keyId | string | 2 | |
overlap | google.protobuf.Duration | 3 | Overlap window during which the previous secret remains valid. The previous secret's expires_at is set to least(existing_expires_at, now() + overlap) so rotation never extends a near-expired secret. |
validFor | optional google.protobuf.Duration | 4 | Must be positive. Mutually exclusive with permanent. |
permanent | bool | 5 |
RotateApiKeyResponse
| Field | Type | Number | Description |
|---|---|---|---|
apiKey | ApiKey | 1 | |
plaintextKey | string | 2 |
UpdateApiKeyMetadataRequest
| Field | Type | Number | Description |
|---|---|---|---|
tenantId | string | 1 | |
keyId | string | 2 | |
displayName | optional string | 3 | If set, replaces the key's display_name. Must be non-empty. |
description | optional string | 4 | If set, replaces the key's description. May be empty to clear. |
UpdateApiKeyMetadataResponse
| Field | Type | Number | Description |
|---|---|---|---|
apiKey | ApiKey | 1 |
DisableApiKeyRequest
Disable is idempotent and rejects permanently revoked keys.
| Field | Type | Number | Description |
|---|---|---|---|
tenantId | string | 1 | |
keyId | string | 2 |
DisableApiKeyResponse
| Field | Type | Number | Description |
|---|---|---|---|
apiKey | ApiKey | 1 |
EnableApiKeyRequest
Enable is idempotent and rejects permanently revoked keys.
| Field | Type | Number | Description |
|---|---|---|---|
tenantId | string | 1 | |
keyId | string | 2 |
EnableApiKeyResponse
| Field | Type | Number | Description |
|---|---|---|---|
apiKey | ApiKey | 1 |
RevokeApiKeyRequest
| Field | Type | Number | Description |
|---|---|---|---|
tenantId | string | 1 | |
keyId | string | 2 |
RevokeApiKeyResponse
ListApiKeysRequest
| Field | Type | Number | Description |
|---|---|---|---|
tenantId | string | 1 | |
onlyOwn | bool | 2 | Restrict the result to keys the caller owns (personal keys: owned by the calling user; service-account keys: created by the calling user). Without it the caller gets every key in the tenant it holds can_read on. |
ListApiKeysResponse
| Field | Type | Number | Description |
|---|---|---|---|
apiKeys | repeated ApiKey | 1 |
Services
ApiKeyService
ApiKeyService manages the lifecycle of API keys. Validation does not live on this surface — it runs via the Envoy ext_authz contract at the Istio ingress (see RFC 2026-03-13-api-key-system.md). This service exposes only lifecycle operations. Every key is scoped to a tenant. The endpoints are split by key type so each RPC carries a single static authorization rule: - personal key creation check tenant membership (can_read). Rotate/Revoke enforce key ownership against the authenticated caller in the handler. - service-account endpoints check can_admin on the tenant.
CreatePersonalApiKey
Personal keys
Request: CreateApiKeyRequest
Response: CreateApiKeyResponse
RotatePersonalApiKey
Request: RotateApiKeyRequest
Response: RotateApiKeyResponse
UpdatePersonalApiKeyMetadata
Request: UpdateApiKeyMetadataRequest
Response: UpdateApiKeyMetadataResponse
DisablePersonalApiKey
Request: DisableApiKeyRequest
Response: DisableApiKeyResponse
EnablePersonalApiKey
Request: EnableApiKeyRequest
Response: EnableApiKeyResponse
RevokePersonalApiKey
Request: RevokeApiKeyRequest
Response: RevokeApiKeyResponse
ListPersonalApiKeys
Request: ListApiKeysRequest
Response: ListApiKeysResponse
GetPersonalApiKey
Request: GetApiKeyRequest
Response: GetApiKeyResponse
CreateServiceAccountApiKey
Service-account keys
Request: CreateApiKeyRequest
Response: CreateApiKeyResponse
RotateServiceAccountApiKey
Request: RotateApiKeyRequest
Response: RotateApiKeyResponse
UpdateServiceAccountApiKeyMetadata
Request: UpdateApiKeyMetadataRequest
Response: UpdateApiKeyMetadataResponse
DisableServiceAccountApiKey
Request: DisableApiKeyRequest
Response: DisableApiKeyResponse
EnableServiceAccountApiKey
Request: EnableApiKeyRequest
Response: EnableApiKeyResponse
RevokeServiceAccountApiKey
Request: RevokeApiKeyRequest
Response: RevokeApiKeyResponse
ListServiceAccountApiKeys
Request: ListApiKeysRequest
Response: ListApiKeysResponse
GetServiceAccountApiKey
Request: GetApiKeyRequest
Response: GetApiKeyResponse