Skip to main content
128 toolsAPI key
linear_example.py
Issues and their relations, comments and reactions, labels, teams, members, workflow states, projects with their milestones and status updates, cycles, initiatives, documents, attachments, customers, notifications, favorites, saved views, search and webhooks. Every tool POSTs the same document-shaped request to the same URL, and the argument the model fills in is always one field: variables. A GraphQL API breaks three assumptions a REST-shaped runtime makes, and this pack is where each one is answered.

Authenticating

This pack takes an API key in Authorization, and configure() is optional when $LINEAR_API_KEY is set. There is no authorization server, no consent screen and no refresh — API keys is the whole story. The key goes out raw — not Bearer, not Token, just the key. This is the one place Linear differs from almost every other API here. An OAuth access token does take Bearer; for that, build the factory yourself with oauth_tool_factory, which adds the prefix. Linear documents where a personal key is created in its authentication guide.

The client

api_key_tool_factory is the whole client: a thin wrapper over httpx that attaches your key and these endpoint constants to each request. No vendor SDK enters your dependency tree.

Paging through a list

A cursor belongs to the tool that returns it, so it is declared on that tool’s builder call:
linear_pagination.py
Pagination is declared on teams_list, users_list, workflow_states_list, team_memberships_list, issues_list, issue_relations_list, comments_list, issue_labels_list, project_labels_list, projects_list, project_statuses_list, project_milestones_list, project_updates_list, cycles_list, initiatives_list, initiative_updates_list, documents_list, attachments_list, attachments_for_url, customers_list, customer_needs_list, customer_statuses_list, customer_tiers_list, notifications_list, favorites_list, custom_views_list, search_issues, search_projects, search_documents and webhooks_list. The other 98 take no cursor.

Tools

