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:
| Method | Header | Format |
|---|---|---|
| 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 Bearer | authorization | Bearer <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.
| Field | Type | Description |
|---|---|---|
page_size | int32 | Number of items to return. Default: 50. Max: 100. |
page_token | string | Opaque cursor (UUID of last item from previous page). Omit for first page. |
| Field | Type | Description |
|---|---|---|
next_page_token | string | Token for the next page. Empty when no more results. |
total_count | int32 | Number of items returned in this page (not a global total). |
Error Codes
| gRPC Code | Domain Cause | Description | Retry? |
|---|---|---|---|
NOT_FOUND | Resource does not exist | The requested entity was not found by ID or lookup key. | No |
ALREADY_EXISTS | Duplicate resource or unique constraint violation | A resource with the given natural key already exists. | No (use idempotency key or GET) |
INVALID_ARGUMENT | Validation failure, bad UUID, bad email, bad page token | The request contains an invalid or missing field. | No (fix the request) |
FAILED_PRECONDITION | Invalid state transition or missing FK reference | The 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_EXHAUSTED | Rate limit exceeded | The server-wide rate limiter rejected the request. | Yes (backoff and retry) |
UNAUTHENTICATED | Missing or invalid credentials | No valid API key or JWT was provided. | No (fix credentials) |
INTERNAL | Unexpected server error | An 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):
CreateUserCreateInvitation
Accepted but not enforced at application level (relies on DB constraints for natural idempotency):
- All other write endpoints that include
idempotency_keyin their proto definition.
When a completed idempotency key is replayed, the original resource is returned without side effects.
Status Enums
TenantStatus
| Value | Proto Name | Meaning |
|---|---|---|
| 0 | TENANT_STATUS_UNSPECIFIED | Default / unknown |
| 1 | TENANT_STATUS_ACTIVE | Normal operating state |
| 2 | TENANT_STATUS_SUSPENDED | Temporarily disabled |
| 3 | TENANT_STATUS_DELETED | Permanently removed |
UserStatus
| Value | Proto Name | Meaning |
|---|---|---|
| 0 | USER_STATUS_UNSPECIFIED | Default / unknown |
| 1 | USER_STATUS_ACTIVE | Normal operating state |
| 2 | USER_STATUS_SUSPENDED | Temporarily disabled |
| 3 | USER_STATUS_DELETED | Permanently removed |
MembershipStatus
| Value | Proto Name | Meaning |
|---|---|---|
| 0 | MEMBERSHIP_STATUS_UNSPECIFIED | Default / unknown |
| 1 | MEMBERSHIP_STATUS_ACTIVE | User is an active member of the tenant |
| 2 | MEMBERSHIP_STATUS_SUSPENDED | Membership temporarily disabled |
| 3 | MEMBERSHIP_STATUS_LEFT | User has left the tenant |
InvitationStatus
| Value | Proto Name | Meaning |
|---|---|---|
| 0 | INVITATION_STATUS_UNSPECIFIED | Default / unknown |
| 1 | INVITATION_STATUS_PENDING | Awaiting acceptance |
| 2 | INVITATION_STATUS_ACCEPTED | User accepted the invitation |
| 3 | INVITATION_STATUS_REVOKED | Invitation was revoked by an admin |
| 4 | INVITATION_STATUS_EXPIRED | Invitation passed its expiry time |
APIKeyStatus
| Value | Proto Name | Meaning |
|---|---|---|
| 0 | API_KEY_STATUS_UNSPECIFIED | Default / unknown |
| 1 | API_KEY_STATUS_ACTIVE | Key is valid for authentication |
| 2 | API_KEY_STATUS_REVOKED | Key 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.
| Field | Type | Required | Description |
|---|---|---|---|
| (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.
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Unique identifier key for the realm |
name | string | Yes | Human-readable name |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID 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.
| Field | Type | Required | Description |
|---|---|---|---|
pagination | PaginationRequest | No | See 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.
| Field | Type | Required | Description |
|---|---|---|---|
realm_id | string | Yes | UUID of the parent realm |
slug | string | Yes | URL-safe unique identifier within the realm |
display_name | string | Yes | Human-readable tenant name |
external_ref | string | No | Optional external reference (e.g. billing ID) |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID 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.
| Field | Type | Required | Description |
|---|---|---|---|
realm_id | string | Yes | UUID of the realm to filter by |
pagination | PaginationRequest | No | See 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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID of the tenant |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID of the tenant |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | No | Email address (validated for format) |
phone_e164 | string | No | Phone number in E.164 format |
display_name | string | No | Human-readable display name |
idempotency_key | string | Yes | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID 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.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email 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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID of the user |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID of the user |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
tenant_id | string | Yes | UUID of the tenant |
user_id | string | Yes | UUID of the user |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID 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.
| Field | Type | Required | Description |
|---|---|---|---|
user_id | string | Yes | UUID of the user |
pagination | PaginationRequest | No | See 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.
| Field | Type | Required | Description |
|---|---|---|---|
tenant_id | string | Yes | UUID of the tenant |
pagination | PaginationRequest | No | See 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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID of the membership |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID of the membership |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
tenant_id | string | Yes | UUID of the tenant |
email | string | Yes | Email of the invitee (validated for format) |
invited_by | string | No | UUID of the user sending the invitation |
idempotency_key | string | Yes | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | The invitation token (received out of band, e.g. via email) |
user_id | string | Yes | UUID of the user accepting the invitation |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID of the invitation |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
tenant_id | string | Yes | UUID of the tenant |
status_filter | InvitationStatus | No | Filter by status (e.g. INVITATION_STATUS_PENDING) |
pagination | PaginationRequest | No | See 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.
| Field | Type | Required | Description |
|---|---|---|---|
tenant_id | string | Yes | UUID of the tenant |
key | string | Yes | Unique key within the tenant (e.g. admin, cashier) |
name | string | Yes | Human-readable role name |
description | string | No | Optional description |
is_system | bool | No | Whether this is a system-managed role (default: false) |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID 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.
| Field | Type | Required | Description |
|---|---|---|---|
tenant_id | string | Yes | UUID of the tenant |
pagination | PaginationRequest | No | See 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).
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Unique permission key (e.g. orders.create, reports.view) |
description | string | No | Optional description |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
role_id | string | Yes | UUID of the role |
permission_id | string | Yes | UUID of the permission |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
role_id | string | Yes | UUID of the role |
permission_id | string | Yes | UUID 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.
| Field | Type | Required | Description |
|---|---|---|---|
membership_id | string | Yes | UUID of the membership |
role_id | string | Yes | UUID of the role |
assigned_by | string | No | UUID of the user performing the assignment |
note | string | No | Optional note explaining the assignment |
idempotency_key | string | No | Client-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.
| Field | Type | Required | Description |
|---|---|---|---|
membership_id | string | Yes | UUID of the membership |
role_id | string | Yes | UUID 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.
| Field | Type | Required | Description |
|---|---|---|---|
membership_id | string | Yes | UUID 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).
| Field | Type | Required | Description |
|---|---|---|---|
membership_id | string | Yes | UUID of the membership |
permission_key | string | Yes | Permission 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.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique human-readable name for the key |
description | string | No | Optional description |
expires_at | Timestamp | No | Optional expiry time. Keys without expiry never expire. |
created_by | string | No | Identifier 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).
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID 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.
| Field | Type | Required | Description |
|---|---|---|---|
pagination | PaginationRequest | No | See 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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | UUID 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