Skip to main content

IAM Service -- gRPC API Reference

The IAM service is the source of truth for tenants, users, memberships, roles, permissions, invitations, and realms. It exposes 8 gRPC services with 39 RPCs total. All proto definitions live in proto/iam/v1/.

IAM does not handle authentication or issue tokens. It defines identity and access boundaries only.


Cross-Cutting Concerns

Authentication

All RPCs (except those in skip_methods config) require one of:

MethodHeaderFormat
API Key (bootstrap)Configurable (e.g. x-api-key)Raw key string (from env var IAM_AUTH_APIKEYS)
API Key (database)Configurable (e.g. x-api-key)Raw key string (created via CreateAPIKey)
JWT BearerauthorizationBearer <token>

Bootstrap API keys (configured via environment variable) have full access to all endpoints, including API key management. Database-backed API keys can access all endpoints except the APIKeyService — those endpoints require a bootstrap key.

Unauthenticated requests return UNAUTHENTICATED.

Pagination

List endpoints accept an optional PaginationRequest and return a PaginationResponse.

FieldTypeDescription
page_sizeint32Number of items to return. Default: 50. Max: 100.
page_tokenstringOpaque cursor (UUID of last item from previous page). Omit for first page.
FieldTypeDescription
next_page_tokenstringToken for the next page. Empty when no more results.
total_countint32Number of items returned in this page (not a global total).

Error Codes

gRPC CodeDomain CauseDescriptionRetry?
NOT_FOUNDResource does not existThe requested entity was not found by ID or lookup key.No
ALREADY_EXISTSDuplicate resource or unique constraint violationA resource with the given natural key already exists.No (use idempotency key or GET)
INVALID_ARGUMENTValidation failure, bad UUID, bad email, bad page tokenThe request contains an invalid or missing field.No (fix the request)
FAILED_PRECONDITIONInvalid state transition or missing FK referenceThe operation cannot be performed in the current state (e.g. suspending an already-suspended tenant, or referencing a non-existent foreign key).No (resolve the precondition)
RESOURCE_EXHAUSTEDRate limit exceededThe server-wide rate limiter rejected the request.Yes (backoff and retry)
UNAUTHENTICATEDMissing or invalid credentialsNo valid API key or JWT was provided.No (fix credentials)
INTERNALUnexpected server errorAn unhandled error occurred.Yes (with backoff)

Idempotency

Write endpoints accept an idempotency_key field (client-generated string). Keys are:

  • Scoped per operation (e.g. create_user, create_invitation) -- the same key string can be used across different scopes without collision.
  • Expire after 24 hours.
  • Not business data -- use UUIDs or similar opaque values.

Fully enforced (checks before creation, returns cached resource on replay):

  • CreateUser
  • CreateInvitation

Accepted but not enforced at application level (relies on DB constraints for natural idempotency):

  • All other write endpoints that include idempotency_key in their proto definition.

When a completed idempotency key is replayed, the original resource is returned without side effects.

Status Enums

TenantStatus

ValueProto NameMeaning
0TENANT_STATUS_UNSPECIFIEDDefault / unknown
1TENANT_STATUS_ACTIVENormal operating state
2TENANT_STATUS_SUSPENDEDTemporarily disabled
3TENANT_STATUS_DELETEDPermanently removed

UserStatus

ValueProto NameMeaning
0USER_STATUS_UNSPECIFIEDDefault / unknown
1USER_STATUS_ACTIVENormal operating state
2USER_STATUS_SUSPENDEDTemporarily disabled
3USER_STATUS_DELETEDPermanently removed

MembershipStatus

ValueProto NameMeaning
0MEMBERSHIP_STATUS_UNSPECIFIEDDefault / unknown
1MEMBERSHIP_STATUS_ACTIVEUser is an active member of the tenant
2MEMBERSHIP_STATUS_SUSPENDEDMembership temporarily disabled
3MEMBERSHIP_STATUS_LEFTUser has left the tenant

InvitationStatus

