--- name: linear-install-auth description: 'Install and configure Linear SDK/CLI authentication. Use when setting up a new Linear integration, configuring API keys, OAuth2 flows, or initializing LinearClient in your project. Trigger: "install linear", "setup linear", "linear auth", "configure linear API key", "linear SDK setup", "linear OAuth". ' allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(pnpm:*), Bash(yarn:*), Grep version: 1.0.0 license: MIT author: Jeremy Longshore tags: - saas - linear - api - authentication compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw --- # Linear Install & Auth ## Overview Install the `@linear/sdk` TypeScript SDK and configure authentication for the Linear GraphQL API at `https://api.linear.app/graphql`. Supports personal API keys for scripts and OAuth 2.0 (with PKCE) for user-facing apps. ## Prerequisites - Node.js 18+ (SDK is TypeScript-first, works in any JS environment) - Package manager (npm, pnpm, or yarn) - Linear account with workspace access - For API key: Settings > Account > API > Personal API keys - For OAuth: Create app at Settings > Account > API > OAuth applications ## Instructions ### Step 1: Install the SDK ```bash set -euo pipefail npm install @linear/sdk # or: pnpm add @linear/sdk # or: yarn add @linear/sdk ``` The SDK exposes `LinearClient`, typed models for every entity (Issue, Project, Cycle, Team), and error classes (`LinearError`, `InvalidInputLinearError`). ### Step 2: API Key Authentication (Scripts & Server-Side) Generate a Personal API key at Linear Settings > Account > API > Personal API keys. Keys start with `lin_api_` and are shown only once. ```typescript import { LinearClient } from "@linear/sdk"; // Environment variable (recommended) const client = new LinearClient({ apiKey: process.env.LINEAR_API_KEY, }); // Verify connection const me = await client.viewer; console.log(`Authenticated as: ${me.name} (${me.email})`); ``` **Environment setup:** ```bash # .env (never commit) LINEAR_API_KEY=lin_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxx # .gitignore echo '.env' >> .gitignore ``` ### Step 3: OAuth 2.0 Authentication (User-Facing Apps) Linear supports the standard Authorization Code flow with optional PKCE. As of October 2025, newly created OAuth apps issue refresh tokens by default. ```typescript import crypto from "crypto"; // 1. Build authorization URL const SCOPES = ["read", "write", "issues:create"]; const state = crypto.randomBytes(16).toString("hex"); const authUrl = new URL("https://linear.app/oauth/authorize"); authUrl.searchParams.set("client_id", process.env.LINEAR_CLIENT_ID!); authUrl.searchParams.set("redirect_uri", process.env.LINEAR_REDIRECT_URI!); authUrl.searchParams.set("response_type", "code"); authUrl.searchParams.set("scope", SCOPES.join(",")); authUrl.searchParams.set("state", state); // Optional PKCE: // authUrl.searchParams.set("code_challenge", challenge); // authUrl.searchParams.set("code_challenge_method", "S256"); // 2. Exchange authorization code for tokens const tokenResponse = await fetch("https://api.linear.app/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code: authorizationCode, client_id: process.env.LINEAR_CLIENT_ID!, client_secret: process.env.LINEAR_CLIENT_SECRET!, redirect_uri: process.env.LINEAR_REDIRECT_URI!, }), }); const { access_token, refresh_token, expires_in } = await tokenResponse.json(); // 3. Create client with OAuth token const client = new LinearClient({ accessToken: access_token }); ``` **Available OAuth scopes:** `read`, `write`, `issues:create`, `admin`, `initiative:read`, `initiative:write`, `customer:read`, `customer:write`. ### Step 4: Token Refresh (OAuth) ```typescript async function refreshAccessToken(refreshToken: string): Promise { const response = await fetch("https://api.linear.app/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: process.env.LINEAR_CLIENT_ID!, client_secret: process.env.LINEAR_CLIENT_SECRET!, }), }); if (!response.ok) { throw new Error(`Token refresh failed: ${response.status}`); } const tokens = await response.json(); // Store new refresh_token — Linear rotates it on each refresh await saveTokens(tokens.access_token, tokens.refresh_token); return tokens.access_token; } ``` ### Step 5: Validate Configuration on Startup ```typescript function validateLinearConfig(): void { const key = process.env.LINEAR_API_KEY; if (!key) throw new Error("LINEAR_API_KEY environment variable is required"); if (!key.startsWith("lin_api_")) throw new Error("LINEAR_API_KEY must start with lin_api_"); if (key.length < 30) throw new Error("LINEAR_API_KEY appears truncated"); } // Call before creating client validateLinearConfig(); ``` ## Error Handling | Error | Cause | Solution | |-------|-------|----------| | `Authentication required` | Invalid, expired, or missing API key | Regenerate at Settings > Account > API | | `Invalid API key format` | Key doesn't start with `lin_api_` | Copy full key from Linear (shown once) | | `Forbidden` | Token lacks required OAuth scope | Re-authorize with correct scopes | | `invalid_grant` on token exchange | Code expired or PKCE verifier mismatch | Restart OAuth flow; codes expire quickly | | `Module not found: @linear/sdk` | SDK not installed | Run `npm install @linear/sdk` | | `ENOTFOUND api.linear.app` | DNS/firewall issue | Ensure outbound HTTPS to `api.linear.app` | ## Examples ### Raw GraphQL Without SDK ```typescript const response = await fetch("https://api.linear.app/graphql", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": process.env.LINEAR_API_KEY!, }, body: JSON.stringify({ query: `{ viewer { id name email } }`, }), }); const { data, errors } = await response.json(); if (errors) console.error("GraphQL errors:", errors); else console.log("Viewer:", data.viewer); ``` ### Server-to-Server (Client Credentials) ```typescript // For apps without user interaction — uses client_credentials grant const response = await fetch("https://api.linear.app/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "client_credentials", client_id: process.env.LINEAR_CLIENT_ID!, client_secret: process.env.LINEAR_CLIENT_SECRET!, scope: "read,write", }), }); const { access_token } = await response.json(); const client = new LinearClient({ accessToken: access_token }); ``` ## Resources - [Linear SDK Getting Started](https://linear.app/developers/sdk) - [OAuth 2.0 Authentication](https://linear.app/developers/oauth-2-0-authentication) - [GraphQL API Endpoint](https://linear.app/developers/graphql) - [Apollo Studio Schema Explorer](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference)