401 Unauthorized |
Invalid or missing API key |
Verify
'Execute MindTickle secondary workflow: Rep Performance & Readiness.
ReadWriteEditBash(npm:*)Grep
MindTickle — Quiz & Assessment
Overview
Create quizzes and assessments, grade submissions, and track scores across your sales
enablement programs in MindTickle. Use this workflow when building certification exams,
knowledge checks after training modules, or competency assessments that feed into
rep readiness scores. This is the secondary workflow — for training content and
course management, see mindtickle-core-workflow-a.
Instructions
Step 1: Create a Quiz with Question Bank
const quiz = await client.assessments.create({
title: 'Q2 Product Knowledge Certification',
module_id: 'mod_product_q2',
passing_score: 80,
time_limit_minutes: 30,
max_attempts: 2,
questions: [
{ type: 'multiple_choice', text: 'What is the primary use case for Feature X?',
options: ['Analytics', 'Automation', 'Security', 'Compliance'],
correct: 1, points: 10 },
{ type: 'true_false', text: 'Feature Y supports SSO out of the box.',
correct: true, points: 5 },
{ type: 'open_ended', text: 'Describe how you would position Feature X to a CFO.',
points: 20, rubric: 'Must mention ROI, time savings, and compliance' },
],
});
console.log(`Quiz ${quiz.id} created — ${quiz.questions.length} questions, pass=${quiz.passing_score}%`);
Step 2: Assign Quiz to a Team
const assignment = await client.assessments.assign(quiz.id, {
team_ids: ['team_sales_west', 'team_sales_east'],
due_date: '2026-04-20',
reminder_days: [7, 3, 1],
notify: true,
});
console.log(`Assigned to ${assignment.total_reps} reps, due ${assignment.due_date}`);
Step 3: Grade Submissions and Review Scores
const submissions = await client.assessments.submissions(quiz.id, {
status: 'completed',
sort: 'score_desc',
});
submissions.items.forEach(s =>
console.log(`${s.rep_name}: ${s.score}% (${s.passed ? 'PASS' : 'FAIL'}) — attempt ${s.attempt}/${quiz.max_attempts}`)
);
const pending = submissions.items.filter(s => s.pending_review);
for (const sub of pending) {
await client.assessments.grade(quiz.id, sub.id, {
question_scores: [{ question_id: 'q3', score: 15, feedback: 'Missed compliance angle' }],
});
}
Step 4: Export Score Analytics
const analytics = await client.assessments.analytics(quiz.id, {
group_by: 'team',
metrics: ['avg_score', 'pass_rate', 'avg_completion_time'],
});
analytics.teams.forEach(t =>
console.log(`${t.team_name}: avg=${t.avg_score}%, pass_rate=${t.pass_rate}%, avg_time=${t.avg_minutes}m`)
);
Error Handling
'Cost Tuning for MindTickle.
ReadWriteEditGrep
MindTickle Cost Tuning
Overview
MindTickle pricing is per-seat with costs driven by course content volume, quiz assessment frequency, and coaching session recordings. Each training module creation, quiz grading event, and call recording analysis consumes platform resources proportional to content complexity and learner count. For sales organizations onboarding hundreds of reps with dozens of active courses, unchecked content duplication and excessive assessment polling accumulate unnecessary spend. Consolidating content, optimizing assessment cadence, and right-sizing seat allocation are the highest-impact cost levers.
Cost Breakdown
| Component |
Cost Driver |
Optimization |
| Seat licenses |
Per-learner/month pricing |
Deprovision churned reps within 7 days; audit quarterly |
| Course content |
Storage and delivery per training module |
Deduplicate content across programs; archive outdated courses |
| Quiz assessments |
Grading compute per quiz submission |
Reduce retake frequency; batch grade submissions |
| Call recordings |
Storage and AI analysis per coaching session |
Set retention policies; analyze only flagged calls |
| API integrations |
Sync events with CRM/HRIS systems |
Batch sync; use webhooks instead of polling |
API Call Reduction
class MindTickleContentOptimizer {
private contentCache = new Map<string, { data: any; expiry: number }>();
private syncTimestamps = new Map<string, number>();
async getCourseContent(courseId: string, fetchFn: () => Promise<any>): Promise<any> {
const cached = this.contentCache.get(courseId);
if (cached && Date.now() < cached.expiry) return cached.data;
const data = await fetchFn();
// Course content changes rarely — cache for 24 hours
this.contentCache.set(courseId, { data, expiry: Date.now() + 86_400_000 });
return data;
}
async incrementalUserSync(users: any[]): Promise<any[]> {
const lastSync = this.syncTimestamps.get('users') || 0;
const changed = users.filter(u => u.updatedAt > lastSync);
this.syncTimestamps.set('users', Date.now());
// Typically reduces sync volume by 70-90% for stable orgs
return this.batchSync(changed);
}
private async batchSync(records: any[]): Promise<any[]> {
const batches = Array.from({ length: Math.ceil(records.length / 50) },
(_, i) => records.slice(i * 50, i * 50 + 50));
return Promise.all(batches.map(b => fetch('/api/users/bulk', {
method: 'POST', body: JSON.stringify(b)
})));
}
}
Usage Monitoring
class MindTickleCostTracker {
private d
'Debug Bundle for MindTickle.
ReadBash(curl:*)Grep
MindTickle Debug Bundle
Overview
This debug bundle collects diagnostic evidence from MindTickle sales enablement API
integrations for troubleshooting course delivery, quiz scoring, user progress tracking,
and CRM sync pipelines. It captures API token validation, course catalog accessibility,
user enrollment status, content module health, and Salesforce integration state. The
resulting tarball provides the evidence needed to diagnose training completion gaps,
broken content assignments, scoring discrepancies, and SSO provisioning failures
without requiring MindTickle admin panel access.
Prerequisites
curl, jq, tar installed
MINDTICKLEAPIKEY set (API token from MindTickle Admin > Integrations > API)
MINDTICKLE_INSTANCE set to your instance URL (e.g., yourcompany.mindtickle.com)
Debug Collection Script
#!/bin/bash
set -euo pipefail
BUNDLE="debug-mindtickle-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BUNDLE"
# Environment check
echo "=== Environment ===" > "$BUNDLE/environment.txt"
echo "API Key: ${MINDTICKLE_API_KEY:+SET (redacted)}" >> "$BUNDLE/environment.txt"
echo "Instance: ${MINDTICKLE_INSTANCE:-NOT SET}" >> "$BUNDLE/environment.txt"
echo "Node: $(node -v 2>/dev/null || echo 'not installed')" >> "$BUNDLE/environment.txt"
echo "Timestamp: $(date -u)" >> "$BUNDLE/environment.txt"
# API connectivity — company info
echo "=== API Health ===" > "$BUNDLE/api-health.txt"
curl -sf -o "$BUNDLE/api-health.txt" -w "HTTP %{http_code} in %{time_total}s\n" \
-H "Authorization: Bearer ${MINDTICKLE_API_KEY}" \
"https://${MINDTICKLE_INSTANCE}/api/v2/company" 2>&1 || echo "UNREACHABLE" > "$BUNDLE/api-health.txt"
# Course catalog
echo "=== Courses ===" > "$BUNDLE/courses.json"
curl -sf -H "Authorization: Bearer ${MINDTICKLE_API_KEY}" \
"https://${MINDTICKLE_INSTANCE}/api/v2/series?limit=10" \
>> "$BUNDLE/courses.json" 2>&1 || echo '{"error":"FAILED"}' > "$BUNDLE/courses.json"
# User enrollment sample
echo "=== Users ===" > "$BUNDLE/users.json"
curl -sf -H "Authorization: Bearer ${MINDTICKLE_API_KEY}" \
"https://${MINDTICKLE_INSTANCE}/api/v2/users?limit=5" \
>> "$BUNDLE/users.json" 2>&1 || echo '{"error":"FAILED"}' > "$BUNDLE/users.json"
# Quiz/assessment results
echo "=== Assessments ===" > "$BUNDLE/assessments.json"
SERIES_ID=$(jq -r '.se
'Deploy Integration for MindTickle.
ReadWriteEditGrep
MindTickle Deploy Integration
Overview
Deploy a containerized MindTickle integration service that manages sales readiness courses, tracks learner progress across quizzes and certifications, and synchronizes enablement data through the MindTickle API. This skill covers Docker multi-stage builds for the MindTickle SDK, API token configuration, health checks that verify course catalog access, and rolling deployments with zero disruption to active training sessions and learner progress tracking.
Prerequisites
- Docker 24+ and Docker Compose v2 installed
- Valid
MINDTICKLEAPIKEY from the MindTickle admin console (Settings > API)
- Node.js 20 LTS (build stage)
- Network access to
api.mindtickle.com on port 443
- Target deployment host with at least 256MB available memory
Docker Configuration
FROM node:20-slim AS builder
WORKDIR /build
COPY package*.json tsconfig.json ./
RUN npm ci
COPY src/ ./src/
RUN npm run build
FROM node:20-slim
RUN groupadd -r mindtickle && useradd -r -g mindtickle -m appuser
WORKDIR /app
COPY --from=builder /build/dist ./dist/
COPY --from=builder /build/node_modules ./node_modules/
COPY package*.json ./
RUN npm prune --production
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
Environment Variables
MINDTICKLE_API_KEY="mt_xxxxxxxxxxxxxxxx" # API key from admin console
MINDTICKLE_BASE_URL="https://api.mindtickle.com" # API base URL
MINDTICKLE_COMPANY_ID="" # Company identifier for multi-tenant access
MINDTICKLE_WEBHOOK_SECRET="" # HMAC secret for inbound webhook verification
LOG_LEVEL="info" # debug | info | warn | error
NODE_ENV="production"
PORT="3000"
Health Check Endpoint
import express from "express";
const app = express();
app.get("/health", async (_req, res) => {
try {
const response = await fetch(`${process.env.MINDTICKLE_BASE_URL}/v2/courses`, {
headers: {
Authorization: `Bearer ${process.env.MINDTICKLE_API_KEY}`,
"X-Company-Id": process.env.MINDTICKLE_COMPANY_ID ?? "",
},
});
if (!response.ok) throw new Error(`MindTickle API returned ${response.status}`);
const data = await response.json();
res.json({ status: "healthy", activeCourses: data.courses?.length ?? 0 });
} catch (err) {
res.status(503).json({ status: "unhealthy", error: (err as Error).message });
}
});
Deployment Steps
Step 1: Build Image
'Create a minimal working MindTickle example.
ReadWriteEditBash(npm:*)Grep
MindTickle Hello World
Overview
Minimal working examples demonstrating core MindTickle API functionality.
Instructions
Step 1: Create Training Module
const module = await client.modules.create({
title: 'Q1 Product Update Training',
type: 'course',
description: 'Learn about new product features for Q1',
tags: ['product', 'q1-2026'],
content: [
{ type: 'video', url: 'https://videos.example.com/q1-update.mp4', title: 'Overview' },
{ type: 'quiz', questions: [
{ text: 'What is the key new feature?', type: 'multiple_choice',
options: ['Feature A', 'Feature B', 'Feature C'], correct: 0 }
]}
]
});
console.log(`Module created: ${module.id}`);
Step 2: Assign to Sales Reps
await client.assignments.create({
module_id: module.id,
assignees: { type: 'team', team_ids: ['team_sales_west', 'team_sales_east'] },
due_date: '2026-04-15',
reminder: { enabled: true, days_before: [7, 3, 1] }
});
Step 3: Track Completion
const progress = await client.analytics.moduleProgress(module.id);
progress.users.forEach(u =>
console.log(`${u.name}: ${u.completion}% | Score: ${u.quiz_score || 'N/A'}`)
);
console.log(`Overall: ${progress.completion_rate}% complete`);
Error Handling
| Error |
Cause |
Solution |
| Auth error |
Invalid credentials |
Check MINDTICKLEAPIKEY |
| Not found |
Invalid endpoint |
Verify API URL |
| Rate limit |
Too many requests |
Implement backoff |
Resources
Next Steps
See mindtickle-local-dev-loop.
'Install and configure MindTickle SDK/API authentication.
ReadWriteEditBash(npm:*)Bash(pip:*)Grep
MindTickle Install & Auth
Overview
Set up MindTickle API for sales readiness, training content management, and rep performance analytics.
Prerequisites
- MindTickle account and API access
- API key/credentials from MindTickle dashboard
- Node.js 18+ or Python 3.8+
Instructions
Step 1: Install SDK
npm install @mindtickle/sdk
# API key from MindTickle Admin > Integrations > API
Step 2: Configure Authentication
export MINDTICKLE_API_KEY="your-api-key-here"
echo 'MINDTICKLE_API_KEY=your-api-key' >> .env
Step 3: Verify Connection (TypeScript)
import { MindTickleClient } from '@mindtickle/sdk';
const client = new MindTickleClient({ apiKey: process.env.MINDTICKLE_API_KEY });
const users = await client.users.list({ limit: 5 });
console.log(`Found ${users.total} users`);
Step 4: Verify Connection (Python)
import mindtickle
client = mindtickle.Client(api_key=os.environ['MINDTICKLE_API_KEY'])
users = client.users.list(limit=5)
print(f'Found {users.total} users')
Error Handling
| Error |
Code |
Solution |
| Invalid API key |
401 |
Verify credentials in dashboard |
| Permission denied |
403 |
Check API scopes/permissions |
| Rate limited |
429 |
Implement backoff |
Resources
Next Steps
After auth, proceed to mindtickle-hello-world.
'Local Dev Loop for MindTickle.
ReadWriteEdit
MindTickle Local Dev Loop
Overview
Local development workflow for MindTickle sales enablement and readiness API integration. Provides a fast feedback loop with mock training modules, user progress, and coaching data so you can build sales readiness dashboards without needing a live MindTickle instance. Toggle between mock mode for rapid iteration and sandbox mode for validating against the real MindTickle platform.
Environment Setup
cp .env.example .env
# Set your credentials:
# MINDTICKLE_API_KEY=mt_xxxxxxxxxxxx
# MINDTICKLE_BASE_URL=https://api.mindtickle.com/v2
# MOCK_MODE=true
npm install express axios dotenv tsx typescript @types/node
npm install -D vitest supertest @types/express
Dev Server
// src/dev/server.ts
import express from "express";
import { createProxyMiddleware } from "http-proxy-middleware";
const app = express();
app.use(express.json());
const MOCK = process.env.MOCK_MODE === "true";
if (!MOCK) {
app.use("/v2", createProxyMiddleware({
target: process.env.MINDTICKLE_BASE_URL,
changeOrigin: true,
headers: { Authorization: `Bearer ${process.env.MINDTICKLE_API_KEY}` },
}));
} else {
const { mountMockRoutes } = require("./mocks");
mountMockRoutes(app);
}
app.listen(3007, () => console.log(`MindTickle dev server on :3007 [mock=${MOCK}]`));
Mock Mode
// src/dev/mocks.ts — realistic sales enablement training data
export function mountMockRoutes(app: any) {
app.get("/v2/modules", (_req: any, res: any) => res.json([
{ id: "mod_1", title: "Q4 Product Launch", type: "course", status: "published", enrolledCount: 85, completionRate: 0.72 },
{ id: "mod_2", title: "Objection Handling", type: "coaching", status: "published", enrolledCount: 120, completionRate: 0.58 },
]));
app.get("/v2/users/:id/progress", (req: any, res: any) => res.json({
userId: req.params.id, completedModules: 8, totalModules: 12, averageScore: 82,
recentActivity: [{ moduleId: "mod_1", score: 91, completedAt: "2025-09-10T15:30:00Z" }],
}));
app.get("/v2/leaderboard", (_req: any, res: any) => res.json({
topPerformers: [
{ userId: "usr_1", name: "Sarah Kim", score: 95, modulesCompleted: 12 },
{ userId: "usr_2", name: "James Park", score: 88, modulesCompleted: 10 },
],
}));
app.post("/v2/coaching/sessions", (req: any, res: any) => res.status(201).json({ id: "cs_1", ...req.body, status: "scheduled" }));
}
Testing Workflow
npm run dev:mock & # Start mock server in background
npm run test
'Optimize MindTickle API integration performance with caching, bulk progress.
ReadWriteEditGrep
MindTickle Performance Tuning
Overview
MindTickle's API serves sales enablement data across courses, quizzes, and analytics — enterprise deployments tracking thousands of reps make bulk progress queries and report generation the primary bottlenecks. This skill covers caching learner data, batching progress operations, and handling rate limits during high-volume training campaigns.
Instructions
- Implement Redis caching (or in-memory Map for development) with TTLs matching data volatility
- Use offset-based pagination for user and course lists to avoid incomplete result sets
- Queue incoming completion webhooks through Redis to handle campaign burst traffic
- Wrap all API calls with the rate limit handler before deploying to production
Prerequisites
- MindTickle API key with admin or integration scope
- Redis instance for caching learner progress and report data
- Node.js 18+ with native fetch
- Webhook endpoint configured for course/quiz completion events
Caching Strategy
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
// Course catalog changes rarely — cache 30 minutes
// User progress updates frequently during campaigns — cache 2 minutes
const TTL = { courses: 1800, progress: 120, reports: 600, users: 900 } as const;
async function getCachedCourses(orgId: string): Promise<MindTickleCourse[]> {
const key = `mt:courses:${orgId}`;
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const courses = await mindtickleApi.listCourses(orgId);
await redis.setex(key, TTL.courses, JSON.stringify(courses));
return courses;
}
async function getCachedProgress(userId: string, courseId: string): Promise<UserProgress> {
const key = `mt:progress:${userId}:${courseId}`;
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const progress = await mindtickleApi.getUserProgress(userId, courseId);
await redis.setex(key, TTL.progress, JSON.stringify(progress));
return progress;
}
Batch Operations
import pLimit from "p-limit";
const limit = pLimit(6); // MindTickle allows moderate concurrency
// Bulk fetch progress for all reps in a training campaign
async function batchFetchTeamProgress(
userIds: string[],
courseId: string
): Promise<UserProgress[]> {
return Promise.all(
userIds.map((uid) => limit(() => getCachedProgress(uid, courseId)))
);
}
// Paginate through all users in an organization
async function fetchAllUsers(orgId: string): Promise<MindTickleUser[]> {
const users: MindTickleUser[] = [];
let offset = 0;
const pageSize = 200;
do {
const page = await mindtickleApi.listUsers(orgId, { offset, limit: pageSize });
users.push(...page.users);
offset += pageSize
'Prod Checklist for MindTickle.
ReadWriteEdit
MindTickle Production Checklist
Overview
MindTickle powers sales readiness at scale, managing user provisioning via SCIM, course progress tracking across thousands of reps, and quiz completion data that feeds pipeline forecasting. A production integration must enforce multi-tenant isolation through company-specific headers, handle SCIM provisioning race conditions during bulk onboarding, and ensure quiz score integrity under concurrent submissions. Misconfigurations here can leak training data across tenants, corrupt completion records, or silently drop user provisioning events during org restructures.
Prerequisites
- Production MindTickle API key and SCIM bearer token (not sandbox credentials)
- Secrets manager configured (Vault, AWS Secrets Manager, or GCP Secret Manager)
- Monitoring stack operational (Datadog, Grafana, or CloudWatch)
- Company ID confirmed for multi-tenant header (
X-Company-Id)
- SCIM endpoint URL registered in your identity provider (Okta, Azure AD)
Authentication & Secrets
- [ ] API keys stored in vault/secrets manager (never in code or config files)
- [ ] Key rotation schedule configured (every 90 days)
- [ ] SCIM bearer token stored separately from general API keys
- [ ]
X-Company-Id header injected server-side (never exposed to client)
- [ ] Service account permissions restricted to required modules only (courses, users, analytics)
API Integration
- [ ] Base URL points to
https://api.mindtickle.com/v2 (production, not sandbox)
- [ ]
X-Company-Id header included on every request for tenant isolation
- [ ] Rate limiting enforced client-side (respect
X-RateLimit-* response headers)
- [ ] Pagination implemented for user and course listing (offset-based with
limit and offset)
- [ ] SCIM provisioning endpoint handles bulk operations (batch user create/update)
- [ ] Request timeout set to 10 seconds for reads, 60 seconds for bulk SCIM operations
- [ ] Course progress sync uses incremental timestamps (not full resync each run)
Error Handling & Resilience
- [ ] Circuit breaker configured for MindTickle API calls (open after 5 consecutive failures)
- [ ] Retry logic with exponential backoff for 429 (rate limit) and 5xx responses
- [ ] SCIM conflict resolution: 409 on duplicate user triggers update-or-skip logic
- [ ] Quiz submission failures queued for retry (never silently dropped)
- [ ] Bulk provisioning errors isolated per-user (one failure does not abort the batch)
- [ ] Stale company ID detection: 403 triggers re-verification of tenant configuration
- [ ] Course progress writes are idempotent (safe to replay on retry)
Monitoring & Alerting
'Rate Limits for MindTickle.
ReadWriteEdit
MindTickle Rate Limits
Overview
MindTickle's API enforces per-API-key rate limits across its sales readiness platform, with content upload and user management endpoints throttled more tightly than read operations on courses and quiz results. Organizations onboarding large sales teams (500+ reps) hit limits fast when bulk-creating user accounts, assigning training modules, and syncing completion data to Salesforce. Quiz result exports and coaching session analytics carry separate lower caps, making it critical to stagger data pulls during quarterly enablement rollouts.
Rate Limit Reference
| Endpoint |
Limit |
Window |
Scope |
| User create/update |
30 req |
1 minute |
Per API key |
| Course assignment |
60 req |
1 minute |
Per API key |
| Quiz results export |
20 req |
1 minute |
Per API key |
| Content upload |
10 req |
1 minute |
Per API key |
| Analytics / reports |
40 req |
1 minute |
Per API key |
Rate Limiter Implementation
class MindTickleRateLimiter {
private tokens: number;
private lastRefill: number;
private readonly max: number;
private readonly refillRate: number;
private queue: Array<{ resolve: () => void }> = [];
constructor(maxPerMinute: number) {
this.max = maxPerMinute;
this.tokens = maxPerMinute;
this.lastRefill = Date.now();
this.refillRate = maxPerMinute / 60_000;
}
async acquire(): Promise<void> {
this.refill();
if (this.tokens >= 1) { this.tokens -= 1; return; }
return new Promise(resolve => this.queue.push({ resolve }));
}
private refill() {
const now = Date.now();
this.tokens = Math.min(this.max, this.tokens + (now - this.lastRefill) * this.refillRate);
this.lastRefill = now;
while (this.tokens >= 1 && this.queue.length) {
this.tokens -= 1;
this.queue.shift()!.resolve();
}
}
}
const userLimiter = new MindTickleRateLimiter(25);
const contentLimiter = new MindTickleRateLimiter(8);
Retry Strategy
async function mindtickleRetry<T>(
limiter: MindTickleRateLimiter, fn: () => Promise<Response>, maxRetries = 3
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
await limiter.acquire();
const res = await fn();
if (res.ok) return res.json();
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get("Retry-After") || "20", 10);
const jitter = Math.random() * 3000;
await new Promise(r => setTimeout(r, retryAfter * 1000 + jitter));
continue;
}
if (res.status >= 500 && attempt < m
'Reference Architecture for MindTickle.
ReadWriteEditGrep
MindTickle Reference Architecture
Overview
Design a multi-tenant integration layer for the MindTickle sales enablement platform. Strict tenant data isolation is the primary driver, enforced at the database, cache, and queue levels so training data, quiz scores, and readiness analytics never cross organizational boundaries.
Instructions
- Provision the prerequisites below with Row-Level Security enabled in PostgreSQL.
- Configure SCIM 2.0 webhook endpoints to receive HR system user events.
- Deploy the enablement service with tenant-scoped database connections.
- Start the SCIM consumer and analytics aggregator as separate worker processes.
- Validate tenant isolation by running cross-tenant query tests against RLS policies.
Prerequisites
- Node.js 18+, TypeScript 5, PostgreSQL 15 with RLS, Redis 7, RabbitMQ or SQS
- MindTickle API key with
users:read, courses:read, analytics:read scopes
- SCIM 2.0 endpoint credentials for user provisioning
Architecture Diagram
HR System --> SCIM Webhook Ingester --> User Sync Service --> MindTickle API
|
Client --> API Gateway --> EnablementService --+--> Analytics Aggregator
|
Tenant-scoped PostgreSQL (RLS)
Service Layer
class EnablementService {
constructor(
private api: MindTickleApiClient,
private db: TenantScopedStore,
private events: EventPublisher
) {}
async syncCourseProgress(tenantId: string, userId: string): Promise<Progress> {
const courses = await this.api.getUserCourses(userId);
const progress = courses.map(c => ({
courseId: c.id, status: c.status, score: c.quizScore, completedAt: c.completedAt,
}));
await this.db.upsertProgress(tenantId, userId, progress);
await this.events.publish('progress.synced', { tenantId, userId });
return { userId, courses: progress };
}
async processQuizResult(tenantId: string, result: QuizSubmission): Promise<void> {
await this.db.recordQuizResult(tenantId, result);
await this.events.publish('quiz.completed', { tenantId, ...result });
}
}
Caching Strategy
class TenantCache {
constructor(private redis: RedisClient) {}
private key(tenantId: string, resource: string): string {
return `tenant:${tenantId}:${resource}`;
}
async getUserRoster(tenantId: string): Promise<User[] | null> {
const raw = await this.redis.get(this.key(tenantId, 'roster'));
return raw ? JSON.parse(raw) : null;
}
async setUserRoster(tenantId: string, users: User[]): Promise<void> {
await this.redis.setEx(this.key(tenantId, 'roster'), 600, JSON.st
'Sdk Patterns for MindTickle.
ReadWriteEdit
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+
MINDTICKLEAPIKEY environment variable (generated in Admin > Integrations > API Keys)
MINDTICKLEORGID for multi-org deployments
axios or node-fetch for 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}
'Security Basics for MindTickle.
ReadWriteEdit
MindTickle Security Basics
Overview
MindTickle integrations process employee PII through SCIM provisioning (names, emails, job titles, manager chains) and HR-sensitive data like course completion scores, certification status, and coaching assessments. The API uses bearer token authentication combined with a Company-Id header for multi-tenant isolation — omitting or spoofing this header can leak data across tenants. Webhook payloads carrying training completion events must be HMAC-verified to prevent injection of fraudulent compliance records.
Prerequisites
- Secrets manager (AWS SSM, GCP Secret Manager, or Vault) for API tokens
- HTTPS enforced on all SCIM and webhook endpoints
Company-Id validated against an allowlist of known tenant identifiers
.env files in .gitignore — never committed to version control
- Data retention policy for employee training records (GDPR/SOC2)
API Key Management
// MindTickle requires both bearer token and company ID for multi-tenant isolation
const MT_API_TOKEN = process.env.MINDTICKLE_API_KEY;
const MT_COMPANY_ID = process.env.MINDTICKLE_COMPANY_ID;
function validateMindTickleConfig(): void {
if (!MT_API_TOKEN) throw new Error('Missing MINDTICKLE_API_KEY');
if (!MT_COMPANY_ID) throw new Error('Missing MINDTICKLE_COMPANY_ID');
}
function mindtickleHeaders(): Record<string, string> {
return {
Authorization: `Bearer ${MT_API_TOKEN}`,
'Company-Id': MT_COMPANY_ID!,
'Content-Type': 'application/json',
};
}
// Call validateMindTickleConfig() at startup — both values are required for every request
Webhook Signature Verification
import crypto from 'node:crypto';
const MT_WEBHOOK_SECRET = process.env.MINDTICKLE_WEBHOOK_SECRET!;
function verifyMindTickleWebhook(payload: string, signature: string, timestamp: string): boolean {
// Reject stale webhooks (>5 min) to prevent replay attacks
const age = Date.now() - parseInt(timestamp, 10) * 1000;
if (age > 300_000) return false;
const signedPayload = `${timestamp}.${payload}`;
const expected = crypto
.createHmac('sha256', MT_WEBHOOK_SECRET)
.update(signedPayload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
app.post('/webhooks/mindtickle', (req, res) => {
const sig = req.headers['x-mindtickle-signature'] as string;
const ts = req.headers['x-mindtickle-timestamp'] as string;
if (!sig || !ts || !verifyMindTickleWebhook(JSON.stringify(req.body), sig, ts)) {
return res.status(401).json({ error: 'Invalid signature or stale timestamp' });
}
// Process verified training completion event
});
Input Vali
'Upgrade Migration for MindTickle.
ReadWriteEdit
MindTickle Upgrade & Migration
Overview
MindTickle is a sales enablement and readiness platform with APIs for managing courses, quizzes, user progress, and coaching sessions. The API exposes endpoints for content management, learner analytics, and CRM integration. Tracking API changes is essential because MindTickle evolves its content schema (course structures, quiz question types, scoring rubrics), user progress tracking fields, and SSO/SCIM provisioning models — breaking integrations that sync training completion data to Salesforce or automate onboarding workflows.
Version Detection
const MINDTICKLE_BASE = "https://api.mindtickle.com/v2";
async function detectMindTickleVersion(apiKey: string): Promise<void> {
const res = await fetch(`${MINDTICKLE_BASE}/users`, {
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
});
const version = res.headers.get("x-mt-api-version") ?? "v2";
console.log(`MindTickle API version: ${version}`);
// Check for deprecated course fields
const coursesRes = await fetch(`${MINDTICKLE_BASE}/courses?limit=1`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await coursesRes.json();
const knownFields = ["id", "title", "modules", "status", "created_at", "assigned_users"];
if (data.courses?.[0]) {
const actual = Object.keys(data.courses[0]);
const newFields = actual.filter((f) => !knownFields.includes(f));
if (newFields.length) console.log(`New course fields: ${newFields.join(", ")}`);
}
}
Migration Checklist
- [ ] Review MindTickle release notes for API schema changes
- [ ] Audit codebase for hardcoded course status enums (
draft, published, archived)
- [ ] Verify quiz question type support — new types may require parser updates
- [ ] Check user progress response for new completion metric fields
- [ ] Update SCIM provisioning payload if user attribute schema changed
- [ ] Test coaching session API for new rubric scoring fields
- [ ] Validate CRM sync field mappings (Salesforce/HubSpot) after API update
- [ ] Check if module ordering mechanism changed (position vs. sort_order)
- [ ] Update webhook handlers for course completion and quiz score events
- [ ] Run learner analytics export to verify report format compatibility
Schema Migration
// MindTickle course progress: flat completion → structured module-level tracking
interface OldProgress {
user_id: string;
course_id: string;
completed: boolean;
score: number;
completed_at?: string;
}
interface NewProgress {
user_id: string;
course_id: string;
status: "not_started" | "in_p
'Webhooks Events for MindTickle.
ReadWriteEditGrep
MindTickle Webhooks & Events
Overview
MindTickle emits webhook events as sales reps progress through enablement programs, complete courses, submit quizzes, and are provisioned or deprovisioned from the platform. These events enable integrations such as pushing completion certificates to an LMS, syncing learner progress to Salesforce rep profiles, triggering manager alerts when quiz scores fall below threshold, and automating user lifecycle management with your IdP. All payloads are HMAC-signed JSON scoped to your company, delivered over HTTPS.
Prerequisites
- MindTickle admin access with API & Webhooks permissions enabled
- Webhook endpoint URL accessible over HTTPS (TLS 1.2+)
- Company-scoped signing secret from MindTickle Admin > Integrations (
MINDTICKLEWEBHOOKSECRET)
- Express.js with raw body parsing for HMAC verification
Webhook Registration
import axios from "axios";
const res = await axios.post(
"https://api.mindtickle.com/v2/webhooks",
{
url: "https://your-app.com/webhooks/mindtickle",
events: ["course.completed", "quiz.submitted", "user.provisioned",
"user.deprovisioned", "module.progress"],
companyId: process.env.MINDTICKLE_COMPANY_ID,
},
{ headers: { Authorization: `Bearer ${process.env.MINDTICKLE_API_TOKEN}`,
"Content-Type": "application/json" } }
);
console.log("Webhook ID:", res.data.webhookId);
Signature Verification
import crypto from "crypto";
import { Request, Response, NextFunction } from "express";
function verifyMindTickleSignature(req: Request, res: Response, next: NextFunction) {
const signature = req.headers["x-mt-webhook-signature"] as string;
const timestamp = req.headers["x-mt-webhook-timestamp"] as string;
if (!signature || !timestamp) return res.status(401).send("Missing signature");
const signedPayload = `${timestamp}:${(req as any).rawBody}`;
const expected = crypto
.createHmac("sha256", process.env.MINDTICKLE_WEBHOOK_SECRET!)
.update(signedPayload)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(403).send("Invalid signature");
}
next();
}
Event Handler
app.post("/webhooks/mindtickle", verifyMindTickleSignature, (req, res) => {
const { event, data, companyId } = req.body;
switch (event) {
case "course.completed":
console.log(`${data.userId} completed "${data.courseName}" — score: ${data.score}%`);
break;
case "quiz.submitted":
console.log(`Quiz "${data.quiz
How It Works
Skills trigger automatically when you discuss MindTickle topics.
Ready to use mindtickle-pack?
|