ValueProto NameMeaning
0INVITATION_STATUS_UNSPECIFIEDDefault / unknown
1INVITATION_STATUS_PENDINGAwaiting acceptance
2INVITATION_STATUS_ACCEPTEDUser accepted the invitation
3INVITATION_STATUS_REVOKEDInvitation was revoked by an admin
4INVITATION_STATUS_EXPIREDInvitation passed its expiry time

APIKeyStatus

ValueProto NameMeaning
0API_KEY_STATUS_UNSPECIFIEDDefault / unknown
1API_KEY_STATUS_ACTIVEKey is valid for authentication
2API_KEY_STATUS_REVOKEDKey has been revoked and can no longer authenticate

1. HealthService

Checks liveness of the service and its dependencies (Postgres, RabbitMQ).

Check

Returns the serving status.

FieldTypeRequiredDescription
(none)Empty request

Returns: CheckResponse with status (SERVING_STATUS_SERVING or SERVING_STATUS_NOT_SERVING).

Errors: None (always returns a response).

Idempotency: N/A (read-only).

grpcurl -plaintext localhost:50051 iam.v1.HealthService/Check

2. RealmService

Realms are top-level isolation boundaries that group tenants. 3 RPCs.

CreateRealm

Creates a new realm.

FieldTypeRequiredDescription
keystringYesUnique identifier key for the realm
namestringYesHuman-readable name
idempotency_keystringNoClient-generated idempotency key

Returns: Realm (id, key, name, created_at).

Errors: INVALID_ARGUMENT (empty key or name), ALREADY_EXISTS (duplicate key).

Idempotency: DB constraint on key. The idempotency_key is accepted but not enforced at the application level.

grpcurl -plaintext -d '{
"key": "acme",
"name": "Acme Corp",
"idempotency_key": "req-abc-123"
}' localhost:50051 iam.v1.RealmService/CreateRealm

GetRealm

Retrieves a realm by ID.

FieldTypeRequiredDescription
idstringYesUUID of the realm

Returns: Realm.

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND.

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"id": "550e8400-e29b-41d4-a716-446655440000"
}' localhost:50051 iam.v1.RealmService/GetRealm

ListRealms

Lists all realms with pagination.

FieldTypeRequiredDescription
paginationPaginationRequestNoSee Pagination

Returns: repeated Realm + PaginationResponse.

Errors: INVALID_ARGUMENT (page_size > 100, invalid page_token).

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"pagination": {"page_size": 20}
}' localhost:50051 iam.v1.RealmService/ListRealms

3. TenantService

Tenants are workspace/billing/isolation boundaries within a realm. 5 RPCs.

CreateTenant

Creates a new tenant within a realm.

FieldTypeRequiredDescription
realm_idstringYesUUID of the parent realm
slugstringYesURL-safe unique identifier within the realm
display_namestringYesHuman-readable tenant name
external_refstringNoOptional external reference (e.g. billing ID)
idempotency_keystringNoClient-generated idempotency key

Returns: Tenant (id, realm_id, slug, display_name, status, external_ref, created_at, updated_at).

Errors: INVALID_ARGUMENT (invalid realm_id UUID), ALREADY_EXISTS (duplicate slug), FAILED_PRECONDITION (realm_id does not exist).

Idempotency: DB constraint on (realm_id, slug). The idempotency_key is accepted but not enforced at the application level.

grpcurl -plaintext -d '{
"realm_id": "550e8400-e29b-41d4-a716-446655440000",
"slug": "acme-store",
"display_name": "Acme Store",
"idempotency_key": "req-tenant-001"
}' localhost:50051 iam.v1.TenantService/CreateTenant

GetTenant

Retrieves a tenant by ID.

FieldTypeRequiredDescription
idstringYesUUID of the tenant

Returns: Tenant.

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND.

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"id": "660e8400-e29b-41d4-a716-446655440000"
}' localhost:50051 iam.v1.TenantService/GetTenant

ListTenants

Lists tenants within a realm.

FieldTypeRequiredDescription
realm_idstringYesUUID of the realm to filter by
paginationPaginationRequestNoSee Pagination

Returns: repeated Tenant + PaginationResponse.

