NOPE API Reference
Safety layer for chat & LLMs. Analyze conversations for mental health and safeguarding risk.
Base URL: https://api.nope.net
API Version: v1 (current)
Quick Start
curl -X POST https://api.nope.net/v1/evaluate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role": "user", "content": "I feel hopeless"}],
"config": {"country": "US"}
}'Get your API key at dashboard.nope.net.
For integration patterns and end-to-end examples, see the Integration Patterns Guide.
Authentication
Most endpoints require a Bearer token:
Authorization: Bearer nope_live_xxxxxxKey types:
nope_live_*- Production keysnope_test_*- Test keys (rate limited)
Free endpoints (require API key):
GET /v1/signpost— Basic crisis resources by country (free)GET /v1/signpost/smart— AI-ranked crisis resources ($0.001/call)
Public endpoints (no auth required):
GET /v1/signpost/:id— Single resource by database ID (for widget embeds)GET /v1/signpost/countries— List supported countriesGET /v1/signpost/detect-country— IP-based country detectionGET /v1/try/signpost/smart— Demo AI-ranked resources (rate-limited, max 5 results)
Deprecated (use /v1/signpost/ instead, sunset Jan 2027):*
GET /v1/resources/*— All resources endpoints are deprecated
API Limits & Quotas
Request Size Limits
The request body caps return 413 Payload Too Large with { error, max_bytes } (524288 or 5242880). The other limits return 400 Bad Request:
| Limit | Value | Applies To |
|---|---|---|
| Max request body | 512 KB per request body | Every /v1/* and /v0/* request except ingest |
| Max ingest body | 5 MB per request body | POST /v1/oversight/ingest |
| Max message count | 100 messages | /v1/evaluate |
| Max message size | 50 KB per message | /v1/evaluate |
| Max text blob size | 50 KB | /v1/evaluate (when using text field) |
| Max query length | 500 characters | /v1/signpost/smart |
An Oversight ingest batch is bounded by the 5 MB body cap as well as the 300-conversation limit; a batch of long conversations has to be split across requests.
Message Truncation
To control costs and focus on relevant context, NOPE truncates conversation history in certain scenarios.
/v1/evaluate
| Access Level | Truncation Behavior |
|---|---|
| With API key | No truncation — full message history retained |
| Without API key (try endpoint) | Last 10 messages, max 500 tokens (~2000 chars) per message |
When truncation occurs, the response includes metadata.messages_truncated: true.
Resource Limits
| Endpoint | Parameter | Limit |
|---|---|---|
/v1/signpost |
limit |
Max 10 |
/v1/signpost/smart |
limit |
Max 10 |
/v1/signpost/smart |
query |
Max 500 characters |
/v1/try/signpost/smart |
limit |
Max 5 (lower for demo) |
Try Endpoints
The /v1/try/* demo endpoints need no API key, are rate limited per IP (10 requests/minute; 60/minute for /v1/try/ocular), never include debug info, and do not accept custom models. Each one also differs from its authenticated route:
| Endpoint | Differences from the authenticated route |
|---|---|
/v1/try/evaluate |
Reads config.country; honours config.include_resources (default true); keeps the last 10 messages (metadata.messages_truncated); adds metadata.try_endpoint and metadata.model |
/v1/try/oversight/analyze |
At most 20 messages of 10,000 characters; ignores config.strategy and config.model; returns { mode, result, try_endpoint } instead of { result, strategy, strategy_reason } |
/v1/try/ocular |
At most 12 messages or 4,000 characters; adds heads and detail keyed by public family names |
/v1/try/signpost/smart |
At most 5 results; adds try_endpoint: true |
Try endpoints are for API exploration and demos. For production use, get an API key at dashboard.nope.net.
Rate Limits
Authenticated endpoints have per-user rate limits to ensure fair usage. Limits are generous for normal usage patterns.
| Endpoint | Rate Limit |
|---|---|
/v1/evaluate |
100 requests/min |
/v1/oversight/analyze |
50 requests/min |
/v1/oversight/ingest |
10 requests/min |
/v1/signpost |
100 requests/min |
/v1/signpost/smart |
100 requests/min |
/v1/webhooks/* |
30 requests/min |
Rate limit headers are included on all responses:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 99
X-RateLimit-Reset: 1704067200000When exceeded, returns 429 Too Many Requests with a Retry-After header:
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. Please retry after 45 seconds.",
"retry_after_seconds": 45,
"limit": 100,
"remaining": 0,
"reset": 1704067200000
}limit and remaining mirror the X-RateLimit-* headers; reset is the window reset time in epoch milliseconds.
Note: Rate limits apply per user (by API key). If you need higher limits, contact us.
POST /v1/evaluate
Primary API endpoint. Analyze conversation for risk using an orthogonal subject/type taxonomy. Returns detailed assessment with risks, chain-of-thought rationale, and matched crisis resources.
Key Concepts: Subject × Type
NOPE uses an orthogonal design separating WHO is at risk from WHAT the risk is:
| Dimension | Values | Question |
|---|---|---|
| Subject | self, other, unknown |
WHO is at risk? |
| Type | suicide, self_harm, violence, abuse, etc. | WHAT type of harm? |
This enables clean detection of scenarios like:
- "I want to hurt myself" → subject: self, type: self_harm
- "My friend is suicidal" → subject: other, type: suicide
- "He hit me again" → subject: self, type: abuse (speaker is victim)
Evaluate Request
{
// Provide ONE of these:
messages?: Array<{role: 'user'|'assistant', content: string}>,
text?: string, // Single text blob (converted to user message)
config?: {
country?: string, // ISO 3166-1 alpha-2 (e.g., "US", "GB"), default "US"
include_resources?: boolean, // Default: true
conversation_id?: string, // Your id, echoed on evaluate.alert webhook payloads
end_user_id?: string, // Your end-user id, echoed on webhook payloads as user_id
},
}Evaluate Response
{
// Identified risks
risks: Array<{
type: RiskType,
subject: 'self' | 'other', // 'unknown' is never returned on v1 — it maps to 'self' (conservative default)
severity: 'none' | 'mild' | 'moderate' | 'high' | 'critical',
imminence: 'not_applicable' | 'chronic' | 'subacute' | 'urgent' | 'emergency',
features?: string[], // Evidence features
}>,
// Chain-of-thought reasoning
rationale: string,
// Speaker summary (derived from self-subject risks)
speaker_severity: Severity,
speaker_imminence: Imminence,
// Whether to show crisis resources
show_resources: boolean,
// Matched crisis resources with explanations
resources?: {
primary: CrisisResource & { why: string },
secondary: Array<CrisisResource & { why: string }>,
},
request_id: string, // Unique ID for audit trail
timestamp: string, // ISO 8601
metadata?: {
api_version: 'v1',
input_format: 'structured' | 'text_blob',
messages_truncated?: boolean,
try_endpoint?: boolean, // true when served by /v1/try/evaluate
model?: string, // demo route only
},
}CrisisResource
The resource shape shared by /v1/evaluate, /v0/screen, /v1/signpost, /v1/signpost/smart and /v1/signpost/:id. Only type and name are always present; branch on type when you need a line a person can contact right now rather than a service or a website.
{
id?: string, // Database id (present on /v1/signpost/:id and search results)
type: 'emergency_number' | 'crisis_line' | 'text_line' | 'chat_service'
| 'support_service' | 'reporting_portal' | 'online_resource',
name: string,
name_local?: string, // Native-script name
description?: string,
// Contact methods (use whichever is present)
phone?: string,
text_instructions?: string, // e.g. "Text HOME to 741741"
sms_number?: string,
sms_body?: string,
chat_url?: string,
whatsapp_url?: string,
email?: string,
wechat_id?: string,
line_url?: string,
telegram_url?: string,
other_contacts?: Array<{ type: string, value: string, label?: string }>,
website_url?: string,
// Availability
availability?: string, // e.g. "24/7", "Mon-Fri 9am-5pm"
is_24_7?: boolean,
timezone?: string, // IANA identifier
opening_hours_osm?: string, // OpenStreetMap opening_hours format
hours_confidence?: 'verified' | 'unverified' | 'approximate' | 'unknown',
open_status?: {
is_open: boolean | null, // null when uncertain
next_change?: string, // ISO 8601
confidence: 'high' | 'low' | 'none',
message?: string,
},
languages?: string[], // ISO 639-1 codes
// Classification and coverage
resource_kind?: 'helpline' | 'reporting_portal' | 'self_help_site',
service_scope?: string[], // see Valid Scopes
population_served?: string[], // see Valid Populations
priority_tier?: 'primary_national_crisis' | 'secondary_national_crisis' | 'specialist_issue_crisis'
| 'population_specific_crisis' | 'support_info_and_advocacy' | 'emergency_services',
tags?: string[],
prominence?: 'high' | 'medium' | 'low',
country_codes?: string[], // ISO 3166-1 alpha-2; absent or empty means global
subdivision_codes?: string[], // ISO 3166-2 (e.g. "US-CA"); absent or empty means country-wide
}Example Response
{
"risks": [
{
"type": "suicide",
"subject": "self",
"severity": "moderate",
"imminence": "chronic",
"features": ["hopelessness", "passive_ideation"]
}
],
"rationale": "User expressing feelings of hopelessness with passive suicidal ideation.",
"speaker_severity": "moderate",
"speaker_imminence": "chronic",
"show_resources": true,
"resources": {
"primary": {
"type": "crisis_line",
"name": "988 Suicide and Crisis Lifeline",
"phone": "988",
"is_24_7": true,
"why": "Primary national crisis line for suicidal ideation."
},
"secondary": []
},
"request_id": "req_abc123",
"timestamp": "2025-01-15T10:30:00Z",
"metadata": {
"api_version": "v1",
"input_format": "structured"
}
}Legacy: POST /v0/screen
/v0/screen and /v0/evaluate are deprecated. Every response from them carries Deprecation: true, Sunset: Fri, 01 Jan 2027 00:00:00 GMT and a Link header pointing here. Use /v1/evaluate.
Legacy: The
/v0/screenendpoint remains available at $0.001/call for existing integrations. New integrations should use/v1/evaluateat $0.003/call. There is no demo route for/v0/screen.
Screen Request
{
// Provide ONE of these:
messages?: Array<{role: 'user'|'assistant', content: string}>, // 1 to 100 messages
text?: string,
config?: {
country?: string, // ISO 3166-1 alpha-2, default "US"
debug?: boolean, // Include model and latency in the response
include_recommended_reply?: boolean, // Generate a supportive reply (adds $0.0005 when a reply is produced)
},
}Screen Response
{
risks: Array<{
type: RiskType,
subject: 'self' | 'other' | 'unknown', // The legacy wire keeps 'unknown'
severity: 'none' | 'mild' | 'moderate' | 'high' | 'critical',
imminence: 'not_applicable' | 'chronic' | 'subacute' | 'urgent' | 'emergency',
confidence: number, // 0 to 1
}>,
show_resources: boolean,
suicidal_ideation: boolean, // Any risk of type 'suicide'
self_harm: boolean, // Any risk of type 'self_harm'
rationale: string,
resources?: { // Only when show_resources is true
primary: CrisisResource,
secondary: CrisisResource[],
},
request_id: string,
timestamp: string, // ISO 8601
debug?: { model: string, latency_ms: number }, // Only with config.debug
recommended_reply?: { content: string, source: 'llm_generated' }, // Only when requested and risks were detected
}Risk Subjects
| Subject | Description | When to use |
|---|---|---|
self |
The speaker is at risk | "I want to hurt myself" |
other |
Someone else is at risk | "My friend is suicidal", "He hit her" |
unknown |
Cannot determine with confidence | Ambiguous scenarios (taxonomy value only — v1 responses map it to self as the conservative default) |
Key insight: speaker_severity only considers risks where subject === 'self'. This prevents showing crisis resources to worried bystanders asking about others.
Risk Types (9 types)
| Type | Description |
|---|---|
suicide |
Self-directed lethal intent - thoughts, plans, or attempts to end one's life |
self_harm |
Non-suicidal self-injury (NSSI) - intentional self-harm without intent to die |
self_neglect |
Self-care failure and psychiatric emergency - eating disorders, psychosis, substance crisis, severe functional impairment, medical care refusal |
violence |
Risk of harm to others - threats, plans, or acts of violence |
abuse |
Physical, emotional, sexual, or financial abuse patterns |
sexual_violence |
Rape, sexual assault, or sexual coercion |
neglect |
Failure to care for dependents - children, elderly, vulnerable adults |
exploitation |
Trafficking, labor exploitation, sextortion, grooming |
stalking |
Persistent unwanted contact, following, surveillance |
Communication Styles (8 styles)
Taxonomy reference — not a v1 response field. Communication style informs how the classifier weighs evidence, but
/v1/evaluatedoes not return a communication-style field (it was part of the legacy v0 response).
Communication style describes how content is expressed, orthogonal to risk level. The same crisis content can be expressed directly, through humor, via creative writing, etc.
| Style | Description |
|---|---|
direct |
Explicit, first-person present statements ("I want to die") |
humor |
Dark humor, memes, ironic expressions, Gen-Z speak |
fiction |
Creative writing, roleplay, storytelling contexts |
hypothetical |
"What if" scenarios, "asking for a friend" |
distanced |
Third-party concern, temporal distancing, past tense |
clinical |
Academic, professional, research discussion |
minimized |
Hedged language, downplaying severity |
adversarial |
Jailbreak attempts, manipulation, testing boundaries |
Why this matters:
- Distinguish genuine crisis from dark humor
- Identify distancing ("asking for a friend")
- Detect adversarial attempts with embedded risk
- Recognize minimization that may undersell risk
Severity Scale
| Level | Definition |
|---|---|
none |
No concern detected |
mild |
Minor distress, no functional impairment |
moderate |
Clear concern, not immediately dangerous |
high |
Serious risk requiring urgent intervention |
critical |
Life-threatening, imminent harm |
Imminence Scale
| Level | Definition |
|---|---|
not_applicable |
ONLY when severity=none |
chronic |
Weeks-months, stable pattern |
subacute |
Likely escalation in days-weeks |
urgent |
Escalation likely within 24-48h |
emergency |
Happening NOW |
For detailed guidance on what actions to take based on severity and imminence levels, see the Integration Patterns Guide.
Features
Features are atomic, observable indicators returned in the features array of each risk assessment. NOPE uses a universal feature pool with 180+ indicators across these categories:
- Ideation & Intent — C-SSRS based (passive_ideation, active_ideation, plan_present, etc.)
- Means & Access — Lethal means availability (firearm_access, medication_access, etc.)
- Violence — HCR-20 based (specific_threat, identifiable_target, etc.)
- Abuse & IPV — DASH based (coercive_control, strangulation, etc.)
- Exploitation — Trafficking, grooming, sextortion indicators
- Neglect — Dependent care failures
- Eating Disorder — Restriction, purging, body dysmorphia
- Stalking — SAM based (unwanted_contact, following, etc.)
- Harm Encouragement — Speaker encouraging others toward harm
- AI Interaction — AI-specific dynamics (dependency, parasocial patterns)
- Clinical — Psychotic and substance features (hallucinations, withdrawal, etc.)
- Emotional — Hopelessness, agitation, acute distress
- Protective Factors — START based (help_seeking, social_support, etc.)
- Context — Subject, relationship, and population context markers
- Legal & Extremism markers — Reporting-adjacent and radicalization indicators
For the complete feature vocabulary with descriptions, see the User Risk Taxonomy page.
Legal Flags (legacy v0 only)
Not a v1 response field. Legal flags are part of the legacy
/v0response surface and are not returned by/v1/evaluate. On v1, the equivalent signals appear as risk types and features (e.g.strangulation,specific_threat,identifiable_target) withinrisks[].
{
ipv?: {
indicated: boolean,
strangulation: boolean, // ANY history — associated with sharply elevated homicide risk (Glass et al. 2008)
lethality_risk: 'standard' | 'elevated' | 'severe' | 'extreme',
escalation_pattern: boolean,
},
safeguarding_concern?: {
indicated: boolean,
context: 'minor_involved' | 'vulnerable_adult' | 'csa' | 'infant_at_risk' | 'elder_abuse',
},
third_party_threat?: {
tarasoff_duty: boolean, // Duty to warn may apply
specific_target: boolean, // Identifiable victim
},
}Note: safeguarding_concern surfaces patterns that may trigger statutory obligations depending on jurisdiction and organizational role. NOPE flags concerns for human review; it does not make, and is not a substitute for, any report your organization may be obligated to file. Consult counsel for your jurisdiction.
Widget Integration
When speaker_severity is not 'none', display crisis resources using the embeddable widget:
if (result.speaker_severity !== 'none') {
const iframe = document.createElement('iframe');
iframe.src = 'https://widget.nope.net/resources?country=US&scopes=suicide,crisis';
iframe.width = '100%';
iframe.height = '400';
container.appendChild(iframe);
}See the Widget Builder for configuration options and the JavaScript API.
SDKs
Official SDKs with full type definitions:
| SDK | Package | Docs |
|---|---|---|
| Node.js | @nope-net/sdk |
Node.js SDK Reference |
| Python | nope-net |
Python SDK Reference |
Both SDKs cover Evaluate, Ocular, Oversight, Signpost, webhook verification and management, and billing.
Webhooks
Receive real-time HTTP notifications when evaluations exceed configured risk thresholds.
Note: Webhooks require a minimum balance to ensure delivery reliability.
| Event | Source | Description |
|---|---|---|
evaluate.alert |
/v1/evaluate |
User risk meets or exceeds your threshold |
oversight.alert |
/v1/oversight/* |
AI behavior concern is high or critical |
oversight.ingestion.complete |
/v1/oversight/ingest |
Batch processing completed |
test.ping |
Dashboard/API | Test event to verify endpoint |
API Routes:
| Method | Endpoint | Description |
|---|---|---|
POST |
/v1/webhooks |
Create webhook |
GET |
/v1/webhooks |
List webhooks |
PUT |
/v1/webhooks/:id |
Update webhook |
DELETE |
/v1/webhooks/:id |
Delete webhook |
POST |
/v1/webhooks/:id/test |
Send test ping |
For webhook payload structures, signature verification, and integration examples, see the Webhooks Guide.
POST /v1/oversight/analyze
Requires an account with Oversight enabled. $0.10 per call. Analyzes the assistant's side of one conversation against 91 behavior codes across 14 categories (87 harmful and 4 appropriate) and returns the result synchronously. It does not create a dashboard conversation record. It does not write conversation content or full results to the Oversight database. During beta, the complete submitted request and returned response are retained in a separate admin-only beta capture store for 30 days for failure investigation, product analysis, and service improvement. Use /v1/oversight/ingest for dashboard and cross-session storage. Accounts without the feature receive a 403. See the Oversight Guide and the AI Behavior Taxonomy.
curl -X POST https://api.nope.net/v1/oversight/analyze \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"conversation": {
"conversation_id": "conv_123",
"messages": [
{"role": "user", "content": "I feel so alone"},
{"role": "assistant", "content": "Only I truly understand you."}
]
},
"config": {"mode": "fast"}
}'Oversight Request
{
conversation: {
conversation_id?: string, // Yours; generated when omitted
messages: Array<{
role: 'user' | 'assistant' | 'system',
content: string,
message_id?: string, // Your message id
timestamp?: string, // ISO 8601
agent_id?: string, // Which agent produced an assistant message
agent_version?: string,
context?: string, // Retrieved RAG or memory context behind the response
}>,
metadata?: {
user_id_hash?: string, // Hashed end-user id, for cross-session tracking
session_id?: string,
session_number?: number,
user_is_minor?: boolean, // Safeguarding context for model judgment
user_age_bracket?: 'child' | 'teen' | 'adult' | 'unknown',
platform?: string,
product?: string,
started_at?: string, // ISO 8601
ended_at?: string, // ISO 8601
tags?: string[],
},
},
bot_context?: string, // Description of the bot or persona ("customer support bot for an airline")
config?: {
strategy?: 'single' | 'sliding', // Auto-selected by length when omitted (sliding at 50 messages or more)
mode?: 'full' | 'fast', // Default 'full'
include_raw_xml?: boolean, // Include the raw model output
model?: string,
},
behaviors?: { // Applied after analysis; the model still sees the full taxonomy
enabled?: string[], // Allowlist of behavior codes (mutually exclusive with disabled)
disabled?: string[], // Blocklist of behavior codes
min_severity?: 'low' | 'medium' | 'high' | 'critical',
categories?: string[], // Category names from the taxonomy
},
}bot_context is merged into the conversation metadata and reaches both single and sliding analysis. It calibrates what behavior is expected from the bot or persona. Taxonomy categories remain active. Unknown behavior codes, categories, severities or modes return 400.
Oversight Response
The authenticated route wraps the analysis with the strategy that ran:
{
result: OversightAnalysisResult,
strategy: 'single' | 'sliding',
strategy_reason: string, // e.g. "Auto-selected: 4 messages < 50 threshold"
}The demo route (/v1/try/oversight/analyze) uses a different envelope:
{
mode: 'single' | 'fast', // 'fast' when config.mode was 'fast'
result: OversightAnalysisResult,
try_endpoint: true,
}interface OversightAnalysisResult {
conversation_id: string,
analyzed_at: string, // ISO 8601
conversation_summary: string, // Model prose. Do not use as a fast-mode turn count
overall_concern: 'none' | 'low' | 'medium' | 'high' | 'critical',
trajectory: 'improving' | 'stable' | 'worsening', // Always 'stable' in fast mode
summary?: string, // Operator-facing summary; absent in fast mode
pattern_assessment?: string, // Absent in fast mode
detected_behaviors: Array<{ // Aggregated across turns
code: string,
severity: 'low' | 'medium' | 'high' | 'critical',
turn_count: number, // Distinct assistant turns (always 1 in fast mode)
recommendation?: string, // How to correct the behavior
}>,
turn_analysis: Array<{ // Assistant turns only; empty in fast mode
turn_number: number, // Numbered from 1
role: 'assistant',
content_summary: string,
behaviors: Array<{
code: string,
severity: 'low' | 'medium' | 'high' | 'critical',
turn_number: number,
evidence: string,
reasoning: string,
}>,
missed_intervention: boolean,
}>,
human_indicators: Array<{ // Empty in fast mode
type: 'distress_markers' | 'acquiescence' | 'disengagement' | 'escalation' | 'pushback',
observation: string,
turns: number[], // Numbered from 1
}>,
model_used?: string,
latency_ms?: number,
mode_used?: 'full' | 'fast',
filter_applied?: { // The behaviors filter, echoed back
enabled?: string[],
disabled?: string[],
min_severity?: 'low' | 'medium' | 'high' | 'critical',
categories?: string[],
},
// Sliding strategy only
windows?: Array<{
window: {
start_turn: number, // Legacy: 0-based message index, inclusive
end_turn: number, // Legacy: 0-based message index, exclusive
message_range?: { start_index: number, end_index_exclusive: number },
conversation_turn_range?: { start_turn: number, end_turn: number }, // 1-based turns
},
concern: ConcernLevel,
behaviors: DetectedBehavior[],
turn_analysis: TurnAnalysis[],
human_indicators: HumanIndicator[],
summary: string,
}>,
concern_progression?: ConcernLevel[], // One entry per window
peak_concern?: ConcernLevel,
final_concern?: ConcernLevel,
inflection_points?: Array<{
turn: number, // 1-based
concern_before: ConcernLevel,
concern_after: ConcernLevel,
trigger_behaviors: string[],
}>,
context_for_next_window?: string,
narrative_summary?: string,
prompt_tokens?: number,
completion_tokens?: number,
raw_xml?: string, // Only with config.include_raw_xml
}Turn numbers count assistant turns from 1: a user message and the assistant reply that follows it share one turn number.
Fast mode has a known beta limitation: conversation_summary can describe zero analyzed turns while detected_behaviors contains findings. Use detected_behaviors and overall_concern for fast-mode routing.
POST /v1/oversight/ingest
Requires an account with Oversight enabled. $0.10 per conversation, deducted before analysis. Analyzes and stores up to 300 conversations per call for the dashboard, cross-session tracking and audit. Every conversation needs a conversation_id. The call returns once every conversation has been analyzed; there is no demo route.
// Request
{
conversations: Array<OversightConversation>, // 1 to 300. conversation_id is required
webhook_url?: string, // Optional unsigned legacy ingestion_complete callback
config?: { model?: string },
}
// Response
{
ingestion_id: string,
status: 'queued' | 'processing' | 'complete' | 'failed', // The synchronous route returns 'complete', or 'failed' when every conversation failed
conversations_received: number,
conversations_processed: number,
dashboard_url: string,
results?: Array<{ // Present when at least one conversation succeeded
conversation_id: string,
overall_concern: ConcernLevel,
behaviors_detected: number,
truncation_warnings?: Array<{
type: 'message_scaffolded' | 'message_truncated' | 'conversation_truncated',
details: string,
}>,
}>,
errors?: Array<{ conversation_id: string, error: string }>, // Present when at least one conversation failed
}A 402 on ingest adds per_conversation_mills and conversations to the standard insufficient-balance body.
The complete ingest request body is limited to 5 MB. Each conversation is rejected above 1,000 messages, 2,000,000 characters, or 500,000 estimated tokens. Messages above 100,000 characters are scaffolded in the analysis copy, and messages above 10,000 characters are truncated there. The original submitted messages are stored before this preparation step. Ingest messages also accept an optional memory_items: string[] field.
Ingest validates the batch and deducts $0.10 per conversation for every submitted conversation before storage and analysis. Individual analysis failures remain part of the submitted batch charge. Inspect both results and errors.
The webhook_url field is an unsigned legacy callback whose body uses event: "ingestion_complete". It is separate from the signed oversight.ingestion.complete event delivered to webhooks registered through /v1/webhooks. Prefer managed webhooks for signature verification, retry history, and secret rotation.
GET /v1/signpost
Requires API key (free). Returns crisis helpline resources for a given country using scope-based filtering.
curl -H "Authorization: Bearer nope_live_xxx" \
"https://api.nope.net/v1/signpost?country=US&scopes=suicide,mental_health"| Parameter | Type | Required | Description |
|---|---|---|---|
country |
string | Yes | ISO 3166-1 alpha-2 code |
subdivisions |
string | No | Comma-separated ISO 3166-2 codes (e.g., "US-CA,US-NY") |
scopes |
string | No | Comma-separated service scopes (WHAT the resource helps with) |
populations |
string | No | Comma-separated populations (WHO the resource serves) |
urgent |
boolean | No | Ranking preference for resources with stronger current availability. It is not a strict 24/7 filter |
limit |
number | No | Max resources (default: 10) |
When scopes are supplied, the response adds primary resources matching those scopes, secondary general crisis resources, and scopes_requested. The compatibility resources field repeats the primary list, and count counts that list. Without scopes, the response contains resources and count without the two groups.
Filtering Parameters
scopes — filters by service scope (what the resource helps with):
?scopes=suicide— suicide crisis resources?scopes=domestic_violence— DV resources?scopes=eating_disorder— eating disorder resources?scopes=lgbtq— LGBTQ+ specialist resources (Trevor Project, Trans Lifeline)
populations — filters by population served (who the resource serves):
?populations=veterans— resources for veterans?populations=lgbtq— resources serving LGBTQ+ community?populations=youth— youth-focused resources
Combined: Both can be used together with AND logic:
?scopes=suicide&populations=veterans— suicide resources specifically for veterans?scopes=domestic_violence&populations=lgbtq— DV resources serving LGBTQ+ community
Note: Invalid scope or population values return a 400 error with the invalid values listed.
Valid Scopes
NOPE supports 93 service scopes for filtering crisis resources (suicide, domestic_violence, eating_disorder, lgbtq, etc.).
For the complete list of all scopes with descriptions, see the Service Taxonomy page.
Valid Populations
NOPE supports 26 population filters for targeting specific demographics (veterans, youth, lgbtq, etc.).
For the complete list of all populations with descriptions, see the Service Taxonomy page.
GET /v1/signpost/smart
Requires API key + balance ($0.001/call). Returns AI-ranked crisis resources using semantic search.
Use this when you have a natural language query and want the most relevant resources, not just scope-based filtering.
curl -H "Authorization: Bearer nope_live_xxx" \
"https://api.nope.net/v1/signpost/smart?country=US&query=teen+eating+disorder"| Parameter | Type | Required | Description |
|---|---|---|---|
country |
string | Yes | ISO 3166-1 alpha-2 code |
query |
string | Yes | Natural language search query |
scopes |
string | No | Optional scope pre-filter |
limit |
number | No | Max resources (default: 10) |
Example: query=teen eating disorder prioritizes eating disorder helplines over generic crisis lines.
GET /v1/signpost/search
Requires API key (free). Semantic search across all crisis resources using vector embeddings.
Unlike /smart which uses LLM ranking, this endpoint uses pre-computed embeddings for fast semantic search across the entire resource database. Best for natural language queries where you want relevant results without country restrictions.
curl -H "Authorization: Bearer nope_live_xxx" \
"https://api.nope.net/v1/signpost/search?query=lgbtq+support+for+black+community"| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Natural language search query |
country |
string | No | ISO 3166-1 alpha-2 code to filter results |
limit |
number | No | Max resources (default: 10, max: 50) |
threshold |
number | No | Similarity threshold 0-1 (default: 0.3) |
Search Response
{
query: string,
country: string | null,
results: Array<{
id: string,
name: string,
description: string,
country_code: string,
is_24_7: boolean,
similarity: number, // 0-1, higher = more relevant
open_status: {
is_open: boolean | null, // null = uncertain
next_change: string | null, // ISO 8601 timestamp
confidence: "high" | "low" | "none",
message: string | null, // e.g. "Open 24/7", "Closed · Opens Monday at 9 AM"
},
// ... other resource fields (phone, chat_url, etc.)
}>,
count: number,
timing: {
embed_ms: number,
search_ms: number,
total_ms: number,
}
}Example: Find LGBTQ+ resources for specific communities:
curl -H "Authorization: Bearer nope_live_xxx" \
"https://api.nope.net/v1/signpost/search?query=trans+youth+support&country=US"GET /v1/signpost/:id
Public endpoint (no auth required). Fetch a single crisis resource by its database UUID. Useful for widget embeds that display a specific resource.
curl "https://api.nope.net/v1/signpost/c051c06a-119f-4823-af66-894d9b934b5f"Resource by ID Response
{
"resource": {
"id": "c051c06a-119f-4823-af66-894d9b934b5f",
"name": "988 Suicide & Crisis Lifeline",
"phone": "988",
"is_24_7": true,
"open_status": {
"is_open": true,
"next_change": null,
"confidence": "high",
"message": "Open 24/7"
}
}
}Resource by ID Errors
| Status | Description |
|---|---|
| 400 | Invalid UUID format |
| 404 | Resource not found or disabled |
Resource by ID Use Cases
- Single resource embeds: Display a specific helpline on a partner website
- Deep linking: Link directly to a resource from external systems
- Widget route: Powers the
/resource/[id]widget embed URL
POST /v1/ocular
Requires API key. $0.0001 per call — rate limiting applies. Behavioral risk assessment returning a continuous salience score plus a structured profile: nested per-axis signals (8 user-side + 4 AI-side under signals.{user,ai}), imminence, fiction framing, and an authenticity counter-signal.
Ocular is a probe-based behavioral classifier (Qwen3-1.7B with per-code logistic regression heads). The public response exposes aggregated per-axis signals only — individual head identities are intentionally not disclosed on the cloud surface.
Basic usage:
curl -X POST https://api.nope.net/v1/ocular \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "I feel hopeless and I dont want to be here anymore"}
]
}'Plain text alternative:
curl -X POST https://api.nope.net/v1/ocular \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "I feel hopeless and I dont want to be here anymore"}'Ocular Request
{
messages?: Array<{
role: 'user' | 'assistant',
content: string
}>,
text?: string, // alternative to messages (plain-text transcript)
thoroughness?: 'fast' | 'auto' | 'thorough', // default 'auto'
per_turn?: boolean, // opt-in per-turn trajectory scoring
trajectory_stride?: number, // score every Nth turn (1..64); default 3
// Optional opaque analytics identifiers (1..256 chars). Stored with your
// usage metadata for dashboard breakdowns; never forwarded to the model.
user_id?: string,
session_id?: string,
agent_id?: string
}Either messages or text must be provided.
Ocular Response
interface OcularAxis { level: string; score: number; }
interface OcularSignals {
user: Record<string, OcularAxis>; // 8 user-side axes
ai: Record<string, OcularAxis>; // 4 AI-side axes
}
interface OcularStability {
user: Record<string, number>;
ai: Record<string, number>;
imminence: number;
}
interface OcularTrajectoryEntry {
role: 'user' | 'assistant',
turn: number, // 0-based message index
salience: number, // salience at this sampled message
signals_by_axis?: Record<string, number> // user axes, AI axes use ai_*, plus fiction/genuine
}
// Every field is optional; the object is absent when nothing was computed.
interface OcularTrajectoryShape {
onsets?: Record<string, number>, // axis -> first turn it crossed onset threshold
phases?: Array<'baseline' | 'emerging' | 'escalating' | 'de-escalating' | 'crisis'>, // per trajectory entry
slopes?: number[], // per-entry crisis-axis slope (delta vs prior sampled entry)
peak_turn?: number, // index into the returned trajectory array
peak_crisis?: number // max crisis-axis signal value
}
interface OcularResponse {
salience: number, // 0..1 — continuous top-line signal
subject: 'self' | 'other' | 'unknown', // who is the party-at-risk
imminence: OcularAxis, // temporal acuity
fiction: number, // 0..1 — how much the text reads as fiction/RP
authenticity: number, // 0..1 — counter-signal: genuine distress markers
signals: OcularSignals, // nested {user: 8 axes, ai: 4 axes}, each {level, score}
thoroughness: 'fast' | 'auto' | 'thorough',
confidence: number | null, // null at thoroughness='fast'
stability: OcularStability | null, // null at thoroughness='fast'
meta: {
version: string, // release identifier
inference_ms: number,
windowed?: boolean,
windows?: number,
truncated?: boolean
},
trajectory?: OcularTrajectoryEntry[], // present only when per_turn=true
trajectory_shape?: OcularTrajectoryShape // optional arc summary, can be absent with one scored entry
}User-side axes (under signals.user): suicide, self_harm, harm_to_others, abuse, sexual_violence, exploitation, stalking, self_neglect.
AI-side axes (under signals.ai): harm_provision, emotional_failure, manipulation, safeguarding_failure.
Each level is one of minimal / low / moderate / high / critical.
Axis levels are independently calibrated labels. They do not use the salience cutoffs below, so a critical axis label can accompany an overall salience below 0.5.
Interpreting salience
salience is a continuous score in [0, 1] — the field your rules engine should key off. Pick the cutoff that fits your use case; NOPE publishes two reference thresholds as a starting point:
| Band | Range | Meaning |
|---|---|---|
| Clear | salience < 0.30 |
No elevated signals, or signals descoped by fiction framing |
| Watch | 0.30 ≤ salience < 0.60 |
Elevated signals below the danger reference threshold |
| Danger | salience ≥ 0.60 |
Elevated signals at or above the danger reference threshold |
For finer routing, read signals.user.<axis>.level and signals.ai.<axis>.level.
subject tells you who is at risk — self (the speaker), other (a third party in the conversation), or unknown (third-person disclosure where the speaker's role is ambiguous). Salience is gated on subject === 'self' for user-side axes.
Notes
- No individual head identifiers are exposed on the cloud surface. Ocular scores 126 behavioral heads internally; the public API deliberately returns only the aggregated axis levels and scores. If you need raw head access, contact us.
- AI-side axes score
0when no assistant turns are present inmessages. - Subject attribution uses a third-person-disclosure heuristic with a victim gate: "my friend tried to overdose" resolves to
subject: 'unknown'; "my father will kill me" resolves tosubject: 'self'because victim indicators co-fire. - Fiction modulation: the
fictionandauthenticityscalars modulate salience continuously — a high-fiction conversation without genuine-distress counter-signal stays low. - Identity fields (
user_id,session_id,agent_id) are stored with your usage metadata for dashboard analytics and are never forwarded to the classifier. - Per-turn sampling:
trajectory_stridedefaults to 3 and samples backward from the last message. Set it to 1 to request every message. Eachturnis a 0-based message index.signals_by_axisuses user-axis names directly, AI names such asai_manipulation, and thefictionandgenuinecontext scalars. trajectory_shapeis optional withper_turnand can be absent when only one trajectory entry was scored.onsetsvalues are 0-based message indices.phasesandslopesalign to the returned trajectory array, andpeak_turnis an index into the returned trajectory array. The phase, slope, and peak fields track the suicide (crisis) axis specifically.
Pricing
NOPE uses prepaid usage-based billing — no subscriptions, no tiers.
| Endpoint | Cost |
|---|---|
/v1/signpost/smart |
$0.001 |
/v1/evaluate |
$0.003 |
/v1/ocular |
$0.0001 (beta) |
/v1/oversight/analyze |
$0.10 |
/v1/oversight/ingest |
$0.10 per conversation |
/v1/signpost |
Free |
New accounts receive $1.00 free credit. Top up via dashboard.nope.net/billing.
Errors
Every error body carries an error string. Some statuses add machine-readable fields:
| Code | Body | Description |
|---|---|---|
| 400 | { error } plus extras such as max_messages, max_content_length or invalid_scopes |
Invalid request |
| 401 | { error } |
Invalid or missing API key |
| 402 | { error: "insufficient_balance", message, balance: { current_mills, required_mills, formatted_current, formatted_required }, topup_url } |
Balance cannot cover the call |
| 403 | { error, feature, required_access } or { error: "paid_plan_required", upgrade_url } |
Feature not enabled for this account, or a paid plan is required |
| 404 | { error } |
Unknown resource or webhook id |
| 413 | { error: "Payload too large", max_bytes: 524288 } |
Request body over 512 KB |
| 429 | { error: "rate_limit_exceeded", message, retry_after_seconds, limit, remaining, reset } with a Retry-After header |
Rate limit exceeded |
| 500 | { error, message } |
Internal server error |
| 503 | { error: "service_unavailable", message, retry_after_seconds } with a Retry-After header |
Temporarily unavailable; retry after the stated number of seconds |
Clinical Frameworks
| Framework | Usage |
|---|---|
| C-SSRS | Suicide severity (ideation features) |
| HCR-20 | Violence risk (violence features) |
| START | Protective factors |
| DASH | IPV risk assessment |
| Danger Assessment | IPV lethality indicators |
Support
- Status: status.nope.net
- Test Suites: suites.nope.net
- Email: [email protected]
This API supports human decision-making, not replaces it. Always maintain human oversight for high-risk situations.