@paceful/sdk
v2.0.0
Published
Official JavaScript/TypeScript SDK for the Paceful Partner API
Maintainers
Readme
@paceful/sdk
Official JavaScript/TypeScript SDK for the Paceful Partner API.
Installation
npm install @paceful/sdk
# or
yarn add @paceful/sdk
# or
pnpm add @paceful/sdkQuick Start
import { PacefulClient } from '@paceful/sdk';
const paceful = new PacefulClient({
apiKey: process.env.PACEFUL_API_KEY,
});
// Register a user
const user = await paceful.users.register({
externalId: 'user_123',
disruptionType: 'breakup', // optional: optimizes ERS scoring for recovery context
});
// Log mood data
await paceful.mood.log({
externalId: 'user_123',
mood: 4,
note: 'Feeling productive today',
factors: ['work', 'exercise'],
});
// Get ERS score
const ers = await paceful.ers.get('user_123');
console.log(`ERS Score: ${ers.ersScore}`);
console.log(`Stage: ${ers.stage}`);
console.log(`Trend: ${ers.trend?.direction}`);Modules
Users
Manage partner users in the Paceful system.
// Register a new user
const user = await paceful.users.register({
externalId: 'user_123',
disruptionType: 'breakup', // optional: breakup, grief, burnout, addiction_recovery, divorce, layoff, trauma, life_transition, other
metadata: { plan: 'premium' },
});
// Get a user
const user = await paceful.users.get('user_123');
// Update user metadata
await paceful.users.update('user_123', {
metadata: { plan: 'enterprise' },
});
// List all users
const { items, total, hasMore } = await paceful.users.list({ limit: 50 });
// Delete a user
await paceful.users.delete('user_123');Disruption Types
The disruptionType parameter is optional. When omitted, default ERS dimension weighting is applied. When provided, ERS scoring is optimized for the user's specific recovery context.
| Value | Description |
|-------|-------------|
| breakup | Romantic relationship ending |
| grief | Loss of a loved one |
| burnout | Work-related emotional exhaustion |
| addiction_recovery | Substance or behavioral addiction recovery |
| divorce | Marital dissolution |
| layoff | Job loss or career disruption |
| trauma | Acute or complex trauma recovery |
| life_transition | Major life change (relocation, retirement, etc.) |
| other | Custom or unspecified disruption |
Mood
Track and retrieve mood data for users.
// Log a mood entry
const entry = await paceful.mood.log({
externalId: 'user_123',
mood: 4, // 1-5 scale
note: 'Great day!',
factors: ['exercise', 'sleep'],
});
// Get mood history
const history = await paceful.mood.history({
externalId: 'user_123',
startDate: '2024-01-01',
limit: 30,
});
// Get latest mood
const latest = await paceful.mood.latest('user_123');
// Get mood statistics
const stats = await paceful.mood.stats('user_123', '30d');Journal
Create and retrieve journal entries.
// Create a journal entry
const entry = await paceful.journal.create({
externalId: 'user_123',
content: 'Today I reflected on my progress...',
mood: 4,
});
// Entry includes AI analysis
console.log('Themes:', entry.themes);
console.log('Sentiment:', entry.sentimentScore);
// Get journal history
const entries = await paceful.journal.history({
externalId: 'user_123',
limit: 10,
});
// Get available prompts
const prompts = await paceful.journal.prompts();ERS (Emotional Readiness Score)
Calculate and retrieve ERS scores.
// Get current ERS score (cached if recent)
const ers = await paceful.ers.get('user_123');
// Force fresh calculation
const result = await paceful.ers.calculate('user_123');
// Batch get scores for multiple users
const batch = await paceful.ers.batch(['user_1', 'user_2', 'user_3']);
// Get score history
const history = await paceful.ers.history('user_123', {
period: '30d',
granularity: 'daily',
});
// Set up threshold alerts
await paceful.ers.setThresholdAlert({
externalId: 'user_123',
threshold: 40,
direction: 'below',
});Assess (Unstructured Text Analysis)
Analyze unstructured text (journals, chat transcripts, therapy notes) to extract ERS signals. This is useful when you have existing text data and want to derive emotional readiness insights without structured mood/journal logging.
// Analyze a single text entry
const result = await paceful.assess.analyze({
userId: 'user_123',
text: 'Today was challenging but I managed to stay calm during the difficult conversation with my ex. I noticed I didnt spiral like I used to.',
sourceType: 'journal', // 'journal' | 'session_notes' | 'chat_transcript' | 'free_text'
config: {
verbosity: 'standard', // 'minimal' | 'standard' | 'clinical'
tone: 'clinical', // 'clinical' | 'casual' | 'motivational'
includeSignals: true, // Include extracted text signals
},
});
console.log(`ERS Snapshot: ${result.ersSnapshot}`);
console.log(`Readiness: ${result.readinessLabel}`); // 'Healing' | 'Rebuilding' | 'Ready'
console.log(`Confidence: ${result.confidence}`);
// Access dimension scores
const { emotionalStability, selfReflection, copingCapacity } = result.dimensions;
console.log(`Emotional Stability: ${emotionalStability.score} (${emotionalStability.label})`);
console.log(`Reasoning: ${emotionalStability.reasoning}`);
console.log(`Signals: ${emotionalStability.topSignals?.join(', ')}`);Batch Analysis
Process multiple text entries in a single request (max 10 entries):
const batch = await paceful.assess.analyzeBatch({
userId: 'user_123',
entries: [
{ text: 'First journal entry about my week...', sourceType: 'journal', entryId: 'j1' },
{ text: 'Therapy session notes from Tuesday...', sourceType: 'session_notes', entryId: 's1' },
{ text: 'Chat conversation with support group...', sourceType: 'chat_transcript', entryId: 'c1' },
],
config: { verbosity: 'minimal' },
});
console.log(`Processed: ${batch.processed}/${batch.total}`);
console.log(`Failed: ${batch.failed}`);
for (const entry of batch.results) {
if (entry.success) {
console.log(`${entry.entryId}: ERS ${entry.ersSnapshot} - ${entry.readinessLabel}`);
} else {
console.log(`${entry.entryId}: Failed - ${entry.error}`);
}
}Source Types
| Source Type | Description | Best For |
|-------------|-------------|----------|
| journal | Personal journal entries | Daily reflections, diary entries |
| session_notes | Therapy/coaching session notes | Clinical documentation |
| chat_transcript | Chat or messaging conversations | Support chats, peer conversations |
| free_text | Generic unstructured text | Any other text content |
Vertical-Specific Analysis
Analyze text with industry-specific signal detection. Combines ERS scoring with vertical risk assessment:
// Gambling vertical example
const result = await paceful.assess.vertical('gambling', {
userId: 'user_123',
text: `Lost another £500 tonight. I know I should stop but I just need
one good run to make it all back. Haven't told my wife. Can't sleep
thinking about it. Maybe tomorrow will be different.`,
sourceType: 'chat_transcript',
});
// Core ERS results
console.log(`ERS Score: ${result.ers.ersSnapshot}`);
console.log(`Readiness: ${result.ers.readinessLabel}`);
// Vertical-specific risk assessment
console.log(`Risk Level: ${result.verticalAnalysis.riskLevel}`); // 'critical'
console.log(`Risk Score: ${result.verticalAnalysis.riskScore}`);
console.log(`Recommended Action: ${result.verticalAnalysis.recommendedAction}`);
// Detected signals
console.log(`Signals: ${result.verticalAnalysis.signalsDetected}/${result.verticalAnalysis.signalsTotal}`);
for (const signal of result.verticalAnalysis.signals) {
if (signal.detected) {
console.log(`⚠️ ${signal.id} (${signal.confidence}): ${signal.evidence}`);
}
}Available verticals:
| Vertical | Description | Key Signals |
|----------|-------------|-------------|
| gambling | Gambling harm detection | Chasing losses, financial distress, secrecy |
| dating | Dating readiness assessment | Attachment patterns, ex-partner mentions |
| workplace | Workplace wellness | Burnout indicators, boundary issues |
| mental_health | Clinical mental health | Crisis indicators, treatment adherence |
| insurance | Insurance risk assessment | Life change indicators, health signals |
Verbosity Levels
| Level | Description | Use Case |
|-------|-------------|----------|
| minimal | Score + label + confidence only | Dashboards, quick assessments |
| standard | Includes reasoning and signals | User-facing insights |
| clinical | Full detail with recommended actions | Clinical/therapeutic contexts |
Verticals
Discover available industry verticals for specialized analysis.
// List all available verticals
const verticals = await paceful.verticals.list();
for (const vertical of verticals) {
console.log(`${vertical.name} (${vertical.slug})`);
console.log(` ${vertical.description}`);
console.log(` Signals: ${vertical.signalCount}`);
console.log(` Risk Levels: ${vertical.riskLevels.join(', ')}`);
}
// Get details for a specific vertical
const gambling = await paceful.verticals.get('gambling');Benchmarks
Access industry benchmark data to compare your users against the market.
// Get benchmark data for a vertical
const benchmarks = await paceful.benchmarks.get('gambling');
console.log(`Sample Size: ${benchmarks.sampleSize}`);
console.log(`Updated: ${benchmarks.updatedAt}`);
// ERS distribution
console.log('ERS Distribution:');
console.log(` 25th percentile: ${benchmarks.ersDistribution.p25}`);
console.log(` Median: ${benchmarks.ersDistribution.p50}`);
console.log(` 75th percentile: ${benchmarks.ersDistribution.p75}`);
// Risk distribution
console.log('Risk Distribution:');
console.log(` Low: ${benchmarks.riskDistribution.low}%`);
console.log(` Moderate: ${benchmarks.riskDistribution.moderate}%`);
console.log(` High: ${benchmarks.riskDistribution.high}%`);
console.log(` Critical: ${benchmarks.riskDistribution.critical}%`);
// Most common signals
console.log('Top Signals:');
for (const signal of benchmarks.topSignals.slice(0, 5)) {
console.log(` ${signal.name}: ${signal.frequency}%`);
}
// Your performance vs industry (if you have data)
if (benchmarks.industryComparison) {
const { yourAvgErs, industryAvgErs, percentile } = benchmarks.industryComparison;
console.log(`Your Avg ERS: ${yourAvgErs} (${percentile}th percentile)`);
console.log(`Industry Avg: ${industryAvgErs}`);
}Partner (Self-Serve Registration)
Register as a partner programmatically:
// Self-serve registration
const result = await paceful.partner.register({
email: '[email protected]',
companyName: 'Acme Wellness',
contactName: 'Jane Developer',
website: 'https://acme.com',
vertical: 'mental_health',
estimatedVolume: '1000-10000',
useCase: 'Integrate ERS into our therapy app',
acceptTerms: true,
});
console.log('Partner ID:', result.partnerId);
console.log('Test API Key:', result.testApiKey); // Works immediately
if (!result.isPersonalEmail) {
console.log('Live API Key:', result.liveApiKey);
console.log('Status:', result.status); // 'pending_verification'
console.log(result.message); // 'Check your email to verify...'
} else {
console.log('Use a business email to get production access');
}Note: Personal email domains (gmail.com, yahoo.com, etc.) only receive sandbox access. Business email addresses receive both test and live keys after email verification.
Analytics
Get aggregate analytics for your partner account.
// Get summary analytics
const summary = await paceful.analytics.summary('30d');
console.log(`Total users: ${summary.totalUsers}`);
console.log(`Average ERS: ${summary.averageErs}`);
// Get engagement metrics
const engagement = await paceful.analytics.engagement({
period: '30d',
granularity: 'daily',
});
// Get ERS distribution
const distribution = await paceful.analytics.ersDistribution('30d');
// Get retention metrics
const retention = await paceful.analytics.retention('30d');
// Export data
const { downloadUrl } = await paceful.analytics.export({
type: 'user_summary',
period: '30d',
format: 'csv',
});Webhooks
Manage webhook endpoints for real-time notifications.
// Register a webhook
const webhook = await paceful.webhooks.register({
url: 'https://yourapp.com/webhooks/paceful',
events: ['ers.stage_changed', 'mood.critical'],
});
// Save the secret for signature verification
console.log('Secret:', webhook.secret);
// List webhooks
const webhooks = await paceful.webhooks.list();
// Update a webhook
await paceful.webhooks.update(webhook.webhookId, {
events: ['ers.stage_changed', 'mood.critical', 'journal.created'],
});
// Test a webhook
const testResult = await paceful.webhooks.test(webhook.webhookId);
// Get recent deliveries (across all webhooks)
const deliveries = await paceful.webhooks.deliveries({
status: 'failed',
limit: 20,
});
for (const delivery of deliveries) {
console.log(`${delivery.event}: ${delivery.status} (${delivery.attempts} attempts)`);
if (delivery.nextRetryAt) {
console.log(` Next retry: ${delivery.nextRetryAt}`);
}
}
// Retry a failed delivery
const retryResult = await paceful.webhooks.retry(deliveries[0].id);
console.log(`Retry status: ${retryResult.status}`); // 'queued' or 'already_succeeded'
// Verify webhook signatures
import { Webhooks } from '@paceful/sdk';
const isValid = Webhooks.verifySignature(
rawBody,
request.headers['x-paceful-signature'],
process.env.PACEFUL_WEBHOOK_SECRET
);Webhook Events
| Event | Description |
|-------|-------------|
| ers.stage_changed | User's ERS stage changed (healing, rebuilding, ready) |
| ers.score_threshold | User's ERS crossed a configured threshold |
| mood.critical | User logged a critical mood (1-2) |
| mood.logged | User logged any mood entry |
| journal.created | User created a journal entry |
React Widgets
The SDK includes embeddable React widgets for quick integration. Widgets use inline styles and have no external dependencies.
PacefulProvider
Wrap your app to provide context to all widgets:
import { PacefulProvider } from '@paceful/sdk';
function App() {
return (
<PacefulProvider
apiKey={process.env.PACEFUL_API_KEY}
userId="user_123"
>
<YourApp />
</PacefulProvider>
);
}MoodWidget
Self-contained mood check-in component:
import { MoodWidget } from '@paceful/sdk';
// With PacefulProvider context
<MoodWidget
theme="light"
brandColor="#5B8A72"
showStreak={true}
onComplete={(mood) => console.log('Logged:', mood)}
/>
// Standalone (without provider)
<MoodWidget
apiKey="your_api_key"
userId="user_123"
theme="dark"
compact={true}
/>Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| apiKey | string | - | API key (optional if using PacefulProvider) |
| userId | string | - | User ID (optional if using PacefulProvider) |
| theme | 'light' | 'dark' | 'light' | Color theme |
| brandColor | string | '#5B8A72' | Primary accent color |
| compact | boolean | false | Skip emotion selection step |
| showStreak | boolean | false | Show mood logging streak |
| onComplete | function | - | Callback when mood is logged |
| onError | function | - | Callback on error |
JournalWidget
Journal entry component with AI reflection:
import { JournalWidget } from '@paceful/sdk';
<JournalWidget
theme="light"
brandColor="#5B8A72"
showPrompt={true}
showAIReflection={true}
maxLength={2000}
onComplete={(entry) => console.log('Saved:', entry)}
/>Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| apiKey | string | - | API key (optional if using PacefulProvider) |
| userId | string | - | User ID (optional if using PacefulProvider) |
| theme | 'light' | 'dark' | 'light' | Color theme |
| brandColor | string | '#5B8A72' | Primary accent color |
| showPrompt | boolean | true | Show random journal prompt |
| showAIReflection | boolean | true | Show AI reflection after saving |
| maxLength | number | 2000 | Maximum character length |
| placeholder | string | "What's on your mind?" | Textarea placeholder |
| onComplete | function | - | Callback when entry is saved |
| onError | function | - | Callback on error |
ERSDisplay
ERS score visualization component:
import { ERSDisplay } from '@paceful/sdk';
<ERSDisplay
theme="light"
brandColor="#5B8A72"
showDimensions={true}
showTrend={true}
compact={false}
onLoad={(ers) => console.log('ERS:', ers)}
/>Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| apiKey | string | - | API key (optional if using PacefulProvider) |
| userId | string | - | User ID (optional if using PacefulProvider) |
| theme | 'light' | 'dark' | 'light' | Color theme |
| brandColor | string | '#5B8A72' | Primary accent color |
| showDimensions | boolean | true | Show 5 ERS dimension bars |
| showTrend | boolean | true | Show weekly trend badge |
| compact | boolean | false | Smaller ring, hide dimensions |
| onLoad | function | - | Callback when ERS loads |
| onError | function | - | Callback on error |
Error Handling
The SDK throws typed errors for different failure scenarios:
import {
PacefulError,
PacefulAuthError,
PacefulRateLimitError,
PacefulNotFoundError,
PacefulValidationError,
PacefulNetworkError,
} from '@paceful/sdk';
try {
const ers = await paceful.ers.get('user_123');
} catch (error) {
if (error instanceof PacefulAuthError) {
// Invalid API key
console.error('Authentication failed');
} else if (error instanceof PacefulRateLimitError) {
// Rate limited - retry after error.retryAfter seconds
console.error(`Rate limited. Retry after ${error.retryAfter}s`);
} else if (error instanceof PacefulNotFoundError) {
// User not found
console.error('User not found');
} else if (error instanceof PacefulValidationError) {
// Invalid request parameters
console.error('Validation error:', error.message);
} else if (error instanceof PacefulNetworkError) {
// Network failure
console.error('Network error:', error.message);
} else if (error instanceof PacefulError) {
// Generic API error
console.error(`Error ${error.code}:`, error.message);
}
}Configuration
const paceful = new PacefulClient({
// Required: Your API key
apiKey: process.env.PACEFUL_API_KEY,
// Optional: Custom base URL (for testing)
baseUrl: 'https://api.paceful.app',
// Optional: Request timeout in ms (default: 30000)
timeout: 30000,
// Optional: Number of retries for failed requests (default: 3)
retries: 3,
});TypeScript Support
The SDK is written in TypeScript and provides full type definitions:
import type {
PacefulConfig,
DisruptionType,
RegisterUserParams,
PartnerUser,
MoodEntry,
JournalEntry,
ERSScore,
ERSStage,
ERSDimensions,
AnalyticsSummary,
WebhookEvent,
} from '@paceful/sdk';
// DisruptionType is a union of valid disruption contexts
type DisruptionType =
| 'breakup'
| 'grief'
| 'burnout'
| 'addiction_recovery'
| 'divorce'
| 'layoff'
| 'trauma'
| 'life_transition'
| 'other';
// RegisterUserParams includes optional disruptionType
interface RegisterUserParams {
externalId: string;
email?: string;
disruptionType?: DisruptionType;
metadata?: Record<string, unknown>;
}Requirements
- Node.js 18.0.0 or higher
- TypeScript 5.0+ (for TypeScript users)
- React 17.0.0+ (for widget components, optional)
License
MIT
