Skip to content

Permissions & Roles

Manage permissions, roles, and access control through perms.io's Resource-Specific Role-Based Access Control (RBAC) system. Control who can access what resources in your application with fine-grained precision.

Overview

The permissions system implements a hierarchical RBAC model with the following key components:

  • Permissions: Individual actions that can be performed (e.g., read, write, delete)
  • Roles: Collections of permissions that can be assigned as a group
  • Principals: Users or entities that can be granted permissions
  • Resources: Specific objects or paths that permissions apply to (e.g., /project/123, /user/456/documents)

Key Features

  • Resource-Specific Access: Permissions apply to specific resource URIs
  • Hierarchical Inheritance: Permissions on parent resources cascade to child resources
  • Flexible Principal Model: Support for users, service accounts, and other principal types
  • High-Performance Checks: Optimized permission checking for real-time applications

Base URL

https://api.perms.io/permissions-service/v1

Permission Management

Create Permission

Creates a new permission within a project. Permissions are idempotent - creating an existing permission will update it.

Required Permission: permission.create on /organisation/{organisation_id}

curl -X POST "https://api.perms.io/permissions-service/v1/permissions" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_name": "production",
    "name": "document.read",
    "description": "Allows reading documents"
  }'
package main

import (
    "context"
    "log"

    permissionsv1 "github.com/PrivateJAR/permio-go/proto/permissions/v1"
)

func createPermission(client permissionsv1.PermissionsServiceClient) {
    req := &permissionsv1.CreatePermissionRequest{
        ProjectName: "production",
        Name:        "document.read",
        Description: "Allows reading documents",
    }

    resp, err := client.CreatePermission(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to create permission: %v", err)
    }

    log.Printf("Created permission: %s (ID: %s)", resp.Name, resp.Id)
}
use tonic::transport::Channel;
use permio_proto::permissions::v1::{
    permissions_service_client::PermissionsServiceClient,
    CreatePermissionRequest,
};

async fn create_permission(client: &mut PermissionsServiceClient<Channel>) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(CreatePermissionRequest {
        project_name: "production".to_string(),
        name: "document.read".to_string(),
        description: "Allows reading documents".to_string(),
    });

    let response = client.create_permission(request).await?;
    println!("Created permission: {} (ID: {})", response.get_ref().name, response.get_ref().id);

    Ok(())
}
import grpc
from proto.permissions.v1 import permissions_service_pb2
from proto.permissions.v1 import permissions_service_pb2_grpc

def create_permission():
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.CreatePermissionRequest(
        project_name="production",
        name="document.read",
        description="Allows reading documents"
    )

    response = client.CreatePermission(request)
    print(f"Created permission: {response.name} (ID: {response.id})")
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import permio.permissions.v1.PermissionsServiceGrpc;
import permio.permissions.v1.Permissions.CreatePermissionRequest;
import permio.permissions.v1.Permissions.CreatePermissionResponse;

public void createPermission() {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    CreatePermissionRequest request = CreatePermissionRequest.newBuilder()
            .setProjectName("production")
            .setName("document.read")
            .setDescription("Allows reading documents")
            .build();

    CreatePermissionResponse response = client.createPermission(request);
    System.out.println("Created permission: " + response.getName() + " (ID: " + response.getId() + ")");

    channel.shutdown();
}
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';

const packageDefinition = protoLoader.loadSync('permissions_service.proto');
const permissionsService = grpc.loadPackageDefinition(packageDefinition).permissions.v1 as any;

const client = new permissionsService.PermissionsService('api.perms.io:443', 
    grpc.credentials.createSsl());

interface CreatePermissionRequest {
    project_name: string;
    name: string;
    description: string;
}

function createPermission(): void {
    const request: CreatePermissionRequest = {
        project_name: 'production',
        name: 'document.read',
        description: 'Allows reading documents'
    };

    client.CreatePermission(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }
        console.log(`Created permission: ${response.name} (ID: ${response.id})`);
    });
}

Get Permission

Retrieves details of a specific permission.

Required Permission: permission.get on /organisation/{organisation_id}/permission/{name}

curl -X GET "https://api.perms.io/permissions-service/v1/permissions/{name}?project_name=production" \
  -H "Authorization: Bearer YOUR_API_KEY"
func getPermission(client permissionsv1.PermissionsServiceClient, projectName, permissionName string) {
    req := &permissionsv1.GetPermissionRequest{
        ProjectName: projectName,
        Name:        permissionName,
    }

    resp, err := client.GetPermission(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to get permission: %v", err)
    }

    log.Printf("Permission: %s - %s", resp.Name, resp.Description)
}
async fn get_permission(client: &mut PermissionsServiceClient<Channel>, project_name: &str, permission_name: &str) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(GetPermissionRequest {
        project_name: project_name.to_string(),
        name: permission_name.to_string(),
    });

    let response = client.get_permission(request).await?;
    let permission = response.get_ref();
    println!("Permission: {} - {}", permission.name, permission.description);

    Ok(())
}
def get_permission(project_name: str, permission_name: str):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.GetPermissionRequest(
        project_name=project_name,
        name=permission_name
    )

    response = client.GetPermission(request)
    print(f"Permission: {response.name} - {response.description}")
public void getPermission(String projectName, String permissionName) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    GetPermissionRequest request = GetPermissionRequest.newBuilder()
            .setProjectName(projectName)
            .setName(permissionName)
            .build();

    GetPermissionResponse response = client.getPermission(request);
    System.out.println("Permission: " + response.getName() + " - " + response.getDescription());

    channel.shutdown();
}
interface GetPermissionRequest {
    project_name: string;
    name: string;
}

function getPermission(projectName: string, permissionName: string): void {
    const request: GetPermissionRequest = {
        project_name: projectName,
        name: permissionName
    };

    client.GetPermission(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }
        console.log(`Permission: ${response.name} - ${response.description}`);
    });
}

