128 toolsAPI key
linear_example.py
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 inAuthorization, 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 aTool, 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
A hundred and twenty-eight identical rows in the Path column is not a rendering
bug. It is the shape of the API.
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.The document is a constant, not a parameter
What distinguishesissues_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 anerrors 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
* stands for whichever operation the tool called:
linear_envelope.py
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 atpageInfo.endCursor and goes
out at variables.after — inside 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
Everything takes UUIDs, not names
Everything takes UUIDs, not names
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.There is no close operation
There is no close operation
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.label_ids replaces; added_label_ids adds
label_ids replaces; added_label_ids adds
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.Variables are camelCase on the wire
Variables are camelCase on the wire
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.Boolean composition is spelled and_ / or_
Boolean composition is spelled and_ / or_
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.Two different things are called a project update
Two different things are called a project update
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.A project's status is a UUID, not a word
A project's status is a UUID, not a word
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.Delete usually means trash
Delete usually means trash
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.Selection sets are fixed
Selection sets are fixed
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.
Related
- Envelopes — paths, wildcards, and failure below the root
- The wire contract —
static_bodyand nested cursor parameters - Shopify — the other GraphQL pack, and what it breaks that this one does not