Get started · 9 / 42
What recall gives you back
recall_and_enrich returns trace_id, known, caller, memory (summary + recent_calls, newest call first, up to five), enrichment, timing_ms and freshness. context_audit_id, memory.facts and memory.fact_ids are optional additive fields: present only when the matching feature is on for your deployment, absent otherwise.
This is the exact response shape POST /v1/recall_and_enrich returns, read from the route handler rather than an older document. Every field below is either always present or explicitly marked optional.
The shape
The caller, memory, and enrichment values below are an illustrative example, not a real caller — the same convention used on API reference.
{
"trace_id": "b6b6c6d2-4f1e-4a57-9d3b-0c9a1f6e2a10",
"context_audit_id": "b6b6c6d2-4f1e-4a57-9d3b-0c9a1f6e2a10",
"known": true,
"caller": {
"name": "Mike Reynolds",
"first_seen_ms": 1750000000000,
"last_seen_ms": 1751000000000,
"total_calls": 3,
"tags": ["fleet", "commercial"],
"custom_attributes": {}
},
"memory": {
"summary": "Repeat customer, 3 calls over 2 months. Fleet account.",
"recent_calls": [
{
"call_id": "7f0c2a64-3b1d-4c8e-8a5f-2d9e1b6c4a37",
"started_at_ms": 1750900000000,
"duration_s": 92,
"summary": "Please confirm my brake inspection for Thursday.",
"outcome": "appointment_booked"
}
]
},
"enrichment": {
"person": { "name": "Mike Reynolds", "title": null, "company": null },
"company": null,
"phone": { "carrier": "Verizon", "line_type": "mobile" }
},
"timing_ms": { "total": "<measured>", "memory_ms": "<measured>", "enrichment_ms": "<measured>", "cache_hit": true },
"freshness": { "memory_age_ms": "<measured>", "enrichment_age_ms": null }
}| Block | What it is |
|---|---|
trace_id | Correlation ID for this request. Log it; it's what support needs to find the request. |
context_audit_id | Optional and additive: present only when the voice feedback loop is enabled for your deployment, absent otherwise. When present it equals trace_id. Do not require it. |
known | Whether a caller record existed before this request — not whether enrichment succeeded. |
caller | Stored profile: name, first_seen_ms, last_seen_ms, total_calls, tags, custom_attributes. total_calls (and the other fields) can be null — that means "unavailable for this request," never "zero" or "none." See API reference for the branching rule. |
memory.summary | A short rolling summary. Empty string for a brand-new caller. |
memory.recent_calls | Call history, newest call first (by when it was recorded), up to five, not ranked by relevance to the current call. Each entry's summary is the text recorded for that call. Your application decides what's worth surfacing. |
enrichment | person, company, phone — each independently nullable. A null block means that source didn't complete inside the request deadline, not that the caller has no such data. |
timing_ms | total, memory_ms, enrichment_ms, cache_hit for this request. A measurement, not a published benchmark — public latency language stays designed for sub-300ms voice recall. |
freshness | memory_age_ms, enrichment_age_ms — how old the served data was when it was served, or null when that isn't known. |
degraded | Present and true only when an authenticated caller-memory read failed and the route returned the safe empty-shape fallback instead of erroring. Absent on a normal response. |
The optional facts field
The response can carry one more field: memory.facts, governed fact strings read from stored memories (the governed-fact substrate described in Locked facts is separate). It is an optional additive field: when the governed-facts read is not enabled for your deployment, the memory block has exactly the fields above, with no facts key at all, so existing integrations parse the same response shape either way. When the field is present its value can be an array, null, or "denied". Alongside it, memory.fact_ids is likewise optional and additive: when present, fact_ids[i] is the memory id of facts[i], so a feedback call can name the memories it refers to.
When facts is present, its value has these meanings:
| Value | Meaning |
|---|---|
| absent | The field is not served in this response. Do not read anything into it. |
[] | The read ran, and this caller has no governed facts. A measurement. |
null | The read did not complete — it exceeded its internal budget, or errored. Unavailable, not empty. |
"denied" | Governed facts were refused for this caller by an authority check, and no governed-facts read ran. Not empty and not a failed read: say nothing about what you do or don't remember. |
null is deliberate, and it is the one to branch on. The governed read is bounded so that a slow read can never delay the voice response, which means it can be dropped while the rest of the packet is served normally under a 200. If a dropped read reported [], a caller whose history we hold would be indistinguishable from a caller we have never seen. So the packet says null instead. Do not coalesce it — facts ?? [] throws away exactly the distinction the field exists to carry.
Treat context_audit_id, facts and fact_ids as optional in your parser: build against the always-present fields, and use the optional ones only when they appear.
Put this in a prompt without wrecking the agent
- Don't dump the JSON. Select the two or three fields that change what the agent should say — usually
caller.name,memory.summaryor the top ofmemory.recent_calls, and one enrichment field — and render them as short natural-language lines, the way the Vapi and Bland kits already do with prompt variables. - Never block the greeting on enrichment.
enrichmentfields are independently nullable by design; a cold caller with all threenullis a normal response, not a failure. Give the agent a useful fallback line for that case. - Don't treat recency as relevance.
recent_callsis ordered newest call first, not by what matters to today's call — filter or pick in your application before it reaches the prompt. - Branch
nullcorrectly.caller.total_calls: nullmeans "unknown," not zero — a prompt that says "this is your first call" on anullcount asserts something you don't know.memory.facts: nullis the same rule: it means the governed read did not complete (and"denied"means it was refused), so say nothing about what you do or don't remember rather than implying you have no history. - Treat
degraded: trueas a signal to soften, not hide. The shape is still safe to render; just don't present a degraded read as complete history.
Next steps
- API reference — the full request/response contract for all three routes.
- Hangup, and what happens next — what changes in this same response after a call ends.
- What isn't built yet — the honest list of what this substrate does not do today.