Errors: INVALID_ARGUMENT (invalid realm_id, page_size > 100, invalid page_token).

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"realm_id": "550e8400-e29b-41d4-a716-446655440000",
"pagination": {"page_size": 25}
}' localhost:50051 iam.v1.TenantService/ListTenants

SuspendTenant

Transitions a tenant from ACTIVE to SUSPENDED.

FieldTypeRequiredDescription
idstringYesUUID of the tenant
idempotency_keystringNoClient-generated idempotency key

Returns: SuspendTenantResponse (empty body on success).

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND, FAILED_PRECONDITION (tenant not in ACTIVE state).

Idempotency: DB constraint-based. Suspending an already-suspended tenant returns FAILED_PRECONDITION.

grpcurl -plaintext -d '{
"id": "660e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "req-suspend-001"
}' localhost:50051 iam.v1.TenantService/SuspendTenant

ReactivateTenant

Transitions a tenant from SUSPENDED to ACTIVE.

FieldTypeRequiredDescription
idstringYesUUID of the tenant
idempotency_keystringNoClient-generated idempotency key

Returns: ReactivateTenantResponse (empty body on success).

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND, FAILED_PRECONDITION (tenant not in SUSPENDED state).

Idempotency: DB constraint-based. Reactivating an already-active tenant returns FAILED_PRECONDITION.

grpcurl -plaintext -d '{
"id": "660e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "req-reactivate-001"
}' localhost:50051 iam.v1.TenantService/ReactivateTenant

4. UserService

Users are global identities (not scoped to a tenant). 5 RPCs.

CreateUser

Creates a new user. At least one identifier (email or phone) should be provided.

FieldTypeRequiredDescription
emailstringNoEmail address (validated for format)
phone_e164stringNoPhone number in E.164 format
display_namestringNoHuman-readable display name
idempotency_keystringYesClient-generated idempotency key

Returns: User (id, email, phone_e164, display_name, status, created_at, updated_at).

Errors: INVALID_ARGUMENT (invalid email format), ALREADY_EXISTS (duplicate email/phone).

Idempotency: Fully enforced. Scoped to create_user. Replayed keys return the originally created user without side effects. Keys expire after 24 hours.

grpcurl -plaintext -d '{
"email": "alice@example.com",
"display_name": "Alice",
"idempotency_key": "req-user-001"
}' localhost:50051 iam.v1.UserService/CreateUser

GetUser

Retrieves a user by ID.

FieldTypeRequiredDescription
idstringYesUUID of the user

Returns: User.

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND.

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"id": "770e8400-e29b-41d4-a716-446655440000"
}' localhost:50051 iam.v1.UserService/GetUser

GetUserByEmail

Retrieves a user by email address.

FieldTypeRequiredDescription
emailstringYesEmail address to look up

Returns: User.

Errors: INVALID_ARGUMENT (empty or invalid email format), NOT_FOUND.

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"email": "alice@example.com"
}' localhost:50051 iam.v1.UserService/GetUserByEmail

SuspendUser

Transitions a user from ACTIVE to SUSPENDED.

FieldTypeRequiredDescription
idstringYesUUID of the user
idempotency_keystringNoClient-generated idempotency key

Returns: SuspendUserResponse (empty body on success).

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND, FAILED_PRECONDITION (user not in ACTIVE state).

Idempotency: DB constraint-based. The idempotency_key is passed to the handler but state-transition logic prevents duplicate effects.

grpcurl -plaintext -d '{
"id": "770e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "req-suspend-user-001"
}' localhost:50051 iam.v1.UserService/SuspendUser

ReactivateUser

Transitions a user from SUSPENDED to ACTIVE.

FieldTypeRequiredDescription
idstringYesUUID of the user
idempotency_keystringNoClient-generated idempotency key

Returns: ReactivateUserResponse (empty body on success).

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND, FAILED_PRECONDITION (user not in SUSPENDED state).

Idempotency: DB constraint-based. The idempotency_key is passed to the handler but state-transition logic prevents duplicate effects.

