Skip to content

Client

LinearClient

LinearClient(
    api_key: str | None = None,
    access_token: str | None = None,
    *,
    endpoint: str = DEFAULT_ENDPOINT,
    timeout: float = 30.0,
    http_client: Client | None = None,
)

Client for Linear's GraphQL API.

Authenticate with either a personal API key or an OAuth access token:

client = LinearClient(api_key="lin_api_...")
client = LinearClient(access_token="...")  # OAuth 2.0 token

If neither argument is given, the LINEAR_API_KEY environment variable is used. The client owns an httpx.Client and can be used as a context manager to ensure it is closed.

Create a client.

Parameters:

Name Type Description Default
api_key str | None

A Linear personal API key, sent verbatim in the Authorization header. Mutually exclusive with access_token.

None
access_token str | None

An OAuth 2.0 access token, sent as Authorization: Bearer <token>. Mutually exclusive with api_key.

None
endpoint str

GraphQL endpoint URL. Defaults to the public Linear API.

DEFAULT_ENDPOINT
timeout float

Per-request timeout in seconds for the owned HTTP client.

30.0
http_client Client | None

An existing httpx.Client to reuse. When supplied, the caller retains ownership and close() will not close it.

None

Raises:

Type Description
ValueError

If both or neither credential is provided (and LINEAR_API_KEY is unset).

Source code in src/linear_python_client/client.py
def __init__(
    self,
    api_key: str | None = None,
    access_token: str | None = None,
    *,
    endpoint: str = DEFAULT_ENDPOINT,
    timeout: float = 30.0,
    http_client: httpx.Client | None = None,
) -> None:
    """Create a client.

    Args:
        api_key: A Linear personal API key, sent verbatim in the
            `Authorization` header. Mutually exclusive with `access_token`.
        access_token: An OAuth 2.0 access token, sent as
            `Authorization: Bearer <token>`. Mutually exclusive with `api_key`.
        endpoint: GraphQL endpoint URL. Defaults to the public Linear API.
        timeout: Per-request timeout in seconds for the owned HTTP client.
        http_client: An existing `httpx.Client` to reuse. When supplied, the
            caller retains ownership and `close()` will not close it.

    Raises:
        ValueError: If both or neither credential is provided (and
            `LINEAR_API_KEY` is unset).
    """
    if api_key and access_token:
        raise ValueError("Provide either api_key or access_token, not both.")
    if not api_key and not access_token:
        api_key = os.environ.get("LINEAR_API_KEY")
    if not api_key and not access_token:
        raise ValueError(
            "No credentials supplied. Pass api_key=... or access_token=..., "
            "or set the LINEAR_API_KEY environment variable."
        )

    # Personal API keys are sent verbatim; OAuth tokens use the Bearer scheme.
    authorization = api_key if api_key else f"Bearer {access_token}"
    self.endpoint = endpoint
    self._owns_client = http_client is None
    self._http = http_client or httpx.Client(timeout=timeout)
    self._headers = {
        "Authorization": authorization,
        "Content-Type": "application/json",
    }

close

close() -> None

Close the underlying HTTP client, unless it was supplied by the caller.

Source code in src/linear_python_client/client.py
def close(self) -> None:
    """Close the underlying HTTP client, unless it was supplied by the caller."""
    if self._owns_client:
        self._http.close()

execute

execute(
    query: str, variables: dict[str, Any] | None = None
) -> dict[str, Any]

Run a raw GraphQL query or mutation and return its data payload.

This is the escape hatch backing every convenience method; use it directly for any operation the typed methods do not cover.

Parameters:

Name Type Description Default
query str

The GraphQL document to send.

required
variables dict[str, Any] | None

Optional variables for the query.

None

Returns:

Type Description
dict[str, Any]

The data object from the response (an empty dict if absent).

Raises:

Type Description
LinearAuthenticationError

The credentials were rejected.

LinearRateLimitError

A rate limit was exceeded.

LinearGraphQLError

The API returned one or more GraphQL errors.

LinearNetworkError

