Read and update Linear issues, projects, teams, comments, and workflow states through OAuth or an API key.
- Integrations
- linear
- issues
- projects
- teams
- comments
- graphql
- oauth
- License
- MIT
- Published
- August 23, 2026
- Pinned commit
c994644- Rating
- No ratings yet
- Forks
- 0
- Stars
- 0
- Adaptation effort
- —
README
@kody/linear
Intent
Provide reusable, account-agnostic Linear helpers so Kody agents can read and
update issues, projects, teams, comments, and workflow states through a saved
linear / linear-* OAuth integration or a personal API key — without
hand-rolling GraphQL. Mutations are previewable with dryRun: true and only
run live after confirm: true.
This listing is meant to be forked. After you fork, connect your Linear
workspace. Do not treat the live @kody/linear package storage as yours.
This official OAuth/API-key package is the preferred invoke path. Setup is
harder: bring your own Linear OAuth app or personal API key. For a faster first
win, connect Linear MCP in Get started and use
@kody/linear-mcp.
When To Use
- List or inspect teams, projects, issues, comments, and workflow states
- Create or update issues, comments, and projects after explicit confirmation
- Call an unwrapped Linear GraphQL operation through
./request - Connect more than one Linear workspace via
integrationName/account
Auth
Linear has no built-in Kody OAuth app. Choose one lane:
| Lane | When to use | Credential |
|---|---|---|
| OAuth (recommended) | Multi-account, refresh tokens, shared helpers | Saved integration linear or linear-<purpose> |
| Personal API key | Fastest for a single workspace / personal scripts | User secret linearApiKey (or linearApiKey-<purpose>) |
OAuth access tokens use Authorization: Bearer …. Personal API keys must be
sent as Authorization: <API_KEY> without the Bearer prefix — this package
handles that.
Required API host: api.linear.app. Approve it in the account secrets UI.
Lane A — BYO OAuth
- Create an OAuth application at https://linear.app/settings/api/applications/new
- Set the redirect URI exactly to
https://kody.codes/connect/oauth - Request scopes
read,write(comma-separated;readis always present). Narrower mutation-only scopes:issues:create,comments:create. - Connect while signed in to Kody:
- Paste the Linear client id and client secret into the Kody wizard (never into
chat). Approve host
api.linear.app. - Reconnect later with https://kody.codes/connect/oauth?provider=linear
OAuth token URL: https://api.linear.app/oauth/token. Authorize URL:
https://linear.app/oauth/authorize. Flow: confidential (client secret).
Linear scopes are comma-separated (scopeSeparator=,).
To connect a second workspace, change provider (for example
provider=linear-work) and pass integrationName: 'linear-work' on every call.
Add prompt=consent on Linear's authorize screen if you need to pick a
different workspace for the same app.
Lane B — Personal API key
- Create a key at https://linear.app/settings/account/security
- Grant Read plus the write permissions you need (
Write, orCreate issues/Create comments). - Save it (do not paste the value in chat):
For a second workspace/key, use a distinct secret name such as
linearApiKey-work and pass secretName: 'linearApiKey-work' (or
account: 'work', which resolves to linear-work / linearApiKey-work).
Scopes
| Scope | Needed for |
|---|---|
read | All queries (viewer, teams, projects, issues, comments, workflow states) |
issues:create | ./create-issue |
comments:create | ./create-comment |
write | ./update-issue, ./create-project, other mutations |
admin | Do not request unless a workflow truly needs admin endpoints |
If Linear returns 401/403 or a GraphQL FORBIDDEN / insufficient-scope error,
helpers throw a message that names the missing scope and the next setup URL
(reconnect OAuth with that scope, or save an API key with the matching
permission).
Multiple accounts
Every export accepts:
integrationName/integration— exact saved OAuth name (linear-work)account—work→linear-work;linear-workused as-is; omitted →linearsecretName— API key secret overrideauth—'oauth'or'api-key'when both exist
Do not hard-code a personal workspace slug or alias.
Safety
Mutating helpers require confirm: true. Pass dryRun: true to inspect the
GraphQL payload without calling Linear. ./request treats query documents as
read-only; mutation documents need confirmation.
Exports
| Export | Description |
|---|---|
. | Package overview, connect URLs, export map |
./accounts | Resolve integration/secret names and report what is connected |
./smoke-test | Local helper checks plus optional live viewer read (no email) |
./viewer | Authenticated viewer id / name / organization id |
./list-teams | List teams |
./get-team | Get one team by UUID or key |
./list-projects | List projects |
./get-project | Get one project by UUID |
./create-project | Preview or create a project (teamIds required) |
./list-issues | List issues (teamId / teamKey, projectId, assigneeId, stateType, query) |
./get-issue | Get one issue by UUID or identifier (ENG-123) |
./create-issue | Preview or create an issue (title + teamId) |
./update-issue | Preview or update an issue |
./list-comments | List comments on an issue |
./create-comment | Preview or create a comment |
./list-workflow-states | List workflow states (teamId / teamKey, type) |
./request | Generic GraphQL escape hatch |
./types | Shared TypeScript types |
Priority values: 0 none, 1 urgent, 2 high, 3 medium, 4 low.
Workflow state types: triage, backlog, unstarted, started, completed,
canceled.
Smoke test
import smokeTest from 'kody:@kody/linear/smoke-test'
export default async function main() {
return await smokeTest()
}Without credentials this returns { ok: true, live: false } plus the connect
and API-key URLs. After OAuth or an API key is saved it reads viewer { id }
and returns { live: true, hasViewerId, hasOrganizationId } without email.
Preview a mutation without credentials:
import createIssue from 'kody:@kody/linear/create-issue'
export default async function main() {
return await createIssue({
title: 'Follow up on onboarding',
teamId: '00000000-0000-0000-0000-000000000000',
dryRun: true,
})
}Examples
import listIssues from 'kody:@kody/linear/list-issues'
import listWorkflowStates from 'kody:@kody/linear/list-workflow-states'
export default async function main() {
const states = await listWorkflowStates({ teamKey: 'ENG' })
return await listIssues({
teamKey: 'ENG',
stateType: 'started',
first: 20,
})
}import createIssue from 'kody:@kody/linear/create-issue'
export default async function main() {
const preview = await createIssue({
title: 'Investigate checkout timeout',
teamId: 'team-uuid',
priority: 2,
dryRun: true,
})
// After the user confirms the exact team and title:
return await createIssue({
title: 'Investigate checkout timeout',
teamId: 'team-uuid',
priority: 2,
confirm: true,
})
}Unwrapped GraphQL:
import request from 'kody:@kody/linear/request'
export default async function main() {
return await request({
query: `query ($key: String!) {
team(id: $key) { id name key }
}`,
variables: { key: 'ENG' },
})
}Notes
- GraphQL endpoint:
https://api.linear.app/graphql - Helpers project slim objects. Use
./requestwhen you need extra fields. assigneeId: 'me'on./list-issuesfilters to the authorizing user.- This package is not affiliated with or endorsed by Linear Orbit, Inc.
Branding
The community icon is Linear's official logomark as served by Linear
(https://static.linear.app/client/assets/favicon-D8hcELd9.svg), scaled to a
square. Paths are unmodified. Linear® is a trademark of Linear Orbit, Inc.
Docs
Report this listing
Log in to report this listing.