Getting Started

Introduction

The SessionMood API is a REST API that infers a user's emotional state in real time from behavioral signals — clicks, scrolls, idle time, errors, and more. Send events as they happen in the browser, then read back a mood label with confidence score, signals, and a suggested intervention action. Every response is JSON. HTTPS only.

💡
How it works: You stream lightweight behavioral events to the API for a given session_id. The engine scores the session and returns one of six mood states — frustrated  confused  decisive  browsing  disengaged  focused — plus a recommended action to trigger in your UI.

Quick start

curl
# 1. Send events
curl -X POST "https://session-mood-api-production.up.railway.app/v1/sessions/user_abc/events" \
     -H "X-Api-Key: YOUR_KEY" \
     -H "Content-Type: application/json" \
     -d '{"events":[{"type":"rage_click","x":240,"y":580,"ts":1746352800}]}'

# 2. Read the mood
curl "https://session-mood-api-production.up.railway.app/v1/sessions/user_abc/mood" \
     -H "X-Api-Key: YOUR_KEY"
Getting Started

Authentication

All endpoints except GET /health require an API key. Pass it using either of the two supported methods below. Both are equally valid; the header approach is preferred for server-side calls.

Method 1 — X-Api-Key header

curl
curl "https://session-mood-api-production.up.railway.app/v1/sessions/abc/mood" \
     -H "X-Api-Key: sm_live_xxxxxxxxxxxx"
javascript
fetch("https://session-mood-api-production.up.railway.app/v1/sessions/abc/mood", {
  headers: { "X-Api-Key": "sm_live_xxxxxxxxxxxx" }
})

Method 2 — Bearer token

curl
curl "https://session-mood-api-production.up.railway.app/v1/sessions/abc/mood" \
     -H "Authorization: Bearer sm_live_xxxxxxxxxxxx"
javascript
fetch("https://session-mood-api-production.up.railway.app/v1/sessions/abc/mood", {
  headers: {
    "Authorization": "Bearer sm_live_xxxxxxxxxxxx"
  }
})
⚠️
Never expose your API key in client-side JavaScript. Use a backend proxy or edge function to forward requests from the browser. The GET /health endpoint requires no authentication and is safe to call from any context.
Getting Started

Base URL

All API requests are made over HTTPS to the following base URL:

url
https://session-mood-api-production.up.railway.app

Every endpoint is prefixed with /v1, for example:

EndpointFull URL
POST /v1/sessions/:id/eventshttps://session-mood-api-production.up.railway.app/v1/sessions/:id/events
GET /v1/sessions/:id/moodhttps://session-mood-api-production.up.railway.app/v1/sessions/:id/mood
POST /v1/sessions/:id/feedbackhttps://session-mood-api-production.up.railway.app/v1/sessions/:id/feedback
GET /v1/analytics/moodshttps://session-mood-api-production.up.railway.app/v1/analytics/moods
DELETE /v1/sessions/:idhttps://session-mood-api-production.up.railway.app/v1/sessions/:id
GET /healthhttps://session-mood-api-production.up.railway.app/health
ℹ️
The /health endpoint lives at the root — it is the only endpoint not prefixed with /v1.
Getting Started

Rate Limits

Rate limits are tracked per API key on a rolling monthly basis. If you exceed your plan's session allowance, the API returns 429 Too Many Requests.

PlanSessions / monthNotes
Free 5,000 Ideal for prototyping and small side projects
Growth 50,000 For production apps with moderate traffic
Pro 200,000 High-volume products; custom limits available on request

There is no documented per-minute hard limit at this time. When a rate limit is exceeded the response body will contain the standard error shape:

json — 429 response
{
  "error": "Rate limit exceeded",
  "plan": "free",
  "limit": 5000,
  "reset_at": "2026-06-01T00:00:00.000Z"
}
Getting Started

session_id

The :id path parameter identifies a unique user session. The API places no format constraints on it — you can use your own session token, a UUID, or any opaque identifier you already generate.

Rules

  • Must be consistent across all event calls for the same session. The mood is accumulated per session_id.
  • URL-encode any special characters if your ID contains them (e.g. spaces, slashes).
  • Never send PII as the session ID — no email addresses, names, or phone numbers. Use an opaque internal token.
  • Maximum length: 256 characters.