The request never produced a usable response.

Source code in src/linear_python_client/client.py
def execute(self, query: str, variables: dict[str, Any] | None = None) -> dict[str, Any]:
    """Run a raw GraphQL query or mutation and return its `data` payload.

    This is the escape hatch backing every convenience method; use it
    directly for any operation the typed methods do not cover.

    Args:
        query: The GraphQL document to send.
        variables: Optional variables for the query.

    Returns:
        The `data` object from the response (an empty dict if absent).

    Raises:
        LinearAuthenticationError: The credentials were rejected.
        LinearRateLimitError: A rate limit was exceeded.
        LinearGraphQLError: The API returned one or more GraphQL errors.
        LinearNetworkError: The request never produced a usable response.
    """
    payload: dict[str, Any] = {"query": query}
    if variables is not None:
        payload["variables"] = variables

    try:
        response = self._http.post(self.endpoint, json=payload, headers=self._headers)
    except httpx.HTTPError as exc:
        raise LinearNetworkError(f"Request to Linear failed: {exc}") from exc

    if response.status_code in (401, 403):
        raise LinearAuthenticationError(
            f"Linear rejected the credentials (HTTP {response.status_code})."
        )

    try:
        body = response.json()
    except ValueError as exc:
        raise LinearNetworkError(
            f"Linear returned a non-JSON response (HTTP {response.status_code})."
        ) from exc

    errors = body.get("errors")
    if errors:
        self._raise_for_errors(errors, response)

    if response.status_code >= 400:
        raise LinearNetworkError(
            f"Linear returned HTTP {response.status_code} with no GraphQL errors."
        )

    return body.get("data") or {}

viewer

viewer() -> ViewerResponse

Fetch the currently authenticated user.

Returns:

Type Description
ViewerResponse
Source code in src/linear_python_client/client.py
def viewer(self) -> ViewerResponse:
    """Fetch the currently authenticated user.

    Returns:
        A [`ViewerResponse`][linear_python_client.ViewerResponse].
    """
    return ViewerResponse.model_validate(self.execute(queries.VIEWER))

user

user(request: UserRequest) -> UserResponse

Fetch a single user by id.

Parameters:

Name Type Description Default
request UserRequest required

Returns:

Type Description
UserResponse

A UserResponse; .user is

UserResponse

None if not found.

Source code in src/linear_python_client/client.py
def user(self, request: UserRequest) -> UserResponse:
    """Fetch a single user by id.

    Args:
        request: A [`UserRequest`][linear_python_client.UserRequest].

    Returns:
        A [`UserResponse`][linear_python_client.UserResponse]; `.user` is
        `None` if not found.
    """
    return UserResponse.model_validate(self.execute(queries.USER, {"id": request.id}))

users

users(request: UsersRequest | None = None) -> UsersResponse

List users in the workspace.

Parameters:

Name Type Description Default
request UsersRequest | None

A UsersRequest. When omitted, the first page is returned with no filter.

None

Returns:

Type Description
UsersResponse
Source code in src/linear_python_client/client.py
def users(self, request: UsersRequest | None = None) -> UsersResponse:
    """List users in the workspace.

    Args:
        request: A [`UsersRequest`][linear_python_client.UsersRequest]. When
            omitted, the first page is returned with no filter.

    Returns:
        A [`UsersResponse`][linear_python_client.UsersResponse].
    """
    request = request or UsersRequest()
    data = self.execute(queries.USERS, request.to_variables())
    return UsersResponse.model_validate(data.get("users") or {})

team

team(request: TeamRequest) -> TeamResponse

Fetch a single team by id.

Parameters:

Name Type Description Default
request TeamRequest required

Returns:

Type Description
TeamResponse

A TeamResponse; .team is

TeamResponse

None if not found.

Source code in src/linear_python_client/client.py
def team(self, request: TeamRequest) -> TeamResponse:
    """Fetch a single team by id.

    Args:
        request: A [`TeamRequest`][linear_python_client.TeamRequest].

    Returns:
        A [`TeamResponse`][linear_python_client.TeamResponse]; `.team` is
        `None` if not found.
    """
    return TeamResponse.model_validate(self.execute(queries.TEAM, {"id": request.id}))

