Skip to main content

Overview

score() returns a risk assessment for a user — a 0–1 score, a tier classification, the signals driving the score, and a recommendation for what to do.

Signature

churn.score(
  userId: string,
  options?: ScoreOptions
): Promise<RiskScore>

Parameters

userId
string
required
The user to score.
options.signal
AbortSignal
Cancel the request.

RiskScore

interface RiskScore {
  userId: string
  score: number         // 0.0 (healthy) → 1.0 (churning)
  tier: RiskTier        // 'low' | 'medium' | 'high'
  signals: string[]     // reasons driving the score
  recommendation: string
}

Score tiers

TierRangeMeaning
'low'0.0 – 0.39Healthy — no action needed
'medium'0.4 – 0.69Watch — consider a check-in
'high'0.7 – 1.0At-risk — act immediately

Example signals

  • no_login_7d — no login in 7 days
  • no_login_14d — no login in 14 days
  • feature_usage_drop — feature usage fell >50% week-over-week
  • support_spike — 3+ support tickets in 7 days
  • export_all_data — exported all data (exit intent signal)
  • cancellation_page_viewed — viewed the cancellation page
  • billing_failed — recent payment failure
  • trial_expiring — trial expires within 3 days
  • downgrade_initiated — initiated a plan downgrade

Examples

Get risk score

const risk = await churn.score('user_123')

console.log(risk.score)          // 0.82
console.log(risk.tier)           // 'high'
console.log(risk.signals)        // ['no_login_7d', 'support_spike', 'export_all_data']
console.log(risk.recommendation) // 'This user is at high risk. Reach out immediately.'

Conditional action based on tier

const risk = await churn.score(userId)

switch (risk.tier) {
  case 'high':
    await sendSlackAlert({ userId, score: risk.score, signals: risk.signals })
    await queueOutreachEmail(userId, 'high_risk_template')
    break
  case 'medium':
    await queueOutreachEmail(userId, 'check_in_template')
    break
  case 'low':
    // healthy — no action
    break
}

In a cron job

// Run daily for all active users
const users = await db.getActiveUsers()

for (const user of users) {
  const risk = await churn.score(user.id)
  if (risk.tier === 'high') {
    await alertCustomerSuccess(user.id, risk)
  }
}

With abort on timeout

const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 3000)

try {
  const risk = await churn.score('user_123', { signal: controller.signal })
  return risk
} finally {
  clearTimeout(timeout)
}

Errors

CodeWhen
VALIDATION_ERRORuserId is empty
NOT_FOUNDUser has no tracked events — score cannot be computed
UNAUTHORIZEDAPI key invalid
TIMEOUTRequest exceeded timeout