Each is a Tool, called with ainvoke as in the snippet above. The name links to its parameters, its response and what it costs.
WorkspaceviewerPOSTGet the authenticated Linear user.organizationPOSTGet the workspace itself — its name, URL key and member count.teams_listPOSTList the workspace’s teams.team_getPOSTGet one team, with its workflow states and current cycle.users_listPOSTList workspace members.user_getPOSTGet one workspace member.workflow_states_listPOSTList workflow states — the statuses an issue can move between.workflow_state_getPOSTGet one workflow state by its UUID.workflow_state_createPOSTCreate a workflow state for a team.workflow_state_updatePOSTRename a workflow state, recolour it or move its position.workflow_state_archivePOSTArchive a workflow state, taking it out of the team’s workflow.team_memberships_listPOSTList who belongs to which team, and who owns each one.team_membership_getPOSTGet one team membership by its UUID.team_membership_createPOSTAdd a member to a team.team_membership_updatePOSTChange a team membership — chiefly to make someone the team’s owner.team_membership_deletePOSTRemove a member from a team.Issuesissues_listPOSTList issues, optionally filtered.issue_getPOSTGet one issue with its description, comments, sub-issues, relations and attachments.issue_createPOSTCreate an issue.issue_updatePOSTUpdate an issue.issue_batch_updatePOSTApply one change to many issues at once — reassigning a queue, moving a set into a cycle.issue_deletePOSTMove an issue to the trash, from where it can be restored.issue_archivePOSTArchive an issue, taking it out of the active lists while keeping it findable.issue_unarchivePOSTRestore an archived issue to the active lists.issue_add_labelPOSTAdd one label to an issue, leaving its other labels alone.issue_remove_labelPOSTRemove one label from an issue, leaving its other labels alone.issue_subscribePOSTSubscribe someone to an issue’s updates.issue_unsubscribePOSTUnsubscribe someone from an issue’s updates.issue_relations_listPOSTList the links between issues across the workspace.issue_relation_getPOSTGet one issue relation by its UUID.issue_relation_createPOSTLink two issues as blocking, duplicate, related or similar. ‘blocks’ and ‘duplicate’ are directional: the issue in issue_id is the one doing the blocking, or the one that is the duplicate.issue_relation_updatePOSTChange how two issues are linked, or which issues the link joins.issue_relation_deletePOSTUnlink two issues.Commentscomments_listPOSTRead comments.comment_getPOSTGet one comment with its replies.comment_createPOSTPost a comment.comment_updatePOSTEdit a comment’s text, by its UUID.comment_deletePOSTDelete a comment.comment_resolvePOSTMark a comment thread resolved.comment_unresolvePOSTReopen a resolved comment thread.reaction_createPOSTReact to a comment, issue or status update with an emoji.reaction_deletePOSTRemove a reaction.Labelsissue_labels_listPOSTList the workspace’s issue labels, with the UUIDs issue_create and issue_update need.issue_label_getPOSTGet one label by its UUID.issue_label_createPOSTCreate a label.issue_label_updatePOSTRename a label, recolour it, or retire it.issue_label_deletePOSTDelete a label, removing it from every issue that carries it.project_labels_listPOSTList the labels that tag projects.Projectsprojects_listPOSTList projects in the workspace, with their status and progress.project_getPOSTGet one project with its content, milestones and members.project_createPOSTCreate a project.project_updatePOSTUpdate a project’s name, description, lead, dates or status.project_deletePOSTMove a project to the trash.project_unarchivePOSTRestore a trashed project.project_add_labelPOSTAdd one project label to a project.project_remove_labelPOSTRemove one project label from a project.project_statuses_listPOSTList the statuses a project can be in, with the UUIDs project_create and project_update need.project_milestones_listPOSTList project milestones.project_milestone_getPOSTGet one project milestone by its UUID.project_milestone_createPOSTCreate a milestone inside a project.project_milestone_updatePOSTRename a milestone, move its target date, or move it to another project.project_milestone_deletePOSTDelete a project milestone.project_updates_listPOSTRead the status updates posted on projects — the periodic ‘on track, here is what moved’ notes.project_update_getPOSTGet one project status update by its UUID.project_update_createPOSTPost a status update on a project, optionally reporting health as ‘onTrack’, ‘atRisk’ or ‘offTrack’.project_update_updatePOSTEdit a project status update that has already been posted.project_update_archivePOSTArchive a project status update.Cyclescycles_listPOSTList cycles — Linear’s name for a team’s time-box.cycle_getPOSTGet one cycle with the issues in it.cycle_updatePOSTRename a cycle or move its dates.cycle_archivePOSTArchive a cycle.cycle_start_upcoming_todayPOSTStart a team’s next cycle today rather than on its scheduled date.Initiativesinitiatives_listPOSTList initiatives — the grouping above a project, and what Linear replaced roadmaps with.initiative_getPOSTGet one initiative with its projects and links.initiative_createPOSTCreate an initiative.initiative_updatePOSTUpdate an initiative’s name, owner, status or target date.initiative_deletePOSTMove an initiative to the trash.initiative_archivePOSTArchive an initiative.initiative_unarchivePOSTRestore an archived initiative.initiative_to_project_createPOSTAdd a project to an initiative.initiative_to_project_deletePOSTRemove a project from an initiative.initiative_updates_listPOSTRead the status updates posted on initiatives.initiative_update_createPOSTPost a status update on an initiative.Documentsdocuments_listPOSTList documents.document_getPOSTGet one document with its full content.document_createPOSTCreate a document.document_updatePOSTUpdate a document.document_deletePOSTMove a document to the trash.document_unarchivePOSTRestore a trashed document.Attachmentsattachments_listPOSTList attachments — the links between Linear issues and things outside it.attachment_getPOSTGet one attachment and the issue it is on.attachments_for_urlPOSTFind which issues an external URL is attached to.attachment_createPOSTLink an issue to something outside Linear.attachment_updatePOSTUpdate an attachment’s title, subtitle or icon.attachment_deletePOSTRemove an attachment from its issue.Customerscustomers_listPOSTList customers — the companies whose requests Linear tracks against issues.customer_getPOSTGet one customer by its UUID.customer_createPOSTCreate a customer.customer_updatePOSTUpdate a customer’s name, owner, tier, revenue or size.customer_deletePOSTDelete a customer.customer_needs_listPOSTList customer requests.customer_need_createPOSTRecord a customer request, optionally attached to an issue or project.customer_need_updatePOSTUpdate a customer request, chiefly to attach it to an issue.customer_need_deletePOSTDelete a customer request.customer_statuses_listPOSTList the statuses a customer can be in, with the UUIDs customer writes need.customer_tiers_listPOSTList the tiers a customer can be assigned to.Inboxnotifications_listPOSTRead the authenticated user’s notifications — what Linear has told them about and what is still unread.notification_getPOSTGet one notification by its UUID.notification_updatePOSTMark a notification read by setting read_at, or snooze it with snoozed_until_at.notification_archivePOSTArchive a notification.notification_unarchivePOSTRestore an archived notification.notification_mark_read_allPOSTMark every notification raised by one issue, project or initiative as read.favorites_listPOSTList what the authenticated user has starred.favorite_createPOSTStar an issue, project, document, view, label, team or user.favorite_updatePOSTMove a favorite into a folder, or reorder it.favorite_deletePOSTUnstar something.custom_views_listPOSTList the workspace’s saved views, each with the filter it holds.custom_view_getPOSTGet one saved view.custom_view_createPOSTSave a filter as a view.custom_view_deletePOSTDelete a saved view.Searchsearch_issuesPOSTSearch issues by text, across titles and descriptions.search_projectsPOSTSearch projects by text, across names and descriptions.search_documentsPOSTSearch documents by text, across titles and content.Webhookswebhooks_listPOSTList the workspace’s webhooks.webhook_getPOSTGet one webhook by its UUID.webhook_createPOSTCreate a webhook.webhook_updatePOSTUpdate a webhook’s URL, resource types, secret, or whether it is enabled.webhook_deletePOSTDelete a webhook.
A hundred and twenty-eight identical rows in the Path column is not a rendering bug. It is the shape of the API.