teams

teams(request: TeamsRequest | None = None) -> TeamsResponse

List teams in the workspace.

Parameters:

Name Type Description Default
request TeamsRequest | None

A TeamsRequest. When omitted, the first page is returned with no filter.

None

Returns:

Type Description
TeamsResponse
Source code in src/linear_python_client/client.py
def teams(self, request: TeamsRequest | None = None) -> TeamsResponse:
    """List teams in the workspace.

    Args:
        request: A [`TeamsRequest`][linear_python_client.TeamsRequest]. When
            omitted, the first page is returned with no filter.

    Returns:
        A [`TeamsResponse`][linear_python_client.TeamsResponse].
    """
    request = request or TeamsRequest()
    data = self.execute(queries.TEAMS, request.to_variables())
    return TeamsResponse.model_validate(data.get("teams") or {})

issue

issue(request: IssueRequest) -> IssueResponse

Fetch a single issue by id or human identifier.

Parameters:

Name Type Description Default
request IssueRequest required

Returns:

Type Description
IssueResponse

An IssueResponse; .issue

IssueResponse

is None if not found.

Source code in src/linear_python_client/client.py
def issue(self, request: IssueRequest) -> IssueResponse:
    """Fetch a single issue by id or human identifier.

    Args:
        request: An [`IssueRequest`][linear_python_client.IssueRequest].

    Returns:
        An [`IssueResponse`][linear_python_client.IssueResponse]; `.issue`
        is `None` if not found.
    """
    return IssueResponse.model_validate(self.execute(queries.ISSUE, {"id": request.id}))

issue_details

issue_details(
    request: IssueRequest,
) -> IssueDetailsResponse

Fetch a single issue with its full related data.

Returns the same core fields as issue plus comments, attachments, project, cycle, parent, sub-issues, subscribers, and relations.

Parameters:

Name Type Description Default
request IssueRequest required

Returns:

Type Description
IssueDetailsResponse
IssueDetailsResponse

.issue is an IssueDetail, or

IssueDetailsResponse

None if not found.

Source code in src/linear_python_client/client.py
def issue_details(self, request: IssueRequest) -> IssueDetailsResponse:
    """Fetch a single issue with its full related data.

    Returns the same core fields as [`issue`][linear_python_client.client.LinearClient.issue]
    plus comments, attachments, project, cycle, parent, sub-issues,
    subscribers, and relations.

    Args:
        request: An [`IssueRequest`][linear_python_client.IssueRequest].

    Returns:
        An [`IssueDetailsResponse`][linear_python_client.IssueDetailsResponse];
        `.issue` is an [`IssueDetail`][linear_python_client.IssueDetail], or
        `None` if not found.
    """
    data = self.execute(queries.ISSUE_DETAILS, {"id": request.id})
    return IssueDetailsResponse.model_validate(data)

issues

issues(
    request: IssuesRequest | None = None,
) -> IssuesResponse

List issues, optionally filtered and ordered.

Parameters:

Name Type Description Default
request IssuesRequest | None

An IssuesRequest. When omitted, the first page is returned with no filter.

None

Returns:

Type Description
IssuesResponse
Source code in src/linear_python_client/client.py
def issues(self, request: IssuesRequest | None = None) -> IssuesResponse:
    """List issues, optionally filtered and ordered.

    Args:
        request: An [`IssuesRequest`][linear_python_client.IssuesRequest].
            When omitted, the first page is returned with no filter.

    Returns:
        An [`IssuesResponse`][linear_python_client.IssuesResponse].
    """
    request = request or IssuesRequest()
    data = self.execute(queries.ISSUES, request.to_variables())
    return IssuesResponse.model_validate(data.get("issues") or {})

create_issue

create_issue(
    request: IssueCreateRequest,
) -> CreateIssueResponse

Create an issue.

Parameters:

Name Type Description Default
request IssueCreateRequest required

Returns:

Type Description
CreateIssueResponse
CreateIssueResponse

exposing success and the created issue.

Source code in src/linear_python_client/client.py
def create_issue(self, request: IssueCreateRequest) -> CreateIssueResponse:
    """Create an issue.

    Args:
        request: An [`IssueCreateRequest`][linear_python_client.IssueCreateRequest].

    Returns:
        A [`CreateIssueResponse`][linear_python_client.CreateIssueResponse]
        exposing `success` and the created `issue`.
    """
    data = self.execute(queries.ISSUE_CREATE, {"input": request.to_input()})
    return CreateIssueResponse.model_validate(data.get("issueCreate") or {})

update_issue

update_issue(
    request: IssueUpdateRequest,
) -> UpdateIssueResponse

Update an issue.

Parameters:

Name Type Description Default
request IssueUpdateRequest

An IssueUpdateRequest with id and at least one field to change.

required

Returns:

Type Description
UpdateIssueResponse
UpdateIssueResponse

exposing success and the updated issue.

Raises:

Type Description
ValueError

If no fields besides id are set.

Source code in src/linear_python_client/client.py
def update_issue(self, request: IssueUpdateRequest) -> UpdateIssueResponse:
    """Update an issue.

    Args:
        request: An [`IssueUpdateRequest`][linear_python_client.IssueUpdateRequest]
            with `id` and at least one field to change.

    Returns:
        An [`UpdateIssueResponse`][linear_python_client.UpdateIssueResponse]
        exposing `success` and the updated `issue`.

    Raises:
        ValueError: If no fields besides `id` are set.
    """
    input_data = request.to_input()
    if not input_data:
        raise ValueError("IssueUpdateRequest requires at least one field to update.")
    data = self.execute(queries.ISSUE_UPDATE, {"id": request.id, "input": input_data})
    return UpdateIssueResponse.model_validate(data.get("issueUpdate") or {})

archive_issue

archive_issue(
    request: IssueArchiveRequest,
) -> ArchiveIssueResponse

Archive an issue.

Parameters:

Name Type Description Default
request IssueArchiveRequest required

Returns:

Type Description
ArchiveIssueResponse
ArchiveIssueResponse

exposing success.

Source code in src/linear_python_client/client.py
def archive_issue(self, request: IssueArchiveRequest) -> ArchiveIssueResponse:
    """Archive an issue.

    Args:
        request: An [`IssueArchiveRequest`][linear_python_client.IssueArchiveRequest].

    Returns:
        An [`ArchiveIssueResponse`][linear_python_client.ArchiveIssueResponse]
        exposing `success`.
    """
    data = self.execute(queries.ISSUE_ARCHIVE, {"id": request.id})
    return ArchiveIssueResponse.model_validate(data.get("issueArchive") or {})

add_label

add_label(
    request: IssueAddLabelRequest,
) -> AddLabelResponse

Add a single label to an issue without disturbing its other labels.

Parameters:

Name Type Description Default
request IssueAddLabelRequest required

Returns:

Type Description
AddLabelResponse

An AddLabelResponse exposing

AddLabelResponse

success and the updated issue.

Source code in src/linear_python_client/client.py
def add_label(self, request: IssueAddLabelRequest) -> AddLabelResponse:
    """Add a single label to an issue without disturbing its other labels.

    Args:
        request: An [`IssueAddLabelRequest`][linear_python_client.IssueAddLabelRequest].

    Returns:
        An [`AddLabelResponse`][linear_python_client.AddLabelResponse] exposing
        `success` and the updated `issue`.
    """
    data = self.execute(
        queries.ISSUE_ADD_LABEL, {"id": request.id, "labelId": request.label_id}
    )
    return AddLabelResponse.model_validate(data.get("issueAddLabel") or {})

remove_label

remove_label(
    request: IssueRemoveLabelRequest,
) -> RemoveLabelResponse

Remove a single label from an issue without disturbing its other labels.

Parameters:

Name Type Description Default
request IssueRemoveLabelRequest required

Returns:

Type Description
RemoveLabelResponse
RemoveLabelResponse

exposing success and the updated issue.

Source code in src/linear_python_client/client.py
def remove_label(self, request: IssueRemoveLabelRequest) -> RemoveLabelResponse:
    """Remove a single label from an issue without disturbing its other labels.

    Args:
        request: An [`IssueRemoveLabelRequest`][linear_python_client.IssueRemoveLabelRequest].

    Returns:
        A [`RemoveLabelResponse`][linear_python_client.RemoveLabelResponse]
        exposing `success` and the updated `issue`.
    """
    data = self.execute(
        queries.ISSUE_REMOVE_LABEL, {"id": request.id, "labelId": request.label_id}
    )
    return RemoveLabelResponse.model_validate(data.get("issueRemoveLabel") or {})

set_issue_state

set_issue_state(
    request: IssueSetStateRequest,
) -> UpdateIssueResponse

Move an issue to a workflow state (status).

A focused wrapper over update_issue that sets only the state. Resolve a state UUID by name with find_workflow_state.

Parameters:

Name Type Description Default
request IssueSetStateRequest required

Returns:

Type Description
UpdateIssueResponse
UpdateIssueResponse

exposing success and the updated issue.

Source code in src/linear_python_client/client.py
def set_issue_state(self, request: IssueSetStateRequest) -> UpdateIssueResponse:
    """Move an issue to a workflow state (status).

    A focused wrapper over `update_issue` that sets only the state. Resolve a
    state UUID by name with
    [`find_workflow_state`][linear_python_client.client.LinearClient.find_workflow_state].

    Args:
        request: An [`IssueSetStateRequest`][linear_python_client.IssueSetStateRequest].

    Returns:
        An [`UpdateIssueResponse`][linear_python_client.UpdateIssueResponse]
        exposing `success` and the updated `issue`.
    """
    data = self.execute(
        queries.ISSUE_UPDATE, {"id": request.id, "input": {"stateId": request.state_id}}
    )
    return UpdateIssueResponse.model_validate(data.get("issueUpdate") or {})

project

project(request: ProjectRequest) -> ProjectResponse

Fetch a single project by id.

Parameters:

Name Type Description Default
request ProjectRequest required

Returns:

Type Description
ProjectResponse
ProjectResponse

.project is None if not found.

Source code in src/linear_python_client/client.py
def project(self, request: ProjectRequest) -> ProjectResponse:
    """Fetch a single project by id.

    Args:
        request: A [`ProjectRequest`][linear_python_client.ProjectRequest].

    Returns:
        A [`ProjectResponse`][linear_python_client.ProjectResponse];
        `.project` is `None` if not found.
    """
    return ProjectResponse.model_validate(self.execute(queries.PROJECT, {"id": request.id}))

projects

projects(
    request: ProjectsRequest | None = None,
) -> ProjectsResponse

List projects in the workspace.

Parameters:

Name Type Description Default
request ProjectsRequest | None

A ProjectsRequest. When omitted, the first page is returned with no filter.

None

Returns:

Type Description
ProjectsResponse
Source code in src/linear_python_client/client.py
def projects(self, request: ProjectsRequest | None = None) -> ProjectsResponse:
    """List projects in the workspace.

    Args:
        request: A [`ProjectsRequest`][linear_python_client.ProjectsRequest].
            When omitted, the first page is returned with no filter.

    Returns:
        A [`ProjectsResponse`][linear_python_client.ProjectsResponse].
    """
    request = request or ProjectsRequest()
    data = self.execute(queries.PROJECTS, request.to_variables())
    return ProjectsResponse.model_validate(data.get("projects") or {})

comment

comment(request: CommentRequest) -> CommentResponse

Fetch a single comment by id.

Parameters:

Name Type Description Default
request CommentRequest required

Returns:

Type Description
CommentResponse
CommentResponse

.comment is None if not found.

