Tracking trends and conversation insights
Use the Bloobability v2 decide endpoint to judge conversations against your own policies with the instant or full model, estimate how often each policy applies, and track how those rates move over time.
The Bloobability decide endpoint answers one question per policy: how likely is it that this policy holds for this conversation? You send a conversation and the policies you care about, such as "customer asked for a refund," "customer is at risk of churning," or "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 result carries two calibrated numbers between 0 and 1:
probability: the policy holds for this conversation.answerable: the conversation contains enough to judge the policy at all. A lowanswerablenever changesprobability; it tells you how much the conversation had to go on.
There is no verdict and no built-in threshold, and neither model points to the messages behind a result. The call is stateless: nothing is stored, there's no idempotency key, and replaying a request re-runs inference. You bring the conversations; Blooio judges them.
Choose a tier
Every request names one of two models with tier. Nothing routes between them automatically, nothing falls back from one to the other, and the response's tier always echoes your choice.
instant |
full |
|
|---|---|---|
| Best for | Short, explicit questions where the conversation states the deciding fact | Reversals ("actually, cancel that"), conditions, several speakers, long threads, and conversations that may be missing a fact |
| Server time, short thread, 1 / 6 policies | 8.2 / 24.9 ms | 25.8 / 83.9 ms |
| Accuracy on 900 fresh public questions | 77.9% | 88.8% |
| Flags missing information | No: caught 0 of 60 deliberately unanswerable cases | Yes: caught 60 of 60 in the same screen |
Server times are medians measured on the RTX 4090 that serves both tiers, for a four-message thread sent one request at a time. They exclude the internet and the public API in front of the model, and the two tiers take turns on one GPU, so the time you see per request is higher; they are not a latency guarantee.
When unsure, use full. Choose by how hard each question is, not by how many you ask: one policy that depends on a reversal can need full, while several simple, stated-fact policies run well on instant. On instant, read answerable as a weak hint only; it does not reliably tell "unlikely" apart from "the conversation never said."
Step 1: Define your policies
A policy is an id (the stable key you'll track over time) and a definition (a plain-language statement that is true or false of the conversation). There's nothing to register: any definition works. Write definitions the way you'd brief a new support lead: plain, specific, one idea each.
[
{ "id": "refund_request", "definition": "The customer currently asks for their money back." },
{ "id": "churn_risk", "definition": "The customer signals they may cancel or stop using the product." },
{ "id": "positive_sentiment", "definition": "The customer expresses satisfaction or praise." }
]The model reads only the definition; the id is echoed back unchanged so you can join results to your own records.
Step 2: Decide a conversation
Send your tier, the messages in chronological order, and your policies. Every message needs a unique id and non-blank text. The other message fields are optional, and you should send them only when they're real:
roleis free text:customer,agent,manager,tool, or your own.speaker_idtells apart two people who share a role, such as two agents.timestampis RFC 3339 with an offset, and must not decrease from one message to the next.
Add a top-level assessment_time when a policy depends on "now" (for example, "the customer has waited more than a day").
curl -X POST https://api.blooio.com/v4/ai/bloobability/v2/decide \
-H "Authorization: Bearer YOUR_BLOOIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tier": "full",
"messages": [
{ "id": "m1", "role": "customer", "speaker_id": "c1", "text": "This is the third time it broke. I want my money back." },
{ "id": "m2", "role": "agent", "speaker_id": "a1", "text": "I am sorry. Let me pull up your order." }
],
"policies": [
{ "id": "refund_request", "definition": "The customer currently asks for their money back." },
{ "id": "churn_risk", "definition": "The customer signals they may cancel or stop using the product." }
]
}'Try itThe response returns one result per policy, in the same order you sent them:
{
"tier": "full",
"model_version": "bloobability-v2-full-392ad04431a8",
"policies": [
{ "id": "refund_request", "probability": 0.97, "answerable": 0.99 },
{ "id": "churn_risk", "probability": 0.41, "answerable": 0.72 }
]
}The probabilities above are illustrative.
Reading a result
| Field | What it tells you |
|---|---|
probability |
The answer. The estimated probability that the policy holds. This is the number to store and aggregate. |
answerable |
How well the conversation supports judging the policy. On full, use it to separate "unlikely" from "can't tell." |
tier |
The model that answered, always the one you asked for. |
model_version |
The exact weights and calibration that produced the numbers. Store it alongside them. |
If your application needs a yes/no, choose the threshold yourself.
Step 3: Estimate a rate across many conversations
To answer "what share of this week's conversations were refund requests?", decide each conversation and average the probabilities for that policy across the cohort.
Warning
Average probabilities rather than counting conversations above a threshold. A threshold count throws away everything below the line, so it drifts with wherever you put the line. The average uses every conversation's probability, not just the ones above it.
const BLOO_API_KEY = process.env.BLOO_API_KEY;
const POLICIES = [
{ id: "refund_request", definition: "The customer currently asks for their money back." },
{ id: "churn_risk", definition: "The customer signals they may cancel or stop using the product." },
];
async function decide(messages) {
const res = await fetch("https://api.blooio.com/v4/ai/bloobability/v2/decide", {
method: "POST",
headers: {
Authorization: `Bearer ${BLOO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ tier: "full", messages, policies: POLICIES }),
});
if (!res.ok) throw new Error(`decide failed: ${res.status}`);
return res.json();
}
// conversations: [{ id, messages: [{ id, role, text }] }, ...]
async function policyRates(conversations) {
const totals = Object.fromEntries(POLICIES.map((p) => [p.id, 0]));
for (const convo of conversations) {
const { policies } = await decide(convo.messages);
for (const r of policies) totals[r.id] += r.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
definitionin every bucket. A wording change is a new measurement, not a continuation of the old one. - Same model. Keep one
tierfor the whole series and check thatmodel_versionstays the same. The two tiers are different models and don't return identical probabilities. Blooio's own Trends feature usesfull. - 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.
Warning
Trust direction more than exact magnitude. Calibration was fitted on research data and isn't guaranteed to hold identically for every cohort, policy wording, or future period. Treat "refund rate is up" as the reliable signal, and an exact point change as an estimate.
Step 5: Route the hard cases to a human
For per-conversation actions (auto-tagging, routing, closing tickets), choose your own thresholds and send everything uncertain to review:
- A high
probabilitywith a highanswerableis the strongest case for acting. - A middling
probability, or a lowanswerableonfull, is a good candidate for review. - Use
fullfor anything you'd act on without review.instantis fast, but it misses more reversals and doesn't flag missing facts.
The endpoint gives you numbers, not decisions: it doesn't certify any threshold as safe for automation, so validate yours on your own conversations before acting without review.
Errors
Errors use the standard v4 shape, { "error": { "code", "message", "details" } }. details never echoes your conversation or policy text, and 5xx errors carry none.
| Status | Meaning |
|---|---|
400 |
The body doesn't match the contract: an unknown tier, unknown or null fields, blank strings, text that isn't valid Unicode, duplicate keys or IDs, or out-of-order timestamps. details names the failing reason, path, and rule. |
413 |
The body is over 1 MiB. |
422 |
The conversation and policies don't fit the chosen tier's context. Nothing is truncated: shorten the thread or send fewer policies per request. |
429 / 503 |
The model is busy or restarting (queue_full, queue_timeout, model_unavailable). Retry with backoff. |
503 |
This request made the model fail (inference_timeout, gpu_memory_exhausted). Retrying it unchanged won't help: shorten the thread or send fewer policies. |
502 / 504 |
The model host gave a bad answer or didn't answer in time. Retry later. |
A request can carry up to 128 policies and 2,048 messages. Those are transport limits, not a promise that the conversation fits the model.
Limitations
Warning
Know these before you build on the scores:
- Domain. Built for English, text-only customer conversations. Attachments aren't read, and other languages or genres are unmeasured.
- Calibrated, not perfect. A probability of
0.9does not mean "correct exactly nine times in ten" for every policy and cohort. Both tiers are overconfident on sentences built to trick word-overlap shortcuts: on one such public set, 36 offull's 233 answers at 90% or more were wrong. The numbers are most reliable in aggregate, for rates and trends.instantdoesn't detect missing information. Itsanswerableflagged none of the 60 deliberately unanswerable cases in our latest screen.- Model-derived labels. Scores reflect a model's judgment of your definition, not human-verified ground truth. Where that judgment is systematically off, the scores will be off the same way.
Recap
- Define stable policies (
id+definition) and keep the wording fixed over time. - Decide conversations with
POST /v4/ai/bloobability/v2/decide; readprobabilityandanswerable. - Pick
fullunless your questions are short and explicit and speed matters most; theninstant. - Estimate rates by averaging probabilities, not by counting conversations over a threshold.
- Track trends with one fixed
tier, identical definitions, and comparable cohorts; trust direction over magnitude. - Pick your own thresholds, and send uncertain cases to review.
See the complete field reference on the Decide policies against a conversation endpoint page, and the benchmarks behind each tier on the Bloobability v2 model page.