BetaThe V4 API is in beta — endpoints and functionality may change.
Analyticsv2

Get risk-tolerance report

GET/analytics/risk-tolerance

Returns the per-number risk-tolerance report for every dedicated and inbound number in the organization over a bounded time window. Numbers are returned sorted by descending risk score (riskiest first). Each entry carries a risk_score (0-100), a risk_level (low/medium/high), a line_type indicating which scoring profile was applied, the raw metrics used to derive it, the ten scored risk_factors (value + points + status per factor), and a daily_data time series of new inbound/outbound conversations for charting. Scope — Org-scoped: any API key in the organization returns the full scorable fleet, regardless of which numbers the key is attached to. Eligibility — Requires at least one dedicated number (Dedicated Commercial or Dedicated Enterprise) or inbound number (Inbound Basic). Organizations with only shared or trial numbers receive 403 dedicated_plan_required. Inbound numbers are scored with the reply-only factor profile described on the Analytics tag. Computation — Real-time; recomputed from Postgres on every request (no cache). Definitions used across the metrics: a conversation is keyed by contact (or group); a new conversation is one whose all-time first message falls inside the window; a reply is a conversation that received 3 or more inbound messages; bursts are the peak count in any single UTC hour of the window. The expensive message-similarity (SimHash) metric is included automatically for fleets of 5 numbers or fewer, and otherwise only when compute=true (larger fleets return message_similarity_percentage: null unless requested).

Headers

AuthorizationRequiredstring

Your API key, sent as a bearer token: Authorization: Bearer <api_key>. Editing this stays in sync with the API key box on the right.

Bearer

Query parameters

daysoptionalinteger

Length of the reporting window in days, ending now. Between 1 and 365; defaults to 30. Ignored when from/to are supplied.

fromoptionalinteger

Window start as a Unix epoch timestamp in milliseconds. When supplied, overrides days.

tooptionalinteger

Window end as a Unix epoch timestamp in milliseconds. Defaults to now.

computeoptionalboolean

Set to true to force the expensive message-similarity metric. Small fleets (<= 5 numbers) always compute it; larger fleets omit it unless this flag is set, so message_similarity_percentage may be null otherwise.

Returns

phone_numbersoptionalRiskPhoneReport[]

Per-number reports, sorted by descending risk score.

Array of RiskPhoneReport

phone_numberoptionalstring
allocation_idoptionalstring
line_typeoptionalstring

The line's allocation type, which determines the scoring profile applied. inbound lines are reply-only and use the reduced factor profile described on the Analytics tag; everything else is scored on the full dedicated profile.

"dedicated""inbound""2fa"null
risk_scoreoptionalnumber

Overall risk score, 0 (lowest) to 100 (highest). Clamped sum of the ten risk_factors.

risk_leveloptionalstring

Banding of risk_score: low = 0-29 (healthy), medium = 30-59 (monitor), high = 60-100 (likely to be filtered/suspended by carriers).

"low""medium""high"
has_activityoptionalboolean

Whether the number sent, received, or reached anyone in the window. Every factor guards on a minimum sample, so a number with no traffic sums to risk_score: 0 -- the same score a well-behaved line earns. Treat false as "nothing to score yet" rather than "lowest risk": don't present the score, and exclude the number from fleet averages.

metricsoptionalRiskMetrics

Raw and derived usage metrics for a number over the window. A *conversation* is keyed by contact (1:1) or group; a *new conversation* is one whose all-time first message falls inside the window; a *reply* is a conversation that received 3 or more inbound messages. Averages divide by the days the number could actually send in -- the window length capped to the number's own tenure -- not by the days that happened to have activity. So a line that was quiet for half the period still reads as low volume, while one that only existed for the last 9 days of a 30-day window reports its real pace instead of a third of it.

avg_outbound_conversations_per_dayoptionalnumber

New outbound-initiated conversations started in the window, divided by days active (see the schema description). High values indicate cold-outreach volume (risk-raising).

avg_inbound_conversations_per_dayoptionalnumber

New inbound-initiated conversations (customer messaged first) in the window, divided by days active. High values indicate organic demand (risk-lowering).

inbound_initiated_percentageoptionalnumber

