--- name: linktree-sdk-patterns description: 'Sdk Patterns for Linktree. Trigger: "linktree sdk patterns". ' allowed-tools: Read, Write, Edit version: 1.0.0 license: MIT author: Jeremy Longshore tags: - saas - linktree - social compatibility: Designed for Claude Code --- # Linktree SDK Patterns ## Overview Linktree's REST API exposes profile management, link CRUD, click analytics, and appearance theming through bearer-token authentication. A structured SDK client matters here because Linktree enforces strict per-minute rate limits on analytics endpoints and returns nested profile objects that benefit from strong typing. These patterns provide a thread-safe singleton, typed error classification, fluent request building for paginated link lists, and test utilities for mocking profile and analytics responses. ## Prerequisites - Node.js 18+, TypeScript 5+ - `LINKTREE_API_KEY` environment variable (generated in Linktree admin > Settings > Developer) - `axios` or `node-fetch` for HTTP transport ## Singleton Client ```typescript interface LinktreeConfig { apiKey: string; baseUrl?: string; timeout?: number; } let client: LinktreeClient | null = null; export function getLinktreeClient(overrides?: Partial): LinktreeClient { if (!client) { const config: LinktreeConfig = { apiKey: process.env.LINKTREE_API_KEY ?? '', baseUrl: 'https://api.linktr.ee/v1', timeout: 10_000, ...overrides, }; if (!config.apiKey) throw new Error('LINKTREE_API_KEY is required'); client = new LinktreeClient(config); } return client; } ``` ## Error Wrapper ```typescript interface LinktreeError { status: number; code: string; detail: string; } async function safeLinktree(fn: () => Promise): Promise { try { return await fn(); } catch (err: any) { const parsed: LinktreeError = { status: err.response?.status ?? 500, code: err.response?.data?.error?.code ?? 'UNKNOWN', detail: err.response?.data?.error?.message ?? err.message, }; if (parsed.status === 429) { const retryAfter = parseInt(err.response?.headers?.['retry-after'] ?? '5', 10); await new Promise(r => setTimeout(r, retryAfter * 1000)); return fn(); } if (parsed.status === 401) throw new Error(`Auth failed: ${parsed.detail}`); throw new Error(`Linktree ${parsed.code} (${parsed.status}): ${parsed.detail}`); } } ``` ## Request Builder ```typescript class LinkQueryBuilder { private params: Record = {}; forProfile(id: string) { this.params.profile_id = id; return this; } active(only = true) { this.params.is_active = String(only); return this; } page(cursor: string) { this.params.cursor = cursor; return this; } limit(n: number) { this.params.limit = String(Math.min(n, 100)); return this; } build(): URLSearchParams { return new URLSearchParams(this.params); } } ``` ## Response Types ```typescript interface LinktreeProfile { id: string; username: string; tier: 'free' | 'pro' | 'premium'; created_at: string; } interface LinktreeLink { id: string; title: string; url: string; position: number; is_active: boolean; thumbnail_url?: string; } interface LinkAnalytics { link_id: string; clicks: number; unique_visitors: number; period: string; } interface PaginatedLinks { data: LinktreeLink[]; cursor?: string; has_more: boolean; } ``` ## Middleware Pattern ```typescript type Middleware = (req: RequestInit, next: () => Promise) => Promise; const authMiddleware: Middleware = (req, next) => { req.headers = { ...req.headers as Record, Authorization: `Bearer ${process.env.LINKTREE_API_KEY}` }; return next(); }; const loggingMiddleware: Middleware = async (req, next) => { const start = Date.now(); const res = await next(); console.log(`[linktree] ${req.method} ${res.status} ${Date.now() - start}ms`); return res; }; ``` ## Testing Utilities ```typescript function mockProfile(overrides?: Partial): LinktreeProfile { return { id: 'prof_test_123', username: 'testuser', tier: 'pro', created_at: '2025-01-01T00:00:00Z', ...overrides }; } function mockLink(overrides?: Partial): LinktreeLink { return { id: 'link_abc', title: 'My Site', url: 'https://example.com', position: 0, is_active: true, ...overrides }; } function mockAnalytics(linkId: string): LinkAnalytics { return { link_id: linkId, clicks: 142, unique_visitors: 98, period: '7d' }; } ``` ## Error Handling | Pattern | When to Use | Example | |---------|-------------|---------| | Retry with backoff | 429 rate limit on analytics endpoints | Parse `Retry-After` header, wait, retry once | | Auth refresh | 401 on any endpoint | Re-fetch API key from vault, rebuild client singleton | | Graceful degrade | Analytics endpoint down | Return cached click counts, log warning | | Validation guard | Creating links with invalid URLs | Check URL format before API call, throw typed error | | Idempotency check | Duplicate link creation | Query existing links by URL before POST | ## Resources - [Linktree API Reference](https://linktr.ee/marketplace/developer) ## Next Steps Apply in `linktree-core-workflow-a`.