Detect grooming patterns in conversations
Analyze a sequence of messages for multi-stage grooming tactics: trust building, isolation, secrecy requests, boundary testing, and sexual escalation. Accepts full conversation history with sender roles to detect patterns that single-message analysis would miss. Returns grooming risk level, identified tactics as flags, and recommended intervention actions.
Request
- Base URL: https://api.tuteliq.ai
- URL: https://api.tuteliq.ai/api/v1/safety/grooming
- Auth: one of:
- HTTP bearer
- API key in header x-api-key
Request body
Opaque signed token from a previous /grooming call. Carries derived analysis state across calls without server-side storage.
Discard any continuation_token and start a fresh conversation.
Optional customer-side identifier for this batch (used as the evidence pointer in the issued continuation_token).
Top-level alias for options.support_threshold. Minimum severity to show crisis support resources (default: high). Critical always shows.
Your unique identifier (e.g. message ID, content ID) for correlating Tuteliq results with your own system. Echoed back in the response and included in webhook payloads so you can match alerts to the original content.
Your end-customer identifier for multi-tenant / B2B2C scenarios. If you serve multiple customers (schools, apps, brands) from a single API key, set this to route webhook alerts to the correct customer. Echoed back in the response and included in webhook payloads as "customerId".
Arbitrary key-value pairs for additional context (e.g. { "channel": "discord", "region": "eu" }). Stored with the detection result, echoed in the response, and included in webhook payloads. Maximum 20 properties.
Per-call override of your account incident logging setting. When set, it takes precedence over the account-level flag for THIS request: true forces the incident to be persisted, false suppresses persistence. Omit to use your account default (which itself defaults to enabled). Lets you control incident logging per request, e.g. suppress logging for test traffic.
Response
Default Response
True when a declared context.conversation_type would have capped an urgent action to flag_for_review and did not, because the case is acute: the analysis named an acute marker, or a separate reading found a person at risk now with something happening as the text is written. A reading that gave no answer also keeps the urgency. Absent otherwise, and never present together with conversation_type_applied.
Human-readable explanation. Present only when urgency_kept is true.
Present only when a caller-declared context.conversation_type actually changed routing. Confirms the setting was taken into account, rather than leaving the platform to assume it was.
Grooming-risk level. "low" indicates a signal worth monitoring but no action required; branch your alerting on recommended_action (flag_for_review / block / immediate_intervention), not the bare presence of a low-level risk.
True when the analysis returned a high or critical verdict with no tactic and no risk score to support it. The verdict was reconciled to what the response actually evidences and recommended_action brought down to review, rather than returning two fields that contradict each other. Never fires on a finding that names a tactic or carries any score. Absent unless it applied.
Human-readable explanation. Present only when verdict_withdrawn is true.
True when an alert rested on a number that is also known as a code, and a separate reading found the number in ordinary use (an address, a count, a price, a room) with nothing else harmful in the content. The verdict was cleared. Absent when this did not happen.
Human-readable explanation. Present only when number_coincidence_cleared is true.
True when the analysis returned a high or critical verdict carrying a risk score but naming no tactic to support it. The verdict is capped to medium and recommended_action to flag_for_review, so the conversation still reaches a reviewer rather than routing an escalation a reviewer cannot check; risk_score is capped to the top of the medium band. A medium verdict naming no tactic keeps its level and action and has only its risk_score capped to the top of the medium band. Where, in addition, no child was declared or stated anywhere, none of the withdrawn claims was a tactic that presupposes a child, no refusal or threat was reported and a separate reading found no sign of a minor and no pressure, the verdict is lowered to low and recommended_action to monitor: kept visible, not sent for review. verdict_capped_reason says which applied. Never fires on a finding that names a tactic. Only ever lowers, never raises. Absent unless it applied.
Human-readable explanation. Present only when verdict_capped is true.
True when an alert that named no tactic was lowered to low / monitor because a separate reading of the conversation found nothing in it but warmth and everyday logistics: greetings, terms of endearment, kisses, "love you", checking where someone is. A parent calling a child "baby" is the case this exists for. It applies ONLY when no tactic survived verification; one verified tactic and it does nothing. It never clears to none, so a pattern can still build across a conversation. Absent when it did not apply.
Human-readable explanation. Present only when affection_only_capped is true.
True when an alert was lowered to low / monitor because the people in the conversation are adults. It applies only when the request declared no child (no child_age under 18, no child sender_role, no sender_age under 18), nobody states an age under 18, and no tactic that presupposes a child was verified; a separate reading must then find that the people taking part state ages of 18 or more (or every message carries a sender_age of 18 or more), nothing indicating that a participant is or may be under 18, and no pressure, threat or refusal. A conversation in which nobody states an age keeps its alert. Grooming is a harm done to a child, and a willing conversation between adults is not one. If your platform knows a participant is a minor, declare it and this never applies. It never clears to none. Absent unless it applied.
Human-readable explanation. Present only when adult_exchange_capped is true.
True when a conversation about a young person who has been approached was kept at monitor and not raised to review, because your platform declared a context.conversation_type that names the responsible party in the conversation and that party is already handling it. Never none. In education and family this applies only when every message is an adult's. Absent otherwise.
Human-readable explanation. Present only when protective_speech_capped is true.
True when a review alert was lowered to low / monitor because the only thing kept quiet in the conversation is a surprise for the person it is kept from (a present, a party), a parent or carer of the child is part of the plan, and the adult asks for nothing alone, personal or deleted. It applies ONLY to flag_for_review below critical, and only when a secrecy request and boundary pushing are the only tactics that survived verification: a photo request, a meeting, a gift to the child, isolation or flattery blocks it. A separate reading must find all three things; anything else, or no answer, keeps the alert. The tactics stay in flags. What defines this harm is an adult keeping their contact with a child from the people who protect the child. Absent when it did not apply.
Human-readable explanation. Present only when shared_surprise_capped is true.
True when a single submitted message was flagged for meeting_request and nothing else, and a separate reading found that it only gives, asks for or arranges practical details for an ordinary purpose. The result is cleared: grooming_risk none, no flags, no action. Never applied to a conversation of more than one message, to a result that names anything else, or to the most urgent action. Absent otherwise.
Human-readable explanation. Present only when meeting_logistics_cleared is true.
True when a long conversation was read a second time with a few of its messages singled out for close reading, and the result shown is that second reading of the whole conversation. Only ever raises a result, never lowers one. Absent otherwise.
Human-readable explanation. Present only when long_conversation_raised is true.
True when the caller declared an adult child_age but a participant states a minor age in the conversation itself. The declared age was not used. Nothing is escalated by this on its own; the analysis simply runs as if no age had been sent. Worth investigating at your end: an age check that disagrees with what the user says in the chat is what a bypassed age verification looks like. A stated ADULT age never has this effect, because message text is attacker-controlled.
Human-readable explanation. Present only when declared_age_overridden is true.
Closed vocabulary. Values outside this list are dropped before the response is built, so this field is safe to switch on. Tuteliq's published grooming taxonomy is EIGHT per-message tactics: flattery, photo_request, gift_giving, secrecy_request, meeting_request, isolation, boundary_pushing and reconnaissance. Those are the eight to quote, and the ones the model card reports. sexual_content also appears here and is NOT a tactic: it is a content category sharing the array. reconnaissance is probing whether a child is unsupervised, alone or reachable: who oversees the child's device or person (supervision), or when the child is alone, free or unobserved (availability). As of 25 September 2026 it is emitted PER MESSAGE on the turn carrying the probe, including probes phrased as statements. A question that is useless to the asker unless the answer is "yes, supervised" is a safeguarding check and is deliberately NOT flagged, so a tutor or coach confirming a guardian will be present does not produce it. CONVERSATION-LEVEL ACCUMULATION IS A SEPARATE CAPABILITY AND IS NOT YET SCORED THAT WAY. This field tells you a given message carried a probe; it does not tell you how many probes make a pattern, and no arc-level threshold is applied. Do not read a single reconnaissance as a completed profiling arc, and do not read the absence of reconnaissance across a conversation as the absence of probing. sexualization and sextortion reach flags by a different route, coded-term matching. Those two assert something weaker than a tactic: that the conversation contains a recognised coded term of that kind, confirmed in context. grooming remains in the vocabulary and is not currently emitted. If you are reporting on the taxonomy, use the eight.
What the CONVERSATION CONTENT establishes about how the two participants know each other, reported factually so a finding can be routed. established_offline = they already knew each other in the physical world before this conversation (school, club, lesson, household, a stated and evidenced role). online_only = the contact exists only online or began online, INCLUDING a conversation working toward a first meeting. unestablished = the content settles neither. DESCRIPTIVE ONLY. This never changes grooming_risk, risk_score, recommended_action or flags, in the same way speaker_role is descriptive on /safety/bullying. DO NOT TREAT established_offline AS REASSURING, and do not build a rule that lowers risk because an adult is known to the child: most child sexual abuse is committed by someone the child already knows. Measured on the 270-conversation PAN12 problem-2 set, online_only was two and a half times MORE common in non-predatory conversations than predatory ones (0.517 vs 0.193), and established_offline was three times more common in predatory ones (0.173 vs 0.058). The stranger intuition runs backwards in real transcript data. Present only when at least one tactic was flagged, since it exists to route a finding. Absent if the classification fails.
True when grooming_risk was cleared to none because this conversation is a DISCLOSURE, not an incident: every tactic the analysis claimed was withdrawn as described rather than performed, and speaker_stance is describing_third_party. A child telling someone for the first time, a bystander report, or a parent reporting what their child told them. The grooming verdict is the only thing cleared. recommended_action is deliberately NOT lowered, because a disclosure still needs to reach a person: treat it as a support and safeguarding route rather than a moderation one, and never action the speaker. Absent when the clear did not apply.
Human-readable explanation of why disclosure_cleared was applied. Present only alongside it.
The adult's own framing of who may know, described factually rather than judged. unconditional_concealment asks the child to keep something from someone with no stated limit; limited_confidentiality offers privacy but names a condition under which the adult would tell someone ("unless someone's at risk"), which is safeguarding practice rather than a secrecy demand; guardian_included tells the child to inform a parent or carer, or says the adult will.
True when secrecy_request was removed from flags because the adult stated the limits of confidentiality or included a guardian. Routing is unaffected: the secrecy_request + isolation escalation reads the pre-removal flag list, so this can never lower an action. Absent when nothing was removed.
Which participant each flag is attributed to: tactic -> the sender_role values of the messages that actually carry it. Derived from the per-message analysis, so it answers "who is doing this?" rather than assuming the non-child role. OMITTED ENTIRELY when speaker_stance is describing_third_party, because a speaker reporting someone else's conduct is not performing it. A child disclosing abuse or reporting a contact would otherwise be named as the author of the tactics they are describing, and a platform actioning this field would sanction the person who asked for help. Use speaker_stance for those conversations. Also omitted when the model produced no per-message tags. Attribution only; it does not change the verdict or imply the other party is blameless.
Conversation-level tactics the model asserted but that could not be supported: either tied to no specific message at all, or tied only to messages that a separate adjudication check found do not express that tactic (#262). Removed from flags and surfaced rather than silently dropped: a tactic here is an UNSUPPORTED claim, not a finding, and should not be treated as evidence.
True when the verdict was floored to at least medium / 0.6 / flag_for_review because a separate factual classifier confirmed adult-minor romantic reciprocity - an adult and a minor declaring or reciprocating romantic love, directly or relayed through a third party (#189). A raise-only floor: it never lowers anything, and it exists because this implicit-outcome shape carries no tactic vocabulary and its model-side detection proved unstable across sittings (#292). Absent when the floor did not apply.
True when recommended_action was raised to immediate_intervention because the model reported an explicit refusal being overridden, or a threat to expose an intimate image or a confidence, in order to obtain compliance. This is a floor and never lowers an action. It exists because urgent routing on this endpoint is otherwise driven by the adult/child age gap, which is structurally absent when both participants are minors, leaving peer sexual coercion unable to reach the urgent band (#278). Absent when the floor did not apply.
The factual finding behind consent_floor_applied, present only when one was reported. Deliberately describes what happened rather than who did it, so it is independent of the participants' ages.
Routing key — a single stable token, safe to switch on. Ordered weakest to strongest: none (no signal), monitor (log, no human needed), flag_for_review (queue for a moderator), block (withhold or limit the content), immediate_intervention (crisis path). Human-readable guidance is in action_detail, not here.
Optional one-to-two-sentence explanation of recommended_action, intended for a moderator UI. Never branch on this field — it is free text and its wording is not stable across releases.
Language code used for analysis (auto-detected or provided)
Language support maturity: "stable" (en) or "beta" (all others)
Number of credits consumed by this request
Privacy-first signed token carrying derived analysis state across calls. Pass back on the next /grooming request.
ISO 8601 expiry timestamp of the continuation_token.
How prior state was sourced for this call.
Conversation-level risk 0-1, distinct from risk_score which scores only this message. Anchored on the highest severity seen so far and decaying slowly across benign turns, so a friendly message after an escalation does not reset it. Absent on the first turn. Derived from the signed token: no message content is stored to produce it.
Direction of travel across the conversation so far.
Per-turn severity, oldest first — the evidence behind trajectory_risk, so a moderator can see why a benign-looking message carries elevated conversation risk.
Status of the analysis. ok is the default. engine_error means the analysis engine could not produce a verdict (malformed/adversarial input) and recommended_action is forced to flag_for_review. out_of_scope_adults means the caller provided 2+ adult ages (via context.participant_age, context.child_age, or per-message sender_age) and the call was short-circuited as out of scope for grooming — consider /safety/coercive-control or /safety/social-engineering for adult-adult dynamics.
The model's own conclusion on whether the content is the speaker deploying a tactic in this exchange, or describing/reporting one performed by a third party (a disclosure, a survivor testimony, a bystander report) — independent of severity. Present when the model returns a recognised value AND the conversation carries at least one tactic, either in flags or in ungrounded_flags. A stance is a statement about a tactic, so it is omitted on a conversation that never had one, where the value is also unstable between runs. It is NOT omitted when every tactic was withdrawn: that is the shape of a disclosure, where a speaker claims tactics that an adjudication then finds are described rather than performed, and it is exactly the case where a platform needs to know this is a report so it can route to support rather than moderation.
True when recommended_action was capped below block because speaker_stance was describing_third_party — withholding a disclosure or a help-seeking report silences the person who needs to be heard. THIS CAP changes routing only and never touches grooming_risk or risk_score. Those fields can still be cleared to none/0 on the same response by the separate disclosure_cleared mechanism, which is a different decision with its own conditions: see that field. Absent when no cap applied.
Present only when action_capped is true.
Echo of the external_id you provided in the request
Echo of the customer_id you provided in the request
Echo of the metadata you provided in the request
Changes
No recorded changes to this operation across all 1 revision of this API.