Share of new conversations that the customer started, as a percentage: inbound_initiated / (inbound_initiated + outbound_initiated) * 100. Higher is healthier. Only scored once there are >= 5 new conversations.

total_contactsoptionalinteger

Distinct contacts (conversation keys) messaged in the window. Large lists suggest broadcast/spam usage.

reply_rateoptionalnumber

Percentage of conversations that received a meaningful reply, defined as 3 or more inbound messages: conversations_with_3plus_inbound / total_conversations * 100. One of the strongest spam signals; only scored once there are >= 10 conversations.

avg_inbound_messages_per_conversationoptionalnumber

Mean inbound messages per conversation in the window. Below 3 (with >= 3 conversations) indicates shallow, low-engagement outreach.

inbound_outbound_message_ratiooptionalnumber

Inbound messages as a percentage of all messages: total_inbound_messages / total_messages * 100. Low values mean one-way (monologuing) traffic. Only scored once there are >= 10 messages.

max_new_conversations_per_houroptionalinteger

Peak number of NEW conversations the number itself opened in any sliding 60-minute window (burst detection). The window slides rather than snapping to clock hours, so a blast straddling :59 is reported at its real rate instead of being split across two buckets. Conversations the customer started are excluded, on every line type. A human pace is roughly <= 15/hour; sustained higher rates signal automation.

max_messages_per_houroptionalinteger

Peak outbound messages sent in any sliding 60-minute window (burst detection). Sustained rates above ~100/hour signal automation.

message_similarity_percentageoptionalnumber | null

Templated-content signal: the largest cluster of near-identical outbound messages (by SimHash) as a percentage of qualifying messages. Only outbound messages with >= 50 characters count, and a valid ratio needs >= 20 qualifying messages. null when not computed (large fleet without compute=true) or below the minimum sample.

inbound_initiatedoptionalinteger

Count of new conversations the customer started in the window.

outbound_initiatedoptionalinteger

Count of new conversations this number started in the window.

total_conversationsoptionalinteger

Conversations with any message activity in the window.

total_messagesoptionalinteger

Inbound + outbound messages in the window (excludes deleted).

total_inbound_messagesoptionalinteger

Inbound message count in the window.

total_outbound_messagesoptionalinteger

Outbound message count in the window.

avg_messages_per_dayoptionalnumber

total_messages divided by days active.

avg_conversation_depthoptionalnumber

total_messages / total_conversations (0 when there are no conversations).

risk_factorsoptionalRiskFactors

The ten additive risk factors that sum (clamped to 0-100) into risk_score. Each factor reports its measured value, the score (points) it contributed, a status, and its impact weight. Positive scores raise risk; negative scores are bonuses. Some factors only score once a minimum sample is reached (otherwise status: insufficient_data, score: 0). The scoring described below is for dedicated lines; where an inbound (reply-only) line differs, the difference is noted on the factor.

outboundConversationsoptionalRiskFactor

Avg outbound-initiated conversations/day. Scoring: >50 = +30, 40-50 = +20, 30-40 = +8, else 0. Cold-outreach volume. **Inbound lines:** not_applicable, always 0 — a reply-only line cannot open cold threads.

valueoptionalnumber | null

The measured value for this factor (units depend on the factor).

scoreoptionalnumber

Points this factor contributed to the total risk score.

statusoptionalstring

Qualitative status for display. not_applicable means the factor doesn't apply to this line type (e.g. outbound-initiation factors on a reply-only inbound number) and contributed 0 points.

"ok""bonus""warning""high""insufficient_data""not_applicable"
impactoptionalstring

Relative weight of this factor in the overall score.

"low""medium""high""very_high"
sampleSizeoptionalinteger

Sample size backing the value, when the factor guards on a minimum sample.

peak_atoptionalstring | null

Burst factors only (newConversationBursts, messageBursts). UTC date (YYYY-MM-DD) on which the window reported in value began.

scored_valueoptionalnumber

Burst factors only. The window count the score was actually taken from. Because each daily candidate is aged before comparison, the costliest burst is not always the biggest one: a month-old 639/h can score less than a 240/h from yesterday. Equals value whenever the peak is also the scored window.

scored_atoptionalstring | null

Burst factors only. UTC date (YYYY-MM-DD) of the window described by scored_value.

decay_weightoptionalnumber