Update Permission

Updates an existing permission.

Required Permission: permission.update on /organisation/{organisation_id}/permission/{name}

curl -X PUT "https://api.perms.io/permissions-service/v1/permissions/{name}" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_name": "production",
    "new_name": "document.read",
    "description": "Updated description for reading documents"
  }'
func updatePermission(client permissionsv1.PermissionsServiceClient, projectName, permissionName string) {
    req := &permissionsv1.UpdatePermissionRequest{
        ProjectName: projectName,
        Name:        permissionName,
        NewName:     "document.read",
        Description: "Updated description for reading documents",
    }

    resp, err := client.UpdatePermission(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to update permission: %v", err)
    }

    log.Printf("Updated permission: %s", resp.Name)
}
async fn update_permission(client: &mut PermissionsServiceClient<Channel>, project_name: &str, permission_name: &str) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(UpdatePermissionRequest {
        project_name: project_name.to_string(),
        name: permission_name.to_string(),
        new_name: Some("document.read".to_string()),
        description: "Updated description for reading documents".to_string(),
    });

    let response = client.update_permission(request).await?;
    println!("Updated permission: {}", response.get_ref().name);

    Ok(())
}
def update_permission(project_name: str, permission_name: str):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.UpdatePermissionRequest(
        project_name=project_name,
        name=permission_name,
        new_name="document.read",
        description="Updated description for reading documents"
    )

    response = client.UpdatePermission(request)
    print(f"Updated permission: {response.name}")
public void updatePermission(String projectName, String permissionName) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    UpdatePermissionRequest request = UpdatePermissionRequest.newBuilder()
            .setProjectName(projectName)
            .setName(permissionName)
            .setNewName("document.read")
            .setDescription("Updated description for reading documents")
            .build();

    UpdatePermissionResponse response = client.updatePermission(request);
    System.out.println("Updated permission: " + response.getName());

    channel.shutdown();
}
interface UpdatePermissionRequest {
    project_name: string;
    name: string;
    new_name?: string;
    description: string;
}

function updatePermission(projectName: string, permissionName: string): void {
    const request: UpdatePermissionRequest = {
        project_name: projectName,
        name: permissionName,
        new_name: 'document.read',
        description: 'Updated description for reading documents'
    };

    client.UpdatePermission(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }
        console.log(`Updated permission: ${response.name}`);
    });
}

Delete Permission

Deletes a permission. This operation is idempotent.

Required Permission: permission.delete on /organisation/{organisation_id}/permission/{name}

curl -X DELETE "https://api.perms.io/permissions-service/v1/permissions/{name}?project_name=production" \
  -H "Authorization: Bearer YOUR_API_KEY"
func deletePermission(client permissionsv1.PermissionsServiceClient, projectName, permissionName string) {
    req := &permissionsv1.DeletePermissionRequest{
        ProjectName: projectName,
        Name:        permissionName,
    }

    _, err := client.DeletePermission(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to delete permission: %v", err)
    }

    log.Printf("Deleted permission: %s", permissionName)
}
async fn delete_permission(client: &mut PermissionsServiceClient<Channel>, project_name: &str, permission_name: &str) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(DeletePermissionRequest {
        project_name: project_name.to_string(),
        name: permission_name.to_string(),
    });

    client.delete_permission(request).await?;
    println!("Deleted permission: {}", permission_name);

    Ok(())
}
def delete_permission(project_name: str, permission_name: str):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.DeletePermissionRequest(
        project_name=project_name,
        name=permission_name
    )

    client.DeletePermission(request)
    print(f"Deleted permission: {permission_name}")
public void deletePermission(String projectName, String permissionName) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    DeletePermissionRequest request = DeletePermissionRequest.newBuilder()
            .setProjectName(projectName)
            .setName(permissionName)
            .build();

    client.deletePermission(request);
    System.out.println("Deleted permission: " + permissionName);

    channel.shutdown();
}
interface DeletePermissionRequest {
    project_name: string;
    name: string;
}

function deletePermission(projectName: string, permissionName: string): void {
    const request: DeletePermissionRequest = {
        project_name: projectName,
        name: permissionName
    };

    client.DeletePermission(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }
        console.log(`Deleted permission: ${permissionName}`);
    });
}

List Permissions

Lists all permissions within a project with optional search and pagination.

Required Permission: permission.list on /organisation/{organisation_id}

curl -X GET "https://api.perms.io/permissions-service/v1/permissions?project_name=production&limit=10&cursor=abc123&search=document" \
  -H "Authorization: Bearer YOUR_API_KEY"
func listPermissions(client permissionsv1.PermissionsServiceClient, projectName string) {
    req := &permissionsv1.ListPermissionsRequest{
        ProjectName: projectName,
        Pagination: &paginationv1.PaginationRequest{
            Limit: proto.Int32(10),
        },
        Search: proto.String("document"),
    }

    resp, err := client.ListPermissions(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to list permissions: %v", err)
    }

    for _, permission := range resp.Permissions {
        log.Printf("Permission: %s - %s", permission.Name, permission.Description)
    }
}
async fn list_permissions(client: &mut PermissionsServiceClient<Channel>, project_name: &str) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(ListPermissionsRequest {
        project_name: project_name.to_string(),
        pagination: Some(PaginationRequest {
            limit: Some(10),
            cursor: None,
        }),
        search: Some("document".to_string()),
    });

    let response = client.list_permissions(request).await?;

    for permission in response.get_ref().permissions.iter() {
        println!("Permission: {} - {}", permission.name, permission.description);
    }

    Ok(())
}
def list_permissions(project_name: str):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.ListPermissionsRequest(
        project_name=project_name,
        pagination=pagination_pb2.PaginationRequest(limit=10),
        search="document"
    )

    response = client.ListPermissions(request)

    for permission in response.permissions:
        print(f"Permission: {permission.name} - {permission.description}")
