Tracking trends and conversation insights

Use the Bloobability assess endpoint to score conversations against your own policies, estimate how often each applies, and track how those rates move over time.

The Bloobability assess endpoint answers one question, per policy: how confident is the model that this policy applies to this conversation? You send a conversation and a list of policies you care about — "customer asked for a refund," "customer is at risk of churning," "agent resolved the issue" — and get back a calibrated probability for each one.

That single primitive is enough to build two things:

  • Conversation insights — tag what happened in any one conversation (for routing, review queues, or a live dashboard).
  • Trends — measure how often a policy applies across many conversations, and watch that rate move week over week.

Note

This is a v4 feature under the AI tag. All requests go to https://api.blooio.com/v4. Make sure the version toggle at the top of the docs is set to v4.

How it works

Every call runs the gauge: it scores each policy you send independently and returns a calibrated probability, a yes/no verdict, a calibration tier, and a frozen accept/defer decision. That's the whole product, and it's the fast path.

Optionally, set include_evidence: true to also run the matcher, which attaches evidence_ids — the message IDs supporting each policy — in parallel. Use it when a human needs to see why; leave it off otherwise.

The call is stateless. Nothing is stored, there's no idempotency key, and replaying a request re-runs inference. You bring the conversations; Blooio scores them.

Step 1 — Define your policies

A policy is an id (the stable key you'll track over time) and a description (what the model actually judges against). Write descriptions the way you'd brief a new support lead — plain, specific, one idea each.

[
  { "id": "refund_request", "description": "The customer asks for money back or a refund." },
  { "id": "churn_risk", "description": "The customer signals they may cancel or stop using the product." },
  { "id": "positive_sentiment", "description": "The customer expresses satisfaction or praise." }
]

Warning

The id is the calibration key. Keep it stable and keep it honest: an id that matches a policy the model was calibrated on returns the roster tier (with a validated accepted decision), while a novel id returns pooled_unseen. If you reuse a known id for a different meaning, you'll get a "trusted" label on a number that isn't. Don't rename a policy mid-series and don't recycle an id for a new concept.

Step 2 — Score a conversation

Send the conversation messages (in order) and your policies. Roles must be customer, agent, or system, and every message id must be unique.

curl -X POST https://api.blooio.com/v4/ai/bloobability/assess \
  -H "Authorization: Bearer YOUR_BLOOIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reference_id": "conv-8891",
    "conversation": [
      { "id": "m1", "role": "customer", "content": "This is the third time it broke. I want my money back." },
      { "id": "m2", "role": "agent", "content": "I am sorry — let me pull up your order." }
    ],
    "policies": [
      { "id": "refund_request", "description": "The customer asks for money back or a refund." },
      { "id": "churn_risk", "description": "The customer signals they may cancel or stop using the product." }
    ],
    "include_evidence": true
  }'
Try it

The response returns one result per policy, in the same order you sent them:

{
  "reference_id": "conv-8891",
  "policies": [
    {
      "id": "refund_request",
      "calibrated_probability": 0.981,
      "verdict": "yes",
      "calibration_tier": "roster",
      "accepted": true,
      "evidence_ids": ["m1"]
    },
    {
      "id": "churn_risk",
      "calibrated_probability": 0.642,
      "verdict": "no",
      "calibration_tier": "roster"
    }
  ]
}

Reading a result

Field What it tells you
calibrated_probability The answer. A 0–1 probability that the policy applies. This is the number to store and aggregate.
verdict The model's discrete yes/no call. Useful for display, but do not aggregate verdicts into rates (see below).
calibration_tier roster (a policy the model was calibrated on — accepted is meaningful) or pooled_unseen (a novel policy — probability is usable, accepted is omitted).
accepted The frozen accept/defer decision. true means the confidence clears a strict, release-validated threshold. Omitted for pooled_unseen — treat a missing accepted as "no validated decision," never as false.
evidence_ids Supporting message IDs, present only with include_evidence: true. An independent judgment from the matcher, so a high probability can still come back with no evidence.

Step 3 — Estimate a rate across many conversations

To answer "what share of this week's conversations were refund requests?", score each conversation and average the calibrated probabilities for that policy across the cohort.

Warning

Average probabilities — don't count yes verdicts. The model deliberately favors precision over recall, so counting verdict === "yes" systematically undercounts the true rate. The calibrated probabilities are built to sum correctly; the verdicts are not. This is the single most common way to misread the results.