Burst factors only. Recency multiplier applied to the base band score, 0.5 ^ (age_days / 21) with a floor of 0.25. A burst is evidence that ages: without this, one two-minute event held a number at high risk for the entire window. Only the burst factors decay; every other factor is already a window average.

inboundConversationsoptionalRiskFactor

Avg inbound-initiated conversations/day (bonus). Scoring: >=100 = -20, 50-100 = -15, 30-50 = -10, 15-30 = -5, else 0. Organic demand lowers risk. **Inbound lines:** the value is still reported but earns no bonus, since inbound volume is the product working as sold rather than evidence of earned trust.

valueoptionalnumber | null

The measured value for this factor (units depend on the factor).

scoreoptionalnumber

Points this factor contributed to the total risk score.

statusoptionalstring

Qualitative status for display. not_applicable means the factor doesn't apply to this line type (e.g. outbound-initiation factors on a reply-only inbound number) and contributed 0 points.

"ok""bonus""warning""high""insufficient_data""not_applicable"
impactoptionalstring

Relative weight of this factor in the overall score.

"low""medium""high""very_high"
sampleSizeoptionalinteger

Sample size backing the value, when the factor guards on a minimum sample.

peak_atoptionalstring | null

Burst factors only (newConversationBursts, messageBursts). UTC date (YYYY-MM-DD) on which the window reported in value began.

scored_valueoptionalnumber

Burst factors only. The window count the score was actually taken from. Because each daily candidate is aged before comparison, the costliest burst is not always the biggest one: a month-old 639/h can score less than a 240/h from yesterday. Equals value whenever the peak is also the scored window.

scored_atoptionalstring | null

Burst factors only. UTC date (YYYY-MM-DD) of the window described by scored_value.

decay_weightoptionalnumber

Burst factors only. Recency multiplier applied to the base band score, 0.5 ^ (age_days / 21) with a floor of 0.25. A burst is evidence that ages: without this, one two-minute event held a number at high risk for the entire window. Only the burst factors decay; every other factor is already a window average.

inboundPercentageoptionalRiskFactor

Percentage of new conversations the customer started (gradient). Scoring ranges from -15 (>=90% inbound) up to +20 (<20% inbound). Requires >= 5 new conversations to score. **Inbound lines:** not_applicable, always 0 — a reply-only line sits at ~100% by construction.

valueoptionalnumber | null

The measured value for this factor (units depend on the factor).

scoreoptionalnumber

Points this factor contributed to the total risk score.

statusoptionalstring

Qualitative status for display. not_applicable means the factor doesn't apply to this line type (e.g. outbound-initiation factors on a reply-only inbound number) and contributed 0 points.

"ok""bonus""warning""high""insufficient_data""not_applicable"
impactoptionalstring

Relative weight of this factor in the overall score.

"low""medium""high""very_high"
sampleSizeoptionalinteger

Sample size backing the value, when the factor guards on a minimum sample.

peak_atoptionalstring | null

Burst factors only (newConversationBursts, messageBursts). UTC date (YYYY-MM-DD) on which the window reported in value began.

scored_valueoptionalnumber

Burst factors only. The window count the score was actually taken from. Because each daily candidate is aged before comparison, the costliest burst is not always the biggest one: a month-old 639/h can score less than a 240/h from yesterday. Equals value whenever the peak is also the scored window.

scored_atoptionalstring | null

Burst factors only. UTC date (YYYY-MM-DD) of the window described by scored_value.

decay_weightoptionalnumber

Burst factors only. Recency multiplier applied to the base band score, 0.5 ^ (age_days / 21) with a floor of 0.25. A burst is evidence that ages: without this, one two-minute event held a number at high risk for the entire window. Only the burst factors decay; every other factor is already a window average.

totalContactsoptionalRiskFactor

Distinct contacts messaged in the window. Scoring: >2000 = +15, 1500-2000 = +8, else 0.

valueoptionalnumber | null

The measured value for this factor (units depend on the factor).

scoreoptionalnumber

Points this factor contributed to the total risk score.

statusoptionalstring

Qualitative status for display. not_applicable means the factor doesn't apply to this line type (e.g. outbound-initiation factors on a reply-only inbound number) and contributed 0 points.