public void listPermissions(String projectName) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    ListPermissionsRequest request = ListPermissionsRequest.newBuilder()
            .setProjectName(projectName)
            .setPagination(PaginationRequest.newBuilder().setLimit(10).build())
            .setSearch("document")
            .build();

    ListPermissionsResponse response = client.listPermissions(request);

    for (Permission permission : response.getPermissionsList()) {
        System.out.println("Permission: " + permission.getName() + " - " + permission.getDescription());
    }

    channel.shutdown();
}
interface ListPermissionsRequest {
    project_name: string;
    pagination?: {
        limit?: number;
        cursor?: string;
    };
    search?: string;
}

function listPermissions(projectName: string): void {
    const request: ListPermissionsRequest = {
        project_name: projectName,
        pagination: {
            limit: 10
        },
        search: "document"
    };

    client.ListPermissions(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }

        response.permissions.forEach((permission: any) => {
            console.log(`Permission: ${permission.name} - ${permission.description}`);
        });
    });
}

Role Management

Create Role

Creates a new role with a collection of permissions.

Required Permissions: - role.create on /organisation/{organisation_id} - permission.get on /organisation/{organisation_id}/permission/{permission_name} for each permission

curl -X POST "https://api.perms.io/permissions-service/v1/roles" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_name": "production",
    "name": "document_editor",
    "description": "Can read and write documents",
    "permissions": ["document.read", "document.write"]
  }'
func createRole(client permissionsv1.PermissionsServiceClient, projectName string, permissionNames []string) {
    req := &permissionsv1.CreateRoleRequest{
        ProjectName: projectName,
        Name:        "document_editor",
        Description: "Can read and write documents",
        Permissions: permissionNames,
    }

    resp, err := client.CreateRole(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to create role: %v", err)
    }

    log.Printf("Created role: %s (ID: %s)", resp.Name, resp.Id)
}
async fn create_role(client: &mut PermissionsServiceClient<Channel>, permission_names: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(CreateRoleRequest {
        project_name: "production".to_string(),
        name: "document_editor".to_string(),
        description: "Can read and write documents".to_string(),
        permissions: permission_names,
    });

    let response = client.create_role(request).await?;
    println!("Created role: {} (ID: {})", response.get_ref().name, response.get_ref().id);

    Ok(())
}
def create_role(permission_names: list[str]):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.CreateRoleRequest(
        project_name="production",
        name="document_editor",
        description="Can read and write documents",
        permissions=permission_names
    )

    response = client.CreateRole(request)
    print(f"Created role: {response.name} (ID: {response.id})")
public void createRole(List<String> permissionNames) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    CreateRoleRequest request = CreateRoleRequest.newBuilder()
            .setProjectName("production")
            .setName("document_editor")
            .setDescription("Can read and write documents")
            .addAllPermissions(permissionNames)
            .build();

    CreateRoleResponse response = client.createRole(request);
    System.out.println("Created role: " + response.getName() + " (ID: " + response.getId() + ")");

    channel.shutdown();
}
interface CreateRoleRequest {
    project_name: string;
    name: string;
    description: string;
    permissions: string[];
}

function createRole(permissionNames: string[]): void {
    const request: CreateRoleRequest = {
        project_name: 'production',
        name: 'document_editor',
        description: 'Can read and write documents',
        permissions: permissionNames
    };

    client.CreateRole(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }
        console.log(`Created role: ${response.name} (ID: ${response.id})`);
    });
}

Get Role

Retrieves details of a specific role, including all associated permissions.

Required Permission: role.get on /organisation/{organisation_id}/role/{id}

curl -X GET "https://api.perms.io/permissions-service/v1/roles/{id}?project_name=production" \
  -H "Authorization: Bearer YOUR_API_KEY"
func getRole(client permissionsv1.PermissionsServiceClient, projectName, roleId string) {
    req := &permissionsv1.GetRoleRequest{
        ProjectName: projectName,
        Id:          roleId,
    }

    resp, err := client.GetRole(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to get role: %v", err)
    }

    log.Printf("Role: %s - %s (Permissions: %d)", resp.Name, resp.Description, len(resp.Permissions))
}
async fn get_role(client: &mut PermissionsServiceClient<Channel>, project_name: &str, role_id: &str) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(GetRoleRequest {
        project_name: project_name.to_string(),
        id: role_id.to_string(),
    });

    let response = client.get_role(request).await?;
    let role = response.get_ref();
    println!("Role: {} - {} (Permissions: {})", role.name, role.description, role.permissions.len());

    Ok(())
}
def get_role(project_name: str, role_id: str):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.GetRoleRequest(
        project_name=project_name,
        id=role_id
    )

    response = client.GetRole(request)
    print(f"Role: {response.name} - {response.description} (Permissions: {len(response.permissions)})")
public void getRole(String projectName, String roleId) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    GetRoleRequest request = GetRoleRequest.newBuilder()
            .setProjectName(projectName)
            .setId(roleId)
            .build();

    GetRoleResponse response = client.getRole(request);
    System.out.println("Role: " + response.getName() + " - " + response.getDescription() + 
            " (Permissions: " + response.getPermissionsList().size() + ")");

    channel.shutdown();
}
interface GetRoleRequest {
    project_name: string;
    id: string;
}

function getRole(projectName: string, roleId: string): void {
    const request: GetRoleRequest = {
        project_name: projectName,
        id: roleId
    };

    client.GetRole(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }
        console.log(`Role: ${response.name} - ${response.description} (Permissions: ${response.permissions.length})`);
    });
}

Update Role

Updates an existing role's permissions and metadata.

Required Permission: role.update on /organisation/{organisation_id}/role/{id}

curl -X PUT "https://api.perms.io/permissions-service/v1/roles/{id}" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_name": "production",
    "name": "document_editor",
    "description": "Updated description",
    "permissions": ["document.read", "document.write", "document.delete"]
  }'