javascript — generating a session ID
// Option A: your own session token (already unique per user)
const sessionId = authToken.userId; // e.g. "u_8f3k2a"

// Option B: UUID generated client-side (persist in sessionStorage)
function getSessionId() {
  const key = "sm_session_id";
  let id = sessionStorage.getItem(key);
  if (!id) {
    id = crypto.randomUUID();
    sessionStorage.setItem(key, id);
  }
  return id;
}
Endpoints

POST /v1/keys/generate

Generate a new API key. No authentication required — this is the endpoint your signup form calls. The key is returned once and cannot be retrieved again, so store it immediately.

Request body

FieldTypeRequiredDescription
customer_namestringYesName or company of the key owner
emailstringNoContact email for the customer
planstringNostarter (default), growth, or pro

Example request

curl -X POST "https://session-mood-api-production.up.railway.app/v1/keys/generate" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_name": "Acme Inc",
    "email": "dev@acme.com",
    "plan": "starter"
  }'

Response 201 Created

{
  "api_key":  "sm_4c9174cbe8250bb1d09ae779c168a4399e765819da244cbf",
  "customer": "Acme Inc",
  "email":    "dev@acme.com",
  "plan":     "starter",
  "limits":   "5,000 sessions/month",
  "message":  "Store this key safely - it won't be shown again."
}

Error responses

// 400 — missing customer_name
{ "error": "customer_name is required" }

// 400 — invalid plan
{
  "error": "Invalid plan",
  "valid_plans": ["starter", "growth", "pro"]
}
Endpoints

POST /v1/sessions/:id/events

Submit one or more behavioral events for a session. Events are batched — send as many as you like in a single request. The engine updates the mood score immediately and returns the current mood in the response body, so you don't need to make a separate GET /mood call after every batch.

Request body

  • events array required Array of event objects. Must contain at least one element.
  • events[].type string required One of the 12 valid event type strings (see table below).
  • events[].ts number optional Unix timestamp in seconds when the event occurred. If omitted, server receipt time is used.
  • events[].x number optional Viewport X coordinate in pixels (for click/rage_click).
  • events[].y number optional Viewport Y coordinate in pixels (for click/rage_click).
  • events[].duration_ms number optional Duration in milliseconds (for idle/hover/input_pause).
  • events[].speed number optional Scroll speed in pixels/second (for scroll).
  • events[].direction string optional "up" or "down" (for scroll).
  • events[].url string optional Page URL (for page_view).
  • events[].message string optional Error message (for error). Max 512 chars.

Valid event types

TypeOptional fieldsDescription
clickx, y, tsStandard single click on any element
rage_clickx, y, tsMultiple rapid clicks in the same area — strong frustration signal
scrollspeed, direction, tsScroll event; fast speed may indicate searching or skimming
input_pauseduration_ms, tsUser stopped mid-input for more than a threshold duration
backtracktsUser scrolled up or reversed direction unexpectedly
back_navtsBrowser back button pressed
page_viewurl, tsUser navigated to a new page or route
errormessage, tsA JS error or API error was surfaced to the user
idleduration_ms, tsUser became inactive for a period — possible disengagement
hoverduration_ms, tsUser hovered over an element for an extended time — possible confusion
focustsUser focused a form input or text field
blurtsUser unfocused a form input — possibly abandoned it

Example request

curl
curl -X POST \
  "https://session-mood-api-production.up.railway.app/v1/sessions/user_abc/events" \
  -H "X-Api-Key: sm_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "type": "rage_click",
        "x": 240, "y": 580,
        "ts": 1746352800
      },
      {
        "type": "error",
        "message": "Payment failed",
        "ts": 1746352802
      },
      {
        "type": "idle",
        "duration_ms": 4500,
        "ts": 1746352807
      }
    ]
  }'
javascript
const res = await fetch(
  `https://session-mood-api-production.up.railway.app/v1/sessions/${sessionId}/events`,
  {
    method: "POST",
    headers: {
      "X-Api-Key": "sm_live_xxxxxxxxxxxx",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      events: [
        { type: "rage_click", x: 240, y: 580, ts: Date.now() / 1000 },
        { type: "error", message: "Payment failed" },
        { type: "idle", duration_ms: 4500 },
      ],
    }),
  }
);
const data = await res.json();