const BLOO_API_KEY = process.env.BLOO_API_KEY;

const POLICIES = [
  { id: "refund_request", description: "The customer asks for money back or a refund." },
  { id: "churn_risk", description: "The customer signals they may cancel or stop using the product." },
];

async function assess(messages) {
  const res = await fetch("https://api.blooio.com/v4/ai/bloobability/assess", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${BLOO_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ conversation: messages, policies: POLICIES }),
  });
  if (!res.ok) throw new Error(`assess failed: ${res.status}`);
  return res.json();
}

// conversations: [{ id, messages: [{ id, role, content }] }, ...]
async function policyRates(conversations) {
  const totals = Object.fromEntries(POLICIES.map((p) => [p.id, 0]));

  for (const convo of conversations) {
    const { policies } = await assess(convo.messages);
    for (const r of policies) totals[r.id] += r.calibrated_probability;
  }

  const n = conversations.length || 1;
  return Object.fromEntries(
    Object.entries(totals).map(([id, sum]) => [id, sum / n]),
  );
}

// { refund_request: 0.18, churn_risk: 0.07 } — the estimated share of the cohort.

Each rate is the estimated fraction of conversations in the cohort where that policy applies. Store these per bucket (day, week, agent, segment) and you have the raw material for trends.

Step 4 — Track a trend over time

Bucket your conversations by period, run policyRates on each bucket, and compare. The result is a time series of prevalence you can chart or alert on.

// buckets: [{ label: "2026-W01", conversations: [...] }, ...]
async function trend(buckets) {
  const series = [];
  for (const bucket of buckets) {
    series.push({ label: bucket.label, rates: await policyRates(bucket.conversations) });
  }
  return series;
}

To keep a trend meaningful, hold three things constant:

  • Same policy definitions. Use the identical id and description in every bucket. A wording change is a new measurement, not a continuation of the old one.
  • Comparable cohorts. Compare like with like — same channel, segment, or funnel stage — so a shift in the mix doesn't masquerade as a shift in behavior.
  • One model. Don't try to re-derive or re-fit calibration yourself per period; the single frozen calibrator is what makes periods comparable.

Warning

Read magnitude as a floor, not a measurement. Reported movement is compressed — a real change tends to show up at less than its full size — so the direction of a trend is more reliable than its exact magnitude. Treat "refund rate is up" as trustworthy and "refund rate is up 4.2 points" as a conservative lower bound. For novel (pooled_unseen) policies, trend fidelity is unmeasured entirely, so calibrate expectations and keep a human in the loop.

Step 5 — Route the hard cases to a human

For per-conversation actions (auto-tagging, auto-routing, closing tickets), use accepted as the automation gate and send everything else to review:

  • accepted: true → the decision cleared the strict, validated threshold. Safe to act on automatically.
  • accepted: false or absent → not validated. Queue it for a human, and turn on include_evidence: true so the reviewer sees the evidence_ids (the messages the model was reacting to).

accepted is intentionally strict — a confident-looking 0.88 yes still won't clear it — so plan for roughly a third to two fifths of decisions to land in review rather than automation. That split is the point: it concentrates the model's mistakes into the queue a human is already looking at.

Limitations

Warning

Know these before you build on the scores:

  • Domain. Tuned for customer-support conversations in English. Other languages, other genres, and non-conversational text are unmeasured.
  • Precision over recall. Misses (a policy that applied but wasn't flagged) are more likely than false alarms. This is why rates come from summed probabilities, not verdict counts.
  • Calibrated, not perfect. A probability of 0.9 does not mean "correct exactly nine times in ten." The probabilities are reliable in aggregate — for rates and trends — more than as a literal per-decision truth.
  • Model-derived labels. Scores reflect a model's judgment of your description, not human-verified ground truth. Where that judgment is systematically off, the scores will be off the same way.

Recap

  • Define stable policies (id + description) and keep them fixed over time.
  • Score conversations with POST /v4/ai/bloobability/assess; read calibrated_probability.
  • Estimate rates by averaging probabilities, never by counting yes verdicts.
  • Track trends by bucketing over time with identical policies and comparable cohorts; trust direction over magnitude.
  • Gate automation on accepted; send the rest to review with include_evidence: true.

See the full field reference on the Assess a conversation endpoint page.