func updateRole(client permissionsv1.PermissionsServiceClient, projectName, roleId string, permissionNames []string) {
    req := &permissionsv1.UpdateRoleRequest{
        ProjectName: projectName,
        Id:          roleId,
        Name:        "document_editor",
        Description: "Updated description",
        Permissions: permissionNames,
    }

    resp, err := client.UpdateRole(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to update role: %v", err)
    }

    log.Printf("Updated role: %s", resp.Name)
}
async fn update_role(client: &mut PermissionsServiceClient<Channel>, project_name: &str, role_id: &str, permission_names: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(UpdateRoleRequest {
        project_name: project_name.to_string(),
        id: role_id.to_string(),
        name: "document_editor".to_string(),
        description: "Updated description".to_string(),
        permissions: permission_names,
    });

    let response = client.update_role(request).await?;
    println!("Updated role: {}", response.get_ref().name);

    Ok(())
}
def update_role(project_name: str, role_id: str, permission_names: list[str]):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.UpdateRoleRequest(
        project_name=project_name,
        id=role_id,
        name="document_editor",
        description="Updated description",
        permissions=permission_names
    )

    response = client.UpdateRole(request)
    print(f"Updated role: {response.name}")
public void updateRole(String projectName, String roleId, List<String> permissionNames) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    UpdateRoleRequest request = UpdateRoleRequest.newBuilder()
            .setProjectName(projectName)
            .setId(roleId)
            .setName("document_editor")
            .setDescription("Updated description")
            .addAllPermissions(permissionNames)
            .build();

    UpdateRoleResponse response = client.updateRole(request);
    System.out.println("Updated role: " + response.getName());

    channel.shutdown();
}
interface UpdateRoleRequest {
    project_name: string;
    id: string;
    name: string;
    description: string;
    permissions: string[];
}

function updateRole(projectName: string, roleId: string, permissionNames: string[]): void {
    const request: UpdateRoleRequest = {
        project_name: projectName,
        id: roleId,
        name: 'document_editor',
        description: 'Updated description',
        permissions: permissionNames
    };

    client.UpdateRole(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }
        console.log(`Updated role: ${response.name}`);
    });
}

Delete Role

Deletes a role. This operation is idempotent.

Required Permission: role.delete on /organisation/{organisation_id}/role/{id}

curl -X DELETE "https://api.perms.io/permissions-service/v1/roles/{id}?project_name=production" \
  -H "Authorization: Bearer YOUR_API_KEY"
func deleteRole(client permissionsv1.PermissionsServiceClient, projectName, roleId string) {
    req := &permissionsv1.DeleteRoleRequest{
        ProjectName: projectName,
        Id:          roleId,
    }

    _, err := client.DeleteRole(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to delete role: %v", err)
    }

    log.Printf("Deleted role: %s", roleId)
}
async fn delete_role(client: &mut PermissionsServiceClient<Channel>, project_name: &str, role_id: &str) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(DeleteRoleRequest {
        project_name: project_name.to_string(),
        id: role_id.to_string(),
    });

    client.delete_role(request).await?;
    println!("Deleted role: {}", role_id);

    Ok(())
}
def delete_role(project_name: str, role_id: str):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.DeleteRoleRequest(
        project_name=project_name,
        id=role_id
    )

    client.DeleteRole(request)
    print(f"Deleted role: {role_id}")
public void deleteRole(String projectName, String roleId) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    DeleteRoleRequest request = DeleteRoleRequest.newBuilder()
            .setProjectName(projectName)
            .setId(roleId)
            .build();

    client.deleteRole(request);
    System.out.println("Deleted role: " + roleId);

    channel.shutdown();
}
interface DeleteRoleRequest {
    project_name: string;
    id: string;
}

function deleteRole(projectName: string, roleId: string): void {
    const request: DeleteRoleRequest = {
        project_name: projectName,
        id: roleId
    };

    client.DeleteRole(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }
        console.log(`Deleted role: ${roleId}`);
    });
}

List Roles

Lists all roles within a project with optional search and pagination.

Required Permission: role.list on /organisation/{organisation_id}

curl -X GET "https://api.perms.io/permissions-service/v1/roles?project_name=production&limit=10&search=editor" \
  -H "Authorization: Bearer YOUR_API_KEY"
func listRoles(client permissionsv1.PermissionsServiceClient, projectName string) {
    req := &permissionsv1.ListRolesRequest{
        ProjectName: projectName,
        Pagination: &paginationv1.PaginationRequest{
            Limit: proto.Int32(10),
        },
        Search: proto.String("editor"),
    }

    resp, err := client.ListRoles(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to list roles: %v", err)
    }

    for _, role := range resp.Roles {
        log.Printf("Role: %s - %s (Permissions: %d)", role.Name, role.Description, len(role.Permissions))
    }
}
async fn list_roles(client: &mut PermissionsServiceClient<Channel>, project_name: &str) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(ListRolesRequest {
        project_name: project_name.to_string(),
        pagination: Some(PaginationRequest {
            limit: Some(10),
            cursor: None,
        }),
        search: Some("editor".to_string()),
    });

    let response = client.list_roles(request).await?;

    for role in response.get_ref().roles.iter() {
        println!("Role: {} - {} (Permissions: {})", role.name, role.description, role.permissions.len());
    }

    Ok(())
}
def list_roles(project_name: str):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.ListRolesRequest(
        project_name=project_name,
        pagination=pagination_pb2.PaginationRequest(limit=10),
        search="editor"
    )

    response = client.ListRoles(request)

    for role in response.roles:
        print(f"Role: {role.name} - {role.description} (Permissions: {len(role.permissions)})")
