Documentation

Hermion API & MCP

Interaction intelligence for any relationship — relationship, sales, hiring, support, fundraising. Built on a proprietary physics theorem. Privacy by architecture.

Overview

How it works

Hermion works in two steps. First, raw messages are classified locally into encoded signals — compact mathematical representations of interaction dynamics. No message content is transmitted. Then those signals are analyzed by Hermion's engine, which models the structural pattern of the relationship over time.

Messages / Transcripts / Group exports → hermion-classifier-os → Encoded Signals → Hermion API → Intelligence

The open source classifier runs on your device or server. Inspect every line at github.com/hermionai/hermion-classifier-os.

5 minutes

Quick start

Get a passkey, classify messages, analyze signals.

1. Get a passkey

curl https://classifier.hermionai.xyz/access

2. Classify messages

curl -X POST https://classifier.hermionai.xyz/classify \
  -H "Content-Type: application/json" \
  -H "X-Hermion-Key: <your-passkey>" \
  -d '{
    "messages": [
      { "actor": "Alex", "ts": "2025-01-01T10:00:00Z", "text": "I really miss you" },
      { "actor": "Sam",  "ts": "2025-01-01T10:05:00Z", "text": "Miss you too" },
      { "actor": "Alex", "ts": "2025-01-01T10:06:00Z", "text": "Back next week" }
    ]
  }'

3. Analyze signals

curl -X POST https://api.hermionai.xyz/api/v1/analyze \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer herm_sk_live_..." \
  -d '{
    "signals": [ /* signals from step 2 */ ],
    "context_type": "relationship",
    "self_name": "Alex"
  }'

Get your API key at hermionai.xyz/get-started

Multi-speaker transcripts

Meeting Intelligence

Meeting transcripts work identically to messaging — the classifier and API are unchanged. The only difference is how you prepare the input: parse the transcript into { actor, ts, text } messages, then call the classifier exactly as normal.

Supported transcript formats

Parse any of these into the standard message array before calling the classifier. All four normalize to identical output.

Format 1 — Zoom / Otter.ai / Fireflies labeled turns

Alex Chen  00:01:23
We've been seeing really strong retention numbers.
 
Chen Wei  00:01:30
That's encouraging. What does churn look like?

Format 2 — WebVTT (.vtt from Zoom / Google Meet)

00:01:23.000 --> 00:01:28.000
<v Alex Chen>We've been seeing really strong retention numbers.
 
00:01:28.500 --> 00:01:34.000
<v Chen Wei>That's encouraging. What does churn look like?

Format 3 — Plain colon-separated (manual / no timestamps)

Alex Chen: We've been seeing really strong retention numbers.
Chen Wei: That's encouraging. What does churn look like?

Format 4 — Notion AI / markdown bold speaker

**Alex Chen:** We've been seeing really strong retention numbers.
 
**Chen Wei:** That's encouraging. What does churn look like?

Parse to standard message array

After parsing, assign synthetic timestamps if the format has none (30 seconds apart is the default). Then call the classifier exactly as you would for messaging — nothing else changes.

// After parsing any format above, your messages look like this:
const messages = [
  {
    actor: "Alex Chen",
    ts: "2025-01-01T00:01:23Z",  // from transcript, or synthetic
    text: "We've been seeing really strong retention numbers."
  },
  {
    actor: "Chen Wei",
    ts: "2025-01-01T00:01:30Z",
    text: "That's encouraging. What does churn look like?"
  }
]
 
// Then call the classifier exactly as normal — nothing changes:
const response = await fetch("https://classifier.hermionai.xyz/classify", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Hermion-Key": "<your-passkey>"
  },
  body: JSON.stringify({ messages })
})

Automating post-meeting intelligence

As your meeting ends, your transcription tool exports the file. Your server parses it, calls the classifier, then calls /api/v1/analyze with the signals. Intelligence is delivered to your team before they finish writing their notes. No manual upload, no interface required.

