/v1/analyze
Analyze a message and return its behavioral annotation plus a routing recommendation. Pass a conversation id to correlate it with earlier messages.
Request body
message(string, required) — the text to analyze.textis accepted as a compatibility alias.conversation_id(string, optional) — your reference id. Supplying it correlates this message with earlier messages sharing the same id.speaker(string, optional) —customer(default) oragent. Send agent replies too if you want false-resolution detection.metadata(object, optional) — passthrough metadata.
{
"message": "Cancel my account immediately. Your bot is completely useless.",
"conversation_id": "conv_123"
}Response
{
"status": "ok",
"session_id": "…",
"framework": "E-Model v2.0 (EVOKE → EXPRESS → ELEVATE)",
"latency_ms": 1,
"annotation": {
"emotional_valence": "negative",
"cognitive_load": "low",
"urgency_level": "high",
"engagement_state": "disengaged",
"social_register": "informal",
"cultural_context": ["global"]
},
"signal_confidence": 0.68,
"signals_fired": 4,
"routing": {
"response_posture": "GENTLE_REFRAME",
"recommended_tone": "soft_warm",
"suggested_action": "reframe_positively"
},
"_meta": { "tenant_remaining": 4999, "overage": false, "gateway_latency_ms": 3 }
}Conversation context
Send each message as it happens, with the same conversation_id. Nothing else changes: the request and response keep the shape above. Once there is history to compare against, the response gains a conversation block with findings a single message cannot carry.
Send the agent's replies too, with "speaker": "agent". Detecting a false resolution requires both what was claimed and how the customer reacted.
{
"conversation_id": "conv_456",
"speaker": "customer",
"message": "It is not resolved, I still have nothing."
}The added block:
"conversation": {
"conversation_id": "conv_456",
"storage_mode": "full",
"prior_turns": 2,
"failure_flags": {
"false_resolution": true,
"false_resolution_reason": "The agent stated \"is resolved\" without supporting evidence, but the customer's following message still signals an unresolved problem.",
"repeat_contact_risk": false
},
"derived": {
"repetition": false,
"repetition_similarity": 0.12,
"escalating": true,
"peak_severity": 4
},
"recommendation": {
"action": "handoff",
"reason": "The case was presented as resolved while the customer remains unresolved.",
"safe_response_guidance": "Acknowledge the problem, confirm the case is being passed to a person, and do not claim it is resolved."
},
"review_status": "needs_review"
}false_resolution— the agent presented the issue as settled while the customer still signals it is not.repetition— the customer re-asked, so the previous answer did not land.escalating— the conversation is deteriorating rather than merely negative.action— one ofclarify,simplify,resolve,routeorhandoff.
The full message is retained so a conversation can be read back as it happened. If redaction is enabled for your environment or plan, the redacted form is stored instead and the response reports storage_mode as redacted. Either way, what was stored is exactly what was analyzed. If context cannot be built the block is marked degraded and recommends handoff. It will never report a problem as resolved.
cURL
curl -X POST https://api.bactlabs.africa/v1/analyze \
-H "Authorization: Bearer sk_bact_your_key" \
-H "Content-Type: application/json" \
-d '{"message": "Cancel my account. Your bot is useless."}'TypeScript
const res = await fetch("https://api.bactlabs.africa/v1/analyze", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BACT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ message }),
});
const { annotation, routing } = await res.json();Live
Try it with your key
Paste an sk_bact_ key and send a message to see a live behavioral annotation.