Documentation

Brand Voice Check

Every post is checked against your brand voice before it publishes or schedules. What it compares, how it scores, and how to read the result.

Before anything goes out, BlackOps checks what's written against the brand voice you've set up for that site. Content that doesn't sound like you is stopped with the rules it broke, so you can fix it or go ahead anyway.

When it runs

The check runs at the moment content is committed: when you publish, and when you schedule. Scheduling counts, so a queue is checked when you build it rather than when each item fires. It covers tweets and threads, blog posts, X articles, LinkedIn, Threads and TikTok, and each item in a campaign.

What it checks against

Your site's brand voice in BlackOps: the voice profile plus its style rules (for example "never use em dashes" or "write in first person"). Rules you've scoped to a platform or mode apply only there.

Tweets and threads are also checked against your active reply-guidance rules — the rules learned from reply skips and from feedback you leave on a draft. A drafted reply is held to those as well as to your voice. Rewrites you make in the reply queue change nothing on their own. A repeated edit pattern becomes a suggestion on the brand voice settings page, and only approving it adds it to your voice.

The voice is per site. A post going to one site is checked against that site's voice, not another's.

Account-aware pronouns and approved facts

A site's voice can carry two more things, set on its brand voice record and read by every check against that site — including real publishes, not just the dry run:

  • pronoun_mode: brand or first_person. On brand, third-person phrasing describing product actions, timing or milestones (for example "a new feature", "on a busy Saturday", "merged by lunch") is read as brand voice, not a personal-voice slip, as long as it doesn't use first-person "I". On first_person, first-person narration ("I built...") is the voice itself, and "we" stays a violation.
  • approved_facts: a list of verified, sourced facts ({text, source}). Each fact is enforced two ways: (1) text that draws from an approved fact (restating it or paraphrasing it at the same level of specificity) is never flagged as inventing information, and (2) text that contradicts a fact or adds specific detail not present in it (for example, turning "minutes later" into "4 minutes later") is flagged as a violation that names the specific fact. Each fact is treated as a checkable rule, so a contradiction produces a named violation and not just a lower score.

Both are null/empty until you set them, so an unconfigured site's check behaves exactly as before. Set them with patch_brand_voice, scope site — a pronoun convention and a list of true stories both describe one domain's voice, not the whole account, so the call is refused with scope global.

If you're checking what you'd post from a specific X account rather than a specific site, pass account_username (or account_id) to check_brand_voice instead of domain/site_id. It resolves to that account's own site — case-insensitively, with or without a leading @ — the same way post_tweets already resolves an account to a site, so a BlackOps-voice caption is judged against BlackOps's pronoun and brand rules even when your account default site is a different one.

How it scores

The check is run by Jev, TypeSafe's typed judgment engine. It answers these questions about the text:

  1. Overall voice. How well does it match your voice? There are five levels, from "a reader who knows you wouldn't recognise it as yours" to "every sentence reads as you". The level sets the score: 15, 40, 60, 80 or 95.
  2. Each style rule. Does the text clearly break it? A rule it's fairly sure is broken is listed as a violation.
  3. How serious each break is. This is a separate question: is the break repeated, in a prominent place like the opening, or bad enough on its own that a reader would doubt you wrote it? A serious break caps the score at 49. A break that's certain but minor, such as one slip in a passing phrase, is still listed but doesn't fail the post on its own.

The pass mark is 70. "Your voice throughout, with at most an isolated slip a reader would pass over" (80) passes. "Slips recur often enough that a reader would notice the voice wavering" (60) does not. The levels describe what a reader notices rather than counting sentences, so a long post isn't held to a stricter standard than a short one.

It judges the text as the platform it's going to. A terse reply on X isn't marked down for being terse the way a blog post would be.

It only judges voice. It doesn't check facts, links or whether the content is good.

When it fails

Publishing or scheduling is refused, and the message names what broke, worst first. If nothing specific broke but the overall voice missed, it names the level it landed on.