public void listRoles(String projectName) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    ListRolesRequest request = ListRolesRequest.newBuilder()
            .setProjectName(projectName)
            .setPagination(PaginationRequest.newBuilder().setLimit(10).build())
            .setSearch("editor")
            .build();

    ListRolesResponse response = client.listRoles(request);

    for (Role role : response.getRolesList()) {
        System.out.println("Role: " + role.getName() + " - " + role.getDescription() + 
                " (Permissions: " + role.getPermissionsList().size() + ")");
    }

    channel.shutdown();
}
interface ListRolesRequest {
    project_name: string;
    pagination?: {
        limit?: number;
        cursor?: string;
    };
    search?: string;
}

function listRoles(projectName: string): void {
    const request: ListRolesRequest = {
        project_name: projectName,
        pagination: {
            limit: 10
        },
        search: "editor"
    };

    client.ListRoles(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }

        response.roles.forEach((role: any) => {
            console.log(`Role: ${role.name} - ${role.description} (Permissions: ${role.permissions.length})`);
        });
    });
}

Access Control

Grant Permissions and Roles

Grants permissions and roles to a principal on a specific resource. This operation is additive - it won't remove existing permissions.

Required Permissions: - permission.get on /organisation/{organisation_id}/permission/{permission_name} for each permission - role.get on /organisation/{organisation_id}/role/{role_id} for each role
- principal.assignment.create on /organisation/{organisation_id}/principal/{user_id}

curl -X POST "https://api.perms.io/permissions-service/v1/permissions/assign" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_name": "production",
    "user_id": "usr_123",
    "resource_uri": "/project/456/documents/789",
    "permissions": ["document.read"],
    "roles": ["role_456"]
  }'
func grantPermissions(client permissionsv1.PermissionsServiceClient, userId, resourceUri string, permissions, roles []string) {
    req := &permissionsv1.GrantUserPermissionsAndRolesOnResourceRequest{
        ProjectName: "production",
        UserId:      userId,
        ResourceUri: resourceUri,
        Permissions: permissions,
        Roles:       roles,
    }

    _, err := client.GrantUserPermissionsAndRolesOnResource(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to grant permissions: %v", err)
    }

    log.Printf("Granted permissions to user %s on resource %s", userId, resourceUri)
}
async fn grant_permissions(client: &mut PermissionsServiceClient<Channel>, user_id: &str, resource_uri: &str, permissions: Vec<String>, roles: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(GrantUserPermissionsAndRolesOnResourceRequest {
        project_name: "production".to_string(),
        user_id: user_id.to_string(),
        resource_uri: resource_uri.to_string(),
        permissions,
        roles,
    });

    client.grant_user_permissions_and_roles_on_resource(request).await?;
    println!("Granted permissions to user {} on resource {}", user_id, resource_uri);

    Ok(())
}
def grant_permissions(user_id: str, resource_uri: str, permissions: list[str], roles: list[str]):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.GrantUserPermissionsAndRolesOnResourceRequest(
        project_name="production",
        user_id=user_id,
        resource_uri=resource_uri,
        permissions=permissions,
        roles=roles
    )

    client.GrantUserPermissionsAndRolesOnResource(request)
    print(f"Granted permissions to user {user_id} on resource {resource_uri}")
public void grantPermissions(String userId, String resourceUri, List<String> permissions, List<String> roles) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    GrantUserPermissionsAndRolesOnResourceRequest request = 
            GrantUserPermissionsAndRolesOnResourceRequest.newBuilder()
                    .setProjectName("production")
                    .setUserId(userId)
                    .setResourceUri(resourceUri)
                    .addAllPermissions(permissions)
                    .addAllRoles(roles)
                    .build();

    client.grantUserPermissionsAndRolesOnResource(request);
    System.out.println("Granted permissions to user " + userId + " on resource " + resourceUri);

    channel.shutdown();
}
interface GrantUserPermissionsAndRolesOnResourceRequest {
    project_name: string;
    user_id: string;
    resource_uri: string;
    permissions: string[];
    roles: string[];
}

function grantPermissions(userId: string, resourceUri: string, permissions: string[], roles: string[]): void {
    const request: GrantUserPermissionsAndRolesOnResourceRequest = {
        project_name: 'production',
        user_id: userId,
        resource_uri: resourceUri,
        permissions,
        roles
    };

    client.GrantUserPermissionsAndRolesOnResource(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }
        console.log(`Granted permissions to user ${userId} on resource ${resourceUri}`);
    });
}

Revoke Permissions and Roles

Revokes specific permissions and roles from a principal on a resource.

Required Permission: principal.assignment.delete on the resource URI

curl -X POST "https://api.perms.io/permissions-service/v1/permissions/revoke" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_name": "production",
    "user_id": "usr_123",
    "resource_uri": "/project/456/documents/789",
    "permissions": ["document.read"],
    "roles": ["role_456"]
  }'
func revokePermissions(client permissionsv1.PermissionsServiceClient, userId, resourceUri string, permissions, roles []string) {
    req := &permissionsv1.RevokeUserPermissionsAndRolesOnResourceRequest{
        ProjectName: "production",
        UserId:      userId,
        ResourceUri: resourceUri,
        Permissions: permissions,
        Roles:       roles,
    }

    _, err := client.RevokeUserPermissionsAndRolesOnResource(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to revoke permissions: %v", err)
    }

    log.Printf("Revoked permissions from user %s on resource %s", userId, resourceUri)
}
async fn revoke_permissions(client: &mut PermissionsServiceClient<Channel>, user_id: &str, resource_uri: &str, permissions: Vec<String>, roles: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(RevokeUserPermissionsAndRolesOnResourceRequest {
        project_name: "production".to_string(),
        user_id: user_id.to_string(),
        resource_uri: resource_uri.to_string(),
        permissions,
        roles,
    });

    client.revoke_user_permissions_and_roles_on_resource(request).await?;
    println!("Revoked permissions from user {} on resource {}", user_id, resource_uri);

    Ok(())
}
def revoke_permissions(user_id: str, resource_uri: str, permissions: list[str], roles: list[str]):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.RevokeUserPermissionsAndRolesOnResourceRequest(
        project_name="production",
        user_id=user_id,
        resource_uri=resource_uri,
        permissions=permissions,
        roles=roles
    )

    client.RevokeUserPermissionsAndRolesOnResource(request)
    print(f"Revoked permissions from user {user_id} on resource {resource_uri}")
