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 |
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 |
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. |
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 |
RevokeApiKeyRequest
| Field | Type | Number | Description |
|---|---|---|---|
tenantId | string | 1 | |
keyId | string | 2 |
RevokeApiKeyResponse
ListApiKeysRequest
| Field | Type | Number | Description |
|---|---|---|---|
tenantId | string | 1 |
ListApiKeysResponse
| Field | Type | Number | Description |
|---|---|---|---|
apiKeys | repeated ApiKey | 1 |
ImportApiKeyRequest
ImportApiKeyRequest registers a pre-existing service-account API key in the new store. Temporary surface for the v1 → v2 migration — remove once migrations complete. The caller supplies the plaintext; the service hashes it and persists a key row identical in shape to a freshly created one, so ext_authz validation works without any code path for legacy material. The plaintext is not echoed back.
| Field | Type | Number | Description |
|---|---|---|---|
tenantId | string | 1 | |
keyId | string | 2 | Caller-supplied key id (legacy id, preserved for cross-system correlation). Must be a UUID. Collision => AlreadyExists. |
displayName | string | 3 | |
description | string | 4 | |
grants | repeated KeyGrant | 5 | |
plaintextKey | string | 6 | Plaintext secret to register. Hashed server-side; collision on the hash => AlreadyExists. |
expiresAt | optional google.protobuf.Timestamp | 7 | Absolute deadline preserved from the legacy key. Mutually exclusive with permanent. May be in the past — already-expired imports persist but never validate. |
permanent | bool | 8 | |
createdAt | optional google.protobuf.Timestamp | 9 | Optional. Preserves the legacy key's creation timestamp. Defaults to now() when unset. |
ImportApiKeyResponse
| Field | Type | Number | Description |
|---|---|---|---|
apiKey | ApiKey | 1 | |
grants | repeated KeyGrant | 2 |
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
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
RevokeServiceAccountApiKey
Request: RevokeApiKeyRequest
Response: RevokeApiKeyResponse
ListServiceAccountApiKeys
Request: ListApiKeysRequest
Response: ListApiKeysResponse
GetServiceAccountApiKey
Request: GetApiKeyRequest
Response: GetApiKeyResponse
ImportApiKey
ImportApiKey registers a pre-existing service-account secret. TEMPORARY — remove after the v1 → v2 migration completes.
Request: ImportApiKeyRequest
Response: ImportApiKeyResponse