Relational Intelligence Engine API
This document is derived from the Phase 2 development process documents and reflects the observed Phase 2 behavior. Documentation was fully validated against code.
Base URL
https://www.relationalmanager.com/api/
Authentication
Provide your API key in one of the following headers:
Authorization: Bearer <API_KEY>(canonical)X-API-Key: <API_KEY>(compatibility)X-Request-Id: <REQUEST_ID>(optional client-supplied request id; echoed back)
Missing or invalid keys return a 401 with a JSON error body and request_id.
Endpoints
POST /v1/score
Scores a message against one or more lenses and returns lens/question results.
Integration Guides
Examples for common frameworks/languages:
integrations/nodejs.mdintegrations/python.mdintegrations/java-okhttp.mdintegrations/csharp-dotnet.mdintegrations/go.mdintegrations/ruby.mdintegrations/php.mdintegrations/swift.mdintegrations/kotlin-ktor.mdintegrations/rust-reqwest.md
Request body
{
"lenses": [1, 4],
"persist": false,
"include_prediction": true,
"message": {
"message_id": "m2",
"conversation_id": "c1",
"text": "I'm not sure this will work.",
"channel_type": "chat",
"channel_id": "support",
"sender_id": "u2",
"sender_type": "user",
"lang": "en",
"timestamp": "1716561012"
},
"previous_message": {
"message_id": "m1",
"conversation_id": "c1",
"text": "We can try a few options.",
"channel_type": "chat",
"channel_id": "support",
"sender_id": "u1",
"sender_type": "agent",
"lang": "en",
"timestamp": "1716560950"
}
}
Curl examples
Bearer auth:
curl -X POST "https://www.relationalmanager.com/api/v1/score" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"lenses": [1, 4],
"persist": false,
"include_prediction": true,
"message": {
"message_id": "m2",
"conversation_id": "c1",
"text": "I am not sure this will work.",
"channel_type": "chat",
"channel_id": "support",
"sender_id": "u2",
"sender_type": "user",
"lang": "en",
"timestamp": "1716561012"
},
"previous_message": {
"message_id": "m1",
"conversation_id": "c1",
"text": "We can try a few options.",
"channel_type": "chat",
"channel_id": "support",
"sender_id": "u1",
"sender_type": "agent",
"lang": "en",
"timestamp": "1716560950"
}
}'
X-API-Key auth:
curl -X POST "https://www.relationalmanager.com/api/v1/score" \
-H "X-API-Key: <API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"lenses": [1, 4],
"persist": false,
"include_prediction": true,
"message": {
"message_id": "m2",
"conversation_id": "c1",
"text": "I am not sure this will work.",
"channel_type": "chat",
"channel_id": "support",
"sender_id": "u2",
"sender_type": "user",
"lang": "en",
"timestamp": "1716561012"
},
"previous_message": {
"message_id": "m1",
"conversation_id": "c1",
"text": "We can try a few options.",
"channel_type": "chat",
"channel_id": "support",
"sender_id": "u1",
"sender_type": "agent",
"lang": "en",
"timestamp": "1716560950"
}
}'
Request fields and acceptable values
lenses(optional, list[int]): Lens IDs to score. Accepts integers only (no numeric strings). Duplicates are removed while preserving order. Omit or pass an empty list to use fallback lenses.persist(optional, bool, default false): Persistence is temporarily disabled to avoid data privacy issues for integration partners. Anytruevalue returns 501persist_not_supported(before auth checks).message(required, object): The current message payload.previous_message(optional, object): The preceding message. If provided,conversation_idmust matchmessage.conversation_id.include_prediction(optional, bool, default false): Whentrue, include SNMP prediction output (memory retention milestones including1_year).
message and previous_message share the same schema and constraints:
message_id(string, required): Any string; should be unique within the conversation. Numeric values are accepted but coerced to strings.conversation_id(string, required): Non-empty string. Must match across messages in the same thread (current vs. previous).text(string, required): 1 to 4000 characters.channel_type(string, required): Must be one of the supported channel types:- text/simple:
instant_message,live_chat,chatbot,text_message,whatsapp,messenger,chat - text/complex:
email,social_media,web,forum,blog - media/simple:
live_conference,live_call - media/complex:
podcast,video_recording,audio_recording
- text/simple:
channel_id(string, required): Any string identifying the channel instance (thread, room, inbox, etc.).sender_id(string, required): Any string identifying the sender.sender_type(string, required): Not strictly validated. Use consistent role labels such asagent,customer,user, orclient.lang(string, required): Non-empty string. ISO 639-1 (e.g.,en) or ISO 639-3 (e.g.,eng) recommended. If the language is not supported by internal scoring, the response includes a warning.timestamp(string, required): Unix epoch seconds (e.g.,1716561012). The API accepts any string, but cadence-related scoring expects integer epoch seconds.
Response (200)
The response includes the X-Request-Id header and the same request_id in the body. A warnings array is included when non-fatal issues are detected, such as unsupported language codes or invalid internal scoring formulas that fell back to average scoring. The prediction field is only included when include_prediction is true.
{
"request_id": "req_123",
"persisted": false,
"lenses_used": [1, 4],
"fallback_lens_used": true,
"messages": [],
"warnings": [],
"prediction": {
"forecast": {
"1_hour": { "date": "2026-02-03", "score": 0.12 },
"1_day": { "date": "2026-02-04", "score": 0.11 },
"1_week": { "date": "2026-02-10", "score": 0.09 },
"1_month": { "date": "2026-03-05", "score": 0.07 },
"6_months": { "date": "2026-08-02", "score": 0.04 },
"1_year": { "date": "2027-02-03", "score": 0.02 }
}
},
"results": [
{
"lens_id": 1,
"lens_name": "Support Empathy",
"score": 0.25,
"classification": 1,
"traffic_light": "yellow",
"traffic_light_ranges": {
"red": {"min": -1.0, "max": -0.2},
"yellow": {"min": -0.2, "max": 0.4},
"green": {"min": 0.4, "max": 1.0}
},
"traffic_light_ranges_source": "organization",
"scoring_coverage": 0.8,
"questions_total": 5,
"questions_scored": 4,
"prescriptions": [
"Acknowledge the customer's concern before proposing a solution.",
"Make the next action explicit."
],
"questions": [
{
"question_id": 101,
"question": "Does the customer experience the response as empathetic?",
"goal": "Determine whether the response acknowledges the customer's concern.",
"indicator_name": "Customer Acknowledgement",
"score": 0.72,
"classification": 1,
"scoring_status": "scored",
"traffic_light": "green",
"prescriptions": [
"Example prescription text."
]
}
]
}
]
}
Lens score is the weighted average of available question contributions. Existing metric or frame-component weights are normalized and applied once. Questions without an available score do not reduce the aggregate score; they reduce scoring_coverage.
When a question is backed by frame components, any configured FrameComponent.formula is evaluated against that message's linked metric and usable frame-component scores. If no formula is configured, scoring preserves the existing average of linked scores. If a formula is invalid at runtime, the API falls back to the same average behavior and includes a warning such as:
{
"warnings": [
"Invalid FrameComponent formula for resolution_formula; references unknown components: missing_metric. Falling back to average score."
]
}
scoring_coverage is questions_scored / questions_total. Question status is one of:
scored: at least one configured contributor returned a valid score.unconfigured: the question has no direct metrics or frame components.unavailable: contributors are configured but none returned a valid score.
For backward compatibility, unscored questions retain score: 0.0 and classification: 0. Consumers must inspect scoring_status. If no questions are scored, the lens retains score: 0.0, has scoring_coverage: 0.0, and returns traffic_light: null.
Traffic-light bands use inclusive min and exclusive max boundaries, except that the final band includes 1.0. Ranges must explicitly and contiguously cover the complete -1.0 to 1.0 score domain. Defaults are red from -1.0 to below -0.2, yellow from -0.2 to below 0.4, and green from 0.4 through 1.0. Organizations can configure inverse ordering for lower-is-better lenses.
Each question includes a lens-specific indicator_name, authored goal, and traffic_light for UI dials and indicator popovers. The question remains the interpretive form of the same weighted score; there is no separate indicator collection. Updating the lens ranges updates every question signal immediately. Question payloads do not expose metric names, components, weights, formulas, or contributor scores.
Question prescriptions use configured frame-component guidance when available. Otherwise, a scored question uses its authored low, medium, or high prescription according to classification. For backward compatibility, a neutral or adverse question without tiered content uses its authored goal. Unconfigured and unavailable questions never receive prescriptions.
Lens-level prescriptions are deduplicated and prioritized by effective question weight. Green lenses return none, yellow lenses return at most two, and red lenses return at most three. These live prescriptions do not invoke the AI target-score workflow; target-driven AI prescriptions remain a separate dashboard process.
Lens resolution behavior
When lenses is provided, those IDs are used and fallback_lens_used is false.
When lenses is omitted or empty, resolution follows this order:
- Organization active lenses
- Organization default lenses
- Platform default lenses (adds
"Platform default lenses applied"tomessages) - If nothing is configured, the API returns a 403
no_lenses_available
Errors
All errors include request_id and are returned as JSON. Observed error codes:
- 400
invalid_request(schema/validation errors, including non-integer lens ids) - 400
invalid_channel_type(invalidchannel_typevalues) - 400
invalid_lens_id - 401
missing_api_key - 401
invalid_api_key - 403
unlicensed_lens - 403
no_active_license - 403
no_lenses_available - 501
persist_not_supported
Example:
{
"error": "missing_api_key",
"request_id": "req_abc123"
}
Invalid channel type example (includes valid options):
{
"error": "invalid_channel_type",
"request_id": "req_abc123",
"invalid_fields": ["message.channel_type"],
"available_channel_types": [
"audio_recording",
"blog",
"chat",
"chatbot",
"email",
"forum",
"instant_message",
"live_call",
"live_chat",
"live_conference",
"messenger",
"podcast",
"social_media",
"text_message",
"video_recording",
"web",
"whatsapp"
]
}
POST /score_and_store (legacy)
Legacy endpoint with the same request/response contract as /v1/score.
Status: supported but deprecated.
GET /v1/lenses
Returns the lenses associated with the organization license derived from the API key.
Response (200)
{
"request_id": "req_123",
"selected_lenses": [
{
"lens_id": 1,
"lens_name": "Support Empathy",
"traffic_light_ranges": {
"red": {"min": -1.0, "max": -0.2},
"yellow": {"min": -0.2, "max": 0.4},
"green": {"min": 0.4, "max": 1.0}
},
"traffic_light_ranges_source": "default"
}
],
"default_lenses": [
{
"lens_id": 2,
"lens_name": "Baseline Service",
"traffic_light_ranges": {
"red": {"min": -1.0, "max": -0.2},
"yellow": {"min": -0.2, "max": 0.4},
"green": {"min": 0.4, "max": 1.0}
},
"traffic_light_ranges_source": "organization"
}
]
}
Errors
- 401
missing_api_key - 401
invalid_api_key - 403
no_active_license
GET /v1/lenses/{lens_id}/traffic-light-ranges
Returns the effective traffic-light ranges for an available lens. Any valid API key for the organization may read the configuration.
{
"request_id": "req_123",
"lens_id": 1,
"lens_name": "Support Empathy",
"traffic_light_ranges": {
"red": {"min": -1.0, "max": -0.2},
"yellow": {"min": -0.2, "max": 0.4},
"green": {"min": 0.4, "max": 1.0}
},
"traffic_light_ranges_source": "default"
}
PUT /v1/lenses/{lens_id}/traffic-light-ranges
Creates or replaces the calling organization's ranges for a licensed lens. The API key must contain the org:write scope.
curl -X PUT "$BASE_URL/v1/lenses/1/traffic-light-ranges" \
-H "Authorization: Bearer $RI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"traffic_light_ranges": {
"red": {"min": -1.0, "max": -0.2},
"yellow": {"min": -0.2, "max": 0.4},
"green": {"min": 0.4, "max": 1.0}
}
}'
Ranges must contain exactly red, yellow, and green, explicitly cover -1.0 through 1.0, and be contiguous and non-overlapping.
Additional errors:
- 400
invalid_traffic_light_ranges - 403
insufficient_scope - 403
unlicensed_lens - 404
invalid_lens_id
GET /healthz
Returns a simple health response.
Example response:
{
"ok": true
}
GET /readyz
Checks database connectivity.
Example response:
{
"db": "ok"
}
If the database is unavailable, the endpoint returns 503 with a db not ready detail message.