Flow Logo

Resource APIs

Group Management API

Groups in Flow provide a powerful way to organize users and manage permissions at scale. This guide covers creating groups, managing members, setting up hierarchies, and using groups for access control.


Understanding Groups

Groups represent teams, departments, or any collection of users who need shared access to resources.

Group Structure

# A typical group contains:
group = {
    "id": 1,
    "name": "Smith Lab",
    "slug": "smith-lab",
    "description": "Dr. Smith's research laboratory",
    "created": "2024-01-01T10:00:00Z",
    "owner": {"id": 1, "username": "dr_smith"},
    "parent": {"id": 5, "name": "Biology Department"},
    "member_count": 12,
    "admin_count": 3,
    "metadata": {
        "institution": "University of Science",
        "department": "Biology",
        "pi": "Dr. Jane Smith",
        "focus": "Cancer genomics",
        "room": "Building A, Room 123"
    },
    "permissions": {
        "can_add_members": True,
        "can_remove_members": True,
        "can_edit": True
    }
}

Creating Groups

Basic Group Creation

# Python client
group = client.create_group(
    name="RNA-seq Analysis Team",
    description="Team focused on RNA-seq data analysis"
)

print(f"Created group: {group.slug}")

REST API Group Creation

curl -X POST https://api.flow.bio/groups/create \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "RNA-seq Analysis Team",
    "description": "Team focused on RNA-seq data analysis",
    "metadata": {
      "team_lead": "john_doe",
      "created_date": "2024-01-15"
    }
  }'

GraphQL Group Creation

mutation CreateGroup($input: CreateGroupInput!) {
  createGroup(input: $input) {
    id
    name
    slug
    description
    created
    owner {
      username
    }
  }
}

# Variables
{
  "input": {
    "name": "RNA-seq Analysis Team",
    "description": "Team focused on RNA-seq data analysis",
    "metadata": {
      "team_lead": "john_doe",
      "focus_area": "transcriptomics"
    }
  }
}

Create with Hierarchy

# Create parent group
department = client.create_group(
    name="Genomics Department",
    description="Main genomics research department"
)

# Create child groups
wet_lab = client.create_group(
    name="Wet Lab Team",
    description="Sample preparation and sequencing",
    parent=department.id
)

dry_lab = client.create_group(
    name="Dry Lab Team", 
    description="Computational analysis",
    parent=department.id
)

# Create sub-teams
rnaseq_team = client.create_group(
    name="RNA-seq Analysis",
    parent=dry_lab.id
)

chipseq_team = client.create_group(
    name="ChIP-seq Analysis",
    parent=dry_lab.id
)

Managing Group Members

Add Members

# Add single member
group.add_member("new_member@example.com")

# Add multiple members
group.add_members([
    "member1@example.com",
    "member2@example.com",
    "member3@example.com"
])

# Add with specific role
group.add_member("admin@example.com", role="admin")

REST API Member Management

# Add members
curl -X POST https://api.flow.bio/groups/smith-lab/members/add \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "users": ["user1", "user2"],
    "role": "member"
  }'

# Remove member
curl -X POST https://api.flow.bio/groups/smith-lab/members/remove \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "user": "former_member"
  }'

GraphQL Member Management

mutation AddGroupMembers($groupId: ID!, $users: [ID!]!, $role: GroupRole!) {
  addGroupMembers(groupId: $groupId, users: $users, role: $role) {
    id
    members {
      edges {
        node {
          id
          username
          role
          joinedAt
        }
      }
    }
  }
}

Member Roles

# Available roles
ROLES = {
    "member": "Can access shared resources",
    "admin": "Can manage group and members",
    "owner": "Full control including deletion"
}

# Update member role
group.update_member_role("member@example.com", "admin")

# List members by role
admins = group.get_members(role="admin")
members = group.get_members(role="member")

Group Hierarchies

Managing Parent-Child Relationships

# Get group hierarchy
def get_group_hierarchy(group_id):
    """Get full hierarchy for a group"""
    
    group = client.get_group(group_id)
    
    hierarchy = {
        "group": group,
        "parent_chain": [],
        "children": []
    }
    
    # Walk up to root
    current = group
    while current.parent:
        current = current.parent
        hierarchy["parent_chain"].append(current)
    
    # Get all children
    def get_children_recursive(parent):
        children = parent.get_children()
        result = []
        
        for child in children:
            result.append({
                "group": child,
                "children": get_children_recursive(child)
            })
        
        return result
    
    hierarchy["children"] = get_children_recursive(group)
    
    return hierarchy

