--- name: mindtickle-sdk-patterns description: 'Sdk Patterns for MindTickle. Trigger: "mindtickle sdk patterns". ' allowed-tools: Read, Write, Edit version: 1.0.0 license: MIT author: Jeremy Longshore tags: - saas - mindtickle - sales compatibility: Designed for Claude Code --- # MindTickle SDK Patterns ## Overview MindTickle's REST API serves sales enablement workflows including course management, quiz administration, user progress tracking, SCIM user provisioning, and coaching analytics. A structured SDK client is critical because MindTickle uses compound API keys with org-scoped tokens, returns progress data as nested completion trees with module-level granularity, and enforces strict SCIM schema compliance for user sync. These patterns provide org-aware authentication, typed models for training content hierarchies, progress query builders, and mock factories for sales readiness test scenarios. ## Prerequisites - Node.js 18+, TypeScript 5+ - `MINDTICKLE_API_KEY` environment variable (generated in Admin > Integrations > API Keys) - `MINDTICKLE_ORG_ID` for multi-org deployments - `axios` or `node-fetch` for HTTP transport ## Singleton Client ```typescript interface MindTickleConfig { apiKey: string; orgId: string; baseUrl?: string; timeout?: number; } let client: MindTickleClient | null = null; export function getMindTickleClient(overrides?: Partial): MindTickleClient { if (!client) { const config: MindTickleConfig = { apiKey: process.env.MINDTICKLE_API_KEY ?? '', orgId: process.env.MINDTICKLE_ORG_ID ?? '', baseUrl: 'https://api.mindtickle.com/v2', timeout: 15_000, ...overrides, }; if (!config.apiKey || !config.orgId) throw new Error('MINDTICKLE_API_KEY and MINDTICKLE_ORG_ID are required'); client = new MindTickleClient(config); } return client; } ``` ## Error Wrapper ```typescript interface MindTickleError { statusCode: number; errorType: string; description: string; requestId: string; } async function safeMindTickle(fn: () => Promise): Promise { try { return await fn(); } catch (err: any) { const parsed: MindTickleError = { statusCode: err.response?.status ?? 500, errorType: err.response?.data?.error?.type ?? 'INTERNAL_ERROR', description: err.response?.data?.error?.description ?? err.message, requestId: err.response?.headers?.['x-request-id'] ?? 'unknown', }; if (parsed.statusCode === 429) { const retryMs = parseInt(err.response?.headers?.['retry-after-ms'] ?? '3000', 10); await new Promise(r => setTimeout(r, retryMs)); return fn(); } if (parsed.errorType === 'SCIM_CONFLICT') throw new Error(`User already provisioned (req: ${parsed.requestId})`); if (parsed.statusCode === 403) throw new Error(`Org ${process.env.MINDTICKLE_ORG_ID} lacks permission: ${parsed.description}`); throw new Error(`MindTickle ${parsed.errorType} (${parsed.statusCode}): ${parsed.description} [${parsed.requestId}]`); } } ``` ## Request Builder ```typescript class ProgressQueryBuilder { private params: Record = {}; forUser(userId: string) { this.params.user_id = userId; return this; } inCourse(courseId: string) { this.params.series_id = courseId; return this; } completedAfter(date: string) { this.params.completed_after = date; return this; } status(s: 'not_started' | 'in_progress' | 'completed') { this.params.status = s; return this; } page(n: number) { this.params.page = String(n); return this; } pageSize(n: number) { this.params.page_size = String(Math.min(n, 50)); return this; } build(): URLSearchParams { return new URLSearchParams(this.params); } } ``` ## Response Types ```typescript interface Course { id: string; title: string; moduleCount: number; status: 'draft' | 'published' | 'archived'; createdAt: string; } interface QuizResult { quizId: string; userId: string; score: number; passingScore: number; passed: boolean; attemptNumber: number; completedAt: string; } interface UserProgress { userId: string; courseId: string; completedModules: number; totalModules: number; percentComplete: number; lastActivityAt: string; } interface ScimUser { id: string; userName: string; displayName: string; emails: { value: string; primary: boolean }[]; active: boolean; groups: string[]; } ``` ## Middleware Pattern ```typescript type Middleware = (req: RequestInit, next: () => Promise) => Promise; const orgScopeMiddleware = (orgId: string): Middleware => (req, next) => { req.headers = { ...req.headers as Record, 'X-MindTickle-Org': orgId, Authorization: `Bearer ${process.env.MINDTICKLE_API_KEY}` }; return next(); }; const metricsMiddleware: Middleware = async (req, next) => { const start = Date.now(); const res = await next(); const endpoint = new URL(req.url as string).pathname; console.log(`[mindtickle] ${req.method} ${endpoint} ${res.status} ${Date.now() - start}ms`); return res; }; ``` ## Testing Utilities ```typescript function mockCourse(overrides?: Partial): Course { return { id: 'series_abc', title: 'Q3 Product Training', moduleCount: 8, status: 'published', createdAt: '2025-07-01T00:00:00Z', ...overrides }; } function mockProgress(userId: string, courseId: string): UserProgress { return { userId, courseId, completedModules: 5, totalModules: 8, percentComplete: 62.5, lastActivityAt: '2025-07-15T14:30:00Z' }; } function mockScimUser(overrides?: Partial): ScimUser { return { id: 'usr_test_001', userName: 'jsmith@acme.com', displayName: 'Jane Smith', emails: [{ value: 'jsmith@acme.com', primary: true }], active: true, groups: ['sales-east'], ...overrides }; } ``` ## Error Handling | Pattern | When to Use | Example | |---------|-------------|---------| | Retry with header delay | 429 on progress or analytics endpoints | Parse `Retry-After-Ms` header, wait exact duration, retry once | | SCIM conflict resolution | 409 when provisioning existing user | Fetch existing user by email, update instead of create | | Org scope validation | 403 on cross-org resource access | Verify `MINDTICKLE_ORG_ID` matches target course owner | | Completion tree unwrap | Nested module progress with null leaves | Flatten tree, filter nulls, compute rollup percentage | | Bulk operation chunking | Enrolling 500+ users in a course | Batch into groups of 50, sequential with rate limit pauses | ## Resources - [MindTickle API Reference](https://www.mindtickle.com/platform/integrations/) ## Next Steps Apply in `mindtickle-core-workflow-a`.