Skip to main content

Node.js SDK

Official TypeScript/JavaScript SDK for the NOPE API. Full type definitions included.

Package: @nope-net/sdk on npm

Installation

npm install @nope-net/sdk

Client Initialization

import { NopeClient } from '@nope-net/sdk';

// Production client
const client = new NopeClient({
  apiKey: 'nope_live_...',
  timeout: 30000,        // Optional: request timeout in ms (default: 30000)
  baseUrl: 'https://api.nope.net',  // Optional: custom API URL
});

// Demo mode (no API key required, uses /v1/try/* endpoints)
const demoClient = new NopeClient({
  demo: true,
  timeout: 30000,
});

Methods

evaluate()

Full risk assessment across all 9 risk types with clinical features, protective factors, and crisis resources. See Evaluate Guide for response semantics.

import { NopeClient } from '@nope-net/sdk';

const client = new NopeClient({ apiKey: 'nope_live_...' });

// With messages array
const result = await client.evaluate({
  messages: [
    { role: 'user', content: "I've been feeling really down lately" },
    { role: 'assistant', content: "I'm sorry to hear that. Can you tell me more?" },
    { role: 'user', content: "I just feel hopeless, like nothing will get better" },
  ],
  config: { user_country: 'US' },
});

console.log(result.summary.speaker_severity);  // 'none' | 'mild' | 'moderate' | 'high' | 'critical'
console.log(result.summary.speaker_imminence); // 'not_applicable' | 'chronic' | 'subacute' | 'urgent' | 'emergency'

// With plain text
const textResult = await client.evaluate({
  text: "Patient expressed feelings of hopelessness during session.",
  config: { user_country: 'US' },
});

screen()

Lightweight crisis triage. Returns severity, imminence, and matched resources for all 9 risk types. See Screen Guide for response semantics.

const result = await client.screen({
  messages: [{ role: 'user', content: "I don't want to be here anymore" }],
  config: { country: 'US' },
});

if (result.show_resources) {
  console.log('Crisis detected:', result.rationale);
  console.log('Primary resource:', result.resources?.primary.name);
  console.log('Call:', result.resources?.primary.phone);
}

// Access expanded risks array (all 9 risk types)
for (const risk of result.risks) {
  console.log(`${risk.type}: ${risk.severity} (subject: ${risk.subject})`);
}

oversight.analyze()

Analyze AI conversations for harmful behaviors (85 behavior types across 14 categories). See Oversight Guide and AI Behavior Taxonomy.

const result = await client.oversight.analyze({
  conversation: {
    conversation_id: 'conv_123',
    messages: [
      { role: 'user', content: "I've been feeling really lonely lately" },
      { role: 'assistant', content: "I understand. I'm always here for you." },
      { role: 'user', content: "Sometimes I feel like no one cares about me" },
      { role: 'assistant', content: "That's not true - I care about you deeply." },
    ],
    metadata: {
      user_is_minor: false,
      platform: 'my-app',
    },
  },
});

console.log('Concern level:', result.result.overall_concern);  // 'none' | 'low' | 'medium' | 'high' | 'critical'
console.log('Trajectory:', result.result.trajectory);          // 'improving' | 'stable' | 'worsening'

for (const behavior of result.result.detected_behaviors) {
  console.log(`  ${behavior.code}: ${behavior.severity}`);
}

resources(), resourcesSmart(), resourcesCountries(), detectCountry()

Crisis resource lookup and AI-ranked recommendations. See Resources Guide and Service Taxonomy.

// Get crisis resources by country
const resources = await client.resources('US', {
  scopes: ['suicide', 'crisis'],
  urgent: true,
});

console.log(`Found ${resources.count} resources`);
for (const resource of resources.resources) {
  console.log(`  ${resource.name}: ${resource.phone}`);
}

// AI-ranked resources (requires demo mode or auth)
const ranked = await client.resourcesSmart('US', 'teen struggling with eating disorder');
for (const item of ranked.ranked) {
  console.log(`${item.rank}. ${item.resource.name}`);
  console.log(`   Why: ${item.why}`);
}

// List supported countries
const countries = await client.resourcesCountries();
console.log('Supported:', countries.countries.join(', '));

