A comprehensive Python wrapper for the Linear API with rich Pydantic models, simplified workflows, and an object-oriented design.
Comprehensive issue management including creation, updates, querying, and relationship management with support for attachments, comments, and metadata.
Basic issue management operations for creating, reading, updating, and deleting issues.
def get(self, issue_id: str) -> LinearIssue:
"""
Fetch a Linear issue by ID with comprehensive details including attachments,
labels, team, project, etc.
Args:
issue_id: The ID of the issue to fetch
Returns:
LinearIssue with complete details
Raises:
ValueError: If issue not found
"""
def create(self, issue: LinearIssueInput) -> LinearIssue:
"""
Create a new issue in Linear with support for team assignment, parent relationships,
and metadata attachments.
Args:
issue: LinearIssueInput with issue details
Returns:
Created LinearIssue object
Raises:
ValueError: If creation fails
"""
def update(self, issue_id: str, update_data: LinearIssueUpdateInput) -> LinearIssue:
"""
Update an existing issue with support for state changes, team transfers,
and metadata updates.
Args:
issue_id: The ID of the issue to update
update_data: LinearIssueUpdateInput with fields to update
Returns:
Updated LinearIssue object
Raises:
ValueError: If update fails
"""
def delete(self, issue_id: str) -> bool:
"""
Delete an issue by its ID.
Args:
issue_id: The ID of the issue to delete
Returns:
True if successful
Raises:
ValueError: If deletion fails
"""Usage examples:
from linear_api import LinearClient, LinearIssueInput, LinearIssueUpdateInput
client = LinearClient()
# Get an issue
issue = client.issues.get("issue-id")
print(f"Issue: {issue.title} ({issue.state.name})")
# Create a new issue
new_issue = client.issues.create(LinearIssueInput(
title="New task from API",
teamName="Engineering",
description="Task created via Python API",
priority=LinearPriority.HIGH
))
# Update an issue
updated = client.issues.update("issue-id", LinearIssueUpdateInput(
title="Updated title",
description="Updated description"
))
# Delete an issue
success = client.issues.delete("issue-id")Advanced querying capabilities for finding issues by various criteria.
def get_by_team(self, team_name: str) -> Dict[str, LinearIssue]:
"""
Get all issues for a specific team.
Args:
team_name: Name of the team
Returns:
Dictionary mapping issue IDs to LinearIssue objects
"""
def get_by_project(self, project_id: str) -> Dict[str, LinearIssue]:
"""
Get all issues for a specific project.
Args:
project_id: The ID of the project
Returns:
Dictionary mapping issue IDs to LinearIssue objects
"""
def get_all(self) -> Dict[str, LinearIssue]:
"""
Get all issues from all teams in the organization.
Processes in batches for performance.
Returns:
Dictionary mapping issue IDs to LinearIssue objects
"""Usage examples:
# Get issues by team
team_issues = client.issues.get_by_team("Engineering")
print(f"Found {len(team_issues)} issues in Engineering team")
# Get issues by project
project_issues = client.issues.get_by_project("project-id")
for issue_id, issue in project_issues.items():
print(f"{issue.title} - {issue.state.name}")
# Get all issues (use with caution for large organizations)
all_issues = client.issues.get_all()Handle issue attachments with URL validation and metadata support.
def create_attachment(self, attachment: LinearAttachmentInput) -> Dict[str, Any]:
"""
Create an attachment for an issue with URL validation and metadata support.
Args:
attachment: LinearAttachmentInput with attachment details
Returns:
Created attachment data
Raises:
ValueError: If attachment creation fails
"""
def get_attachments(self, issue_id: str) -> List[Dict[str, Any]]:
"""
Get all attachments for an issue with creator and metadata information.
Args:
issue_id: The ID of the issue
Returns:
List of attachment dictionaries
"""Usage examples:
from linear_api import LinearAttachmentInput
# Create an attachment
attachment = client.issues.create_attachment(LinearAttachmentInput(
url="https://example.com/document.pdf",
issueId="issue-id",
title="Requirements Document",
subtitle="Project requirements v1.0",
metadata={"category": "documentation", "version": "1.0"}
))
# Get issue attachments
attachments = client.issues.get_attachments("issue-id")
for attach in attachments:
print(f"Attachment: {attach['title']} - {attach['url']}")Access issue change history and comment management.
def get_history(self, issue_id: str) -> List[Dict[str, Any]]:
"""
Get the change history for an issue including state transitions and actors.
Args:
issue_id: The ID of the issue
Returns:
List of history entries
"""
def get_comments(self, issue_id: str) -> List[Comment]:
"""
Get all comments for an issue with automatic pagination and Comment model conversion.
Args:
issue_id: The ID of the issue
Returns:
List of Comment objects
"""Usage examples:
# Get issue history
history = client.issues.get_history("issue-id")
for entry in history:
print(f"Change: {entry.get('action')} by {entry.get('actor', {}).get('name')}")
# Get issue comments
comments = client.issues.get_comments("issue-id")
for comment in comments:
print(f"Comment by {comment.creator.name}: {comment.body[:50]}...")Manage issue relationships including parent-child hierarchies and inter-issue relations.
def get_children(self, issue_id: str) -> Dict[str, LinearIssue]:
"""
Get child issues for a parent issue.
Args:
issue_id: The ID of the parent issue
Returns:
Dictionary mapping issue IDs to LinearIssue objects
"""
def get_relations(self, issue_id: str) -> List[IssueRelation]:
"""
Get relations for an issue (blocks, duplicates, etc.)
Args:
issue_id: The ID of the issue
Returns:
List of IssueRelation objects
"""
def get_inverse_relations(self, issue_id: str) -> List[IssueRelation]:
"""
Get inverse relations for an issue (blocked by, duplicate of, etc.)
Args:
issue_id: The ID of the issue
Returns:
List of IssueRelation objects
"""Usage examples:
# Get child issues
children = client.issues.get_children("parent-issue-id")
print(f"Parent has {len(children)} child issues")
# Get issue relations
relations = client.issues.get_relations("issue-id")
for relation in relations:
print(f"Relation: {relation.type} to issue {relation.relatedIssue['id']}")
# Get inverse relations
inverse_relations = client.issues.get_inverse_relations("issue-id")Access reactions, subscribers, and social interactions on issues.
def get_reactions(self, issue_id: str) -> List[Reaction]:
"""
Get reactions to an issue with user information and emoji data.
Args:
issue_id: The ID of the issue
Returns:
List of Reaction objects
"""
def get_subscribers(self, issue_id: str) -> List[LinearUser]:
"""
Get subscribers of an issue with automatic pagination and LinearUser model conversion.
Args:
issue_id: The ID of the issue
Returns:
List of LinearUser objects
"""Usage examples:
# Get issue reactions
reactions = client.issues.get_reactions("issue-id")
for reaction in reactions:
print(f"{reaction.user.name} reacted with {reaction.emoji}")
# Get issue subscribers
subscribers = client.issues.get_subscribers("issue-id")
print(f"Issue has {len(subscribers)} subscribers")Access customer needs associated with issues for product management workflows.
def get_needs(self, issue_id: str) -> List[CustomerNeedResponse]:
"""
Get customer needs associated with an issue.
Args:
issue_id: The ID of the issue
Returns:
List of CustomerNeedResponse objects
"""Usage examples:
# Get customer needs for an issue
needs = client.issues.get_needs("issue-id")
for need in needs:
print(f"Customer need: {need.priority} priority")Control caching for issue-related data to optimize performance.
def invalidate_cache(self) -> None:
"""
Invalidate all issue-related caches.
"""Usage examples:
# Clear issue caches after bulk operations
client.issues.invalidate_cache()
# Or clear all caches
client.clear_cache()For creating new issues with comprehensive field support.
class LinearIssueInput(LinearModel):
title: str # Required: Issue title
teamName: str # Required: Team name for issue
description: Optional[str] = None
priority: Optional[LinearPriority] = None
assigneeId: Optional[str] = None
projectId: Optional[str] = None
stateId: Optional[str] = None
parentId: Optional[str] = None
labelIds: Optional[List[str]] = None
estimate: Optional[int] = None
dueDate: Optional[datetime] = None
metadata: Optional[Dict[str, Any]] = None # Auto-converted to attachmentFor updating existing issues with all optional fields.
class LinearIssueUpdateInput(LinearModel):
title: Optional[str] = None
description: Optional[str] = None
priority: Optional[LinearPriority] = None
assigneeId: Optional[str] = None
projectId: Optional[str] = None
stateId: Optional[str] = None
teamId: Optional[str] = None
labelIds: Optional[List[str]] = None
estimate: Optional[int] = None
dueDate: Optional[datetime] = None
metadata: Optional[Dict[str, Any]] = None # Auto-converted to attachmentFor creating issue attachments with metadata support.
class LinearAttachmentInput(LinearModel):
url: str # Required: Attachment URL
issueId: str # Required: Issue ID
title: Optional[str] = None
subtitle: Optional[str] = None
metadata: Optional[Dict[str, Any]] = None# Create multiple issues efficiently
issues_to_create = [
LinearIssueInput(title=f"Task {i}", teamName="Engineering")
for i in range(1, 11)
]
created_issues = []
for issue_input in issues_to_create:
created = client.issues.create(issue_input)
created_issues.append(created)# Use metadata for custom fields
issue_with_metadata = client.issues.create(LinearIssueInput(
title="Feature Request",
teamName="Engineering",
description="New feature from customer feedback",
metadata={
"customer_id": "cust_123",
"feature_type": "enhancement",
"business_value": "high",
"complexity": "medium"
}
))
# Metadata is automatically converted to an attachment
attachments = client.issues.get_attachments(issue_with_metadata.id)
metadata_attachment = next((a for a in attachments if a.get('title') == 'Metadata'), None)
print(metadata_attachment['metadata'])# Create parent issue
parent = client.issues.create(LinearIssueInput(
title="Epic: User Authentication System",
teamName="Engineering",
description="Complete user authentication overhaul"
))
# Create child issues
child1 = client.issues.create(LinearIssueInput(
title="Implement JWT tokens",
teamName="Engineering",
parentId=parent.id
))
child2 = client.issues.create(LinearIssueInput(
title="Add OAuth integration",
teamName="Engineering",
parentId=parent.id
))
# Get all children
children = client.issues.get_children(parent.id)
print(f"Epic has {len(children)} child issues")Install with Tessl CLI
npx tessl i tessl/pypi-linear-api