formattimestampsource
Labeled turns (Name HH:MM:SS)From transcriptZoom, Otter.ai, Fireflies
WebVTT (.vtt)From cue timestampsZoom, Google Meet
Plain colon (Name: text)Synthetic (30s apart)Manual notes, plain paste
Markdown bold (**Name:** text)Synthetic (30s apart)Notion AI, meeting notes
Multi-actor conversations

Group Intelligence

Group Intelligence works by preparing the message array before the classifier runs. The classifier and API are unchanged — they always operate on exactly two actors. Your job is to decide which actors matter, which to ignore, and how to label the two sides.

Why preparation happens on your side

Hermion never sees your messages. The classifier converts them to encoded signals — no text is transmitted or stored. This means actor filtering and proximity analysis must happen before classification. You control which messages reach the classifier at all. You can adopt the algorithm below exactly, or tune it — you have full access to your messages, we do not.

Step 1 — Define your groups

const actorGroups = {
  "Alex":  "Startup",         // Group 1
  "Maya":  "Startup",         // Group 1
  "Chen":  "Investor Group",  // Group 2
  "James": "Investor Group",  // Group 2
  // Any actor not listed is ignored — stripped completely before classification
}

Step 2 — Apply the window algorithm

In a real group conversation, assigned actors may not always be speaking to each other. Stripping ignored actors and keeping all assigned actor messages risks pairing messages that were never in the same exchange. The window algorithm solves this: only keep a message from an assigned actor if the other group contributed at least once within ±N positions in the original full array.

Deriving N: compute how often each group contributes on average, take the maximum (the less frequent group sets the window), cap at 20. If 20 messages pass without the other group contributing, these actors are not in the same exchange.

function prepareGroupMessages(allMessages, actorGroups) {
  const total = allMessages.length
 
  // Bypass: if no actors are ignored, all messages are between the two groups
  const ignoredExists = allMessages.some(m => !(m.actor in actorGroups))
  if (!ignoredExists) {
    return allMessages
      .filter(m => m.actor in actorGroups)
      .map(m => ({ ...m, actor: actorGroups[m.actor] }))
  }
 
  // Compute group-level spacing (combined message count per group)
  const groupNames = [...new Set(Object.values(actorGroups))]
  const groupCounts = Object.fromEntries(groupNames.map(g => [g, 0]))
  for (const m of allMessages) {
    if (m.actor in actorGroups) groupCounts[actorGroups[m.actor]]++
  }
 
  // N = min(max(spacingA, spacingB), 20)
  const spacings = groupNames.map(g =>
    groupCounts[g] > 0 ? total / groupCounts[g] : total
  )
  const N = Math.round(Math.min(Math.max(...spacings), 20))
 
  // Keep only messages where the other group appears within ±N positions
  // Original array (incl. ignored actors) is the ruler — ignored messages
  // count toward position distance but are never kept
  const kept = []
  for (let i = 0; i < allMessages.length; i++) {
    const msg = allMessages[i]
    if (!msg || !(msg.actor in actorGroups)) continue
 
    const myGroup = actorGroups[msg.actor]
    const start = Math.max(0, i - N)
    const end = Math.min(allMessages.length - 1, i + N)
 
    let found = false
    for (let j = start; j <= end; j++) {
      if (j === i) continue
      const neighbor = allMessages[j]
      if (!neighbor) continue
      if (neighbor.actor in actorGroups && actorGroups[neighbor.actor] !== myGroup) {
        found = true; break
      }
    }
 
    if (found) kept.push({ ...msg, actor: actorGroups[msg.actor] })
  }
 
  return kept
}
 
const preparedMessages = prepareGroupMessages(rawMessages, actorGroups)
// Only exchanges in genuine proximity — ignored actors stripped completely

Step 3 — Classify and analyze