grpcurl -plaintext -d '{
"id": "770e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "req-reactivate-user-001"
}' localhost:50051 iam.v1.UserService/ReactivateUser

5. MembershipService

Memberships represent the relationship between a user and a tenant. 6 RPCs.

CreateMembership

Creates a membership linking a user to a tenant.

FieldTypeRequiredDescription
tenant_idstringYesUUID of the tenant
user_idstringYesUUID of the user
idempotency_keystringNoClient-generated idempotency key

Returns: Membership (id, tenant_id, user_id, status, authz_version, created_at, updated_at).

Errors: INVALID_ARGUMENT (invalid UUID), ALREADY_EXISTS (duplicate (tenant_id, user_id)), FAILED_PRECONDITION (tenant or user does not exist).

Idempotency: Natural idempotency via DB unique constraint on (tenant_id, user_id). Duplicate inserts return ALREADY_EXISTS.

grpcurl -plaintext -d '{
"tenant_id": "660e8400-e29b-41d4-a716-446655440000",
"user_id": "770e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "req-membership-001"
}' localhost:50051 iam.v1.MembershipService/CreateMembership

GetMembership

Retrieves a membership by ID.

FieldTypeRequiredDescription
idstringYesUUID of the membership

Returns: Membership.

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND.

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"id": "880e8400-e29b-41d4-a716-446655440000"
}' localhost:50051 iam.v1.MembershipService/GetMembership

ListUserMemberships

Lists all memberships for a given user.

FieldTypeRequiredDescription
user_idstringYesUUID of the user
paginationPaginationRequestNoSee Pagination

Returns: repeated Membership + PaginationResponse.

Errors: INVALID_ARGUMENT (invalid UUID, page_size > 100, invalid page_token).

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"user_id": "770e8400-e29b-41d4-a716-446655440000",
"pagination": {"page_size": 10}
}' localhost:50051 iam.v1.MembershipService/ListUserMemberships

ListTenantMembers

Lists all memberships for a given tenant.

FieldTypeRequiredDescription
tenant_idstringYesUUID of the tenant
paginationPaginationRequestNoSee Pagination

Returns: repeated Membership + PaginationResponse.

Errors: INVALID_ARGUMENT (invalid UUID, page_size > 100, invalid page_token).

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"tenant_id": "660e8400-e29b-41d4-a716-446655440000",
"pagination": {"page_size": 10}
}' localhost:50051 iam.v1.MembershipService/ListTenantMembers

SuspendMembership

Transitions a membership from ACTIVE to SUSPENDED.

FieldTypeRequiredDescription
idstringYesUUID of the membership
idempotency_keystringNoClient-generated idempotency key

Returns: SuspendMembershipResponse (empty body on success).

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND, FAILED_PRECONDITION (membership not in ACTIVE state).

Idempotency: DB constraint-based. State-transition logic prevents duplicate effects.

grpcurl -plaintext -d '{
"id": "880e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "req-suspend-mem-001"
}' localhost:50051 iam.v1.MembershipService/SuspendMembership

ReactivateMembership

Transitions a membership from SUSPENDED to ACTIVE.

FieldTypeRequiredDescription
idstringYesUUID of the membership
idempotency_keystringNoClient-generated idempotency key

Returns: ReactivateMembershipResponse (empty body on success).

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND, FAILED_PRECONDITION (membership not in SUSPENDED state).

Idempotency: DB constraint-based. State-transition logic prevents duplicate effects.

grpcurl -plaintext -d '{
"id": "880e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "req-reactivate-mem-001"
}' localhost:50051 iam.v1.MembershipService/ReactivateMembership

6. InvitationService

Invitations allow existing members to invite new users to a tenant. Invitations expire after 7 days. 4 RPCs.

CreateInvitation

Creates a pending invitation for an email address to join a tenant.

FieldTypeRequiredDescription
tenant_idstringYesUUID of the tenant
emailstringYesEmail of the invitee (validated for format)
invited_bystringNoUUID of the user sending the invitation
idempotency_keystringYesClient-generated idempotency key

Returns: Invitation (id, tenant_id, email, invited_by, status, expires_at, accepted_by, accepted_at, created_at, updated_at).

Errors: INVALID_ARGUMENT (invalid email, invalid tenant_id, invalid invited_by), ALREADY_EXISTS (pending invitation for this email+tenant already exists), FAILED_PRECONDITION (tenant does not exist).

Idempotency: Fully enforced. Scoped to create_invitation. Replayed keys return the originally created invitation without side effects. Keys expire after 24 hours. Note: on replay, the token field is not returned (it is only available on initial creation).

grpcurl -plaintext -d '{
"tenant_id": "660e8400-e29b-41d4-a716-446655440000",
"email": "bob@example.com",
"invited_by": "770e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "req-invite-001"
}' localhost:50051 iam.v1.InvitationService/CreateInvitation

AcceptInvitation

Accepts a pending invitation using the invitation token.

FieldTypeRequiredDescription
tokenstringYesThe invitation token (received out of band, e.g. via email)
user_idstringYesUUID of the user accepting the invitation
idempotency_keystringNoClient-generated idempotency key

Returns: AcceptInvitationResponse (empty body on success).

Errors: INVALID_ARGUMENT (empty token, invalid user_id), NOT_FOUND (token does not match any pending invitation), FAILED_PRECONDITION (invitation not in PENDING state or expired).

Idempotency: DB constraint-based. Accepting an already-accepted invitation returns FAILED_PRECONDITION.

grpcurl -plaintext -d '{
"token": "a1b2c3d4e5f6...",
"user_id": "770e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "req-accept-001"
}' localhost:50051 iam.v1.InvitationService/AcceptInvitation

RevokeInvitation

Revokes a pending invitation.

FieldTypeRequiredDescription
idstringYesUUID of the invitation
idempotency_keystringNoClient-generated idempotency key

Returns: RevokeInvitationResponse (empty body on success).

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND, FAILED_PRECONDITION (invitation not in PENDING state).

Idempotency: DB constraint-based. Revoking an already-revoked invitation returns FAILED_PRECONDITION.

grpcurl -plaintext -d '{
"id": "990e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "req-revoke-001"
}' localhost:50051 iam.v1.InvitationService/RevokeInvitation

ListTenantInvitations

Lists invitations for a tenant, optionally filtered by status.

FieldTypeRequiredDescription
tenant_idstringYesUUID of the tenant
status_filterInvitationStatusNoFilter by status (e.g. INVITATION_STATUS_PENDING)
paginationPaginationRequestNoSee Pagination

Returns: repeated Invitation + PaginationResponse.

Errors: INVALID_ARGUMENT (invalid tenant_id, page_size > 100, invalid page_token).

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"tenant_id": "660e8400-e29b-41d4-a716-446655440000",
"status_filter": "INVITATION_STATUS_PENDING",
"pagination": {"page_size": 20}
}' localhost:50051 iam.v1.InvitationService/ListTenantInvitations

7. RoleService

Manages roles, permissions, role-permission associations, and role assignments to memberships. 10 RPCs.

CreateRole

Creates a new role within a tenant.

FieldTypeRequiredDescription
tenant_idstringYesUUID of the tenant
keystringYesUnique key within the tenant (e.g. admin, cashier)
namestringYesHuman-readable role name
descriptionstringNoOptional description
is_systemboolNoWhether this is a system-managed role (default: false)
idempotency_keystringNoClient-generated idempotency key

Returns: Role (id, tenant_id, key, name, description, is_system, created_at, updated_at).

Errors: INVALID_ARGUMENT (invalid tenant_id), ALREADY_EXISTS (duplicate (tenant_id, key)), FAILED_PRECONDITION (tenant does not exist).

Idempotency: DB constraint on (tenant_id, key). The idempotency_key is accepted but not enforced at the application level.

grpcurl -plaintext -d '{
"tenant_id": "660e8400-e29b-41d4-a716-446655440000",
"key": "admin",
"name": "Administrator",
"is_system": true,
"idempotency_key": "req-role-001"
}' localhost:50051 iam.v1.RoleService/CreateRole

GetRole

Retrieves a role by ID, including its attached permissions.

