---
title: "Detect coercive control patterns"
method: POST
path: "/api/v1/safety/coercive-control"
tags: ["Safety Extended"]
---

# Detect coercive control patterns

`POST /api/v1/safety/coercive-control`

## Request

- Base URL: `https://api.tuteliq.ai`
- URL: `https://api.tuteliq.ai/api/v1/safety/coercive-control`
- Auth: one of:
  - HTTP bearer
  - API key in header `x-api-key`

Analyzes text and conversation history for coercive control patterns including monitoring, isolation, financial control, threats, degradation, micromanagement, gaslighting, and child weaponisation.

## Request body

- object
  - `text` string, required: The text content to analyze
  - `context` object
    - `language` string: Language code (e.g., "en"). 32 supported languages: en is stable, the other 31 are beta (ar, bg, cs, da, de, el, es, et, fi, fr, ga, hr, hu, it, ja, ko, lt, lv, mt, nl, no, pl, pt, ro, ru, sk, sl, sv, tr, uk, zh). The code is the documented form; the English name of a supported language is also accepted ("English" as "en"). A region or script suffix is read as its base language ("en-US" as "en"), and "nb" or "nn" as "no". A value that is not supported is refused with a 400 that lists the supported codes. Omit the field, or send "auto", and the language is detected from the text.
    - `conversation_type` 'fraud_report' | 'abuse_report' | 'support_disclosure' | 'helpline' | 'clinical' | 'investigative' | 'legal' | 'advisory' | 'training' | 'journalism' | 'research' | 'moderation_review' | 'healthcare' | 'law_enforcement' | 'education' | 'family' | 'workplace' | 'dating' | 'adult_community' | 'direct_message' | 'group_chat' | 'public_post' | 'customer_service' | 'gaming_chat': Optional, no default: omit it and behaviour is unchanged. The setting this conversation was captured in, declared by your platform from its own configuration, never from end-user input. A declared setting names the responsible party in the conversation: a counsellor or helpline worker (`helpline`, `clinical`), a clinician (`healthcare`), an officer or investigator (`law_enforcement`, `investigative`), an adviser taking a report (`fraud_report`, `abuse_report`, `support_disclosure`, `advisory`), a moderator (`moderation_review`), a lawyer, trainer, journalist or researcher (`legal`, `training`, `journalism`, `research`), a teacher, tutor or safeguarding lead (`education`), a parent or guardian (`family`). In any of these settings, a conversation in which a person is at risk, that responsible party is already handling it, and nothing is happening as the text is written, is kept visible at `monitor` without an alert, and `protective_speech_capped` says so. Harm being done in the conversation, by anyone including the responsible party, a person at risk with nobody acting, and anything acute alert as they would with no setting. Where the harm is discussed and not enacted (`fraud_report`, `abuse_report`, `support_disclosure`, `helpline`, `clinical`, `investigative`, `legal`, `advisory`, `training`, `journalism`, `research`, `moderation_review`) an urgent action is also capped to `flag_for_review`, and the response echoes `conversation_type_applied`. `direct_message`, `group_chat`, `public_post`, `customer_service` and `gaming_chat` change nothing. In `education` and `family` this applies only when the speaker is not the person at risk: a young person speaking for themselves to a teacher or a parent alerts as with no setting.
    - `age_group` string: Age group (e.g., "13-15")
    - `ageGroup` string: Age group (camelCase alias, e.g., "under 18")
    - `platform` string: Platform name (e.g., "Discord", "Telegram")
    - `country` string: ISO 3166-1 alpha-2 country code (e.g., "GB", "US", "SE") for geo-localised helpline data. Falls back to user profile country if omitted.
    - `prior_messages` object[]: Prior turns of this conversation, oldest first, submitted by you for this request only -- Tuteliq stores no message content, so nothing here is retained after the call completes. Use this when you already have the whole conversation in hand; use continuation_token instead when you are calling once per new message as it arrives.
      - `role` string, required: Role of the sender (e.g., "user", "contact", "adult", "child")
      - `text` string, required: Message text
      - `timestamp` string: ISO 8601 timestamp of the message (optional)
    - `conversation_history` object[]: Deprecated: use prior_messages instead (same shape, same behavior). Kept for backward compatibility.
      - `role` string, required: Role of the sender (e.g., "user", "contact", "adult", "child")
      - `text` string, required: Message text
      - `timestamp` string: ISO 8601 timestamp of the message (optional)
  - `options` object
    - `include_evidence` boolean: Include evidence excerpts in response. Default: true.
    - `support_threshold` 'low' | 'medium' | 'high' | 'critical': Minimum severity level to include crisis support resources. Defaults to "high". Note: "critical" severity always includes support resources regardless of this setting.
    - `verdict_only` boolean: Fast mode: omit the per-message `message_analysis` array from the response. All other fields are unchanged. Cuts output length (and therefore latency) significantly on multi-turn input.
  - `support_threshold` 'low' | 'medium' | 'high' | 'critical': Top-level alias for options.support_threshold. Minimum severity to show crisis support resources (default: high). Critical always shows.
  - `include_evidence` boolean: Top-level alias for options.include_evidence. When false, the `evidence` array is omitted from the response. Default: true.
  - `continuation_token` string: Opaque, signed token returned by a previous call to this endpoint. Pass it back here to continue the analysis with prior context — no need to re-send prior_messages. The token IS the state; we store nothing.
  - `reset_conversation` boolean: When true, discard any continuation_token and treat this call as the first turn of a new conversation.
  - `message_id` string: Optional customer-side identifier for this turn. If provided, evidence pointers in the continuation_token will reference this id so you can map detections back to your own messages. If omitted we use a deterministic short hash of the text.
  - `bypass_cache` boolean: Deprecated, no effect: results are never cached — every request runs a fresh evaluation and nothing derived from the input is stored. The field is still accepted so existing integrations do not break.
  - `external_id` string: 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.
  - `customer_id` string: 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".
  - `metadata` object: 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.
  - `incident_moderation_enabled` boolean: Per-call override of your account incident logging setting. When set, it takes precedence for THIS request: true forces the incident to be persisted, false suppresses persistence. Omit to use your account default (which itself defaults to enabled). Useful to suppress logging for test traffic or to opt specific calls in or out.

