npm package: @paceful/sdk
Description: Official JavaScript/TypeScript SDK for the Paceful Partner API
Author: Paceful
Latest version: 2.0.0

# @paceful/sdk

Official JavaScript/TypeScript SDK for the Paceful Partner API.

## Installation

```bash
npm install @paceful/sdk
# or
yarn add @paceful/sdk
# or
pnpm add @paceful/sdk
```

## Quick Start

```typescript
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.

```typescript
// 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.

```typescript
// 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.

```typescript
// 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.

```typescript
// 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.

```typescript
// 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):

```typescript
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:

```typescript
// 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.

```typescript
// 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.

```typescript
// 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:

```typescript
// Self-serve registration
const result = await paceful.partner.register({
  email: 'developer@company.com',
  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.

```typescript
// 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.

```typescript
// 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:

```tsx
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:

```tsx
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:

```tsx
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:

```tsx
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:

```typescript
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

```typescript
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:

```typescript
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