Mindtickle sdk patterns
'Sdk Patterns for MindTickle.From its SKILL.md
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill mindtickle-sdk-patternsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
6.5 KB, ~1.6k tokens by cl100k_base, as published. Nobody here has run it
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_KEYenvironment variable (generated in Admin > Integrations > API Keys)MINDTICKLE_ORG_IDfor multi-org deploymentsaxiosornode-fetchfor HTTP transport
Singleton Client
interface MindTickleConfig {
apiKey: string;
orgId: string;
baseUrl?: string;
timeout?: number;
}
let client: MindTickleClient | null = null;
export function getMindTickleClient(overrides?: Partial<MindTickleConfig>): 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
interface MindTickleError { statusCode: number; errorType: string; description: string; requestId: string; }
async function safeMindTickle<T>(fn: () => Promise<T>): Promise<T> {
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
class ProgressQueryBuilder {
private params: Record<string, string> = {};
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
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
type Middleware = (req: RequestInit, next: () => Promise<Response>) => Promise<Response>;
const orgScopeMiddleware = (orgId: string): Middleware => (req, next) => {
req.headers = { ...req.headers as Record<string, string>, '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
function mockCourse(overrides?: Partial<Course>): 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>): ScimUser {
return { id: 'usr_test_001', userName: '[email protected]', displayName: 'Jane Smith', emails: [{ value: '[email protected]', 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
Next Steps
Apply in mindtickle-core-workflow-a.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.