Skip to main content
LinearService is a container-managed wrapper around the official @linear/sdk client. It exposes a typed, promise-based API for reading and writing Linear issues, teams, projects, labels, workflow states, priorities, and comments, and it normalizes Linear’s GraphQL objects into plain Talos types (Issue, LinearLabelType, LinearStateType, and friends) so call sites never touch the SDK directly. Configuration comes from either the constructor or AppEnv, and every failure is surfaced as a LinearException with a machine-readable key.

Installation

LinearService ships as its own package and depends on the Linear SDK.
bun add @talosjs/linear

Configuration

The service needs a Linear API key. Provide it through the constructor config or expose it through AppEnv as LINEAR_API_KEY. An optional default team id can be set the same way, so team-scoped calls don’t need a teamId argument.
import { AppEnv } from "@talosjs/app-env";
import { LinearService } from "@talosjs/linear";

const linear = new LinearService(new AppEnv(), {
  apiKey: "lin_api_...",
  teamId: "team-id",
});
The API key is validated in the constructor, so a missing key fails fast at startup with API_KEY_REQUIRED. Generate a personal API key from your Linear settings under Security & access → Personal API keys.

Environment variables

VariableRequiredPurpose
LINEAR_API_KEYYesLinear API key. Missing throws LinearException (API_KEY_REQUIRED).
LINEAR_TEAM_IDNoDefault team id for team-scoped reads and writes when no teamId is passed.
LINEAR_API_KEY=lin_api_xxxxxxxxxxxxxxxxxxxxxxxx
LINEAR_TEAM_ID=team-id
Config passed to the constructor takes precedence over the environment: config.apiKey || env.LINEAR_API_KEY and config.teamId || env.LINEAR_TEAM_ID.

Usage

Resolve the service from the container and call its methods. Reads return normalized types; writes return the updated entity.
import { container } from "@talosjs/container";
import { LinearService } from "@talosjs/linear";

const linear = container.get(LinearService);

// Read a single issue by id or identifier (e.g. "OO-123")
const issue = await linear.getIssue("OO-123");

// Create an issue — title and team.id are required
const created = await linear.createIssue({
  title: "Document Linear package",
  description: "Add public package usage documentation.",
  team: { id: "team-id", name: "Engineering", key: "ENG" },
});

// Comment on it
await linear.createComment(created.id, "Documentation added.");
Inject it into a service to drive Linear as part of your domain logic:
import { inject } from "@talosjs/container";
import { LinearService } from "@talosjs/linear";

export class SupportService {
  constructor(
    @inject(LinearService) private readonly linear: LinearService,
  ) {}

  public async escalate(summary: string, details: string) {
    const issue = await this.linear.createIssue({
      title: summary,
      description: details,
      team: { id: "team-id", name: "Engineering", key: "ENG" },
    });
    await this.linear.setPriority(issue.id, 1); // Urgent
    return issue;
  }
}

API

Issues

MethodPurpose
getIssue(id)Fetch one issue by id or identifier.
getIssues(teamId?, filters?)Fetch issues, optionally scoped to a team and Linear filters.
createIssue(input)Create an issue. title and team.id are required.
updateIssue(id, input)Update issue fields.
deleteIssue(id)Delete an issue; returns Linear’s success flag.
When teamId is omitted from getIssues, the default team id from config or LINEAR_TEAM_ID is used.

Teams, projects, and the viewer

MethodPurpose
getTeams()List available teams.
getProjects(teamId?)List projects, optionally filtered by team.
getViewer()Return the authenticated user behind the API key.

Labels

MethodPurpose
getLabel(id)Fetch one label.
getLabels(teamId?)List labels, optionally by team.
createLabel(input)Create a label; name required.
updateLabel(id, input)Update a label.
deleteLabel(id)Delete a label.
checkLabelById(id)Test whether a label id exists.
checkLabelByName(name, teamId?)Test whether a label name exists.

Workflow states