// Classify — same call as always
const { signals } = await (await fetch("https://classifier.hermionai.xyz/classify", {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-Hermion-Key": passkey },
  body: JSON.stringify({ messages: preparedMessages })
})).json()
 
// Analyze — self_name is your group name
const intelligence = await (await fetch("https://api.hermionai.xyz/api/v1/analyze", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer herm_sk_live_..."
  },
  body: JSON.stringify({
    signals,
    context_type: "professional",
    self_name: "Startup"  // whichever group name represents you
  })
})).json()

Preserving individual speaker attribution

The backend receives group names only. Build display labels client-side — never send them to the API or the backend will use them as actor identifiers.

// Build display labels from original mapping — client-side only
const displayMap = {}
Object.entries(actorGroups).forEach(([speaker, group]) => {
  displayMap[speaker] = `${speaker} (${group})`
})
// { "Alex": "Alex (Startup)", ... }
// Use for your own signal trace display only — never sent to the API
patternactor mappingself_name
Two-side meetingYour team → "Us", their team → "Them""Us"
Investor callFounders → "Startup", GPs → "Investors""Startup"
Team pair evaluationAlice → "Alice", Bob → "Bob", everyone else ignored"Alice"
GP splitChen → "Startup", James → "Investors", others ignored"Startup"
Team composition analysisRun multiple pairings — vary who is assigned vs ignored to find structural peaksvaries

On tuning the algorithm

The window algorithm is a privacy-first approximation — designed to work without reading message content, using only position and actor identity. Since you run the classifier on your own infrastructure and have full access to your messages, you can go further: use semantic similarity to identify topically related messages, use reply threading data if your platform exposes it, use time windows instead of message count windows for slow-moving groups, or run multiple actor groupings to find structural peaks automatically. Hermion's engine is indifferent to how you prepared the messages — it reads the signals.

Input

Message format

The classifier accepts four message types. Mix them freely in the same array.

Text message

{
  "actor": "Alex",
  "ts": "2025-01-01T10:00:00Z",
  "text": "I really miss you right now"
}

Gap signal (silence)

{
  "actor": "Alex",
  "ts": "2025-01-15T00:00:00Z",
  "is_gap": true,
  "gap_days": 14
}

Voice / video call

{
  "actor": "Alex",
  "ts": "2025-01-01T10:00:00Z",
  "call_type": "voice",
  "call_duration_min": 22,
  "missed_call": false
}

Missed call

{
  "actor": "Alex",
  "ts": "2025-01-01T10:00:00Z",
  "call_type": "voice",
  "call_duration_min": 0,
  "missed_call": true
}
fieldtypedescription
actorstring — requiredMessage sender identifier
tsISO8601 — requiredMessage timestamp
textstringMessage content (text messages)
is_gapbooleanMarks a silence period between messages
gap_daysfloatDuration of silence in days
call_typestring"voice" or "video"
call_duration_minintegerCall duration in minutes
missed_callbooleanWhether the call was answered
Classifier response

Signal output

The classifier returns encoded Hermion Signals. All category names, event types, and numeric values are encoded — no message content is present in the output.

{
  "signals": [
    {
      "actor": "Alex",
      "ts": "2025-01-01T10:00:00Z",
      "target": "Sam",
      "cat": "cat_a1f2",
      "evt": "evt_085010",
      "a": "u*0.923*1.0",
      "b": "min(q,s*0.923+0.15*0.077)",
      "gh": "gh_c1",
      "meta": {
        "response_latency_sec": null,
        "message_length": 27,
        "read_state": "unknown",
        "time_of_day": "morning"
      },
      "confidence": 0.923,
      "is_gap_signal": false,
      "gap_days": null,
      "call_type": null,
      "call_duration_min": null,
      "missed_call": false
    }
  ],
  "count": 3
}

The encoded fields (cat, evt, gh, a, b) are decoded and evaluated by Hermion's backend before analysis. They are not intended to be human-readable.

api.hermionai.xyz

REST API