FieldTypeRequiredDescription
idstringYesUUID of the role

Returns: Role + repeated Permission.

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND.

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"id": "aa0e8400-e29b-41d4-a716-446655440000"
}' localhost:50051 iam.v1.RoleService/GetRole

ListRoles

Lists roles within a tenant.

FieldTypeRequiredDescription
tenant_idstringYesUUID of the tenant
paginationPaginationRequestNoSee Pagination

Returns: repeated Role + PaginationResponse.

Errors: INVALID_ARGUMENT (invalid tenant_id, page_size > 100, invalid page_token).

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"tenant_id": "660e8400-e29b-41d4-a716-446655440000",
"pagination": {"page_size": 50}
}' localhost:50051 iam.v1.RoleService/ListRoles

CreatePermission

Creates a global permission (not scoped to a tenant).

FieldTypeRequiredDescription
keystringYesUnique permission key (e.g. orders.create, reports.view)
descriptionstringNoOptional description
idempotency_keystringNoClient-generated idempotency key

Returns: Permission (id, key, description, created_at).

Errors: ALREADY_EXISTS (duplicate key).

Idempotency: DB constraint on key. The idempotency_key is accepted but not enforced at the application level.

grpcurl -plaintext -d '{
"key": "orders.create",
"description": "Can create new orders",
"idempotency_key": "req-perm-001"
}' localhost:50051 iam.v1.RoleService/CreatePermission

AddPermissionToRole

Attaches a permission to a role.

FieldTypeRequiredDescription
role_idstringYesUUID of the role
permission_idstringYesUUID of the permission
idempotency_keystringNoClient-generated idempotency key

Returns: Empty response on success.

Errors: INVALID_ARGUMENT (invalid UUID), ALREADY_EXISTS (permission already attached), FAILED_PRECONDITION (role or permission does not exist).

Idempotency: DB constraint on (role_id, permission_id). Duplicate calls return ALREADY_EXISTS.

grpcurl -plaintext -d '{
"role_id": "aa0e8400-e29b-41d4-a716-446655440000",
"permission_id": "bb0e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "req-add-perm-001"
}' localhost:50051 iam.v1.RoleService/AddPermissionToRole

RemovePermissionFromRole

Detaches a permission from a role.

FieldTypeRequiredDescription
role_idstringYesUUID of the role
permission_idstringYesUUID of the permission

Returns: Empty response on success.

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND (association does not exist).

Idempotency: Naturally idempotent -- removing a non-existent association returns NOT_FOUND.

grpcurl -plaintext -d '{
"role_id": "aa0e8400-e29b-41d4-a716-446655440000",
"permission_id": "bb0e8400-e29b-41d4-a716-446655440000"
}' localhost:50051 iam.v1.RoleService/RemovePermissionFromRole

AssignRole

Assigns a role to a membership.

FieldTypeRequiredDescription
membership_idstringYesUUID of the membership
role_idstringYesUUID of the role
assigned_bystringNoUUID of the user performing the assignment
notestringNoOptional note explaining the assignment
idempotency_keystringNoClient-generated idempotency key

Returns: RoleAssignment (id, membership_id, role_id, assigned_by, assigned_at, note).

Errors: INVALID_ARGUMENT (invalid UUID), ALREADY_EXISTS (role already assigned to this membership), FAILED_PRECONDITION (membership or role does not exist).

Idempotency: DB constraint on (membership_id, role_id). Duplicate assignments return ALREADY_EXISTS.

grpcurl -plaintext -d '{
"membership_id": "880e8400-e29b-41d4-a716-446655440000",
"role_id": "aa0e8400-e29b-41d4-a716-446655440000",
"assigned_by": "770e8400-e29b-41d4-a716-446655440000",
"idempotency_key": "req-assign-001"
}' localhost:50051 iam.v1.RoleService/AssignRole

UnassignRole

Removes a role assignment from a membership.

FieldTypeRequiredDescription
membership_idstringYesUUID of the membership
role_idstringYesUUID of the role

Returns: Empty response on success.

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND (assignment does not exist).