public void revokePermissions(String userId, String resourceUri, List<String> permissions, List<String> roles) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    RevokeUserPermissionsAndRolesOnResourceRequest request = 
            RevokeUserPermissionsAndRolesOnResourceRequest.newBuilder()
                    .setProjectName("production")
                    .setUserId(userId)
                    .setResourceUri(resourceUri)
                    .addAllPermissions(permissions)
                    .addAllRoles(roles)
                    .build();

    client.revokeUserPermissionsAndRolesOnResource(request);
    System.out.println("Revoked permissions from user " + userId + " on resource " + resourceUri);

    channel.shutdown();
}
interface RevokeUserPermissionsAndRolesOnResourceRequest {
    project_name: string;
    user_id: string;
    resource_uri: string;
    permissions: string[];
    roles: string[];
}

function revokePermissions(userId: string, resourceUri: string, permissions: string[], roles: string[]): void {
    const request: RevokeUserPermissionsAndRolesOnResourceRequest = {
        project_name: 'production',
        user_id: userId,
        resource_uri: resourceUri,
        permissions,
        roles
    };

    client.RevokeUserPermissionsAndRolesOnResource(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }
        console.log(`Revoked permissions from user ${userId} on resource ${resourceUri}`);
    });
}

Get Principal's Permissions

Retrieves all permissions and roles assigned to a principal on a specific resource.

Required Permission: principal.assignment.get on the resource URI

curl -X GET "https://api.perms.io/permissions-service/v1/permissions/user/{user_id}/resource/{resource_uri}?project_name=production" \
  -H "Authorization: Bearer YOUR_API_KEY"
func getUserPermissions(client permissionsv1.PermissionsServiceClient, userId, resourceUri string) {
    req := &permissionsv1.GetUserPermissionsAndRolesOnResourceRequest{
        ProjectName: "production",
        UserId:      userId,
        ResourceUri: resourceUri,
    }

    resp, err := client.GetUserPermissionsAndRolesOnResource(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to get user permissions: %v", err)
    }

    log.Printf("User has %d permissions and %d roles on resource %s", 
        len(resp.Permissions), len(resp.Roles), resourceUri)
}
async fn get_user_permissions(client: &mut PermissionsServiceClient<Channel>, user_id: &str, resource_uri: &str) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(GetUserPermissionsAndRolesOnResourceRequest {
        project_name: "production".to_string(),
        user_id: user_id.to_string(),
        resource_uri: resource_uri.to_string(),
    });

    let response = client.get_user_permissions_and_roles_on_resource(request).await?;
    let result = response.get_ref();
    println!("User has {} permissions and {} roles on resource {}", 
        result.permissions.len(), result.roles.len(), resource_uri);

    Ok(())
}
def get_user_permissions(user_id: str, resource_uri: str):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.GetUserPermissionsAndRolesOnResourceRequest(
        project_name="production",
        user_id=user_id,
        resource_uri=resource_uri
    )

    response = client.GetUserPermissionsAndRolesOnResource(request)
    print(f"User has {len(response.permissions)} permissions and {len(response.roles)} roles on resource {resource_uri}")
public void getUserPermissions(String userId, String resourceUri) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    GetUserPermissionsAndRolesOnResourceRequest request = 
            GetUserPermissionsAndRolesOnResourceRequest.newBuilder()
                    .setProjectName("production")
                    .setUserId(userId)
                    .setResourceUri(resourceUri)
                    .build();

    GetUserPermissionsAndRolesOnResourceResponse response = 
            client.getUserPermissionsAndRolesOnResource(request);
    System.out.println("User has " + response.getPermissionsList().size() + 
            " permissions and " + response.getRolesList().size() + 
            " roles on resource " + resourceUri);

    channel.shutdown();
}
interface GetUserPermissionsAndRolesOnResourceRequest {
    project_name: string;
    user_id: string;
    resource_uri: string;
}

function getUserPermissions(userId: string, resourceUri: string): void {
    const request: GetUserPermissionsAndRolesOnResourceRequest = {
        project_name: 'production',
        user_id: userId,
        resource_uri: resourceUri
    };

    client.GetUserPermissionsAndRolesOnResource(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }
        console.log(`User has ${response.permissions.length} permissions and ${response.roles.length} roles on resource ${resourceUri}`);
    });
}

User Management

List Users

Lists all users within a project.

Required Permission: principal.list on /organisation/{organisation_id}

curl -X GET "https://api.perms.io/permissions-service/v1/permissions/principals?project_name=production&limit=10&search=user" \
  -H "Authorization: Bearer YOUR_API_KEY"
func listPrincipals(client permissionsv1.PermissionsServiceClient, projectName string) {
    req := &permissionsv1.ListPrincipalsRequest{
        ProjectName: projectName,
        Pagination: &paginationv1.PaginationRequest{
            Limit: proto.Int32(10),
        },
    }

    resp, err := client.ListPrincipals(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to list principals: %v", err)
    }

    for _, principal := range resp.Principals {
        log.Printf("Principal: %s", principal)
    }
}
async fn list_principals(client: &mut PermissionsServiceClient<Channel>, project_name: &str) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(ListPrincipalsRequest {
        project_name: project_name.to_string(),
        pagination: Some(PaginationRequest {
            limit: Some(10),
            cursor: None,
        }),
    });

    let response = client.list_principals(request).await?;

    for principal in response.get_ref().principals.iter() {
        println!("Principal: {}", principal);
    }

    Ok(())
}
def list_principals(project_name: str):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.ListPrincipalsRequest(
        project_name=project_name,
        pagination=pagination_pb2.PaginationRequest(limit=10)
    )

    response = client.ListPrincipals(request)

    for principal in response.principals:
        print(f"Principal: {principal}")