"ok""bonus""warning""high""insufficient_data""not_applicable"
impactoptionalstring

Relative weight of this factor in the overall score.

"low""medium""high""very_high"
sampleSizeoptionalinteger

Sample size backing the value, when the factor guards on a minimum sample.

peak_atoptionalstring | null

Burst factors only (newConversationBursts, messageBursts). UTC date (YYYY-MM-DD) on which the window reported in value began.

scored_valueoptionalnumber

Burst factors only. The window count the score was actually taken from. Because each daily candidate is aged before comparison, the costliest burst is not always the biggest one: a month-old 639/h can score less than a 240/h from yesterday. Equals value whenever the peak is also the scored window.

scored_atoptionalstring | null

Burst factors only. UTC date (YYYY-MM-DD) of the window described by scored_value.

decay_weightoptionalnumber

Burst factors only. Recency multiplier applied to the base band score, 0.5 ^ (age_days / 21) with a floor of 0.25. A burst is evidence that ages: without this, one two-minute event held a number at high risk for the entire window. Only the burst factors decay; every other factor is already a window average.

replyRateoptionalRiskFactor

Percentage of conversations with 3+ inbound messages. Scoring: <20% = +35, 20-30% = +20, 30-40% = +8, >=50% = -5. Requires >= 10 conversations to score. Highest-weight (very_high) factor.

valueoptionalnumber | null

The measured value for this factor (units depend on the factor).

scoreoptionalnumber

Points this factor contributed to the total risk score.

statusoptionalstring

Qualitative status for display. not_applicable means the factor doesn't apply to this line type (e.g. outbound-initiation factors on a reply-only inbound number) and contributed 0 points.

"ok""bonus""warning""high""insufficient_data""not_applicable"
impactoptionalstring

Relative weight of this factor in the overall score.

"low""medium""high""very_high"
sampleSizeoptionalinteger

Sample size backing the value, when the factor guards on a minimum sample.

peak_atoptionalstring | null

Burst factors only (newConversationBursts, messageBursts). UTC date (YYYY-MM-DD) on which the window reported in value began.

scored_valueoptionalnumber

Burst factors only. The window count the score was actually taken from. Because each daily candidate is aged before comparison, the costliest burst is not always the biggest one: a month-old 639/h can score less than a 240/h from yesterday. Equals value whenever the peak is also the scored window.

scored_atoptionalstring | null

Burst factors only. UTC date (YYYY-MM-DD) of the window described by scored_value.

decay_weightoptionalnumber

Burst factors only. Recency multiplier applied to the base band score, 0.5 ^ (age_days / 21) with a floor of 0.25. A burst is evidence that ages: without this, one two-minute event held a number at high risk for the entire window. Only the burst factors decay; every other factor is already a window average.

inboundMsgCountoptionalRiskFactor

Avg inbound messages per conversation. Scoring: <3 = +25 (with >= 3 conversations), else 0. **Inbound lines:** relaxed to <1.5 = +10, because a customer asking one question and getting an answer is the expected shape of an inbound thread.

valueoptionalnumber | null

The measured value for this factor (units depend on the factor).

scoreoptionalnumber

Points this factor contributed to the total risk score.

statusoptionalstring

Qualitative status for display. not_applicable means the factor doesn't apply to this line type (e.g. outbound-initiation factors on a reply-only inbound number) and contributed 0 points.

"ok""bonus""warning""high""insufficient_data""not_applicable"
impactoptionalstring

Relative weight of this factor in the overall score.

"low""medium""high""very_high"
sampleSizeoptionalinteger

Sample size backing the value, when the factor guards on a minimum sample.

peak_atoptionalstring | null

Burst factors only (newConversationBursts, messageBursts). UTC date (YYYY-MM-DD) on which the window reported in value began.

scored_valueoptionalnumber

Burst factors only. The window count the score was actually taken from. Because each daily candidate is aged before comparison, the costliest burst is not always the biggest one: a month-old 639/h can score less than a 240/h from yesterday. Equals value whenever the peak is also the scored window.

scored_atoptionalstring | null

Burst factors only. UTC date (YYYY-MM-DD) of the window described by scored_value.

decay_weightoptionalnumber