All API requests require a Bearer token in the Authorization header.

POST/api/v1/analyze

Analyze encoded signals and return full interaction intelligence.

parameterdescription
signalsArray of encoded signals from hermion-classifier-os
context_typeRelationship context — see Context types
self_nameYour actor name in the conversation
goal_labelOptional custom label for the goal
GET/api/v1/rate

Returns current rate limit status for your API key.

{
  "tier": "free",
  "rpm_limit": 10,
  "rpm_remaining": 8,
  "rpm_reset_s": 42,
  "daily_limit": 100,
  "daily_remaining": 94,
  "daily_reset_s": 51240
}
tierrpmdaily
Free10 / minute100 / day
Pro60 / minute2,000 / day
Model Context Protocol

MCP

Hermion exposes five tools via the Model Context Protocol. Any MCP-compatible agent can call them.

Claude Desktop config

{
  "mcpServers": {
    "hermion": {
      "url": "https://api.hermionai.xyz/mcp/sse",
      "headers": {
        "Authorization": "Bearer herm_sk_live_..."
      }
    }
  }
}
toolinputreturns
hermion_classifymessages[]Encoded Hermion Signals
hermion_analyzesignals[], context_type, self_nameFull intelligence report
hermion_get_statesignals[], context_type, self_nameProgression + momentum + interest level
hermion_recommend_movesignals[], context_type, self_nameNext move + reasons
hermion_detect_riskssignals[], context_type, self_nameRisk scores + short circuits

Agent flow

1. hermion_classify(messages) → signals
2. hermion_get_state(signals) → current state + progression
3. hermion_recommend_move(signals) → what to do next
4. hermion_detect_risks(signals) → what to watch
5. hermion_analyze(signals) → full diagnostic

Always call hermion_classify first. All other tools take signals as input.

Open source

Self-hosting

Run the classifier on your own infrastructure. Messages never leave your server.

git clone https://github.com/hermionai/hermion-classifier-os
cd hermion-classifier-os
npm install
cp .env.example .env
npm start

Environment variables

PORT=3002
HERMION_INTERNAL_SECRET=   # optional — bypasses rate limits for internal use

Self-hosted instances have no rate limits. Use X-Hermion-Secret header instead of passkey when HERMION_INTERNAL_SECRET is set.

Analysis

Context types

The context_type parameter tells Hermion which relationship model to apply. The physics is the same — the goal geometry changes.

context_typeuse case
relationshipLong-term romantic relationship — commitment, depth, future orientation
flingCasual / fling — romantic but no desire to commit long term
friendshipMeaningful friendship
professionalProfessional partnerships, hiring
salesSales conversations
dealDeal negotiation and closure
familyFamily relationships
API response

Response shape

The analyze endpoint returns a structured intelligence report.

{
  "progression_percentage": 37,
  "peak_percentage": 62,
  "momentum": "progressing",
  "interest_level": "high",
  "binding_constraint": "balanced",
  "next_move": "increase_investment",
  "other_next_move": "wait_and_test",
  "next_move_reason": "...",
  "next_move_reason_detail": "...",
  "goal_label": "Long-term relationship",
  "risk_scores": {
    "ghosting": 0.12,
    "rejection": 0.08,
    "stall": 0.31
  },
  "actor_posture": {
    "self_positive_ratio": 0.67,
    "other_positive_ratio": 0.71,
    "self_is_withdrawing": false,
    "other_is_withdrawing": false,
    "self_signal_share": 0.52,
    "total_gap_days_recent": 0.0,
    "other_next_move": "wait_and_test"
  },
  "system_diagnosis": {
    "primary_driver": "affection",
    "primary_resistance": "structural friction",
    "short_circuits": [],
    "r_decomposition": { ... }
  },
  "topology_mode": "phase_plus_quantum",
  "quantum": { ... },
  "phase_count": 4,
  "signal_count": 47,
  "gap_signal_count": 2
}