## Response `200`

Default Response

- object
  - `declared_age_overridden` boolean: True when the caller declared an adult age group but the speaker states a minor age in the message or in the earlier turns sent with it. The declared age was not used. Nothing is escalated by this on its own; the analysis runs as if no age had been sent. Worth investigating at your end: an age check that disagrees with what the user says is what a bypassed age verification looks like. A stated ADULT age never has this effect. Absent unless it applied.
  - `declared_age_overridden_reason` string: Human-readable explanation. Present only when declared_age_overridden is true.
  - `urgency_kept` boolean: 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`.
  - `urgency_kept_reason` string: Human-readable explanation. Present only when urgency_kept is true.
  - `conversation_type_applied` string: 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. `severity`, `level` and `risk_score` are never altered by it.
  - `absence_reason_cleared` boolean: romance-scam only. Present when a detection was cleared because the reason given for not appearing on video or in person was a passing obstacle (a router down, out of data, an early start) rather than a standing property of the claimed persona (a deployment, a rig, a clearance), and the only tactics were IDENTITY_FABRICATION / SECRECY_REQUEST / URGENCY_PRESSURE. Before this, an ordinary "no wifi until Thursday" message was told its sender was fabricating an identity (#224). Absent when the gate did not fire.
  - `past_account_cleared` boolean: romance-scam only. Present when a result that asked for no action was not reported as a detection, because a separate reading found the message is an account of something that is over, or is about nobody in particular (a warning, a lesson, a story about a relative years ago). Never present when the analysis asked for monitoring or review.
  - `unseen_partner_raised` boolean: Romance scam only. True when the analysis left the result below review and a separate reading found all of: a romantic relationship with someone met online and never met in person; that person has never been seen on video or gives a reason they cannot be seen or checked; talk of love, marriage or keeping it private. The result is raised to `flag_for_review`. This pattern is complete before any money is asked for.
  - `unseen_partner_raised_reason` string: Human-readable explanation. Present only when unseen_partner_raised is true.
  - `love_bombing_raised` boolean: Romance scam only. True when the analysis left the result below review and a separate reading found all of: a declaration of lifelong or exclusive devotion to the reader; the message itself showing that the two barely know each other or have not met; and nothing of a shared life in it. The result is raised to review, never higher. This is how a romance scam opens, before anything is asked for, and it can also be sincere, so a moderator decides. Absent otherwise. ADVISORY: for a moderator UI and triage queues. Branch production logic on `recommended_action`.
  - `love_bombing_raised_reason` string: Human-readable explanation. Present only when love_bombing_raised is true.
  - `ordinary_shared_finances_capped` boolean: Romance scam only. True when, across a whole conversation (earlier turns submitted, or a conversation pasted into `text`), a separate reading found that money moves the way it does between people who share a life (a cost split, a sum covered and paid back, a shared expense), with no unmet or unverifiable partner, unusual payment method, crisis, secrecy or promised returns. A result at review or block is then brought down to `monitor`. Only `recommended_action` changes; `detected`, the categories, the level and the score are as the analysis gave them. Never applied to a single message, a declared minor, a kept urgency, or a result another reading cleared or raised. Absent otherwise.
  - `ordinary_shared_finances_capped_reason` string: Human-readable explanation. Present only when ordinary_shared_finances_capped is true.
  - `protective_speech_capped` boolean: True when an alert was lowered because a separate reading found a text that works AGAINST the harm it describes: explaining it, warning about it, counselling or supporting someone, reporting it or asking what to do. Two outcomes: with nobody at risk in the text and the detector itself reading the speaker as describing someone else, the result is nothing detected at `monitor`; with a person at risk and a responsible adult in the text already acting, an urgent action becomes `flag_for_review` and the finding is kept. A text that does the harm, a person at risk with nobody acting, anything happening as the text is written, and any failed reading are never lowered. Absent when not applied.
  - `protective_speech_capped_reason` string: Human-readable explanation, naming which of the two outcomes applied. Present only when protective_speech_capped is true.
  - `endpoint` string: The detection endpoint that produced this result
  - `detected` boolean: A signal was observed at ANY severity (including low / monitor cases). For production branching prefer `recommended_action` (`flag_for_moderator` / `immediate_intervention` indicate action required).
  - `severity` number: Severity score (0.0-1.0)
  - `confidence` number: Model confidence in the classification (0.0-1.0)
  - `risk_score` number: Age-adjusted risk score (0.0-1.0)
  - `level` 'none' | 'low' | 'medium' | 'high' | 'critical': Human-readable severity level
  - `categories` object[]: Detected categories with confidence scores
    - `tag` string: Category identifier (e.g., "URGENCY_FABRICATION")
    - `label` string: Human-readable category label
    - `confidence` number: Confidence for this category (0.0-1.0)
  - `evidence` object[]: Evidence excerpts supporting the detection
    - `text` string: Evidence excerpt from the input
    - `tactic` string: The tactic or indicator this evidence relates to
    - `weight` number: Evidence weight (0.0-1.0)
  - `age_calibration` object
    - `applied` boolean
    - `age_group` string
    - `multiplier` number
  - `recommended_action` string: Recommended action to take
  - `rationale` string: Explanation of the detection result
  - `message_analysis` object[]: Per-message risk breakdown (only present when prior_messages is provided)
    - `message_index` number: Index of the message in the conversation history (1-based)
    - `risk_score` number: Risk score for this individual message (0.0-1.0)
    - `flags` string[]: Tactics or indicators detected in this message
    - `summary` string: Brief explanation of this message's risk contribution
  - `language` string: Language code used for analysis
  - `language_status` 'stable' | 'beta': Language support maturity
  - `credits_used` number: Credits consumed by this request
  - `processing_time_ms` number: Processing time in milliseconds
  - `continuation_token` string: Opaque, signed token carrying derived analysis state. Pass it back on the next call to continue the conversation without re-sending history. Server stores nothing — the token IS the state.
  - `continuation_expires_at` string: ISO 8601 timestamp at which the continuation_token expires (24h default).
  - `state_source` 'token' | 'fresh' | 'reset': How the prior analysis state was sourced for this call: "token" (decoded from continuation_token), "fresh" (no prior state), "reset" (reset_conversation forced a fresh start).
  - `external_id` string: Echo of the external_id you provided in the request
  - `customer_id` string: Echo of the customer_id you provided in the request
  - `metadata` object: Echo of the metadata you provided in the request
  - `support` object: Country-specific crisis helplines and response guidance (only for high/critical severity)
    - `country` string
    - `country_name` string
    - `emergency_number` string
    - `helplines` object[]
      - `name` string
      - `number` string
      - `description` string
      - `category` string
      - `available` string
    - `response_guide` object
      - `category` string
      - `immediateActions` string[]
      - `resources` object[]
        - `name` string
        - `description` string
        - `url` string
  - `speaker_role` 'enacting' | 'experiencing' | 'reporting_other' | 'unclear': The speaker's relationship to the conduct described. `speaker_stance` cannot separate a victim from a professional reporting on someone else, because both are `describing_third_party`; this can. `enacting` = the speaker carries the conduct out, `experiencing` = the conduct is directed at them, `reporting_other` = neither party.
  - `reporter_capped` boolean: True when `recommended_action` was lowered from `immediate_intervention` to `flag_for_review` because the speaker is reporting on a third party rather than involved in the conduct. Requires both `speaker_role: reporting_other` AND no first-person self-reference in the text. `severity`, `level` and `risk_score` are NOT affected and the action never drops below `flag_for_review`, so the case still reaches a human queue at full weight. Absent when no cap applied.
  - `soft_evidence_capped` boolean: True when `recommended_action` was lowered from `flag_for_review` to `monitor` because the content is in the medium band and names only directive or checking-in behaviour (a rule, a curfew, a request for a location, a disagreement), with no threat, isolation, financial control, surveillance tooling or sign of impact on the person. `detected`, `severity`, `level` and `risk_score` are NOT changed, and the case stays visible at `monitor` so a pattern can still build across a conversation. Never applied at or above 0.6 severity, never to an urgent action. Absent when it did not apply.
  - `soft_evidence_capped_reason` string: Human-readable explanation. Present only when soft_evidence_capped is true.
  - `document_safekeeping_capped` boolean: True when a review alert was lowered to `monitor` because a separate reading found a message about documents held for safekeeping or a practical purpose (a parent carrying the family's passports on a journey, papers kept together at home), with nothing saying the owner wants them and cannot have them, and nobody restricted, threatened or afraid. What defines this harm is documents taken or kept against a person's will. It never applies to an urgent action, to a severity of 0.6 or more, or when anything harder is named beside it. `detected` is false and the result stays visible at `monitor`.
  - `document_safekeeping_capped_reason` string: Human-readable explanation. Present only when document_safekeeping_capped is true.
  - `ordinary_discipline_capped` boolean: True when a review alert was lowered to `monitor` because a separate reading found a parent or carer giving a child a rule or a consequence that has a stated reason and an end (no phone tonight, staying in until the weekend, a console put away until the homework is handed in), with no basic need and no contact with others taken away, nobody frightened or degraded, and no threat or violence. What defines this harm is a pattern that leaves a child unsafe, alone, deprived or afraid. It never applies to an urgent action, to a critical level, or when a threat, degradation, money or anything harder is named. `detected` is false and the result stays visible at `monitor`.
  - `ordinary_discipline_capped_reason` string: Human-readable explanation. Present only when ordinary_discipline_capped is true.
  - `speaker_stance` 'deploying' | 'describing_third_party' | 'unclear': The model's own conclusion on whether the speaker is exercising a controlling tactic themselves or asking about/describing/reporting one performed by someone else -- e.g. a danger-assessment screening question from an advocate or counsellor. Feeds into severity/action -- see action_capped/severity_capped below.
  - `action_capped` boolean: True when speaker_stance capped a suppressive recommended_action (e.g. block) below itself, because the model identified the speaker as describing/countering a third party's conduct rather than deploying it themselves.
  - `action_capped_reason` string: Human-readable explanation of the action_capped adjustment, present only when action_capped is true.
  - `severity_capped` boolean: True when speaker_stance capped severity/risk_score/level at a ceiling (not to zero) for the same reason as action_capped -- a genuine third-party disclosure or exposure report still reaches human review at the ceiling; only a purely educational/protective description is prevented from reading as comparable to a real attack.
  - `severity_capped_reason` string: Human-readable explanation of the severity_capped adjustment, present only when severity_capped is true.

---

[API](https://skmtc.dev/tuteliq/apis/tuteliq-api.md) · [All operations](https://skmtc.dev/tuteliq/apis/tuteliq-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tuteliq/apis/tuteliq-api/revisions/8490332e0031?raw)
