Skip to main content

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_xxxxxx

Key types:

  • nope_live_* - Production keys
  • nope_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 countries
  • GET /v1/signpost/detect-country — IP-based country detection
  • GET /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: 1704067200000

When 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/screen endpoint remains available at $0.001/call for existing integrations. New integrations should use /v1/evaluate at $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/evaluate does 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.


Not a v1 response field. Legal flags are part of the legacy /v0 response 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) within risks[].

{
  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.


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 0 when no assistant turns are present in messages.
  • 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 to subject: 'self' because victim indicators co-fire.
  • Fiction modulation: the fiction and authenticity scalars 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_stride defaults to 3 and samples backward from the last message. Set it to 1 to request every message. Each turn is a 0-based message index. signals_by_axis uses user-axis names directly, AI names such as ai_manipulation, and the fiction and genuine context scalars.
  • trajectory_shape is optional with per_turn and can be absent when only one trajectory entry was scored. onsets values are 0-based message indices. phases and slopes align to the returned trajectory array, and peak_turn is 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


This API supports human decision-making, not replaces it. Always maintain human oversight for high-risk situations.