A comprehensive Python wrapper for the Linear API with rich Pydantic models, simplified workflows, and an object-oriented design.
Team configuration and member management including workflow states, labels, cycles, templates, and organizational hierarchy.
Basic team management operations for accessing team information and configuration.
def get(self, team_id: str) -> LinearTeam:
"""
Fetch a team by ID with comprehensive details including configuration,
cycles, states, and memberships.
Args:
team_id: The ID of the team to fetch
Returns:
LinearTeam with complete details
Raises:
ValueError: If team not found
"""
def get_all(self) -> Dict[str, LinearTeam]:
"""
Get all teams in the organization.
Returns:
Dictionary mapping team IDs to LinearTeam objects
"""
def get_id_by_name(self, team_name: str) -> str:
"""
Get a team ID by its name.
Args:
team_name: Name of the team to find
Returns:
Team ID string
Raises:
ValueError: If team not found
"""Usage examples:
from linear_api import LinearClient
client = LinearClient()
# Get a specific team
team = client.teams.get("team-id")
print(f"Team: {team.name} ({team.key})")
# Get all teams
all_teams = client.teams.get_all()
print(f"Organization has {len(all_teams)} teams")
# Find team by name
team_id = client.teams.get_id_by_name("Engineering")
engineering_team = client.teams.get(team_id)Manage team workflow states that control issue progression.
def get_states(
self,
team_id: str,
include_issue_ids: bool = False,
force_refresh: bool = False
) -> List[LinearState]:
"""
Get all workflow states for a team with optional issue ID inclusion
and cache refresh.
Args:
team_id: The ID of the team
include_issue_ids: Whether to include issue IDs using each state
force_refresh: Whether to force cache refresh
Returns:
List of LinearState objects
"""
def get_state_id_by_name(self, state_name: str, team_id: str) -> str:
"""
Get a state ID by its name within a team.
Args:
state_name: Name of the state to find
team_id: The ID of the team
Returns:
State ID string
Raises:
ValueError: If state not found
"""Usage examples:
# Get team workflow states
states = client.teams.get_states("team-id")
for state in states:
print(f"State: {state.name} ({state.type}) - {state.color}")
# Get states with issue counts
states_with_issues = client.teams.get_states("team-id", include_issue_ids=True)
# Find specific state
todo_state_id = client.teams.get_state_id_by_name("Todo", "team-id")Manage team membership and access controls.
def get_members(self, team_id: str) -> List[LinearUser]:
"""
Get members of a team with automatic pagination and LinearUser model conversion.
Args:
team_id: The ID of the team
Returns:
List of LinearUser objects
"""
def get_membership(self, team_id: str, user_id: Optional[str] = None) -> Optional[TeamMembership]:
"""
Get the membership of a user in a team, uses current user if user_id not provided.
Args:
team_id: The ID of the team
user_id: Optional user ID, uses current user if not provided
Returns:
TeamMembership object or None if not a member
Raises:
ValueError: If user not found
"""Usage examples:
# Get team members
members = client.teams.get_members("team-id")
print(f"Team has {len(members)} members:")
for member in members:
print(f" - {member.name} ({member.email})")
# Check current user's membership
my_membership = client.teams.get_membership("team-id")
if my_membership:
print(f"I am a member with owner status: {my_membership.owner}")
# Check specific user's membership
user_membership = client.teams.get_membership("team-id", "user-id")Manage team labels for issue categorization and organization.
def get_labels(self, team_id: str, include_issue_ids: bool = False) -> List[LinearLabel]:
"""
Get labels for a team with optional issue ID inclusion.
Args:
team_id: The ID of the team
include_issue_ids: Whether to include issue IDs using each label
Returns:
List of LinearLabel objects
"""
def get_label_children(self, label_id: str) -> List[LinearLabel]:
"""
Get child labels for a parent label.
Args:
label_id: The ID of the parent label
Returns:
List of child LinearLabel objects
"""Usage examples:
# Get team labels
labels = client.teams.get_labels("team-id")
for label in labels:
print(f"Label: {label.name} ({label.color})")
# Get labels with usage stats
labels_with_usage = client.teams.get_labels("team-id", include_issue_ids=True)
for label in labels_with_usage:
issue_count = len(label.issue_ids) if hasattr(label, 'issue_ids') else 0
print(f"Label: {label.name} - used on {issue_count} issues")
# Get label hierarchy
parent_label = labels[0] # Assuming first label has children
children = client.teams.get_label_children(parent_label.id)Manage team development cycles and sprint planning.
def get_active_cycle(self, team_id: str) -> Optional[Dict[str, Any]]:
"""
Get the active cycle for a team, returns None if no active cycle.
Args:
team_id: The ID of the team
Returns:
Active cycle dictionary or None
"""
def get_cycles(self, team_id: str) -> List[Dict[str, Any]]:
"""
Get all cycles for a team with pagination support.
Args:
team_id: The ID of the team
Returns:
List of cycle dictionaries
"""Usage examples:
# Get current cycle
active_cycle = client.teams.get_active_cycle("team-id")
if active_cycle:
print(f"Active cycle: {active_cycle['name']} (ends {active_cycle['endsAt']})")
else:
print("No active cycle")
# Get all cycles
cycles = client.teams.get_cycles("team-id")
print(f"Team has {len(cycles)} cycles")
for cycle in cycles[-3:]: # Last 3 cycles
print(f"Cycle: {cycle['name']} - {cycle['startsAt']} to {cycle['endsAt']}")Access team templates for consistent issue and project creation.
def get_templates(self, team_id: str) -> List[Dict[str, Any]]:
"""
Get templates for a team with error handling.
Args:
team_id: The ID of the team
Returns:
List of template dictionaries
"""Usage examples:
# Get team templates
templates = client.teams.get_templates("team-id")
for template in templates:
print(f"Template: {template.get('name')}")Navigate team hierarchies and organizational structure.
def get_children(self, team_id: str) -> List[LinearTeam]:
"""
Get child teams for a parent team.
Args:
team_id: The ID of the parent team
Returns:
List of child LinearTeam objects
"""
def get_parent(self, team_id: str) -> Optional[LinearTeam]:
"""
Get the parent team of a team, returns None if no parent.
Args:
team_id: The ID of the team
Returns:
Parent LinearTeam object or None
"""Usage examples:
# Navigate team hierarchy
parent_team = client.teams.get_parent("team-id")
if parent_team:
print(f"Parent team: {parent_team.name}")
child_teams = client.teams.get_children("team-id")
print(f"Has {len(child_teams)} child teams:")
for child in child_teams:
print(f" - {child.name}")Access issues, projects, and other content associated with teams.
def get_issues(self, team_id: str) -> List[Dict[str, Any]]:
"""
Get issues for a team with basic issue information.
Args:
team_id: The ID of the team
Returns:
List of issue dictionaries
"""
def get_projects(self, team_id: str) -> Dict[str, Any]:
"""
Get projects for a team (delegates to ProjectManager).
Args:
team_id: The ID of the team
Returns:
Dictionary of project data
"""Usage examples:
# Get team issues
issues = client.teams.get_issues("team-id")
print(f"Team has {len(issues)} issues")
# Get team projects
projects = client.teams.get_projects("team-id")
print(f"Team has projects: {list(projects.keys())}")Manage team integrations and webhooks.
def get_webhooks(self, team_id: str) -> List[Dict[str, Any]]:
"""
Get webhooks for a team.
Args:
team_id: The ID of the team
Returns:
List of webhook dictionaries
"""Usage examples:
# Get team webhooks
webhooks = client.teams.get_webhooks("team-id")
for webhook in webhooks:
print(f"Webhook: {webhook.get('url')}")Access team triage responsibility and configuration.
def get_triage_responsibility(self, team_id: str) -> TriageResponsibility:
"""
Get triage responsibility data for a team.
Args:
team_id: The ID of the team
Returns:
TriageResponsibility object
"""Usage examples:
# Get triage configuration
triage = client.teams.get_triage_responsibility("team-id")
print(f"Triage action: {triage.action}")Control caching for team-related data.
def invalidate_cache(self) -> None:
"""
Invalidate all team-related caches.
"""Usage examples:
# Clear team caches
client.teams.invalidate_cache()Teams in Linear have extensive configuration options that control their workflow and behavior:
# Teams have various automation settings
team = client.teams.get("team-id")
print(f"Auto archive period: {team.autoArchivePeriod}")
print(f"Auto close child issues: {team.autoCloseChildIssues}")
print(f"Auto close parent issues: {team.autoCloseParentIssues}")# Check cycle settings
print(f"Cycles enabled: {team.cyclesEnabled}")
print(f"Cycle duration: {team.cycleDuration}")
print(f"Cycle start day: {team.cycleStartDay}")# Check estimation configuration
print(f"Default issue estimate: {team.defaultIssueEstimate}")
print(f"Issue estimation type: {team.issueEstimationType}")def analyze_team_performance(team_id: str):
team = client.teams.get(team_id)
issues = client.teams.get_issues(team_id)
states = client.teams.get_states(team_id)
members = client.teams.get_members(team_id)
# Calculate state distribution
state_counts = {}
for issue in issues:
state_name = issue.get('state', {}).get('name', 'Unknown')
state_counts[state_name] = state_counts.get(state_name, 0) + 1
print(f"Team: {team.name}")
print(f"Members: {len(members)}")
print(f"Total issues: {len(issues)}")
print(f"Workflow states: {len(states)}")
print("\nIssue distribution by state:")
for state, count in state_counts.items():
print(f" {state}: {count} issues")
return {
"team": team,
"member_count": len(members),
"issue_count": len(issues),
"state_distribution": state_counts
}
# Analyze team performance
analytics = analyze_team_performance("team-id")def get_organization_structure():
all_teams = client.teams.get_all()
# Build hierarchy
root_teams = []
team_children = {}
for team_id, team in all_teams.items():
if not team.parentId:
root_teams.append(team)
else:
if team.parentId not in team_children:
team_children[team.parentId] = []
team_children[team.parentId].append(team)
def print_hierarchy(team, indent=0):
print(" " * indent + f"- {team.name} ({team.key})")
children = team_children.get(team.id, [])
for child in children:
print_hierarchy(child, indent + 1)
print("Organization Structure:")
for root in root_teams:
print_hierarchy(root)
# Print organization structure
get_organization_structure()def organize_team_labels(team_id: str):
labels = client.teams.get_labels(team_id, include_issue_ids=True)
# Group by color
by_color = {}
for label in labels:
color = label.color
if color not in by_color:
by_color[color] = []
by_color[color].append(label)
# Sort by usage (if issue_ids available)
for color, color_labels in by_color.items():
color_labels.sort(key=lambda l: len(getattr(l, 'issue_ids', [])), reverse=True)
print(f"Team labels organized by color:")
for color, color_labels in by_color.items():
print(f"\n{color}:")
for label in color_labels:
usage = len(getattr(label, 'issue_ids', []))
print(f" - {label.name} ({usage} issues)")
# Organize labels
organize_team_labels("team-id")def plan_next_cycle(team_id: str):
active_cycle = client.teams.get_active_cycle(team_id)
team_issues = client.teams.get_issues(team_id)
if active_cycle:
print(f"Current cycle: {active_cycle['name']}")
print(f"Ends: {active_cycle['endsAt']}")
# Get backlog issues (not in active cycle)
backlog = [issue for issue in team_issues
if not issue.get('cycle')]
print(f"\nBacklog items for next cycle: {len(backlog)}")
# Prioritize by priority and estimate
prioritized = sorted(backlog,
key=lambda i: (i.get('priority', 4), i.get('estimate', 0)))
for issue in prioritized[:10]: # Top 10 candidates
title = issue.get('title', 'Untitled')
priority = issue.get('priority', 'None')
estimate = issue.get('estimate', 'Unestimated')
print(f" - {title} (Priority: {priority}, Estimate: {estimate})")
# Plan next cycle
plan_next_cycle("team-id")Install with Tessl CLI
npx tessl i tessl/pypi-linear-api