public void listPrincipals(String projectName) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    ListPrincipalsRequest request = ListPrincipalsRequest.newBuilder()
            .setProjectName(projectName)
            .setPagination(PaginationRequest.newBuilder().setLimit(10).build())
            .build();

    ListPrincipalsResponse response = client.listPrincipals(request);

    for (String principal : response.getPrincipalsList()) {
        System.out.println("Principal: " + principal);
    }

    channel.shutdown();
}
interface ListPrincipalsRequest {
    project_name: string;
    pagination?: {
        limit?: number;
        cursor?: string;
    };
}

function listPrincipals(projectName: string): void {
    const request: ListPrincipalsRequest = {
        project_name: projectName,
        pagination: {
            limit: 10
        }
    };

    client.ListPrincipals(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }

        response.principals.forEach((principal: string) => {
            console.log(`Principal: ${principal}`);
        });
    });
}

Get User Permissions

Retrieves all permission assignments for a specific user within an organization.

Required Permission: principal.assignment.list on /organisation/{organisation_id}/principal/{principal_id}

curl -X GET "https://api.perms.io/permissions-service/v1/permissions/assignments/principal/{principal_id}?project_name=production" \
  -H "Authorization: Bearer YOUR_API_KEY"
func getAllAssignments(client permissionsv1.PermissionsServiceClient, projectName, principalId string) {
    req := &permissionsv1.GetAllAssignmentsForPrincipalRequest{
        ProjectName: projectName,
        PrincipalId: principalId,
    }

    resp, err := client.GetAllAssignmentsForPrincipal(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to get assignments: %v", err)
    }

    for _, assignment := range resp.Assignments {
        log.Printf("Resource: %s, Permissions: %d, Roles: %d", 
            assignment.ResourceUri, len(assignment.Permissions), len(assignment.Roles))
    }
}
async fn get_all_assignments(client: &mut PermissionsServiceClient<Channel>, project_name: &str, principal_id: &str) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(GetAllAssignmentsForPrincipalRequest {
        project_name: project_name.to_string(),
        principal_id: principal_id.to_string(),
    });

    let response = client.get_all_assignments_for_principal(request).await?;

    for assignment in response.get_ref().assignments.iter() {
        println!("Resource: {}, Permissions: {}, Roles: {}", 
            assignment.resource_uri, assignment.permissions.len(), assignment.roles.len());
    }

    Ok(())
}
def get_all_assignments(project_name: str, principal_id: str):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.GetAllAssignmentsForPrincipalRequest(
        project_name=project_name,
        principal_id=principal_id
    )

    response = client.GetAllAssignmentsForPrincipal(request)

    for assignment in response.assignments:
        print(f"Resource: {assignment.resource_uri}, Permissions: {len(assignment.permissions)}, Roles: {len(assignment.roles)}")
public void getAllAssignments(String projectName, String principalId) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    GetAllAssignmentsForPrincipalRequest request = 
            GetAllAssignmentsForPrincipalRequest.newBuilder()
                    .setProjectName(projectName)
                    .setPrincipalId(principalId)
                    .build();

    GetAllAssignmentsForPrincipalResponse response = 
            client.getAllAssignmentsForPrincipal(request);

    for (Assignment assignment : response.getAssignmentsList()) {
        System.out.println("Resource: " + assignment.getResourceUri() + 
                ", Permissions: " + assignment.getPermissionsList().size() + 
                ", Roles: " + assignment.getRolesList().size());
    }

    channel.shutdown();
}
interface GetAllAssignmentsForPrincipalRequest {
    project_name: string;
    principal_id: string;
}

function getAllAssignments(projectName: string, principalId: string): void {
    const request: GetAllAssignmentsForPrincipalRequest = {
        project_name: projectName,
        principal_id: principalId
    };

    client.GetAllAssignmentsForPrincipal(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }

        response.assignments.forEach((assignment: any) => {
            console.log(`Resource: ${assignment.resource_uri}, Permissions: ${assignment.permissions.length}, Roles: ${assignment.roles.length}`);
        });
    });
}

Permission Checking

Check Permissions

The core permission checking endpoint that determines if a principal has specific permissions on resources.

curl -X POST "https://api.perms.io/permissions-service/v1/permissions/check" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project_name": "production",
    "principal_id": "usr_123",
    "resource_uris": ["/project/456/documents/789", "/project/456/documents/101"],
    "permissions": ["document.read", "document.write"]
  }'
func checkPermissions(client permissionsv1.PermissionsServiceClient, principalId string, resourceUris, permissions []string) {
    req := &permissionsv1.CheckPermissionRequest{
        ProjectName:  "production",
        PrincipalId:  principalId,
        ResourceUris: resourceUris,
        Permissions:  permissions,
    }

    resp, err := client.Check(context.Background(), req)
    if err != nil {
        log.Fatalf("Failed to check permissions: %v", err)
    }

    if resp.Passed {
        log.Printf("Permission check passed for principal %s", principalId)
    } else {
        log.Printf("Permission check failed:")
        for _, missing := range resp.MissingPermissions {
            log.Printf("  Resource %s missing: %v", missing.ResourceUri, missing.MissingPermissions)
        }
    }
}
async fn check_permissions(client: &mut PermissionsServiceClient<Channel>, principal_id: &str, resource_uris: Vec<String>, permissions: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
    let request = tonic::Request::new(CheckPermissionRequest {
        project_name: "production".to_string(),
        principal_id: principal_id.to_string(),
        resource_uris,
        permissions,
    });

    let response = client.check(request).await?;
    let result = response.get_ref();

    if result.passed {
        println!("Permission check passed for principal {}", principal_id);
    } else {
        println!("Permission check failed:");
        for missing in result.missing_permissions.iter() {
            println!("  Resource {} missing: {:?}", missing.resource_uri, missing.missing_permissions);
        }
    }

    Ok(())
}
def check_permissions(principal_id: str, resource_uris: list[str], permissions: list[str]):
    channel = grpc.secure_channel('api.perms.io:443', grpc.ssl_channel_credentials())
    client = permissions_service_pb2_grpc.PermissionsServiceStub(channel)

    request = permissions_service_pb2.CheckPermissionRequest(
        project_name="production",
        principal_id=principal_id,
        resource_uris=resource_uris,
        permissions=permissions
    )

    response = client.Check(request)

    if response.passed:
        print(f"Permission check passed for principal {principal_id}")
    else:
        print("Permission check failed:")
        for missing in response.missing_permissions:
            print(f"  Resource {missing.resource_uri} missing: {missing.missing_permissions}")