Burst factors only. Recency multiplier applied to the base band score, 0.5 ^ (age_days / 21) with a floor of 0.25. A burst is evidence that ages: without this, one two-minute event held a number at high risk for the entire window. Only the burst factors decay; every other factor is already a window average.

inboundOutboundRatiooptionalRiskFactor

Inbound share of all messages. Scoring: <=10% = +20, 11-29% = +10, >=30% = 0. Requires >= 10 messages to score. Detects one-way traffic.

valueoptionalnumber | null

The measured value for this factor (units depend on the factor).

scoreoptionalnumber

Points this factor contributed to the total risk score.

statusoptionalstring

Qualitative status for display. not_applicable means the factor doesn't apply to this line type (e.g. outbound-initiation factors on a reply-only inbound number) and contributed 0 points.

"ok""bonus""warning""high""insufficient_data""not_applicable"
impactoptionalstring

Relative weight of this factor in the overall score.

"low""medium""high""very_high"
sampleSizeoptionalinteger

Sample size backing the value, when the factor guards on a minimum sample.

peak_atoptionalstring | null

Burst factors only (newConversationBursts, messageBursts). UTC date (YYYY-MM-DD) on which the window reported in value began.

scored_valueoptionalnumber

Burst factors only. The window count the score was actually taken from. Because each daily candidate is aged before comparison, the costliest burst is not always the biggest one: a month-old 639/h can score less than a 240/h from yesterday. Equals value whenever the peak is also the scored window.

scored_atoptionalstring | null

Burst factors only. UTC date (YYYY-MM-DD) of the window described by scored_value.

decay_weightoptionalnumber

Burst factors only. Recency multiplier applied to the base band score, 0.5 ^ (age_days / 21) with a floor of 0.25. A burst is evidence that ages: without this, one two-minute event held a number at high risk for the entire window. Only the burst factors decay; every other factor is already a window average.

newConversationBurstsoptionalRiskFactor

Peak new conversations the number itself opened in any sliding 60-minute window; conversations the customer started are excluded on every line type, so a rush of inbound demand is never mistaken for a blast. Base scoring: >60 = +40, 40-60 = +30, 25-40 = +20, 15-25 = +10, else 0. The base score is then multiplied by decay_weight (see RiskFactor), so an old burst costs less than the same burst today. Very-high weight; hallmark of mass-outreach automation.

valueoptionalnumber | null

The measured value for this factor (units depend on the factor).

scoreoptionalnumber

Points this factor contributed to the total risk score.

statusoptionalstring

Qualitative status for display. not_applicable means the factor doesn't apply to this line type (e.g. outbound-initiation factors on a reply-only inbound number) and contributed 0 points.

"ok""bonus""warning""high""insufficient_data""not_applicable"
impactoptionalstring

Relative weight of this factor in the overall score.

"low""medium""high""very_high"
sampleSizeoptionalinteger

Sample size backing the value, when the factor guards on a minimum sample.

peak_atoptionalstring | null

Burst factors only (newConversationBursts, messageBursts). UTC date (YYYY-MM-DD) on which the window reported in value began.

scored_valueoptionalnumber

Burst factors only. The window count the score was actually taken from. Because each daily candidate is aged before comparison, the costliest burst is not always the biggest one: a month-old 639/h can score less than a 240/h from yesterday. Equals value whenever the peak is also the scored window.

scored_atoptionalstring | null

Burst factors only. UTC date (YYYY-MM-DD) of the window described by scored_value.

decay_weightoptionalnumber

Burst factors only. Recency multiplier applied to the base band score, 0.5 ^ (age_days / 21) with a floor of 0.25. A burst is evidence that ages: without this, one two-minute event held a number at high risk for the entire window. Only the burst factors decay; every other factor is already a window average.

messageBurstsoptionalRiskFactor

Peak outbound messages in any sliding 60-minute window. Base scoring: >200 = +30, 150-200 = +20, 100-150 = +10, else 0, then multiplied by decay_weight.

valueoptionalnumber | null

The measured value for this factor (units depend on the factor).

scoreoptionalnumber

Points this factor contributed to the total risk score.

statusoptionalstring

Qualitative status for display. not_applicable means the factor doesn't apply to this line type (e.g. outbound-initiation factors on a reply-only inbound number) and contributed 0 points.

