Skip to main content

Overview

atRisk() returns a paginated list of users whose churn risk score exceeds a given threshold. Use it for bulk outreach campaigns, CS dashboards, or nightly reports. For streaming all at-risk users without pagination management, see atRiskAll().

Signature

churn.atRisk(options?: AtRiskOptions): Promise<AtRiskResult>

AtRiskOptions

threshold
number
Minimum score to include (0–1). Default: 0.5. Must be a finite number between 0 and 1.
plan
string
Filter to users on a specific plan (e.g. 'pro', 'enterprise'). Requires plan to have been set via identify().
limit
number
Page size. Default: 50. Maximum: 200.
offset
number
Pagination offset. Default: 0.
options.signal
AbortSignal
Cancel the request.

AtRiskResult

interface AtRiskResult {
  users: AtRiskUser[]
  total: number    // total matching users (across all pages)
  offset: number
  limit: number
}

interface AtRiskUser {
  userId: string
  score: number
  tier: RiskTier
  signals: string[]
  recommendation: string
}

Examples

Get high-risk users

const { users, total } = await churn.atRisk({ threshold: 0.7 })

console.log(`${total} high-risk users`)
for (const user of users) {
  console.log(user.userId, user.score, user.signals)
}

Filter by plan

// Only enterprise users above 0.5 risk
const { users } = await churn.atRisk({
  threshold: 0.5,
  plan: 'enterprise',
})

Paginate all high-risk users

async function getAllAtRisk(threshold = 0.7): Promise<AtRiskUser[]> {
  const all: AtRiskUser[] = []
  let offset = 0

  while (true) {
    const { users, total } = await churn.atRisk({ threshold, limit: 100, offset })
    all.push(...users)
    offset += users.length
    if (offset >= total) break
  }

  return all
}

Nightly CS report

// cron: every night at 9pm
const { users, total } = await churn.atRisk({ threshold: 0.65, limit: 200 })

await sendSlackMessage({
  channel: '#customer-success',
  text: `🚨 ${total} at-risk users tonight`,
  blocks: users.slice(0, 10).map((u) => ({
    userId: u.userId,
    score: (u.score * 100).toFixed(0) + '%',
    signals: u.signals.join(', '),
  })),
})

Errors

CodeWhen
VALIDATION_ERRORthreshold is not between 0 and 1
UNAUTHORIZEDAPI key invalid
TIMEOUTRequest timed out