Responses

200 Success

json
{
  "session_id":    "user_abc",
  "events_stored": 3,
  "total_events":  6,
  "current_mood":  "frustrated"
}
  • events_stored — events accepted this call
  • total_events — cumulative for this session

400 Invalid event type

json
{
  "error": "Invalid event type(s)",
  "invalid": ["page_visit"],
  "valid_types": [
    "click", "rage_click",
    "scroll", "input_pause",
    "backtrack", "back_nav",
    "page_view", "error",
    "idle", "hover",
    "focus", "blur"
  ]
}
Endpoints

GET /v1/sessions/:id/mood

Retrieve the current inferred mood state for a session. The response includes the mood label, a confidence score, human-readable signal descriptions, and a recommended UI action to trigger based on the mood.

Path parameters

  • :id string required The session identifier. Must match the ID used when submitting events.

Example request

curl
curl \
  "https://session-mood-api-production.up.railway.app/v1/sessions/user_abc/mood" \
  -H "X-Api-Key: sm_live_xxxxxxxxxxxx"
javascript
const res = await fetch(
  `https://session-mood-api-production.up.railway.app/v1/sessions/${sessionId}/mood`,
  { headers: { "X-Api-Key": "sm_live_xxxxxxxxxxxx" } }
);
const mood = await res.json();

if (mood.suggested_action === "show_live_chat") {
  openLiveChat();
}

Response fields

  • session_idstringEcho of the requested session ID.
  • moodstringCurrent mood label. One of: frustrated, confused, decisive, browsing, disengaged, focused, neutral.
  • confidencenumberConfidence in the mood classification, from 0 (no data) to 1 (very high).
  • signalsstring[]Array of human-readable signal identifiers that drove the current mood. E.g. "rage_click_detected", "repeated_errors".
  • suggested_actionstringRecommended UI action to trigger. Use this to decide whether to show live chat, a tooltip, a discount, or take no action.
  • event_countnumberTotal number of events stored for this session.
  • updated_atstringISO 8601 timestamp of the last event processed for this session.

Responses

200 Success

json
{
  "session_id":       "user_abc",
  "mood":             "frustrated",
  "confidence":       0.89,
  "signals": [
    "rage_click_detected",
    "repeated_errors"
  ],
  "suggested_action": "show_live_chat",
  "event_count":      6,
  "updated_at":       "2026-05-04T10:00:00.000Z"
}

404 Session not found

json
{
  "error": "Session not found",
  "session_id": "user_abc"
}

Returned when :id has no stored events. Submit at least one event before calling this endpoint.

Endpoints

POST /v1/sessions/:id/feedback

Record the outcome of a mood-triggered action. Sending feedback helps the engine learn over time which interventions work for which signals. This endpoint is optional but strongly recommended for production integrations.

Request body

  • action_taken string required The action you triggered (e.g. "show_live_chat", "show_discount", "no_action"). Max 128 chars.
  • was_helpful boolean optional Whether the action improved the user experience. Omit if unknown.
  • notes string optional Free-text notes for your own records. Max 512 chars.

Example request

curl
curl -X POST \
  "https://session-mood-api-production.up.railway.app/v1/sessions/user_abc/feedback" \
  -H "X-Api-Key: sm_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "action_taken": "show_live_chat",
    "was_helpful": true,
    "notes": "User started chat immediately"
  }'
javascript
await fetch(
  `https://session-mood-api-production.up.railway.app/v1/sessions/${sessionId}/feedback`,
  {
    method: "POST",
    headers: {
      "X-Api-Key": "sm_live_xxxxxxxxxxxx",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      action_taken: "show_live_chat",
      was_helpful: true,
    }),
  }
);

Responses

200 Success

json
{
  "session_id": "user_abc",
  "feedback_recorded": true
}

400 Missing required field

json
{
  "error": "Missing required field",
  "field": "action_taken"
}
Endpoints

GET /v1/analytics/moods

