Expand description
§linear-api
Unofficial, typed async Rust client for the Linear GraphQL API — not affiliated with Linear Orbit, Inc.
Curated, handwritten operations (no codegen) with every GraphQL document schema-validated in CI against Linear’s committed SDL snapshot. Built for programmatic board automation: honest rate-limit handling, mutation-safe retries, tri-state update inputs, and typed IDs.
§Installation
cargo add linear-api§Authentication
API keys only (no OAuth). Keys look like lin_api_… and are created under
Linear → Settings → Security & access → Personal API keys. Linear expects the raw key in the
Authorization header — no Bearer prefix — and this crate sends it exactly that way,
stored as a secrecy secret and marked sensitive on the wire.
// Reads LINEAR_API_KEY from the environment:
let client = linear_api::LinearClient::from_env()?;
// Or configure explicitly:
let client = linear_api::LinearClient::builder()
.api_key("lin_api_...")
.timeout(std::time::Duration::from_secs(30))
.build()?;§Quickstart
use linear_api::issues::{IssueCreateInput, ListIssuesRequest};
use linear_api::{IssueFilter, LinearClient, StringComparator, TeamFilter};
let client = LinearClient::from_env()?;
// Who am I?
let viewer = client.viewer().await?;
println!("logged in as {} <{}>", viewer.display_name, viewer.email);
// Resolve the ENG team.
let team = client.teams().get(&linear_api::TeamId::new("ENG")).await?;
// List its open issues (one page; see `list_stream` for lazy full drains).
let page = client
.issues()
.list(
ListIssuesRequest::builder()
.filter(
IssueFilter::builder()
.team(
TeamFilter::builder()
.key(StringComparator::builder().eq("ENG".to_string()).build())
.build(),
)
.build(),
)
.first(25)
.build(),
)
.await?;
for issue in &page.nodes {
println!("{} {} [{}]", issue.identifier, issue.title, issue.state.name);
}
// Create an issue.
let issue = client
.issues()
.create(
IssueCreateInput::builder()
.team_id(team.id.clone())
.title("Fix the flux capacitor")
.build(),
)
.await?;
// Comment on it, then link a blocker.
client.comments().create_on(&issue.id, "On it.").await?;
client
.relations()
.create_blocks(linear_api::IssueRef::identifier("ENG-1"), &issue.id)
.await?;Update mutations use Undefinable<T> for tri-state semantics — leave unchanged (default),
clear (Undefinable::Null), or set:
use linear_api::Undefinable;
use linear_api::issues::IssueUpdateInput;
client
.issues()
.update(
linear_api::IssueRef::identifier("ENG-123"),
IssueUpdateInput::builder()
.title("New title") // set
.assignee_id(Undefinable::Null) // clear (unassign)
.build(), // everything else: unchanged
)
.await?;Runnable examples:
examples/viewer.rs
(auth + rate-limit snapshot) and
examples/workspace_tour.rs
(a read-only tour of the typed surface). Both need LINEAR_API_KEY:
LINEAR_API_KEY=lin_api_... cargo run --example workspace_tour§Coverage
| Tier | Area | Operations | Status |
|---|---|---|---|
| P0 | Viewer / workspace | viewer, organization | ✅ shipped |
| P0 | Teams | teams().list / list_stream / get / states | ✅ shipped |
| P0 | Users | users().list / list_stream / get | ✅ shipped |
| P0 | Workflow states | workflow_states().list | ✅ shipped |
| P0 | Issues | get / list / list_stream / search / create / batch_create / update / archive / delete / add_label / remove_label | ✅ shipped |
| P0 | Projects | list / list_stream / get / create / update / archive / statuses | ✅ shipped |
| P0 | Project milestones | milestones / create_milestone / update_milestone / delete_milestone | ✅ shipped |
| P0 | Labels | list / list_stream / create / update / delete / ensure (find-or-create) | ✅ shipped |
| P0 | Issue relations | create / create_blocks / delete / of_issue (blocks / blocked-by views) | ✅ shipped |
| P0 | Comments | list_for_issue / list_for_issue_stream / create / create_on / update / delete | ✅ shipped |
| P0 | Typed filters | IssueFilter, ProjectFilter, TeamFilter, UserFilter, WorkflowStateFilter, IssueLabelFilter, CommentFilter, … | ✅ shipped |
| P0 | Escape hatch | execute_raw (arbitrary documents, same error taxonomy) | ✅ shipped |
| P1 | Cycles | list/get, issue scheduling | 🔲 planned |
| P1 | Attachments | attachmentCreate, attachmentLinkURL | 🔲 planned |
| P1 | Project updates | status posts + health reporting | 🔲 planned |
| P1 | Batch issue update | issueBatchUpdate | 🔲 planned |
| P2 | Webhooks | payload types + HMAC-SHA256 verification helper | 🔲 roadmap |
| P2 | Documents / initiatives / customer needs | — | 🔲 roadmap |
| P2 | OAuth | — | 🔲 roadmap |
Deprecated operations (e.g. issueSearch) are never exposed; searchIssues is used instead.
Until a tier lands, execute_raw covers the long tail.
§Rate limits & retries
The client is header-aware and mutation-safe:
- Every response’s budget headers (
X-RateLimit-Requests-*,X-Complexity,X-RateLimit-Complexity-*) are parsed into a snapshot readable viaLinearClient::last_rate_limit(). Budgets are never hardcoded — Linear’s documented numbers are inconsistent; the headers are the source of truth. - Rate-limit rejections (HTTP 429, or Linear’s HTTP 400 +
RATELIMITEDextension code) are retried after the header-indicated wait — these are rejected before execution, so retrying is always safe, mutations included. If the wait exceedsRetryConfig::max_rate_limit_wait(default 30s) the call fails fast withError::RateLimited { retry_after, .. }instead of parking your task. - Queries retry on transient failures (connect errors, post-send transport errors, 5xx) with jittered exponential backoff (default 3 attempts, 250ms base, 8s cap).
- Mutations retry only on connect errors (nothing was sent). Linear has no idempotency
keys — a timed-out
issueCreatemay have landed, and a blind retry double-creates. Opt in withRetryConfig::retry_mutations_on_transientif your caller dedupes. - GraphQL errors are fail-closed: if
errorsis non-empty the call returnsError::Api { .. }even when partialdatawas present (execute_rawis the lenient escape hatch).
§Errors
One Error enum with honest variants: Config, Transport, Http, Api (typed via
LinearErrorType, e.g. AuthenticationError / Forbidden / InvalidInput), RateLimited
(with the server-indicated wait), Decode (the runtime schema-drift tripwire), MissingData,
and MutationFailed (success: false without errors). Helpers: is_rate_limited(),
is_authentication(), error_types().
§Schema drift defense
Every GraphQL document ships in a compile-time registry and is validated against the committed
SDL snapshot (schema/linear.graphql) by tests/schema_validation.rs using apollo-compiler —
a required CI check. A nightly job diffs the live SDL against the snapshot, and env-gated live
smoke tests (cargo test -- --ignored with LINEAR_API_KEY; read-only) exercise the real API.
§Features
| Feature | Default | Description |
|---|---|---|
rustls-tls | yes | TLS via rustls with webpki roots |
native-tls | no | TLS via the platform’s native stack |
tracing | no | tracing events: debug! per request, warn! per retry |
§MSRV
Rust 1.85 (edition 2024). Checked in CI.
§License
Licensed under either of MIT or Apache-2.0, at your option.
Modules§
- comments
- Issue comment threads: read an issue’s comments and create, update, or delete comments (threading included).
- issues
- Issue CRUD, list/search, batch create, and label convenience operations —
IssuesService, obtained viaLinearClient::issues. - labels
- Issue labels: listing, create/update/delete, and the
LabelsService::ensurefind-or-create convenience. - projects
- Projects, project statuses, and project milestones.
- relations
- Issue relations — the blocks/blocked-by DAG edges between issues.
- workspace
- Teams, users, and workflow states — the workspace lookup surface every board and plan operation needs (resolving team keys, assignees, and state names to the IDs that issue and project mutations require).
Structs§
- Boolean
Comparator - Comparator for boolean fields.
- Boolean
Comparator Builder - Use builder syntax to set the inputs and finish with
build(). - Comment
Filter - Filter for comments (
CommentFilterinput). - Comment
Filter Builder - Use builder syntax to set the inputs and finish with
build(). - Comment
Id - UUID of a Linear comment.
- CycleId
- UUID of a Linear cycle.
- Date
Comparator - Comparator for date fields. Values are strings: ISO-8601 instants
(
"2026-07-01T00:00:00Z") or relative durations like"-P2W". - Date
Comparator Builder - Use builder syntax to set the inputs and finish with
build(). - Error
Extensions - Linear’s
extensionsobject on GraphQL errors. - Graph
QlError - One GraphQL error object as returned by Linear.
- IdComparator
- Comparator for
IDfields. - IdComparator
Builder - Use builder syntax to set the inputs and finish with
build(). - Issue
Filter - Filter for issues (
IssueFilterinput). - Issue
Filter Builder - Use builder syntax to set the inputs and finish with
build(). - IssueId
- UUID of a Linear issue.
- Issue
Label Filter - Filter for issue labels (
IssueLabelFilterinput). - Issue
Label Filter Builder - Use builder syntax to set the inputs and finish with
build(). - Issue
Relation Id - UUID of a Linear issue relation.
- Issue
Stub - Minimal reference to an issue.
- LabelId
- UUID of a Linear issue label.
- Label
Ref - Minimal reference to an issue label.
- Linear
Client - Async client for the Linear GraphQL API.
- Linear
Client Builder - Builder for
LinearClient. Obtain viaLinearClient::builder. - Milestone
Ref - Minimal reference to a project milestone.
- Nullable
Number Comparator - Comparator for nullable number fields (e.g.
priority,estimate). - Nullable
Number Comparator Builder - Use builder syntax to set the inputs and finish with
build(). - Number
Comparator - Comparator for non-nullable number fields.
- Number
Comparator Builder - Use builder syntax to set the inputs and finish with
build(). - Organization
- The workspace (organization) the API key belongs to.
- Organization
Id - UUID of a Linear organization (workspace).
- Page
- One page of results plus its pagination metadata.
- Page
Info - Relay-style page metadata returned by every Linear connection.
- Project
Filter - Filter for projects (
ProjectFilterinput). - Project
Filter Builder - Use builder syntax to set the inputs and finish with
build(). - Project
Id - UUID of a Linear project.
- Project
Milestone Filter - Filter for project milestones (
ProjectMilestoneFilterinput). - Project
Milestone Filter Builder - Use builder syntax to set the inputs and finish with
build(). - Project
Milestone Id - UUID of a Linear project milestone.
- Project
Ref - Minimal reference to a project.
- Project
Status Id - UUID of a Linear project status.
- Rate
Limit Info - Snapshot of Linear’s rate-limit budget headers, parsed from every response. The reset headers are UTC epoch milliseconds on the wire.
- Relation
Exists Comparator - Comparator asserting the existence (or absence) of a relation.
- Relation
Exists Comparator Builder - Use builder syntax to set the inputs and finish with
build(). - Retry
Config - Retry policy. Defaults are safe for non-idempotent mutations: Linear has
no idempotency keys, so mutations are not retried on post-send
transport errors or 5xx unless
retry_mutations_on_transientis opted into. Rate-limit rejections happen before execution and are always retried (withinmax_rate_limit_wait). - State
Ref - Minimal reference to a workflow state.
- String
Comparator - Comparator for
Stringfields. - String
Comparator Builder - Use builder syntax to set the inputs and finish with
build(). - Team
Filter - Filter for teams (
TeamFilterinput). - Team
Filter Builder - Use builder syntax to set the inputs and finish with
build(). - TeamId
- UUID of a Linear team.
- TeamRef
- Minimal reference to a team.
- Template
Id - UUID of a Linear template.
- Timeless
Date - Linear’s
TimelessDatescalar: a calendar date with no time component, serialized as"YYYY-MM-DD". - User
Filter - Filter for users (
UserFilterinput). - User
Filter Builder - Use builder syntax to set the inputs and finish with
build(). - UserId
- UUID of a Linear user.
- UserRef
- Minimal reference to a user.
- Viewer
- The user the API key authenticates as.
- Workflow
State Filter - Filter for workflow states (
WorkflowStateFilterinput). - Workflow
State Filter Builder - Use builder syntax to set the inputs and finish with
build(). - Workflow
State Id - UUID of a Linear workflow state.
Enums§
- Error
- Everything that can go wrong talking to the Linear API.
- Issue
Ref - Reference to an issue where the API accepts a UUID or a human
identifier such as
"ENG-123": issue getters,issueUpdate’sid, andissueRelationCreateinputs. - Issue
Relation Type - The kind of an issue relation.
- Linear
Error Type - Typed classification of Linear API errors, mirroring
@linear/sdk’sLinearErrorType. Wire strings vary in casing and separators (e.g."authentication error"), so parsing is case-, space-, underscore-, and hyphen-insensitive; unmatched values are preserved inLinearErrorType::Unrecognized. - Pagination
Order By - Sort order for paginated list queries.
- Priority
- Issue priority. On the wire this is an integer
0..=4: 0 = no priority, 1 = urgent, 2 = high, 3 = medium, 4 = low. - Project
Health - Project health as reported in project updates.
- Project
Status Type - The kind of a project status.
- Undefinable
- GraphQL nullable-input tri-state: absent (leave unchanged) /
null(clear) / value (set). - Workflow
State Type - The kind of a workflow state (column category on a Linear board).
Functions§
- collect_
all - Drains
paginateinto aVec, stopping afterlimititems when given. - nodes
- Deserializes a GraphQL connection object
{"nodes": [...]}directly into aVec<T>. - paginate
- Lazily streams every item across pages by repeatedly invoking
fetchwith the previous page’send_cursor(Noneon the first call).