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 41 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 per-IP 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. 8 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
owner_user_idstringYesUUID of the user who will own this tenant

Returns: Tenant + Membership (the owner's membership record).

Atomically creates the tenant, the owner's membership, a system owner role (is_system=true, key="owner"), and assigns the owner role to the membership. All four writes happen in a single transaction.

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

Idempotency: DB constraint on (realm_id, slug). Duplicate calls return the existing tenant and owner membership without creating duplicate records.

grpcurl -plaintext -d '{
"realm_id": "550e8400-e29b-41d4-a716-446655440000",
"slug": "acme-store",
"display_name": "Acme Store",
"owner_user_id": "770e8400-e29b-41d4-a716-446655440000",
"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: Tenant (with updated status TENANT_STATUS_SUSPENDED).

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: Tenant (with updated status TENANT_STATUS_ACTIVE).

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

TransferOwnership​

Transfers tenant ownership from the current owner to a new user. If the new user is not already a member of the tenant, a membership is created for them. The old owner retains their membership but loses the owner role.

FieldTypeRequiredDescription
tenant_idstringYesUUID of the tenant
new_owner_user_idstringYesUUID of the user to become the new owner
idempotency_keystringNoClient-generated idempotency key

Returns: Membership (the new owner's membership record).

Errors: INVALID_ARGUMENT (invalid UUID, missing new_owner_user_id), NOT_FOUND (tenant not found, owner role not found).

Idempotency: Transferring to the current owner is a no-op. DB constraint UNIQUE(membership_id, role_id) prevents duplicate assignments.

grpcurl -plaintext -d '{
"tenant_id": "660e8400-e29b-41d4-a716-446655440000",
"new_owner_user_id": "880e8400-e29b-41d4-a716-446655440000"
}' localhost:50051 iam.v1.TenantService/TransferOwnership

GetTenantOwner​

Returns the current owner of a tenant.

FieldTypeRequiredDescription
tenant_idstringYesUUID of the tenant

Returns: Membership (the owner's membership) + user_id (the owner's user UUID).

Errors: INVALID_ARGUMENT (invalid UUID), NOT_FOUND (tenant or owner role not found).

Idempotency: N/A (read-only).

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

CheckOwnership​

Checks whether a given membership is the tenant owner.

FieldTypeRequiredDescription
tenant_idstringYesUUID of the tenant
membership_idstringYesUUID of the membership to check

Returns: is_owner (bool).

Errors: INVALID_ARGUMENT (invalid UUID).

Idempotency: N/A (read-only).

grpcurl -plaintext -d '{
"tenant_id": "660e8400-e29b-41d4-a716-446655440000",
"membership_id": "990e8400-e29b-41d4-a716-446655440000"
}' localhost:50051 iam.v1.TenantService/CheckOwnership

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: User (with updated status USER_STATUS_SUSPENDED).

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: User (with updated status USER_STATUS_ACTIVE).

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: Membership (with updated status MEMBERSHIP_STATUS_SUSPENDED).

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: Membership (with updated status MEMBERSHIP_STATUS_ACTIVE).

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: Invitation (with updated status INVITATION_STATUS_ACCEPTED).

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: Invitation (with updated status INVITATION_STATUS_REVOKED).

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