The document is a constant, not a parameter

What distinguishes issues_list from issue_create is the query document, which belongs to the tool rather than to the call. It goes on the wire through static_body:
linear_declare_issues_list.py
static_body keys are sent verbatim, are not case-converted, and never appear in the LLM schema — so the document costs nothing in context and a model cannot see or rewrite it. Static values are applied last, so a schema field named query would lose to the document rather than overwrite it. That is the whole boundary. A GraphQL endpoint accepts arbitrary documents, so a tool that let the model supply one would not be an integration, it would be a shell. The documents live in charter.packs.linear.queries.

Failure arrives as HTTP 200, twice over

GraphQL puts document-level problems in an errors array beside the data. But a mutation Linear understood and then declined comes back with no errors at all, and the refusal one level below the operation name:
linear_failure_200.json
Every signal a runtime normally trusts says the write happened. So the envelope’s field names are paths, and * stands for whichever operation the tool called:
linear_envelope.py
One declaration covers both places, every mutation in the pack, and any mutation added later by someone who never read the source. A query has no success anywhere, and a wildcard that matches nothing is not a failure, so queries pass through untouched. See envelopes for why this is a declaration rather than a check inside each mutation’s response handler.

A cursor that goes out nested

Linear pages Relay-style: the cursor comes back at pageInfo.endCursor and goes out at variables.afterinside the variables object, not beside it. cursor_param takes a dotted path for exactly this.
linear_page_through_issues.py
pageInfo.hasNextPage is declared because Linear keeps sending an endCursor on the last page — without it the walk never terminates. The paths are read against the trimmed payload: every tool runs an unwrap handler that strips data and the operation name first, which is why pageInfo sits at the root here rather than three levels down.

Gotchas

issue_create wants a team_id, not "ENG"; assignee_id, not a person’s name; state_id, not "Done". Resolve them first with teams_list, users_list and workflow_states_list. Workflow states are per-team, so filter by team when resolving one.
Closing an issue is issue_update with a state_id whose type is completed or canceled. workflow_states_list returns each state’s type, which is how you find the right id without hard-coding it.
issue_update takes both. label_ids overwrites the issue’s labels outright, the way GitHub’s issues_update behaves. added_label_ids and removed_label_ids adjust the set that is already there, so the read-modify-write round trip is not needed — and neither is issue_add_label, which does the same thing for a single label.
body_case="camel", so team_id in your arguments arrives as teamId in the GraphQL variables. That is the cascade doing its job; it is called out here because GraphQL’s convention happens to match Charter’s default and it is easy to assume nothing is happening.
Linear’s IssueFilter is recursive. Conditions on one filter object combine with AND; and_ and or_ each take a list of filters and nest arbitrarily deep, which is how “urgent or assigned to me” is written. The trailing underscores are there because and and or are Python keywords — the casing layer strips them on the way to the wire, exactly as it does for in_.What is modelled is the issue’s own columns and its relationships one level deep. Linear’s own type also carries comparators for source metadata, agent-session existence, SLA timings and suggestion collections; those describe Linear’s internals rather than the work, and are absent.
The mutation projectUpdate edits a project. The resource ProjectUpdate is a status post written on one, with its own projectUpdateCreate and projectUpdates connection. The tools mirror Linear’s names rather than inventing clearer ones, so each can be checked against the documentation it came from: project_update edits a project, and project_update_create, project_update_update, project_update_get and project_updates_list work on status posts.
project_create and project_update take a status_id that comes from project_statuses_list. There is no state field on either input, and a pack that offered one sent a field Linear rejects. Setting the status is also how a project is completed or cancelled — there is no separate operation, the same way there is none for closing an issue.
issue_delete, project_delete and document_delete move the thing to the trash and answer with it, so it can be restored. Only issue_delete’s permanently_delete erases, and it is never sent unless asked for. comment_delete, webhook_delete and the rest are true deletes, and answer with the id they removed and nothing else.
Each tool asks for the fields in its document and no others. That is deliberate: it is what makes the response shape a property of the pack source rather than of the call, and it is what keeps Linear’s complexity budget predictable.
  • Envelopes — paths, wildcards, and failure below the root
  • The wire contractstatic_body and nested cursor parameters
  • Shopify — the other GraphQL pack, and what it breaks that this one does not