Chat with a persona
1:1 conversations with memory.
Endpoint: POST /api/v1/chat (alias of POST /api/v1/tools/chat_with_audience). Runs on the CONVERSATION tier (Opus) for realism. Every turn meters tokens → credits.
The full input schema
| Field | Type | What it does |
|---|---|---|
message (required) | string | What you say to the persona this turn. |
audienceId | string | Draw a respondent from this audience. (Provide this or personaId.) |
personaId | string | Talk to one specific persona (sustained interviews, diary studies). |
chatId | string | The memory handle. Returned on turn 1; pass it back on every later turn. |
audienceVersionId | string | Pin a specific audience version for reproducibility. |
seed | integer | Deterministic respondent draw + sampling — same seed, same person. |
interviewGuide | string[] | Topics/questions to steer toward; also focuses world-awareness. |
branchFromMessageId | string | Fork a new line of inquiry from an earlier turn without disturbing the main thread. |
effort | low·medium·high | Reasoning depth (adaptive thinking budget). Higher = more considered, more tokens. |
verbosity | terse·natural·detailed | Answer length. |
worldAware | boolean | Prime the persona with current, dated, sourced real-world facts (live web search). |
awarenessTopic | string | Focus the world-awareness lookup (e.g. "SMB accounting tools 2026"). |
showGroundingPerTurn | boolean | Return the evidence refs that shaped each reply. |
modelTier | string | Override the model tier (defaults to CONVERSATION/Opus). |
The response shape
{ "ok": true, "data": {
"chatId": "chat_abc123", // ← keep this; it's the memory handle
"reply": {
"messageId": "msg_7",
"content": "Honestly? Month-end is a mess of spreadsheets...",
"abstained": false, // true when the persona declined (out of role/knowledge)
"groundingRefs": [ { "sourceId": "src_cadence_stripe", "quote": "..." } ]
},
"transcript": [
{ "messageId": "msg_6", "role": "user", "content": "Walk me through your month-end close." },
{ "messageId": "msg_7", "role": "persona", "content": "Honestly? ..." }
],
"honesty": {
"synthetic": true, // ALWAYS true — this is a synthetic respondent
"groundingStrength": "grounded", // ungrounded | weak | moderate | grounded
"worldAwareness": { "asOf": "2026-06-30", "sources": ["..."] } // present when worldAware:true
}
} }Turn-by-turn: the one rule that matters
Open — no chatId
{ "audienceId": "aud_f1_1", "message": "Walk me through your month-end close.", "seed": 42 }
# → data.chatId = "chat_abc123"Continue — always pass chatId back
{ "audienceId": "aud_f1_1", "chatId": "chat_abc123",
"message": "You said spreadsheets — what breaks first as you grow?" }No chatId = no memory. A call without it starts a brand-new conversation every time. It'll sound coherent (the persona is re-anchored) but it will not remember anything you said before. 90% of "the persona forgot what I told it" bugs are a missing chatId.
Branch — explore a tangent without losing the thread
{ "audienceId": "aud_f1_1", "chatId": "chat_abc123",
"branchFromMessageId": "msg_7",
"message": "Hypothetically, if pricing doubled, does your answer change?" }The branch forks from that message; your main line stays intact. For automated divergence exploration see Branching & flip-tests.
Realism controls, in depth
effort — how hard the persona thinks
low for quick gut reactions (cheaper, faster), high for considered, multi-factor reasoning (a CFO weighing a purchase). Maps to an adaptive thinking-token budget.
verbosity — how much they say
terse = one or two sentences (great for surveys-as-chat), detailed = a fuller narrative (great for discovery interviews).
worldAware — break the training-cutoff frozen-in-time problem
By default a model persona doesn't know what happened after its training cutoff. With worldAware:true, Mavera runs an out-of-character web search before the conversation, distills dated, sourced facts a person in that role would plausibly know, and injects them into the persona's context. The persona references the substance naturally and never says "I looked it up."
{ "audienceId": "aud_f1_1",
"message": "What worries you about the market right now?",
"worldAware": true,
"awarenessTopic": "SMB fintech and interest rates, 2026" }
# → honesty.worldAwareness = { asOf: "2026-06-30", sources: ["...", "..."] }The facts are real and cited; the opinions stay synthetic. World-awareness changes what the persona knows, never fabricates what they think.
Abstention — a feature, not a failure
When you ask something outside what that person would credibly know or decide, the reply comes back with abstained: true:
{ "reply": { "content": "That's not my call — finance owns the budget. I can tell you what I'd push for, but I'm not signing it.", "abstained": true } }This is the bright line protecting you: a synthetic founder won't invent their CFO's exact budget. Treat a cluster of abstentions as a signal you're probing past this persona's role — switch to a personaId who would own that decision, or a different audience.
Talking to one specific person
Pass personaId instead of audienceId to hold a sustained relationship with a single synthetic individual — essential for depth interviews and diary studies where continuity of character matters across many turns.
Cost & headers
Each turn meters tokens → credits (CONVERSATION tier). The response carries X-Credits-Used and X-Tokens-Total; your running balance is on /account and GET /api/v1/usage. Use verbosity:"terse" + effort:"low" for high-volume fielding to control spend.
Recipe: a 4-turn discovery interview
# turn 1 — open (capture chatId)
POST /api/v1/chat { "audienceId":"aud_f1_1", "message":"What's the hardest part of your week?", "verbosity":"detailed" }
# turn 2 — ladder down (reuse chatId)
POST /api/v1/chat { "audienceId":"aud_f1_1", "chatId":"chat_x", "message":"Why does that matter to you?" }
# turn 3 — probe the objection
POST /api/v1/chat { "audienceId":"aud_f1_1", "chatId":"chat_x", "message":"What would have to be true for you to switch tools?" }
# turn 4 — pressure-test with world context
POST /api/v1/chat { "audienceId":"aud_f1_1", "chatId":"chat_x", "worldAware":true, "message":"Given where rates are headed, does that change your timeline?" }
# then: feed the transcript to /docs/insights to mine themes + divergencesCommon pitfalls
| Symptom | Cause / fix |
|---|---|
| "Persona forgot the previous turn" | Missing chatId. Pass the one from turn 1 on every turn. |
| "audience not found" | Build the audience first (Creating an audience); use the returned id. |
| Answers feel generic/averaged | Audience is weakly grounded — check honesty.groundingStrength; ground it (ground_audience) or build from richer sources. |
| Persona keeps abstaining | You're past its role. Use a personaId who owns that decision. |
| Persona seems stuck in the past | Set worldAware:true with an awarenessTopic. |