A violation tied to a specific passage always carries why and what to try instead: reason names the rule or voice anchor it misses, and rewrite is a rewrite of the quoted excerpt in your voice. reason never reads as a generic "drift" note repeated across every passage — it names a rule, or (when none is a clean match) the closest one by the check's own signal, and only falls back to a general description on a surface with no rules configured at all. rewrite prefers a model-generated rewrite, made in the same failing check, and falls back to a mechanical edit (dropping filler words, splitting a run-on sentence) when that call doesn't come back in time — never left blank.

An item that isn't tied to any specific line, such as the closest rule when nothing clearly broke, or the overall-voice summary, is marked advisory: true. It's context for you, not something that moved the score.

A check never fails with nothing to act on. If the voice judged the text poorly but nothing concrete could be pinned to a specific passage or rule, the check passes rather than refusing a publish over an advisory note alone.

The result also carries a score_breakdown: a one-line read of where the score landed (basis), how many points short of the pass mark it is (points_to_pass, 0 once passed), whether a major violation is capping it (cap), and the list of what to fix to clear the pass mark (to_fix, which names the passage and reason behind each item so two violations are never identical strings, and skips advisory items).

A blog post's title is never flagged as a violation. You set it in review, and the check holds you to your own words, not the other way around.

Revise the text and try again, or go ahead anyway with override_brand_voice: true. An override is recorded with who did it, so it stays answerable.

Checking before you publish

To find out whether a draft would pass without publishing it, run the same check as a dry run. In an AI assistant connected to BlackOps, ask it to check the draft's voice; it calls the check_brand_voice tool. Over the API, it's POST /api/v2/brand/voice/check:

{
  "domain": "example.com",
  "surface": "tweet",
  "content": "Shipped the scheduler fix. It polls every 5 minutes now."
}

surface is where the text would go: post (a blog post, with an optional title), tweet, thread (an X thread, with content as one string per tweet), linkedin, threads or tiktok. In place of domain, pass account_username (for example "BlackOpsTools") to check against that X account's own site.

The answer is the verdict publishing would give for that exact text: passed, score, pass_mark, every violation worst first, the passages it quoted as least like you, and on a failure the same message a refused publish returns. Each violation may carry reason and rewrite, and is marked advisory: true when it isn't tied to specific text. A score_breakdown explains how the score was built. It also carries the voice_check object described below.

A dry run publishes nothing and records nothing. Publishing the same text later still runs its own check.

When content goes through without a verdict

The check lets content through rather than blocking your publishing when it can't give a real opinion:

  • No brand voice set up for the site.
  • The site's very first piece of content. There's nothing yet to drift from.
  • The check couldn't run, for example the service didn't answer in time.

These show up as degraded in the result (see below), so they're never confused with a pass.

Empty content (an image-only post, say) is skipped.

Checked once per version

A verdict applies to the exact text that was checked. Publishing or rescheduling the same text reuses it. Change one character and it's checked again.

Reading the result

Publish and schedule responses include a voice_check object:

{
  "status": "passed",
  "engine": "jev",
  "model": "jev:jev-1.13.0",
  "score": 80,
  "threshold": 70
}
FieldMeaning
statuspassed, failed, overridden, degraded (went through without a verdict) or skipped (nothing to check)
reasonWhy there was no verdict: no_voice_configured, first_content, critic_error, critic_unavailable or empty_content
engineWhat judged it: jev, llm (the fallback checker) or mechanical
modelThe exact model behind the verdict
score, thresholdThe score, and the pass mark it was held to
violationsUp to three non-advisory violations, worst first. Advisory items (context notes not tied to specific text) are never listed here; they appear in the full violations array of the check result, marked advisory: true
scalePresent when engine is jev: the five quantised score levels [15,40,60,80,95]. A steady 60 is level 2 (one step below the pass mark); the next level up is 80
reusedThe verdict came from an earlier check of the same text

On a refusal, the same object comes back with the error so you can see exactly why.

Related

Want this page as machine-readable markdown? GET /docs/features/brand-voice-check.md