Source code in src/linear_python_client/client.py
def comment(self, request: CommentRequest) -> CommentResponse:
    """Fetch a single comment by id.

    Args:
        request: A [`CommentRequest`][linear_python_client.CommentRequest].

    Returns:
        A [`CommentResponse`][linear_python_client.CommentResponse];
        `.comment` is `None` if not found.
    """
    return CommentResponse.model_validate(self.execute(queries.COMMENT, {"id": request.id}))

comments

comments(
    request: CommentsRequest | None = None,
) -> CommentsResponse

List comments, optionally scoped to a single issue.

Parameters:

Name Type Description Default
request CommentsRequest | None

A CommentsRequest. Set issue_id to scope to one issue. When omitted, the first page is returned with no filter.

None

Returns:

Type Description
CommentsResponse
Source code in src/linear_python_client/client.py
def comments(self, request: CommentsRequest | None = None) -> CommentsResponse:
    """List comments, optionally scoped to a single issue.

    Args:
        request: A [`CommentsRequest`][linear_python_client.CommentsRequest].
            Set `issue_id` to scope to one issue. When omitted, the first page
            is returned with no filter.

    Returns:
        A [`CommentsResponse`][linear_python_client.CommentsResponse].
    """
    request = request or CommentsRequest()
    variables = request.to_variables()
    if request.issue_id:
        issue_filter = {"issue": {"id": {"eq": request.issue_id}}}
        existing = variables.get("filter")
        variables["filter"] = {**issue_filter, **existing} if existing else issue_filter
    data = self.execute(queries.COMMENTS, variables)
    return CommentsResponse.model_validate(data.get("comments") or {})

create_comment

create_comment(
    request: CommentCreateRequest,
) -> CreateCommentResponse

Add a comment to an issue.

Parameters:

Name Type Description Default
request CommentCreateRequest required

Returns:

Type Description
CreateCommentResponse
CreateCommentResponse

exposing success and the created comment.

Source code in src/linear_python_client/client.py
def create_comment(self, request: CommentCreateRequest) -> CreateCommentResponse:
    """Add a comment to an issue.

    Args:
        request: A [`CommentCreateRequest`][linear_python_client.CommentCreateRequest].

    Returns:
        A [`CreateCommentResponse`][linear_python_client.CreateCommentResponse]
        exposing `success` and the created `comment`.
    """
    data = self.execute(queries.COMMENT_CREATE, {"input": request.to_input()})
    return CreateCommentResponse.model_validate(data.get("commentCreate") or {})

workflow_states

workflow_states(
    request: WorkflowStatesRequest | None = None,
) -> WorkflowStatesResponse

List workflow states, optionally scoped to a single team.

Parameters:

Name Type Description Default
request WorkflowStatesRequest | None

A WorkflowStatesRequest. Set team_id to scope to one team. When omitted, the first page is returned with no filter.

None

Returns:

Type Description
WorkflowStatesResponse
Source code in src/linear_python_client/client.py
def workflow_states(
    self, request: WorkflowStatesRequest | None = None
) -> WorkflowStatesResponse:
    """List workflow states, optionally scoped to a single team.

    Args:
        request: A [`WorkflowStatesRequest`][linear_python_client.WorkflowStatesRequest].
            Set `team_id` to scope to one team. When omitted, the first page is
            returned with no filter.

    Returns:
        A [`WorkflowStatesResponse`][linear_python_client.WorkflowStatesResponse].
    """
    request = request or WorkflowStatesRequest()
    variables = request.to_variables()
    if request.team_id:
        team_filter = {"team": {"id": {"eq": request.team_id}}}
        existing = variables.get("filter")
        variables["filter"] = {**team_filter, **existing} if existing else team_filter
    data = self.execute(queries.WORKFLOW_STATES, variables)
    return WorkflowStatesResponse.model_validate(data.get("workflowStates") or {})

find_workflow_state

find_workflow_state(
    request: FindWorkflowStateRequest,
) -> WorkflowStateResponse

Resolve a workflow state by name within a team.