Idempotency: Naturally idempotent -- removing a non-existent assignment returns NOT_FOUND.

grpcurl -plaintext -d '{
"membership_id": "880e8400-e29b-41d4-a716-446655440000",
"role_id": "aa0e8400-e29b-41d4-a716-446655440000"
}' localhost:50051 iam.v1.RoleService/UnassignRole

ListMembershipRoles

Lists all roles assigned to a membership.

FieldTypeRequiredDescription
membership_idstringYesUUID of the membership

Returns: repeated Role.

Errors: INVALID_ARGUMENT (invalid UUID).

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"membership_id": "880e8400-e29b-41d4-a716-446655440000"
}' localhost:50051 iam.v1.RoleService/ListMembershipRoles

CheckPermission

Checks whether a membership has a specific permission (via any of its assigned roles).

FieldTypeRequiredDescription
membership_idstringYesUUID of the membership
permission_keystringYesPermission key to check (e.g. orders.create)

Returns: CheckPermissionResponse with allowed (bool).

Errors: INVALID_ARGUMENT (invalid UUID).

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"membership_id": "880e8400-e29b-41d4-a716-446655440000",
"permission_key": "orders.create"
}' localhost:50051 iam.v1.RoleService/CheckPermission

8. APIKeyService

Manages database-backed API keys for service-to-service authentication. All 4 RPCs require a bootstrap API key (configured via IAM_AUTH_APIKEYS env var). Database-backed API keys cannot call these endpoints. 4 RPCs.

CreateAPIKey

Creates a new API key. The raw key is returned only once in the response — it cannot be retrieved later.

FieldTypeRequiredDescription
namestringYesUnique human-readable name for the key
descriptionstringNoOptional description
expires_atTimestampNoOptional expiry time. Keys without expiry never expire.
created_bystringNoIdentifier of who created the key

Returns: APIKey + raw_key (the plaintext key, only available on first creation).

Errors: INVALID_ARGUMENT (empty name), PERMISSION_DENIED (caller is not using a bootstrap key).

Idempotency: DB constraint on name. Duplicate name returns the existing key with an empty raw_key (the plaintext is not recoverable).

grpcurl -plaintext -H "X-API-Key: your-bootstrap-key" -d '{
"name": "pos-service",
"description": "API key for the POS service"
}' localhost:50051 iam.v1.APIKeyService/CreateAPIKey

GetAPIKey

Retrieves an API key by ID. Returns metadata only (never the raw key).

FieldTypeRequiredDescription
idstringYesUUID of the API key

Returns: APIKey.

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND, PERMISSION_DENIED (caller is not using a bootstrap key).

Idempotency: N/A (read-only).

grpcurl -plaintext -H "X-API-Key: your-bootstrap-key" -d '{
"id": "cc0e8400-e29b-41d4-a716-446655440000"
}' localhost:50051 iam.v1.APIKeyService/GetAPIKey

ListAPIKeys

Lists all API keys with pagination.

FieldTypeRequiredDescription
paginationPaginationRequestNoSee Pagination

Returns: repeated APIKey + PaginationResponse.

Errors: INVALID_ARGUMENT (page_size > 100, invalid page_token), PERMISSION_DENIED (caller is not using a bootstrap key).

Idempotency: N/A (read-only).

grpcurl -plaintext -H "X-API-Key: your-bootstrap-key" -d '{
"pagination": {"page_size": 20}
}' localhost:50051 iam.v1.APIKeyService/ListAPIKeys

RevokeAPIKey

Revokes an active API key. Revoked keys can no longer authenticate.

FieldTypeRequiredDescription
idstringYesUUID of the API key to revoke

Returns: APIKey (with status REVOKED).

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND, PERMISSION_DENIED (caller is not using a bootstrap key).

Idempotency: Revoking an already-revoked key is a no-op (returns the revoked key without error or event).

grpcurl -plaintext -H "X-API-Key: your-bootstrap-key" -d '{
"id": "cc0e8400-e29b-41d4-a716-446655440000"
}' localhost:50051 iam.v1.APIKeyService/RevokeAPIKey