Retrieve aggregate mood statistics across all sessions associated with your API key. Use this to understand the emotional landscape of your user base over time — what percentage of sessions are frustrated, how many have resulted in feedback, and so on.

Example request

curl
curl \
  "https://session-mood-api-production.up.railway.app/v1/analytics/moods" \
  -H "X-Api-Key: sm_live_xxxxxxxxxxxx"

Response

json — 200 success
{
  "total_sessions": 1842,
  "feedback_count": 310,
  "mood_distribution": [
    { "mood": "browsing",    "count": 640,  "percentage": 34.7 },
    { "mood": "focused",     "count": 510,  "percentage": 27.7 },
    { "mood": "neutral",     "count": 280,  "percentage": 15.2 },
    { "mood": "frustrated",  "count": 198,  "percentage": 10.7 },
    { "mood": "confused",    "count": 144,  "percentage": 7.8  },
    { "mood": "disengaged", "count": 70,   "percentage": 3.8  },
    { "mood": "decisive",   "count": 0,    "percentage": 0.0  }
  ]
}

Response fields

  • total_sessionsnumberTotal sessions ever recorded for this API key.
  • feedback_countnumberNumber of sessions that have at least one feedback record.
  • mood_distributionarrayOne object per mood state, ordered by count descending.
  • mood_distribution[].moodstringThe mood label.
  • mood_distribution[].countnumberNumber of sessions currently at this mood (based on the latest event for each session).
  • mood_distribution[].percentagenumberPercentage of total sessions (rounded to one decimal).
Endpoints

DELETE /v1/sessions/:id

Permanently erase all stored data for a session — events, mood state, and feedback. Intended for GDPR right-to-erasure compliance. Once deleted, the session cannot be recovered, and a subsequent GET /mood for the same ID will return 404.

⚠️
This operation is irreversible. Ensure you have user consent or a valid legal basis before calling it.

Example request

curl
curl -X DELETE \
  "https://session-mood-api-production.up.railway.app/v1/sessions/user_abc" \
  -H "X-Api-Key: sm_live_xxxxxxxxxxxx"

Response

json — 200 success
{
  "session_id": "user_abc",
  "deleted":    true,
  "message":    "Session data permanently erased."
}
Endpoints

GET /health

A simple health check endpoint. No authentication required. Use this in uptime monitors, load balancer health checks, or CI/CD pipelines to verify the API is reachable.

Example request

curl
curl "https://session-mood-api-production.up.railway.app/health"

Response

json — 200 success
{
  "status":  "ok",
  "version": "1.0.0"
}
Reference

Event Types

The full catalogue of recognized event types with their optional fields and the moods they contribute to. All fields are optional except type. Providing more fields increases classification accuracy.

Type Description Optional fields Signals toward
click Standard single pointer click x, y, ts decisive browsing
rage_click Rapid repeated clicks in tight area — indicates UI unresponsiveness perceived by user x, y, ts frustrated
scroll Scroll gesture with optional speed & direction metadata speed, direction, ts browsing confused
input_pause User stopped typing mid-field for longer than a threshold duration_ms, ts confused frustrated
backtrack Unexpected scroll reversal — user went back up on the page ts confused
back_nav Browser back button pressed ts frustrated confused
page_view Navigation to a new page or SPA route change url, ts browsing
error A JS exception or API error surfaced to the user message, ts frustrated
idle User was inactive for a prolonged period duration_ms, ts disengaged
hover Extended hover over an element without clicking duration_ms, ts confused browsing
focus User focused a form field — indicates intent ts focused decisive
blur User unfocused a form field — possible abandonment ts confused disengaged
Reference

Mood States

The engine classifies each session into one of seven states. neutral is returned when there is insufficient data for a confident classification. All others are ranked by confidence and come with a suggested_action.

Mood Meaning Key signals suggested_action
frustrated User is actively struggling — errors, repeated failed actions, rage clicks rage_click, error, back_nav show_live_chat
confused User seems lost — backtracking, long hovers, input pauses, scroll reversals backtrack, hover, input_pause, scroll show_tooltip
decisive User knows what they want — quick clicks, form focus, forward navigation click, focus no_action
browsing User is exploring at a relaxed pace — multiple page views, moderate scroll page_view, scroll, click show_recommendations
disengaged User has lost interest — prolonged idle, blurs without re-focus idle, blur show_exit_offer
focused User is actively completing a task — sustained form interaction, rapid inputs focus, click no_action
neutral Insufficient events to classify confidently — fewer than ~3–5 events in the session Any / none no_action
Reference

