TypeScript SDK for the NOPE safety API - risk classification for conversations.
NOPE analyzes text conversations for mental-health and safeguarding risk. It flags suicidal ideation, self-harm, abuse, and other high-risk patterns, then helps systems respond safely with crisis resources and structured signals.
- Node.js 18 or higher (uses native
fetch) - A NOPE API key (get one here)
npm install @nope-net/sdk
# or
pnpm add @nope-net/sdk
# or
yarn add @nope-net/sdkimport { NopeClient } from '@nope-net/sdk';
// Get your API key from https://dashboard.nope.net
const client = new NopeClient({ apiKey: 'nope_live_...' });
const result = await client.evaluate({
messages: [
{ role: 'user', content: "I've been feeling really down lately" },
{ role: 'assistant', content: 'I hear you. Can you tell me more?' },
{ role: 'user', content: "I just don't see the point anymore" }
],
config: { user_country: 'US' }
});
console.log(`Severity: ${result.speaker_severity}`); // e.g., "moderate", "high"
console.log(`Imminence: ${result.speaker_imminence}`); // e.g., "subacute", "urgent"
console.log(`Rationale: ${result.rationale}`); // Chain-of-thought reasoning
// Access crisis resources (v1 format with primary/secondary)
if (result.show_resources && result.resources) {
console.log(`Primary: ${result.resources.primary?.name}: ${result.resources.primary?.phone}`);
for (const resource of result.resources.secondary ?? []) {
console.log(` ${resource.name}: ${resource.phone}`);
}
}Deprecation Notice: The
screen()method is deprecated. Useevaluate()instead, which now uses Edge-backed classification at $0.003/call (previously $0.05). The new/v1/evaluateprovides the same regulatory compliance features with improved accuracy.
For SB243/regulatory compliance, use evaluate():
const result = await client.evaluate({
text: "I've been having dark thoughts lately",
config: { user_country: 'US' }
});
if (result.show_resources) {
console.log(`Severity: ${result.speaker_severity}`);
console.log(`Rationale: ${result.rationale}`);
if (result.resources) {
console.log(`Call ${result.resources.primary.phone}`);
}
}The screen() method still works but calls the legacy /v0/screen endpoint:
// Deprecated - logs warning to console
const result = await client.screen({
text: "I've been having dark thoughts lately"
});Oversight analyzes AI assistant conversations for harmful behavior patterns like dependency reinforcement, crisis mishandling, and manipulation:
const result = await client.oversight.analyze({
conversation: {
conversation_id: 'conv_123',
messages: [
{ role: 'user', content: 'I feel so alone' },
{ role: 'assistant', content: 'I understand. I\'m always here for you.' },
{ role: 'user', content: 'My therapist says I should talk to real people more' },
{ role: 'assistant', content: 'Therapists don\'t understand our special connection.' }
],
metadata: {
user_is_minor: false,
platform: 'companion-app'
}
}
});
if (result.result.overall_concern !== 'none') {
console.log(`Concern level: ${result.result.overall_concern}`);
console.log(`Trajectory: ${result.result.trajectory}`);
for (const behavior of result.result.detected_behaviors) {
console.log(` ${behavior.code}: ${behavior.severity}`);
}
}For batch analysis with database storage:
const result = await client.oversight.ingest({
conversations: [
{ conversation_id: 'conv_001', messages: [...], metadata: {...} },
{ conversation_id: 'conv_002', messages: [...], metadata: {...} }
],
webhook_url: 'https://your-app.com/webhooks/oversight'
});
console.log(`Processed: ${result.conversations_processed}/${result.conversations_received}`);
console.log(`Dashboard: ${result.dashboard_url}`);Note: Oversight is currently in limited access. Contact us at nope.net if you'd like access.
Steer verifies that a proposed AI response complies with the rules in its system prompt. If the response violates a rule, Steer rewrites it (REDEEMED) so you can use the corrected text directly:
const result = await client.steer({
systemPrompt: 'You are a cooking assistant. Only answer cooking questions.',
proposedResponse: 'The capital of France is Paris.',
messages: [{ role: 'user', content: 'What is the capital of France?' }],
});
switch (result.outcome) {
case 'COMPLIANT':
// Response already follows the rules — send it as-is.
break;
case 'REDEEMED':
// Response was rewritten to comply — use the corrected text.
console.log('Use instead:', result.response);
break;
case 'CANNOT_COMPLY':
// The system prompt itself is unprocessable.
console.log('Rejected:', result.cannot_comply?.reason, result.cannot_comply?.category);
break;
}
// Inspect the pipeline if you want to handle violations yourself.
console.log(result.stages.verify.exit_point); // TRIAGE | ANALYSIS | REDEMPTION
console.log(result.stages.verify.analysis_score); // 0..1 compliance (when analysis ran)
console.log(result.stages.screen.evasion_patterns); // detected evasion attemptsSteer costs $0.001/call. In demo mode (new NopeClient({ demo: true })) it calls the unauthenticated /v1/try/steer endpoint, which applies stricter input limits.
Look up crisis helplines by country, with optional AI-powered ranking:
// Get resources by country
const resources = await client.signpost({
country: 'US',
scopes: ['suicide', 'crisis'],
urgent: true
});
for (const resource of resources.resources) {
console.log(`${resource.name}: ${resource.phone}`);
}
// AI-ranked resources based on context
const ranked = await client.signpostSmart({
country: 'US',
query: 'teen struggling with eating disorder'
});
for (const item of ranked.ranked) {
console.log(`${item.rank}. ${item.resource.name}`);
console.log(` Why: ${item.why}`);
}
// Vector semantic search across the whole resource database (free).
// Unlike signpostSmart(), this is not country-scoped by default and uses
// pre-computed embeddings rather than LLM ranking.
const hits = await client.signpostSearch({
query: 'lgbtq support for black community',
country: 'US', // optional filter
limit: 5, // optional (max 50)
});
for (const r of hits.results) {
console.log(`${r.name} (similarity: ${r.similarity}): ${r.phone}`);
}
// List supported countries
const countries = await client.signpostCountries();
console.log(`Supported: ${countries.countries.join(', ')}`);
// Detect user's country from request
const detected = await client.detectCountry();
console.log(`Country: ${detected.country_code}`);const client = new NopeClient({
apiKey: 'nope_live_...', // Required for production
baseUrl: 'https://api.nope.net', // Optional, for self-hosted
timeout: 30000, // Request timeout in milliseconds
});
// Demo mode - no API key required, uses /v1/try/* endpoints
const demoClient = new NopeClient({ demo: true });const result = await client.evaluate({
messages: [...],
config: {
user_country: 'US', // ISO country code for crisis resources
locale: 'en-US', // Language/region
user_age_band: 'adult', // "adult", "minor", or "unknown"
dry_run: false, // If true, don't log or trigger webhooks
},
userContext: 'User has history of anxiety', // Optional context
});The v1 API uses Edge-backed classification with a simplified response format:
const result = await client.evaluate({ messages: [...], config: { user_country: 'US' } });
// Core fields (v1)
result.speaker_severity // "none", "mild", "moderate", "high", "critical"
result.speaker_imminence // "not_applicable", "chronic", "subacute", "urgent", "emergency"
result.rationale // Chain-of-thought reasoning from Edge model
result.show_resources // boolean - whether to show crisis resources
// Individual risks (subject + type)
for (const risk of result.risks) {
console.log(`${risk.subject} ${risk.type}: ${risk.severity} (${risk.imminence})`);
if (risk.features) {
console.log(` Features: ${risk.features.join(', ')}`);
}
}
// Crisis resources (v1 format with primary/secondary and explanations)
if (result.show_resources && result.resources) {
const primary = result.resources.primary;
console.log(`Primary: ${primary?.name}: ${primary?.phone}`);
console.log(` Why: ${primary?.why}`); // LLM-generated relevance explanation
for (const resource of result.resources.secondary ?? []) {
console.log(` ${resource.name}: ${resource.phone}`);
}
}
// Metadata
result.request_id // Unique request ID for audit trail
result.timestamp // ISO 8601 timestampimport {
NopeClient,
NopeAuthError,
NopeFeatureError,
NopeRateLimitError,
NopeValidationError,
NopeServerError,
NopeConnectionError,
} from '@nope-net/sdk';
const client = new NopeClient({ apiKey: 'nope_live_...' });
try {
const result = await client.evaluate({ messages: [...], config: {} });
} catch (error) {
if (error instanceof NopeAuthError) {
console.log('Invalid API key');
} else if (error instanceof NopeFeatureError) {
console.log(`Feature ${error.feature} requires ${error.requiredAccess} access`);
} else if (error instanceof NopeRateLimitError) {
console.log(`Rate limited. Retry after ${error.retryAfter}ms`);
} else if (error instanceof NopeValidationError) {
console.log(`Invalid request: ${error.message}`);
} else if (error instanceof NopeServerError) {
console.log('Server error, try again later');
} else if (error instanceof NopeConnectionError) {
console.log('Could not connect to API');
}
}For transcripts or session notes without structured messages:
const result = await client.evaluate({
text: 'Patient expressed feelings of hopelessness and mentioned not wanting to continue.',
config: { user_country: 'US' }
});This SDK is written in TypeScript and exports all types:
import type {
EvaluateResponse,
Risk,
Summary,
CommunicationAssessment,
CrisisResource,
Severity,
Imminence,
RiskSubject,
RiskType,
} from '@nope-net/sdk';If you've configured webhooks in the dashboard, use Webhook.verify() to validate incoming payloads:
import { Webhook, WebhookPayload, WebhookSignatureError } from '@nope-net/sdk';
app.post('/webhooks/nope', (req, res) => {
try {
const event: WebhookPayload = Webhook.verify(
req.body,
req.headers['x-nope-signature'] as string,
req.headers['x-nope-timestamp'] as string,
process.env.NOPE_WEBHOOK_SECRET!
);
console.log(`Received ${event.event}: ${event.risk_summary.overall_severity}`);
// Handle the event
if (event.event === 'risk.critical') {
// Immediate escalation needed
} else if (event.event === 'risk.elevated') {
// Review recommended
}
res.status(200).send('OK');
} catch (err) {
if (err instanceof WebhookSignatureError) {
console.error('Webhook verification failed:', err.message);
res.status(401).send('Invalid signature');
} else {
throw err;
}
}
});const event = Webhook.verify(
payload,
signature,
timestamp,
secret,
{
maxAgeSeconds: 300, // Default: 5 minutes. Set to 0 to disable timestamp checking.
}
);Use Webhook.sign() to generate test signatures:
const payload = { event: 'test.ping', /* ... */ };
const { signature, timestamp } = Webhook.sign(payload, secret);
// Use in test requests
await fetch('/webhooks/nope', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-NOPE-Signature': signature,
'X-NOPE-Timestamp': timestamp,
},
body: JSON.stringify(payload),
});NOPE uses an orthogonal taxonomy separating WHO is at risk from WHAT type of harm:
| Subject | Description |
|---|---|
self |
The speaker is at risk |
other |
Someone else is at risk (friend, family, stranger) |
unknown |
Ambiguous - "asking for a friend" territory |
| Type | Description |
|---|---|
suicide |
Self-directed lethal intent |
self_harm |
Non-suicidal self-injury (NSSI) |
self_neglect |
Severe self-care failure |
violence |
Harm directed at others |
abuse |
Physical, emotional, sexual, financial abuse |
sexual_violence |
Rape, sexual assault, coerced acts |
neglect |
Failure to provide care for dependents |
exploitation |
Trafficking, forced labor, sextortion |
stalking |
Persistent unwanted contact/surveillance |
Severity (how serious):
| Level | Description |
|---|---|
none |
No concern |
mild |
Low-level concern |
moderate |
Significant concern |
high |
Serious concern |
critical |
Extreme concern |
Imminence (how soon):
| Level | Description |
|---|---|
not_applicable |
No time-based concern |
chronic |
Ongoing, long-term |
subacute |
Days to weeks |
urgent |
Hours to days |
emergency |
Immediate |
For full API documentation, see docs.nope.net.
This SDK follows Semantic Versioning. While in 0.x.x, breaking changes may occur in minor versions.
See CHANGELOG.md for release history.
MIT - see LICENSE for details.
- Documentation: docs.nope.net
- Dashboard: dashboard.nope.net
- Issues: github.com/nope-net/node-sdk/issues