lineark-sdk
Typed, async-first Rust SDK for the Linear GraphQL API.
Part of the lineark project — an unofficial Linear ecosystem for Rust.
Install
Quick start
use Client;
use ;
async
Authentication
Create a Linear API token and provide it via any of these methods (in priority order):
| Method | Example |
|---|---|
| Direct | Client::from_token("lin_api_...") |
| Env var | export LINEAR_API_TOKEN="lin_api_..." then Client::from_env() |
| File | echo "lin_api_..." > ~/.linear_api_token then Client::from_file() |
| Auto | Client::auto() — tries env var, then file |
Queries
Collection queries use a builder pattern with optional pagination and filtering:
// Paginate with first/last/after/before
let issues = client..first.send.await?;
let page2 = client..first.after.send.await?;
// Search with extra filters
let results = client.
.first
.team_id
.include_comments
.send
.await?;
| Method | Returns | Description |
|---|---|---|
whoami() |
User |
Authenticated user |
teams() |
Connection<Team> |
List teams |
team(id) |
Team |
Get team by ID |
users() |
Connection<User> |
List users |
projects() |
Connection<Project> |
List projects |
project(id) |
Project |
Get project by ID |
issues() |
Connection<Issue> |
List issues |
issue(id) |
Issue |
Get issue by ID |
search_issues(term) |
Connection<IssueSearchResult> |
Full-text issue search |
issue_labels() |
Connection<IssueLabel> |
List labels |
cycles() |
Connection<Cycle> |
List cycles |
cycle(id) |
Cycle |
Get cycle by ID |
documents() |
Connection<Document> |
List documents |
document(id) |
Document |
Get document by ID |
issue_relations() |
Connection<IssueRelation> |
List issue relations |
issue_relation(id) |
IssueRelation |
Get issue relation by ID |
project_milestones() |
Connection<ProjectMilestone> |
List project milestones |
project_milestone(id) |
ProjectMilestone |
Get project milestone by ID |
workflow_states() |
Connection<WorkflowState> |
List workflow states |
All collection queries support .first(n), .last(n), .after(cursor), .before(cursor), and .include_archived(bool).
Custom field selection
All queries are generic over T: DeserializeOwned + GraphQLFields. By default they use the generated types (which fetch all scalar fields), but you can define custom lean structs to fetch only the fields you need.
The derive macro is re-exported by lineark-sdk — you don't need to add lineark-derive to your Cargo.toml. Just import and use:
use GraphQLFields; // gives you both the trait AND #[derive(GraphQLFields)]
Custom types must include #[graphql(full_type = X)] pointing to the corresponding generated type — this is required for the type to satisfy the query's FullType constraint, and it also validates your fields at compile time (misspelled or nonexistent fields are a compile error):
use ;
use Issue;
use Deserialize;
let client = auto?;
let issues = client..first.send.await?;
for issue in &issues.nodes
The #[derive(GraphQLFields)] macro generates a selection() method from the struct's field names, so the query fetches exactly those fields — no overfetching. For nested objects, annotate with #[graphql(nested)] (each nested type also needs its own #[graphql(full_type = X)]):
use Team;
Mutations
Mutations are also generic — use turbofish or let the type be inferred:
use IssueCreateInput;
use Issue;
let payload = client..await?;
| Method | Description |
|---|---|
issue_create(input) |
Create an issue |
issue_update(input, id) |
Update an issue |
issue_archive(trash, id) |
Archive an issue |
issue_unarchive(id) |
Unarchive a previously archived issue |
issue_delete(permanently, id) |
Delete an issue |
comment_create(input) |
Create a comment |
document_create(input) |
Create a document |
document_update(input, id) |
Update a document |
document_delete(id) |
Delete a document |
project_milestone_create(input) |
Create a project milestone |
project_milestone_update(input, id) |
Update a project milestone |
project_milestone_delete(id) |
Delete a project milestone |
issue_relation_create(override_created_at, input) |
Create an issue relation |
file_upload(meta, public, size, type, name) |
Request a signed upload URL |
image_upload_from_url(url) |
Upload image from URL |
File upload and download
The SDK provides high-level helpers for Linear's file operations:
// Upload a file (two-step: get signed URL from Linear, then PUT to GCS)
let bytes = read?;
let result = client.upload_file.await?;
println!;
// Download a file from Linear's CDN
let result = client.download_url.await?;
write?;
Blocking (synchronous) API
For non-async contexts, enable the blocking feature:
[]
= { = "...", = ["blocking"] }
The blocking client mirrors the async API exactly:
use Client;
use Document;
let client = auto?;
let me = client.whoami?;
println!;
let teams = client.teams.first.send?;
for team in &teams.nodes
// Mutations are generic — use turbofish or type inference
let payload = client.?;
// File operations too
let result = client.upload_file?;
let downloaded = client.download_url?;
Error handling
All methods return Result<T, LinearError>. Error variants:
Authentication— invalid or expired tokenForbidden— insufficient permissionsRateLimited— API rate limit hit (includesretry_after)InvalidInput— bad request parametersGraphQL— errors returned in the GraphQL response (includes query name for diagnostics)Network— connection/transport failuresHttpError— non-200 responses not covered aboveMissingData— expected data path not found in responseAuthConfig— auth configuration error (no token found)Internal— internal error (e.g. runtime creation failure)
Codegen
All types, enums, inputs, and query functions are generated from Linear's official GraphQL schema. The generated code lives in src/generated/ and is checked in for reproducible builds.
License
MIT