Permission Inheritance

# Check effective permissions
def get_effective_permissions(user, resource):
    """Get user's highest permission from all groups"""
    
    user_groups = user.get_groups()
    max_permission = 0
    
    for group in user_groups:
        # Check direct group permissions
        perm = resource.get_group_permission(group.id)
        max_permission = max(max_permission, perm)
        
        # Check parent group permissions
        parent = group.parent
        while parent:
            parent_perm = resource.get_group_permission(parent.id)
            max_permission = max(max_permission, parent_perm)
            parent = parent.parent
    
    return max_permission

Retrieving Groups

Get Group Details

# Python client
group = client.get_group("smith-lab")  # by slug
# or
group = client.get_group(group_id=123)  # by ID

print(f"Name: {group.name}")
print(f"Members: {group.member_count}")
print(f"Created: {group.created}")

REST API Group Retrieval

curl -H "Authorization: Bearer <token>" \
  https://api.flow.bio/groups/smith-lab

GraphQL Group Query

query GetGroupDetails($slug: String!) {
  group(slug: $slug) {
    id
    name
    slug
    description
    created
    owner {
      id
      username
    }
    parent {
      id
      name
      slug
    }
    children {
      id
      name
      slug
    }
    memberCount
    adminCount
    members(limit: 100) {
      edges {
        node {
          id
          username
          email
          role
          joinedAt
        }
      }
    }
    metadata
    permissions {
      canAddMembers
      canRemoveMembers
      canEdit
      canDelete
    }
  }
}

List Groups

# Get user's groups
my_groups = client.get_my_groups()

# Get all accessible groups
all_groups = client.get_groups()

# Search groups
lab_groups = client.search_groups("lab")

# Get groups with filters
large_groups = client.get_groups(
    min_members=10,
    has_children=True
)

Group Administration

Update Group Information

# Update basic info
group.update(
    name="Updated Group Name",
    description="New description"
)

# Update metadata
group.update_metadata({
    "budget_code": "ABC123",
    "expiry_date": "2025-12-31",
    "contact_email": "lab@university.edu"
})

# Set group settings
group.update_settings({
    "auto_approve_members": False,
    "require_admin_approval": True,
    "member_can_invite": False
})

Transfer Ownership

# Transfer group ownership
group.transfer_ownership("new_owner@example.com")

# Batch transfer for user leaving
def transfer_all_groups(from_user, to_user):
    """Transfer all groups from one user to another"""
    
    groups = client.get_user_owned_groups(from_user)
    
    for group in groups:
        group.transfer_ownership(to_user)
        print(f"Transferred {group.name} to {to_user}")

Delete Groups

# Delete group (must be owner)
group.delete()

# Safe delete with checks
def safe_delete_group(group_id):
    """Delete group with safety checks"""
    
    group = client.get_group(group_id)
    
    # Check for shared resources
    shared_samples = client.search_samples(shared_with_groups=[group.slug])
    shared_projects = client.search_projects(shared_with_groups=[group.slug])
    
    if shared_samples or shared_projects:
        print(f"Warning: Group has {len(shared_samples)} samples and {len(shared_projects)} projects")
        confirm = input("Continue with deletion? (yes/no): ")
        
        if confirm.lower() != "yes":
            return False
    
    # Check for child groups
    if group.get_children():
        print("Error: Cannot delete group with child groups")
        return False
    
    group.delete()
    return True

Group Permissions

Managing Group Access

# Share resources with group
sample.share(groups=["smith-lab"], permission="read")
project.share(groups=["smith-lab"], permission="edit")
execution.share(groups=["smith-lab"], permission="read")

# Bulk share with group
client.bulk_share(
    resource_type="sample",
    resource_ids=[123, 124, 125],
    groups=["smith-lab"],
    permission="read"
)

Group-Based Access Control

def setup_lab_permissions(lab_group_slug):
    """Set up standard permissions for a lab"""
    
    group = client.get_group(lab_group_slug)
    
    # Create standard sub-groups
    pi_group = client.create_group(
        name=f"{group.name} - PIs",
        parent=group.id
    )
    
    postdoc_group = client.create_group(
        name=f"{group.name} - Postdocs",
        parent=group.id
    )
    
    student_group = client.create_group(
        name=f"{group.name} - Students",
        parent=group.id
    )
    
    # Set up permission templates
    permissions = {
        pi_group.slug: "share",      # Full access
        postdoc_group.slug: "edit",  # Can modify
        student_group.slug: "read"   # View only
    }
    
    return permissions