public void checkPermissions(String principalId, List<String> resourceUris, List<String> permissions) {
    ManagedChannel channel = ManagedChannelBuilder.forAddress("api.perms.io", 443)
            .useTransportSecurity()
            .build();

    PermissionsServiceGrpc.PermissionsServiceBlockingStub client = 
            PermissionsServiceGrpc.newBlockingStub(channel);

    CheckPermissionRequest request = CheckPermissionRequest.newBuilder()
            .setProjectName("production")
            .setPrincipalId(principalId)
            .addAllResourceUris(resourceUris)
            .addAllPermissions(permissions)
            .build();

    CheckPermissionResponse response = client.check(request);

    if (response.getPassed()) {
        System.out.println("Permission check passed for principal " + principalId);
    } else {
        System.out.println("Permission check failed:");
        for (MissingPermission missing : response.getMissingPermissionsList()) {
            System.out.println("  Resource " + missing.getResourceUri() + 
                    " missing: " + missing.getMissingPermissionsList());
        }
    }

    channel.shutdown();
}
interface CheckPermissionRequest {
    project_name: string;
    principal_id: string;
    resource_uris: string[];
    permissions: string[];
}

function checkPermissions(principalId: string, resourceUris: string[], permissions: string[]): void {
    const request: CheckPermissionRequest = {
        project_name: 'production',
        principal_id: principalId,
        resource_uris: resourceUris,
        permissions: permissions
    };

    client.Check(request, (error: grpc.ServiceError | null, response: any) => {
        if (error) {
            console.error('Error:', error);
            return;
        }

        if (response.passed) {
            console.log(`Permission check passed for principal ${principalId}`);
        } else {
            console.log('Permission check failed:');
            response.missing_permissions.forEach((missing: any) => {
                console.log(`  Resource ${missing.resource_uri} missing: ${missing.missing_permissions}`);
            });
        }
    });
}

Resource URI Patterns

Resource URIs follow a hierarchical pattern that enables inheritance:

URI Structure

/organisation/{org_id}/project/{project_name}/resource/{resource_id}

Examples

  • /project/456 - Top-level project resource
  • /project/456/documents - All documents in project 456
  • /project/456/documents/789 - Specific document 789
  • /user/123 - User resource
  • /user/123/profile - User's profile

Inheritance Rules

Permissions granted on parent resources automatically apply to child resources:

  • Permission on /project/456 applies to /project/456/documents/789
  • Permission on /user/123 applies to /user/123/profile

Common RBAC Patterns

Document Management System

// Create permissions
permissions := []string{"document.read", "document.write", "document.delete"}

// Create roles
editorRole := createRole("document_editor", []string{"document.read", "document.write"})
viewerRole := createRole("document_viewer", []string{"document.read"})
adminRole := createRole("document_admin", []string{"document.read", "document.write", "document.delete"})

// Grant permissions
grantPermissions("user_123", "/project/456/documents", []string{}, []string{editorRole.Id})
grantPermissions("user_456", "/project/456/documents/789", []string{}, []string{viewerRole.Id})

Multi-tenant Application

// Organization-level permissions
grantPermissions("user_123", "/organization/789", []string{}, []string{"org_admin"})

// Project-level permissions  
grantPermissions("user_456", "/organization/789/project/456", []string{}, []string{"project_manager"})

// Resource-specific permissions
grantPermissions("user_789", "/organization/789/project/456/resource/123", []string{"document.read"}, []string{})

Best Practices

1. Permission Naming

Use descriptive, hierarchical names: - document.read instead of read - user.profile.update instead of update - billing.invoice.create instead of create

2. Resource URI Design

Design URIs to reflect your application's hierarchy: - Use consistent patterns across resources - Consider future scalability - Group related resources under common parents

3. Role Management

  • Create roles for common permission sets
  • Use descriptive role names
  • Regularly audit role permissions
  • Consider role inheritance patterns

4. Performance Optimization

  • Batch permission checks when possible
  • Cache permission results where appropriate
  • Use specific resource URIs rather than wildcards
  • Consider permission inheritance in your URI design

5. Security Considerations

  • Always validate resource URIs
  • Use least privilege principle
  • Regularly audit permission assignments
  • Monitor for permission escalation

Error Handling

Common Error Codes

  • INVALID_ARGUMENT (3): Invalid request parameters
  • NOT_FOUND (5): Resource not found
  • PERMISSION_DENIED (7): Insufficient permissions
  • ALREADY_EXISTS (6): Resource already exists

Example Error Response

{
  "error": {
    "code": 7,
    "message": "Permission denied: missing required permission 'permission.create' on resource '/organisation/org_123'"
  }
}

Rate Limits

  • Permission Checks: 10,000 requests per minute
  • Management Operations: 1,000 requests per minute
  • Bulk Operations: 100 requests per minute

Monitoring and Metrics

The service provides metrics for: - Permission check latency - Grant/revoke operation counts - Principal assignment statistics - Resource access patterns

These metrics are available through the organisation dashboard and usage APIs.