Brand voice, shared with Socie
Works todayReuses the one brand voice your workspace already set for Socie — tone, audience and an avoid list — rather than keeping a second copy that could drift.
Available now
In build
The whole team
Nineteen specialists, each with a defined job and an honest status label.
See all nineteenLekha drafts copy in your saved brand voice and checks its own output afterward — holding any price, statistic or health, financial or legal claim your brief never actually supplied, and it will not accept 'make this pass review' as an instruction.
Lekha writes seven formats of copy — WhatsApp messages, social captions, product descriptions, marketing emails, SMS, ad headlines and website sections — in eleven languages including Hinglish, drawing on facts your workspace has actually recorded rather than inventing them, plus dedicated tools for SEO articles, personas, campaign asset sets, originality checking and AI-detection. All twenty-one of Lekha's specified capabilities are built; most need an LLM key to actually generate, and two need a separate Originality.ai credential.
The distinguishing idea is that the boundary is enforced after generation, not asked for in the prompt. A draft is scanned for regulated language and for any specific number, price or proper noun that appears in nothing you supplied, and a document cannot reach approved while a flag from that scan is still open — an approval a later edit genuinely re-opens, not a checkbox that survives a rewrite.
It will not state a price, statistic or claim your brief never gave it
Every draft is scanned for prices, percentages, ratings, customer counts, founding years and warranty durations, and anything that appears in nothing you supplied — brief, pasted source material, or your workspace's own recorded facts — is held for review rather than published quietly.
It will not soften a regulated claim so it 'passes'
Health, financial and legal language is held for a human even when you typed the words into the brief yourself — 'cures diabetes' in your own brief does not make it safe to publish. There is deliberately no rewrite action that means 'make this pass review'; every rewrite re-runs the same guard over its own output.
It will not let an approval survive a later edit
Every write path — a new version, a rewrite, a restore — runs through the same function, and a document that was approved drops back out of approved the moment new content lands, because an approval is of specific words, not of the document forever. There is no override parameter and no role that skips the flag decisions.
It will not publish anything, ever
Lekha hands back text. There is no send route and no published status — Socie, Patri and Wavy each have their own approval gate and consent machinery, and a shortcut from here into any of them would route around all three.
It will not keep a second brand voice
There is no Lekha-owned brand-voice table. It reads the one profile your workspace already set for Socie, because a shop's tone forking depending on which agent it was last edited from is a worse failure than the coupling.
Getting started
Add your real prices, materials, certifications or hours as facts. This is the single highest-leverage step: the guard holds any specific your sources don't contain, so a shop with a real price list that never records it gets its own true price flagged every time.
Give a brief, pick a format and a language, and get back copy in your brand voice — with the check result attached, not a separate step you have to remember to run.
Turn a draft into a document to get version history, a diff on every change, and the approval workflow — or just paste your own copy into /check with no model configured at all.
Capabilities
Reuses the one brand voice your workspace already set for Socie — tone, audience and an avoid list — rather than keeping a second copy that could drift.
whatsapp_message, social_caption, product_description, email, sms, ad_headline and website_section, each with a real word or character limit that is now measured, not just described in the prompt. Draws source material from your recorded facts by default. Needs an LLM key; the fact store does not.
Needs: LLM_API_KEY. The knowledge base and the length check work without one.
Hinglish, English, Hindi, Marathi, Gujarati, Tamil, Telugu, Kannada, Bengali, Punjabi and Malayalam — named individually, because a model told only 'Hindi' produces transliteration nobody actually speaks.
Needs: LLM_API_KEY.
Holds numbers, prices and proper nouns fixed while making the rest read as a native speaker would write it. The avoid list is deliberately not re-checked across a language change — a Hindi draft can't be honestly checked against terms you wrote in English.
Needs: LLM_API_KEY.
The guard re-checks that the rewrite introduced no fact the original lacked, and a word-level diff reports any price, number or proper noun the rewrite dropped — the guard catches additions and this catches deletions.
Needs: LLM_API_KEY.
Shorten, expand, simplify, fix grammar, more formal, more casual — each returns what it dropped. There is deliberately no action meaning 'make this pass review': the guard runs again over every rewrite's own output.
Needs: LLM_API_KEY.
A workspace's own guideline rules, beyond the brand voice's avoid list: forbidden-term and required-phrase rules are pattern-matched — no model, no credential, the same check the avoid list already uses — and persisted per-guideline so 'which rule keeps failing' is a query rather than a re-read of blobs. tone_style rules cannot be pattern-matched by definition ('always write in second person' is not a regex) and are skipped and named as skipped, never silently passed, when no model is configured.
Needs: LLM_API_KEY for the tone/style half only — forbidden-term and required-phrase rules work without one.
URL extraction (through the same SSRF-guarded fetch Khoji's audits use) and pasted-text sources both work, one guarded generation call per target format, all conditioned on the same extracted source. Each output carries a source_excerpt — the source sentence it overlaps with most by word-overlap — so a derived caption is traceable back to a specific passage. Audio and video sources are refused by name: paste a transcript as source_type='text' instead.
Needs: LLM_API_KEY. Extracting the source itself needs no credential.
Platform-aware captions with hashtags, guarded and length-checked the same way every other draft is. image_ref is passed to the model as text context only — there is no vision call, so a caption is not actually grounded in what the photo shows; that gap is real and unbuilt.
Needs: LLM_API_KEY.
Single-item and row-by-row bulk generation, each guarded against inventing a material, ingredient or spec the row did not supply. Bulk rows arrive as a JSON array in the request body, not a CSV upload — a deliberate simplification named honestly. Capped at 50 rows per batch, with partial-failure handling: one bad row is marked failed and the rest of the batch still completes.
Needs: LLM_API_KEY.
Subject, body and CTA in one call; subject-only regeneration is a separate short call so A/B variants can be refreshed without redrafting the body. The guard also checks for unverified promotional superlatives ('guaranteed', 'the best', 'world-class') on top of the usual checks.
Needs: LLM_API_KEY.
Outline-first, then one model call per section, then a meta description and FAQ — chained so a 1200-word article costs roughly what its length costs. The draft runs through the same claim guard as every other Lekha output, plus a new pattern that catches uncited 'studies show' statistics.
Needs: LLM_API_KEY. Capturing the brief and hand-editing a draft afterward need no credential.
Reuses Khoji's own on-page checklist and its keyword-placement scorer rather than rebuilding either. A URL is fetched through the same SSRF-guarded client Khoji's audits use; pasted body copy is scored directly with no fetch at all. Every score carries checked_at so it never implies a live rank. No credential, no vendor.
A flag per finding, a decision that carries who decided and when, and a document that cannot reach approved while a flag on its current version is open. Needs no model — the same offline pattern-match guard Patri already enforces a hold with.
Every version is kept, never overwritten. A word-level diff names what a later version dropped, and a restore writes a new version rather than rewinding — the history between never disappears. No model call: diffing plain text doesn't need one.
The three states work, with approval gated on flag decisions and a queue of what's waiting. Inline comments (anchored to a position, resolvable) and per-role permissions — viewer, editor, approver, admin — now sit beside the workflow. The gate is opt-in per workspace: no roles configured means no restriction, the right default for a one-person workspace, and it turns on the moment a workspace assigns anyone a role. A comment or status change notifies everyone else with a role via the existing in-app bell — no credential.
Persona CRUD, with a 'reviewed' gate before a persona is treated as ready to use broadly, works with no credential; deleting one retires rather than deletes so a document generated against it keeps a resolvable reference. /write accepts a persona_id and blends its pain points and preferred tone into the same generation pipeline and the same guard — not a second code path.
Needs: LLM_API_KEY, the same requirement multi-format-generator already carries. Persona CRUD needs none.
A first-party copy-strength checklist (CTA presence, hook strength, specificity, length-for-format, filler-word count), genuinely deterministic and never called a prediction — every response says outright that there is no real outcome data behind it, unlike a vendor score dressed up as machine learning. Scoring and ranking several drafts against each other work with no credential.
Needs: LLM_API_KEY, only for the boost rewrite targeted at the lowest-scoring factors.
One key message derived from the brief, then one generation call per asset type conditioned on that same message rather than each asset inventing its own summary. A real consistency check compares every asset's own offer numbers against the brief's declared key facts, or against each other when none were declared, and flags the ones that disagree.
Needs: LLM_API_KEY for generating assets. Creating a campaign works without one (the key message falls back to a plain concatenation).
Cannot be built in-house — checking against 'everything published on the web' needs a web-scale index nobody here has. The full local half is real: request storage, response parsing defensive against an undocumented provider shape, and a fix for a false-positive problem — marking a match 'acceptable boilerplate' carries that acknowledgement forward automatically onto the same URL on a later check.
Needs: ORIGINALITY_API_KEY (Originality.ai). Unset on this deployment, so every check refuses with a 503 naming it.
Uses the same provider account plagiarism-originality-check uses (Originality.ai's scan/ai endpoint), since telling AI-generated text from human text needs a classifier trained for exactly that, not a language model guessing about its own kind. No accuracy percentage is ever stated in a response. A provider failure is recorded on the row rather than surfaced as a 500.
Needs: ORIGINALITY_API_KEY (Originality.ai). Unset on this deployment, so every check refuses with a 503 naming it.
API surface
Every route below is mounted and reachable today. Requests and responses are real shapes, not illustrations.
/api/v1/agents/lekha/factsRecord a fact the guard may treat as evidence. A testimonial needs an attribution.
Request
{
"category": "pricing",
"label": "2BHK deposit",
"body": "Security deposit is ₹50,000, refundable."
}Response
{
"id": "f-102",
"category": "pricing",
"label": "2BHK deposit",
"body": "Security deposit is ₹50,000, refundable.",
"is_active": true
}Every draft returns the guard's own verdict attached, not a separate call to make.
/api/v1/agents/lekha/writeDraft copy from a brief, checked before it's handed back.
Request
{
"brief": "New 2BHK listing in Kandivali, ready to move in",
"format": "social_caption",
"language": "hinglish",
"variations": 1
}Response
{
"copy": "2BHK ready to move in — Kandivali mein aapka naya ghar. Deposit sirf ₹50,000. DM for a visit.",
"format": "social_caption",
"language": "hinglish",
"held_for_review": false,
"summary": "",
"findings": [],
"facts_used": [
{
"id": "f-102",
"category": "pricing",
"label": "2BHK deposit"
}
]
}/api/v1/agents/lekha/writeThe same request, but the model added a price the brief and facts don't support.
Request
{
"brief": "New 2BHK listing",
"format": "social_caption",
"use_knowledge_base": false
}Response
{
"copy": "2BHK ready to move in — only ₹35,000 deposit, limited time!",
"held_for_review": true,
"summary": "1 unsupported specific found.",
"findings": [
{
"kind": "unsupported_specific",
"category": "price",
"text": "₹35,000",
"reason": "This price does not appear in the brief, the source material, or your recorded facts."
}
]
}/api/v1/agents/lekha/rewriteShorten existing copy — the guard reports what the shortened version dropped.
Request
{
"text": "2BHK ready to move in, deposit ₹50,000, near Kandivali station.",
"action": "shorten"
}Response
{
"copy": "2BHK ready to move in near Kandivali station.",
"action": "shorten",
"changes": {
"dropped_specifics": [
"₹50,000"
],
"added": [],
"removed": [
"deposit",
"₹50,000"
]
},
"held_for_review": false
}/api/v1/agents/lekha/formatsThe seven formats with their real limits, the eleven languages, and the six rewrite actions.
Response
{
"formats": [
{
"value": "sms",
"description": "an SMS under 160 characters",
"max_chars": 160
},
{
"value": "social_caption",
"description": "a social caption with a hook in the first line, under 50 words",
"max_words": 50
}
],
"languages": [
"hinglish",
"english",
"hindi",
"marathi",
"..."
],
"max_variations": 5
}/api/v1/agents/lekha/checkRun the guard over copy you wrote yourself. Needs no AI model.
Request
{
"text": "Guaranteed returns, no risk — invest today!",
"use_knowledge_base": false
}Response
{
"held_for_review": true,
"summary": "2 regulated claims found.",
"findings": [
{
"kind": "regulated_claim",
"category": "financial",
"text": "Guaranteed returns",
"reason": "Reads as a financial guarantee."
},
{
"kind": "regulated_claim",
"category": "financial",
"text": "no risk",
"reason": "Reads as a financial guarantee."
}
]
}/api/v1/agents/lekha/documentsKeep a piece of copy, with its history and its flags, starting from a written draft.
Request
{
"content": "2BHK ready to move in — ₹35,000 deposit only.",
"title": "Kandivali listing",
"format": "social_caption"
}Response
{
"id": "doc-9",
"title": "Kandivali listing",
"status": "in_review",
"current_version_number": 1,
"version": {
"version_number": 1,
"held_for_review": true
},
"flags": [
{
"id": "flag-1",
"kind": "unsupported_specific",
"category": "price",
"text": "₹35,000",
"status": "open"
}
]
}/api/v1/agents/lekha/documents/{document_id}/statusTry to approve — refused while a flag on the current version is still open.
Request
{
"status": "approved"
}Response
{
"detail": {
"message": "1 flag(s) on this version still need a decision. A person has to look at each one before this can be approved.",
"open_flags": 1,
"document_id": "doc-9"
}
}/api/v1/agents/lekha/claims/{flag_id}/decisionDecide the flag. Only after every open flag is decided can the document be approved.
Request
{
"decision": "rejected",
"note": "Real deposit is ₹50,000 — draft needs a fix, not an override."
}Response
{
"id": "flag-1",
"status": "rejected",
"reviewer_user_id": "usr_9",
"open_flags_remaining": 0
}/api/v1/agents/lekha/documents/{document_id}/diff?from_version=1&to_version=2What changed between two versions, and what specifically went missing.
Response
{
"changes": {
"added": [
"fixed"
],
"removed": [],
"dropped_specifics": []
}
}/api/v1/agents/lekha/translateLocalise, holding numbers and proper nouns fixed.
Request
{
"text": "2BHK ready to move in, deposit ₹50,000.",
"language": "tamil"
}Response
{
"copy": "2BHK உடனடியாக குடியேற தயார், டெபாசிட் ₹50,000.",
"language": "tamil",
"changes": {
"dropped_specifics": []
},
"held_for_review": false
}Limits and gotchas
The guard never withholds the text entirely — withholding it would just push you to regenerate until the guard happened to stay quiet, which selects for copy that evades the check rather than copy that is true.
A held variation does not hold the others — often three of five are fine and one invented a warranty.
A Hindi draft can't be honestly checked against terms you wrote in English, so the brand-voice avoid list is skipped specifically on /translate.
An unattributed testimonial is a fabricated one with the name left off — recording one without saying who said it is refused.
A fact that was true last quarter is the evidence behind copy that went out last quarter; deleting it would make an approved document unexplainable afterward.
Word and character caps are measured against FORMAT_SPECS, not freely configurable per workspace.
What this needs from you
Everything else on this page works with none of these connected.
| Credential | Unlocks |
|---|---|
| LLM_API_KEY | Almost every generation route: /write, /adjust-tone, /rewrite, /translate, /captions/generate, /product-copy/generate, /email-copy/generate, /seo/articles, the campaign asset generator, the persona-conditioned draft, the copy-score boost rewrite, and the tone/style half of brand-guideline compliance. The knowledge base, the claim guard, /check, and creating (not generating) a persona or campaign all work without it. |
| ORIGINALITY_API_KEY (Originality.ai) | The two capabilities an LLM key cannot unlock: plagiarism-originality-check (POST /originality/check) and ai-content-detector (POST /ai-detection/check), both against the same provider account. Unset on this deployment, so both refuse with a 503 naming it. |
Questions
No — it will mark the gap or hold the draft instead. Every specific number, price, rating, count, year or duration in the output is checked against your brief, any pasted source material, and your recorded facts; anything that appears in none of them is held for review.
There is no action for that. Every rewrite action re-runs the same guard over its own output, so a held phrase only clears the hold if it genuinely stopped being a claim — not because it was rephrased around the check.
No. Any new content — a rewrite, a new version, even a restore of an older version — takes the document back out of approved, because the approval was of specific words a person read, not a permanent state.
No, never. It hands back text. Posting it is Socie's, Patri's or Wavy's job, each with its own approval and consent gate.
No — Lekha reads the one brand voice profile your workspace already set for Socie. There is no second profile to keep in sync.
Coming soon