Optimize Linear API usage, reduce unnecessary calls, and maximize
efficiency within rate limit budgets.
Trigger: "linear cost", "reduce linear API calls", "linear efficiency",
"linear API usage", "optimize linear costs", "linear budget".
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Optimize Linear API usage, reduce unnecessary calls, and maximize
efficiency within rate limit budgets.
Trigger: "linear cost", "reduce linear API calls", "linear efficiency",
"linear API usage", "optimize linear costs", "linear budget".
allowed-tools
Read, Write, Edit, Grep
version
1.12.0
license
MIT
author
Jeremy Longshore <jeremy@intentsolutions.io>
tags
["saas","linear","api","cost-optimization"]
compatibility
Designed for Claude Code, also compatible with Codex and OpenClaw
Linear Cost Tuning
Overview
Optimize Linear API usage to stay within rate budgets and minimize infrastructure costs. Linear's API is free (no per-request billing), but rate limits (5,000 requests/hour, 250,000 complexity/hour) constrain throughput. Efficient patterns let you do more within these limits.
// BAD: ~12,500 pts — deeply nested with large page// issues(50) * (labels(50 default) * fields + comments(50) * user)const expensive = `query {
issues(first: 50) {
nodes {
id title
assignee { name }
labels { nodes { name } }
comments(first: 10) { nodes { body user { name } } }
}
}
}`;
// GOOD: ~55 pts — flat fields onlyconst cheap = `query {
issues(first: 50) {
nodes { id identifier title priority estimate }
}
}`;
// Fetch relations separately only when neededconst issueDetail = `query($id: String!) {
issue(id: $id) {
id identifier title description priority
assignee { name email }
state { name type }
labels { nodes { name color } }
}
}`;
Step 4: Request Coalescing
Deduplicate concurrent identical requests.
const inflight = newMap<string, Promise<any>>();
asyncfunction coalesce<T>(key: string, fn: () =>Promise<T>): Promise<T> {
if (inflight.has(key)) return inflight.get(key)!;
const promise = fn().finally(() => inflight.delete(key));
inflight.set(key, promise);
return promise;
}
// 10 concurrent requests for same team = 1 actual API callasyncfunctiongetTeam(teamKey: string) {
returncoalesce(`team:${teamKey}`, async () => {
const result = await client.teams({ filter: { key: { eq: teamKey } } });
return result.nodes[0];
});
}
Step 5: Cache with Smart TTLs
constCACHE_TTLS = {
teams: 600, // 10 min — teams almost never changeworkflowStates: 1800, // 30 min — states rarely changelabels: 600, // 10 min — labels rarely changeissues: 60, // 1 min — issues change frequentlyviewer: 3600, // 1 hr — your identity doesn't change
};
// Combined with webhook invalidation, even short TTLs// dramatically reduce redundant requests
Step 6: Filter Webhook Events
Skip irrelevant events to reduce processing costs.
asyncfunctionprocessEvent(event: any): Promise<void> {
// Skip bot/automation events to avoid loopsif (event.actor?.type === "application") return;
// Skip trivial field updates (e.g., sortOrder changes)if (event.type === "Issue" && event.action === "update") {
const significantFields = ["stateId", "assigneeId", "priority", "title"];
const changedFields = Object.keys(event.updatedFrom ?? {});
if (!changedFields.some(f => significantFields.includes(f))) return;
}
// Skip specific teams if not relevantconst relevantTeamKeys = ["ENG", "PRODUCT"];
if (event.data?.team?.key && !relevantTeamKeys.includes(event.data.team.key)) return;
// Process significant eventawaithandleEvent(event);
}
Step 7: Incremental Sync Pattern
// Instead of fetching ALL issues every sync:// Sort by updatedAt, stop when you reach already-synced dataasyncfunctionincrementalSync(client: LinearClient, lastSyncTime: string) {
letcursor: string | undefined;
let synced = 0;
while (true) {
const issues = await client.issues({
first: 100,
after: cursor,
filter: { updatedAt: { gte: lastSyncTime } },
orderBy: "updatedAt",
});
for (const issue of issues.nodes) {
awaitupsertLocally(issue);
synced++;
}
if (!issues.pageInfo.hasNextPage) break;
cursor = issues.pageInfo.endCursor;
}
console.log(`Synced ${synced} issues since ${lastSyncTime}`);
return synced;
}
Optimization Checklist
Replace all polling with webhooks
Implement request caching (static data: 10-30 min TTL)
Add request coalescing for concurrent identical calls