Error Codes

All errors follow a consistent JSON shape with at minimum an error string. Additional fields such as field, invalid, or valid_types may be present depending on the error type.

StatusMeaningCommon causes
200 OK Request succeeded
400 Bad Request Invalid request body Unknown event type, missing events array, missing action_taken
401 Unauthorized Authentication failed Missing API key, revoked key, malformed Authorization header
404 Not Found Resource does not exist Session ID has no stored events, or was deleted
429 Too Many Requests Rate limit exceeded Monthly session quota exhausted for your plan
500 Internal Server Error Unexpected server-side failure Transient infrastructure issue — retry with exponential backoff

Example error bodies

401

json
{
  "error": "Unauthorized",
  "message": "Missing or invalid API key"
}

500

json
{
  "error": "Internal Server Error",
  "request_id": "req_7f3a2b"
}

404

json
{
  "error": "Session not found",
  "session_id": "user_abc"
}

429

json
{
  "error":    "Rate limit exceeded",
  "plan":     "free",
  "limit":    5000,
  "reset_at": "2026-06-01T00:00:00.000Z"
}
Reference

Response Schemas

TypeScript interfaces for all response types. Use these as a reference when integrating in typed projects, or to generate client types with tools like zod or io-ts.

POST /v1/sessions/:id/events

typescript
interface PostEventsResponse {
  session_id:    string;   // the session identifier
  events_stored: number;   // events accepted in this request
  total_events:  number;   // cumulative event count for session
  current_mood:  MoodLabel; // updated mood after this batch
}

type MoodLabel =
  | "frustrated" | "confused" | "decisive"
  | "browsing"   | "disengaged" | "focused"
  | "neutral";

GET /v1/sessions/:id/mood

typescript
interface GetMoodResponse {
  session_id:       string;
  mood:             MoodLabel;
  confidence:       number;          // 0–1
  signals:          string[];        // e.g. ["rage_click_detected"]
  suggested_action: SuggestedAction;
  event_count:      number;
  updated_at:       string;          // ISO 8601
}

type SuggestedAction =
  | "no_action"          // do nothing
  | "show_live_chat"     // open chat widget
  | "show_tooltip"       // display contextual help
  | "show_recommendations" // surface personalized content
  | "show_exit_offer";   // discount / retention popup

POST /v1/sessions/:id/feedback

typescript
interface PostFeedbackRequest {
  action_taken: string;            // required
  was_helpful?: boolean;           // optional
  notes?:       string;            // optional, max 512 chars
}

interface PostFeedbackResponse {
  session_id:        string;
  feedback_recorded: boolean;
}

GET /v1/analytics/moods

typescript
interface MoodDistributionItem {
  mood:       MoodLabel;
  count:      number;
  percentage: number;   // 0–100, one decimal
}

interface GetAnalyticsResponse {
  total_sessions:    number;
  feedback_count:    number;
  mood_distribution: MoodDistributionItem[];
}

DELETE /v1/sessions/:id

typescript
interface DeleteSessionResponse {
  session_id: string;
  deleted:    boolean;
  message:    string;
}

GET /health

typescript
interface HealthResponse {
  status:  "ok" | "degraded" | "down";
  version: string;  // semver, e.g. "1.0.0"
}

Error (all endpoints)

typescript
interface ApiError {
  error:        string;             // human-readable error description
  message?:     string;             // additional detail (optional)
  field?:       string;             // which field caused the 400
  invalid?:     string[];           // invalid event types (400)
  valid_types?: string[];           // full list of valid types (400)
  plan?:        string;             // current plan (429)
  limit?:       number;             // plan session limit (429)
  reset_at?:    string;             // ISO 8601 reset date (429)
  session_id?:  string;             // affected session (404)
  request_id?:  string;             // opaque ID for support (500)
}