MethodPurpose
getState(id)Fetch one workflow state.
getStates(teamId?)List workflow states, optionally by team.
createState(input)Create a state; name, color, type, and team required.
updateState(id, input)Update a state.
deleteState(id)Archive a state; returns Linear’s success flag.
checkStateById(id)Test whether a state id exists.
checkStateByName(name, teamId?)Test whether a state name exists.

Priorities

Linear priorities are a fixed scale. The service exposes them synchronously and validates any value you set against the scale.
ValueLabel
0No priority
1Urgent
2High
3Normal
4Low
MethodPurpose
getPriorities()Return the full priority scale (synchronous).
getPriority(issueId)Return the priority of an issue.
setPriority(issueId, priority)Set an issue’s priority; rejects invalid values.
clearPriority(issueId)Reset an issue’s priority to 0 (No priority).
checkPriorityById(value)Test whether a numeric priority is valid.
checkPriorityByName(name)Test whether a priority label is valid.

Comments

MethodPurpose
createComment(issueId, body)Add a comment to an issue.

Use in the app

In an @talosjs/app application, Linear isn’t a dedicated App config slot. LinearService is @injectable(), so it registers with the container as soon as the class is imported, and you resolve or inject it wherever you talk to Linear.
bun add @talosjs/app @talosjs/linear
Set LINEAR_API_KEY (and optionally LINEAR_TEAM_ID) in your .env.yml (or environment) so the client can initialize, then inject the service into a controller or service:
import { inject } from "@talosjs/container";
import { LinearService } from "@talosjs/linear";

export class ReleaseService {
  constructor(
    @inject(LinearService) private readonly linear: LinearService,
  ) {}

  public async openReleaseTicket(version: string) {
    return this.linear.createIssue({
      title: `Release ${version}`,
      team: { id: "team-id", name: "Engineering", key: "ENG" },
    });
  }
}
The CLI can scaffold your Linear credentials into .env.yml for you — see linear:credentials:create.

Exceptions

Every method throws LinearException on failure, carrying a machine-readable key and a data payload with the relevant ids and the original cause. All exceptions map to an InternalServerError status.
KeyWhen
API_KEY_REQUIREDThe service is constructed without an API key.
ISSUE_FETCH_ERRORgetIssue fails.
ISSUES_FETCH_ERRORgetIssues fails.
ISSUE_CREATE_ERRORcreateIssue fails or is missing title/team.
ISSUE_UPDATE_ERRORupdateIssue fails.
ISSUE_DELETE_ERRORdeleteIssue fails.
TEAMS_FETCH_ERRORgetTeams fails.
PROJECTS_FETCH_ERRORgetProjects fails.
VIEWER_FETCH_ERRORgetViewer fails.
LABEL_FETCH_ERROR / LABELS_FETCH_ERRORLabel reads fail.
LABEL_CREATE_ERROR / LABEL_UPDATE_ERROR / LABEL_DELETE_ERRORLabel writes fail.
LABEL_CHECK_ERRORcheckLabelByName fails.
STATE_FETCH_ERROR / STATES_FETCH_ERRORState reads fail.
STATE_CREATE_ERROR / STATE_UPDATE_ERROR / STATE_DELETE_ERRORState writes fail.
STATE_CHECK_ERRORcheckStateByName fails.
PRIORITY_FETCH_ERRORgetPriority fails or returns an unknown value.
PRIORITY_SET_ERRORsetPriority fails or is given an invalid value.
COMMENT_CREATE_ERRORcreateComment fails.
import { LinearException } from "@talosjs/linear";

try {
  const issue = await linear.getIssue("OO-123");
} catch (error) {
  if (error instanceof LinearException && error.key === "ISSUE_FETCH_ERROR") {
    logger.error("Could not load the Linear issue", error.data);
  }
  throw error;
}

Types

The package exports Issue, LinearService, LinearException, the ILinearService interface, LinearConfigType, and the supporting payload types (LinearTeamType, LinearProjectType, LinearUserType, LinearLabelType, LinearStateType, LinearPriorityType, LinearCommentType) from @talosjs/linear.