Useful for turning a human status name (e.g. "In Progress") into the UUID that set_issue_state expects. Matching is case-insensitive.

Parameters:

Name Type Description Default
request FindWorkflowStateRequest required

Returns:

Type Description
WorkflowStateResponse
WorkflowStateResponse

.state is None if no state matches.

Source code in src/linear_python_client/client.py
def find_workflow_state(self, request: FindWorkflowStateRequest) -> WorkflowStateResponse:
    """Resolve a workflow state by name within a team.

    Useful for turning a human status name (e.g. `"In Progress"`) into the
    UUID that [`set_issue_state`][linear_python_client.client.LinearClient.set_issue_state]
    expects. Matching is case-insensitive.

    Args:
        request: A [`FindWorkflowStateRequest`][linear_python_client.FindWorkflowStateRequest].

    Returns:
        A [`WorkflowStateResponse`][linear_python_client.WorkflowStateResponse];
        `.state` is `None` if no state matches.
    """
    filter_ = {
        "team": {"id": {"eq": request.team_id}},
        "name": {"eqIgnoreCase": request.name},
    }
    data = self.execute(queries.WORKFLOW_STATES, {"first": 1, "filter": filter_})
    return WorkflowStateResponse.model_validate({"state": _first_node(data, "workflowStates")})

find_team

find_team(request: FindTeamRequest) -> TeamResponse

Resolve a team by display name or key.

Parameters:

Name Type Description Default
request FindTeamRequest

A FindTeamRequest with name and/or key.

required

Returns:

Type Description
TeamResponse

A TeamResponse; .team is

TeamResponse

None if no team matches.

Source code in src/linear_python_client/client.py
def find_team(self, request: FindTeamRequest) -> TeamResponse:
    """Resolve a team by display name or key.

    Args:
        request: A [`FindTeamRequest`][linear_python_client.FindTeamRequest]
            with `name` and/or `key`.

    Returns:
        A [`TeamResponse`][linear_python_client.TeamResponse]; `.team` is
        `None` if no team matches.
    """
    data = self.execute(queries.TEAMS, {"first": 1, "filter": request.to_filter()})
    return TeamResponse.model_validate({"team": _first_node(data, "teams")})

find_user

find_user(request: FindUserRequest) -> UserResponse

Resolve a user by name, display name, or email.

Parameters:

Name Type Description Default
request FindUserRequest

A FindUserRequest with name and/or email.

required

Returns:

Type Description
UserResponse

A UserResponse; .user is

UserResponse

None if no user matches.

Source code in src/linear_python_client/client.py
def find_user(self, request: FindUserRequest) -> UserResponse:
    """Resolve a user by name, display name, or email.

    Args:
        request: A [`FindUserRequest`][linear_python_client.FindUserRequest]
            with `name` and/or `email`.

    Returns:
        A [`UserResponse`][linear_python_client.UserResponse]; `.user` is
        `None` if no user matches.
    """
    data = self.execute(queries.USERS, {"first": 1, "filter": request.to_filter()})
    return UserResponse.model_validate({"user": _first_node(data, "users")})

find_project

find_project(
    request: FindProjectRequest,
) -> ProjectResponse

Resolve a project by name (case-insensitive).

Parameters:

Name Type Description Default
request FindProjectRequest required

Returns:

Type Description
ProjectResponse

A ProjectResponse; .project

ProjectResponse

is None if no project matches.

Source code in src/linear_python_client/client.py
def find_project(self, request: FindProjectRequest) -> ProjectResponse:
    """Resolve a project by name (case-insensitive).

    Args:
        request: A [`FindProjectRequest`][linear_python_client.FindProjectRequest].

    Returns:
        A [`ProjectResponse`][linear_python_client.ProjectResponse]; `.project`
        is `None` if no project matches.
    """
    data = self.execute(queries.PROJECTS, {"first": 1, "filter": request.to_filter()})
    return ProjectResponse.model_validate({"project": _first_node(data, "projects")})

find_label

find_label(request: FindLabelRequest) -> IssueLabelResponse