Group Analytics

Group Statistics

def get_group_statistics(group_slug):
    """Get comprehensive group statistics"""
    
    group = client.get_group(group_slug)
    members = group.get_members()
    
    stats = {
        "members": {
            "total": len(members),
            "admins": len([m for m in members if m.role == "admin"]),
            "active": len([m for m in members if m.last_login_days < 30])
        },
        "resources": {
            "samples": 0,
            "projects": 0,
            "executions": 0,
            "total_storage_gb": 0
        },
        "activity": {
            "samples_created_this_month": 0,
            "executions_this_month": 0
        }
    }
    
    # Count shared resources
    stats["resources"]["samples"] = len(
        client.search_samples(shared_with_groups=[group.slug])
    )
    stats["resources"]["projects"] = len(
        client.search_projects(shared_with_groups=[group.slug])
    )
    
    return stats

Group Activity Report

def generate_group_activity_report(group_slug, days=30):
    """Generate activity report for group"""
    
    group = client.get_group(group_slug)
    since = datetime.now() - timedelta(days=days)
    
    report = {
        "group": group.name,
        "period": f"Last {days} days",
        "members": {},
        "resources_created": {
            "samples": [],
            "projects": [],
            "executions": []
        }
    }
    
    # Get member activity
    for member in group.get_members():
        member_activity = {
            "last_login": member.last_login,
            "samples_created": 0,
            "executions_run": 0
        }
        
        # Count samples created
        samples = client.search_samples(
            owner=member.username,
            created_after=since
        )
        member_activity["samples_created"] = len(samples)
        
        report["members"][member.username] = member_activity
    
    return report

Group Workflows

Onboarding New Lab Members

def onboard_lab_member(username, lab_group_slug, role="member"):
    """Complete onboarding process for new lab member"""
    
    # Add to group
    group = client.get_group(lab_group_slug)
    group.add_member(username, role=role)
    
    # Share standard resources
    standard_resources = group.metadata.get("onboarding_resources", [])
    for resource_id in standard_resources:
        resource = client.get_resource(resource_id)
        resource.share(users=[username], permission="read")
    
    # Create welcome project
    welcome_project = client.create_project(
        name=f"Welcome - {username}",
        organism="human",
        description="Getting started with Flow"
    )
    welcome_project.share(users=[username], permission="edit")
    
    # Send notification
    client.send_notification(
        to_user=username,
        subject=f"Welcome to {group.name}",
        message=f"You've been added to {group.name}. Check your welcome project for getting started resources."
    )
    
    return {
        "user": username,
        "group": group.name,
        "role": role,
        "welcome_project": welcome_project.id
    }

Lab Collaboration Setup

class LabCollaboration:
    """Manage collaboration between labs"""
    
    def __init__(self, lab1_slug, lab2_slug):
        self.lab1 = client.get_group(lab1_slug)
        self.lab2 = client.get_group(lab2_slug)
        
    def create_shared_workspace(self, project_name):
        """Create shared project and groups"""
        
        # Create collaboration group
        collab_group = client.create_group(
            name=f"{self.lab1.name} + {self.lab2.name} Collaboration",
            description=f"Shared workspace for {project_name}"
        )
        
        # Add PIs from both labs
        for lab in [self.lab1, self.lab2]:
            pis = lab.get_members(role="admin")
            for pi in pis:
                collab_group.add_member(pi.username, role="admin")
        
        # Create shared project
        project = client.create_project(
            name=project_name,
            organism="human",
            metadata={
                "collaboration": True,
                "labs": [self.lab1.name, self.lab2.name]
            }
        )
        
        project.share(groups=[collab_group.slug], permission="edit")
        
        return collab_group, project

Group Templates

Research Lab Template