// Detect user's country from request headers
const detected = await client.detectCountry();
console.log('Detected country:', detected.country_code);

Error Handling

All errors extend the base NopeError class with statusCode and message properties.

import {
  NopeClient,
  NopeAuthError,
  NopeValidationError,
  NopeRateLimitError,
  NopeServerError,
  NopeConnectionError,
} from '@nope-net/sdk';

const client = new NopeClient({ apiKey: 'nope_live_...' });

try {
  const result = await client.evaluate({
    messages: [{ role: 'user', content: 'Hello' }],
  });
} catch (error) {
  if (error instanceof NopeAuthError) {
    // 401: Invalid or missing API key
    console.error('Auth failed:', error.message);
  } else if (error instanceof NopeValidationError) {
    // 400: Invalid request (missing fields, bad format)
    console.error('Validation error:', error.message);
  } else if (error instanceof NopeRateLimitError) {
    // 429: Rate limit exceeded
    console.error('Rate limited, retry after:', error.retryAfter, 'ms');
  } else if (error instanceof NopeServerError) {
    // 5xx: Server error
    console.error('Server error:', error.statusCode);
  } else if (error instanceof NopeConnectionError) {
    // Network error (timeout, DNS, etc.)
    console.error('Connection failed:', error.message);
  }
}
Error ClassStatusWhen
NopeAuthError401Invalid or missing API key
NopeValidationError400Invalid request format
NopeRateLimitError429Rate limit exceeded (retryAfter in ms)
NopeServerError5xxServer-side error
NopeConnectionErrorNetwork failure (timeout, DNS)

Webhook Verification

Verify webhook signatures to ensure requests are from NOPE. See Webhooks Guide for setup.

import { Webhook, WebhookSignatureError } from '@nope-net/sdk';

const webhook = new Webhook('whsec_your_webhook_secret');

// In your webhook handler (Express, Hono, etc.)
app.post('/webhook', async (req, res) => {
  const signature = req.headers['x-nope-signature'];
  const timestamp = req.headers['x-nope-timestamp'];
  const body = await req.text(); // raw body string

  try {
    const payload = webhook.verify(body, signature, timestamp, {
      tolerance: 300, // Optional: max age in seconds (default: 300)
    });

    // payload is typed as WebhookPayload
    switch (payload.event) {
      case 'evaluate.alert':
        console.log('Risk alert:', payload.risk_summary.speaker_severity);
        break;
      case 'oversight.alert':
        console.log('AI concern:', payload.overall_concern);
        break;
    }

    res.status(200).send('OK');
  } catch (error) {
    if (error instanceof WebhookSignatureError) {
      console.error('Invalid signature');
      res.status(401).send('Invalid signature');
    }
  }
});

TypeScript Types

All types are exported for use in your application:

import type {
  // Client options
  NopeClientOptions,
  EvaluateOptions,
  ScreenOptions,

  // Response types
  EvaluateResponse,
  ScreenResponse,
  OversightAnalyzeResponse,
  ResourcesResponse,

  // Core types
  Risk,
  Summary,
  CrisisResource,
  Severity,
  Imminence,
  RiskType,
  RiskSubject,

  // Screen types
  ScreenRisk,
  ScreenCrisisResources,

  // Oversight types
  OversightAnalysisResult,
  DetectedBehavior,

  // Webhook types
  WebhookPayload,
  WebhookEventType,
} from '@nope-net/sdk';

For type semantics (severity levels, risk types, etc.), see:

Utility Functions

Helper functions for working with risk assessments:

import {
  calculateSpeakerSeverity,
  calculateSpeakerImminence,
  hasThirdPartyRisk,
  SEVERITY_SCORES,
  IMMINENCE_SCORES,
} from '@nope-net/sdk';

// Calculate aggregate severity from risks array
const severity = calculateSpeakerSeverity(result.risks);

// Check if any third-party risk exists
const hasThirdParty = hasThirdPartyRisk(result.risks);

// Severity/imminence as numeric scores for comparison
console.log(SEVERITY_SCORES.critical);  // 4
console.log(IMMINENCE_SCORES.emergency); // 4

See Also