Resolve an issue label by name, optionally scoped to a team.

Parameters:

Name Type Description Default
request FindLabelRequest required

Returns:

Type Description
IssueLabelResponse
IssueLabelResponse

.label is None if no label matches.

Source code in src/linear_python_client/client.py
def find_label(self, request: FindLabelRequest) -> IssueLabelResponse:
    """Resolve an issue label by name, optionally scoped to a team.

    Args:
        request: A [`FindLabelRequest`][linear_python_client.FindLabelRequest].

    Returns:
        An [`IssueLabelResponse`][linear_python_client.IssueLabelResponse];
        `.label` is `None` if no label matches.
    """
    data = self.execute(queries.ISSUE_LABELS, {"first": 1, "filter": request.to_filter()})
    return IssueLabelResponse.model_validate({"label": _first_node(data, "issueLabels")})

issue_labels

issue_labels(
    request: IssueLabelsRequest | None = None,
) -> IssueLabelsResponse

List issue labels in the workspace.

Parameters:

Name Type Description Default
request IssueLabelsRequest | None

An IssueLabelsRequest. When omitted, the first page is returned with no filter.

None

Returns:

Type Description
IssueLabelsResponse
Source code in src/linear_python_client/client.py
def issue_labels(self, request: IssueLabelsRequest | None = None) -> IssueLabelsResponse:
    """List issue labels in the workspace.

    Args:
        request: An [`IssueLabelsRequest`][linear_python_client.IssueLabelsRequest].
            When omitted, the first page is returned with no filter.

    Returns:
        An [`IssueLabelsResponse`][linear_python_client.IssueLabelsResponse].
    """
    request = request or IssueLabelsRequest()
    data = self.execute(queries.ISSUE_LABELS, request.to_variables())
    return IssueLabelsResponse.model_validate(data.get("issueLabels") or {})

paginate

paginate(
    method: Callable[[RequestT], ConnectionResponse[NodeT]],
    request: RequestT,
    *,
    page_size: int | None = None,
) -> Iterator[NodeT]

Yield every node across all pages of a list method.

Transparently follows the cursor (page_info.end_cursor) until has_next_page is false, so you can iterate an entire result set without managing pagination yourself:

from linear_python_client import IssuesRequest

for issue in client.paginate(client.issues, IssuesRequest(filter={...})):
    print(issue.identifier, issue.title)

Parameters:

Name Type Description Default
method Callable[[RequestT], ConnectionResponse[NodeT]]

A list method on this client (e.g. client.issues, client.teams, client.projects).

required
request RequestT

The request to start from. It is copied; after is advanced automatically each page.

required
page_size int | None

Results to request per page, set as first. Leave unset to use Linear's default of 50.

None

Yields:

Type Description
NodeT

Each node from every page, in order.

Source code in src/linear_python_client/client.py
def paginate(
    self,
    method: Callable[[RequestT], ConnectionResponse[NodeT]],
    request: RequestT,
    *,
    page_size: int | None = None,
) -> Iterator[NodeT]:
    """Yield every node across all pages of a list method.

    Transparently follows the cursor (`page_info.end_cursor`) until
    `has_next_page` is false, so you can iterate an entire result set without
    managing pagination yourself:

    ```python
    from linear_python_client import IssuesRequest

    for issue in client.paginate(client.issues, IssuesRequest(filter={...})):
        print(issue.identifier, issue.title)
    ```

    Args:
        method: A list method on this client (e.g. `client.issues`,
            `client.teams`, `client.projects`).
        request: The request to start from. It is copied; `after` is advanced
            automatically each page.
        page_size: Results to request per page, set as `first`. Leave unset to
            use Linear's default of 50.

    Yields:
        Each node from every page, in order.
    """
    current = request.model_copy(deep=True)
    if page_size is not None:
        current.first = page_size
    while True:
        response = method(current)
        yield from response.nodes
        page = response.page_info
        if not page.has_next_page or not page.end_cursor:
            break
        current = current.model_copy(update={"after": page.end_cursor})