"ok""bonus""warning""high""insufficient_data""not_applicable"
impactoptionalstring

Relative weight of this factor in the overall score.

"low""medium""high""very_high"
sampleSizeoptionalinteger

Sample size backing the value, when the factor guards on a minimum sample.

peak_atoptionalstring | null

Burst factors only (newConversationBursts, messageBursts). UTC date (YYYY-MM-DD) on which the window reported in value began.

scored_valueoptionalnumber

Burst factors only. The window count the score was actually taken from. Because each daily candidate is aged before comparison, the costliest burst is not always the biggest one: a month-old 639/h can score less than a 240/h from yesterday. Equals value whenever the peak is also the scored window.

scored_atoptionalstring | null

Burst factors only. UTC date (YYYY-MM-DD) of the window described by scored_value.

decay_weightoptionalnumber

Burst factors only. Recency multiplier applied to the base band score, 0.5 ^ (age_days / 21) with a floor of 0.25. A burst is evidence that ages: without this, one two-minute event held a number at high risk for the entire window. Only the burst factors decay; every other factor is already a window average.

messageSimilarityoptionalRiskFactor

Largest near-identical outbound cluster (SimHash) as a percentage of qualifying messages (>= 50 chars). Scoring: >70% = +15, 50-70% = +8, <30% = -2. Only evaluated with >= 20 qualifying messages and when the expensive metric was computed; otherwise value: null, score: 0.

valueoptionalnumber | null

The measured value for this factor (units depend on the factor).

scoreoptionalnumber

Points this factor contributed to the total risk score.

statusoptionalstring

Qualitative status for display. not_applicable means the factor doesn't apply to this line type (e.g. outbound-initiation factors on a reply-only inbound number) and contributed 0 points.

"ok""bonus""warning""high""insufficient_data""not_applicable"
impactoptionalstring

Relative weight of this factor in the overall score.

"low""medium""high""very_high"
sampleSizeoptionalinteger

Sample size backing the value, when the factor guards on a minimum sample.

peak_atoptionalstring | null

Burst factors only (newConversationBursts, messageBursts). UTC date (YYYY-MM-DD) on which the window reported in value began.

scored_valueoptionalnumber

Burst factors only. The window count the score was actually taken from. Because each daily candidate is aged before comparison, the costliest burst is not always the biggest one: a month-old 639/h can score less than a 240/h from yesterday. Equals value whenever the peak is also the scored window.

scored_atoptionalstring | null

Burst factors only. UTC date (YYYY-MM-DD) of the window described by scored_value.

decay_weightoptionalnumber

Burst factors only. Recency multiplier applied to the base band score, 0.5 ^ (age_days / 21) with a floor of 0.25. A burst is evidence that ages: without this, one two-minute event held a number at high risk for the entire window. Only the burst factors decay; every other factor is already a window average.

daily_dataoptionalRiskDailyData
outbound_conversationsoptionalRiskDailySeriesPoint[]

Array of RiskDailySeriesPoint

dateoptionalstring

UTC calendar day (YYYY-MM-DD).

countoptionalinteger
inbound_conversationsoptionalRiskDailySeriesPoint[]

Array of RiskDailySeriesPoint

dateoptionalstring

UTC calendar day (YYYY-MM-DD).

countoptionalinteger
period_daysoptionalinteger
windowoptionalRiskWindow

The concrete time window the report was computed over.

fromoptionalinteger

Window start (Unix epoch ms).

tooptionalinteger

Window end (Unix epoch ms).

is_defaultoptionalboolean

True when the caller supplied no window params (default 30 days).

calculated_atoptionalinteger

When the report was computed (Unix epoch ms).

computed_with_expensive_metricsoptionalboolean

Whether the message-similarity metric was included.

Response codes

200Risk report for the organization's dedicated and inbound numbers
401Authentication required or invalid
403Organization has no dedicated or inbound numbers (`dedicated_plan_required`)

Sends a live request with your values and shows the real response below. Your key is stored only in this browser.