def create_research_lab(pi_username, lab_name, department):
    """Create standard research lab structure"""
    
    # Main lab group
    lab = client.create_group(
        name=lab_name,
        description=f"{pi_username}'s research laboratory",
        metadata={
            "pi": pi_username,
            "department": department,
            "type": "research_lab"
        }
    )
    
    # Add PI as owner
    lab.add_member(pi_username, role="owner")
    
    # Create standard sub-groups
    subgroups = {
        "admins": "Lab administrators and senior members",
        "postdocs": "Postdoctoral researchers",
        "grad_students": "Graduate students",
        "undergrads": "Undergraduate researchers",
        "collaborators": "External collaborators"
    }
    
    created_groups = {"main": lab}
    
    for key, description in subgroups.items():
        subgroup = client.create_group(
            name=f"{lab_name} - {key.replace('_', ' ').title()}",
            description=description,
            parent=lab.id
        )
        created_groups[key] = subgroup
    
    # Set up permissions template
    lab.update_metadata({
        "permission_template": {
            "admins": "share",
            "postdocs": "edit",
            "grad_students": "edit",
            "undergrads": "read",
            "collaborators": "read"
        }
    })
    
    return created_groups

Course/Class Template

def create_course_groups(course_code, instructor, year, semester):
    """Create groups for academic course"""
    
    # Main course group
    course = client.create_group(
        name=f"{course_code} - {year} {semester}",
        description=f"Course group for {course_code}",
        metadata={
            "instructor": instructor,
            "year": year,
            "semester": semester,
            "type": "course"
        }
    )
    
    # Sub-groups
    instructors = client.create_group(
        name=f"{course_code} - Instructors",
        parent=course.id
    )
    
    tas = client.create_group(
        name=f"{course_code} - Teaching Assistants",
        parent=course.id
    )
    
    students = client.create_group(
        name=f"{course_code} - Students",
        parent=course.id
    )
    
    # Add instructor
    instructors.add_member(instructor, role="admin")
    
    # Create course resources
    course_project = client.create_project(
        name=f"{course_code} Resources",
        organism="human",
        description="Course materials and examples"
    )
    
    # Set permissions
    course_project.share(groups=[instructors.slug], permission="share")
    course_project.share(groups=[tas.slug], permission="edit")
    course_project.share(groups=[students.slug], permission="read")
    
    return {
        "course": course,
        "instructors": instructors,
        "tas": tas,
        "students": students,
        "project": course_project
    }

Best Practices

1. Naming Conventions

def validate_group_name(name):
    """Validate group name follows conventions"""
    
    import re
    
    # Check length
    if len(name) < 3 or len(name) > 50:
        return False, "Name must be 3-50 characters"
    
    # Check format
    if not re.match(r'^[A-Za-z0-9\s\-_]+$', name):
        return False, "Name can only contain letters, numbers, spaces, hyphens, and underscores"
    
    # Check for reserved words
    reserved = ["admin", "system", "root", "flow"]
    if any(word in name.lower() for word in reserved):
        return False, "Name contains reserved words"
    
    return True, "Valid"

2. Group Lifecycle Management

def archive_inactive_group(group_slug, days_inactive=180):
    """Archive groups with no recent activity"""
    
    group = client.get_group(group_slug)
    
    # Check last activity
    last_activity = get_group_last_activity(group)
    
    if (datetime.now() - last_activity).days > days_inactive:
        # Archive process
        group.update_metadata({
            "status": "archived",
            "archived_date": str(datetime.now()),
            "archived_by": "system"
        })
        
        # Remove from active listings
        group.update_settings({"visible": False})
        
        # Notify admins
        for admin in group.get_members(role="admin"):
            client.send_notification(
                to_user=admin.username,
                subject=f"Group {group.name} archived",
                message=f"Due to {days_inactive} days of inactivity"
            )

3. Security Best Practices

def audit_group_permissions(group_slug):
    """Audit group access and permissions"""
    
    group = client.get_group(group_slug)
    
    audit = {
        "group": group.name,
        "audit_date": datetime.now(),
        "members": {
            "total": group.member_count,
            "by_role": {}
        },
        "shared_resources": {
            "samples": 0,
            "projects": 0,
            "executions": 0
        },
        "permission_levels": {},
        "external_members": []
    }
    
    # Count members by role
    for member in group.get_members():
        role = member.role
        audit["members"]["by_role"][role] = \
            audit["members"]["by_role"].get(role, 0) + 1
        
        # Check for external members
        if not member.email.endswith("@company.com"):
            audit["external_members"].append(member.username)
    
    # Audit shared resources
    for resource_type in ["samples", "projects", "executions"]:
        resources = client.search(
            resource_type=resource_type,
            shared_with_groups=[group.slug]
        )
        audit["shared_resources"][resource_type] = len(resources)
    
    return audit

Next Steps

Previous
Pipeline Execution