Request
curl -X GET https://api.blooio.com/v2/api/analytics/risk-tolerance?days=30&from=0&to=0&compute=false \
Response objectexample
{  "phone_numbers": [    {      "phone_number": "+15551234567",      "allocation_id": "obj_a1b2c3d4",      "line_type": "dedicated",      "risk_score": 0,      "risk_level": "low",      "has_activity": true,      "metrics": {        "avg_outbound_conversations_per_day": 0,        "avg_inbound_conversations_per_day": 0,        "inbound_initiated_percentage": 0,        "total_contacts": 3,        "reply_rate": 0,        "avg_inbound_messages_per_conversation": 0,        "inbound_outbound_message_ratio": 0,        "max_new_conversations_per_hour": 0,        "max_messages_per_hour": 0,        "message_similarity_percentage": 0,        "inbound_initiated": 0,        "outbound_initiated": 0,        "total_conversations": 3,        "total_messages": 3,        "total_inbound_messages": 3,        "total_outbound_messages": 3,        "avg_messages_per_day": 0,        "avg_conversation_depth": 0      },      "risk_factors": {        "outboundConversations": {          "value": 0,          "score": 0,          "status": "ok",          "impact": "low",          "sampleSize": 50,          "peak_at": "2025-01-15T09:30:00Z",          "scored_value": 0,          "scored_at": "2025-01-15T09:30:00Z",          "decay_weight": 0        },        "inboundConversations": {          "value": 0,          "score": 0,          "status": "ok",          "impact": "low",          "sampleSize": 50,          "peak_at": "2025-01-15T09:30:00Z",          "scored_value": 0,          "scored_at": "2025-01-15T09:30:00Z",          "decay_weight": 0        },        "inboundPercentage": {          "value": 0,          "score": 0,          "status": "ok",          "impact": "low",          "sampleSize": 50,          "peak_at": "2025-01-15T09:30:00Z",          "scored_value": 0,          "scored_at": "2025-01-15T09:30:00Z",          "decay_weight": 0        },        "totalContacts": {          "value": 0,          "score": 0,          "status": "ok",          "impact": "low",          "sampleSize": 50,          "peak_at": "2025-01-15T09:30:00Z",          "scored_value": 0,          "scored_at": "2025-01-15T09:30:00Z",          "decay_weight": 0        },        "replyRate": {          "value": 0,          "score": 0,          "status": "ok",          "impact": "low",          "sampleSize": 50,          "peak_at": "2025-01-15T09:30:00Z",          "scored_value": 0,          "scored_at": "2025-01-15T09:30:00Z",          "decay_weight": 0        },        "inboundMsgCount": {          "value": 0,          "score": 0,          "status": "ok",          "impact": "low",          "sampleSize": 50,          "peak_at": "2025-01-15T09:30:00Z",          "scored_value": 0,          "scored_at": "2025-01-15T09:30:00Z",          "decay_weight": 0        },        "inboundOutboundRatio": {          "value": 0,          "score": 0,          "status": "ok",          "impact": "low",          "sampleSize": 50,          "peak_at": "2025-01-15T09:30:00Z",          "scored_value": 0,          "scored_at": "2025-01-15T09:30:00Z",          "decay_weight": 0        },        "newConversationBursts": {          "value": 0,          "score": 0,          "status": "ok",          "impact": "low",          "sampleSize": 50,          "peak_at": "2025-01-15T09:30:00Z",          "scored_value": 0,          "scored_at": "2025-01-15T09:30:00Z",          "decay_weight": 0        },        "messageBursts": {          "value": 0,          "score": 0,          "status": "ok",          "impact": "low",          "sampleSize": 50,          "peak_at": "2025-01-15T09:30:00Z",          "scored_value": 0,          "scored_at": "2025-01-15T09:30:00Z",          "decay_weight": 0        },        "messageSimilarity": {          "value": 0,          "score": 0,          "status": "ok",          "impact": "low",          "sampleSize": 50,          "peak_at": "2025-01-15T09:30:00Z",          "scored_value": 0,          "scored_at": "2025-01-15T09:30:00Z",          "decay_weight": 0        }      },      "daily_data": {        "outbound_conversations": [          {            "date": "2026-07-01",            "count": 3          }        ],        "inbound_conversations": [          {            "date": "2026-07-01",            "count": 3          }        ]      }    }  ],  "period_days": 0,  "window": {    "from": 0,    "to": 0,    "is_default": true  },  "calculated_at": 37.7749,  "computed_with_expensive_metrics": false}