Conversations with an agent. There is a streaming door too, on a WebSocket that OpenAPI cannot describe — see The WebSocket in the docs.
When and why to call these: the conversations guide.
Every operation here takes an end user’s ephemeral token, never an API key: an API key gets 403. Mint one with POST /v1/ephemeral-keys — see End users and their keys.
List conversations
Section titled “List conversations”GET /v1/agent/sessions
Most recently used first, a page at a time. Messages are not included — read one session for its transcript.
The key’s own end user and nobody else, and only conversations with the agents the key may talk to.
curl "https://api.lumavox.ai/v1/agent/sessions" \ -H "Authorization: Bearer $EPHEMERAL_TOKEN"Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | How many to answer, 1 to 200. 1–200. Default 50. | |
cursor | string | The next_cursor of the page before. At least 1 characters. |
Responses
| Status | Description |
|---|---|
200 |
application/json — One page of conversations, most recent first |
401 |
Missing or invalid credentials |
403 |
The key is valid but its scopes do not cover this capability |
422 |
The request failed validation |
429 |
Rate limit exceeded |
200 response body
| Field | Type | Description |
|---|---|---|
data |
array of SessionInfo | |
data[].agent_id |
string | |
data[].created_at |
string (date-time) | |
data[].end_user_id |
string | Who this conversation is with. Send it again to open another conversation with the same person, carrying their memory. |
data[].id |
string | |
data[].title |
string or null | |
data[].turn_seq |
integer | |
data[].updated_at |
string (date-time) | |
next_cursor |
string or null | Pass as cursor for the next page; null when this is the last. |
Open or continue a conversation
Section titled “Open or continue a conversation”POST /v1/agent/sessions
Called with the end user’s ephemeral key — the conversation is always that person’s, and end_user_id, if sent, must name them.
It continues the person’s most recent conversation with this agent, if there is one young enough to continue — the platform’s agents.sessions.resume_max_age_days (GET /v1/platform/limits), unless the organisation sets its own. Send resume: false to always start a new one — that is what a “new chat” button should do. The response’s resumed says which happened, and a continued conversation arrives with its messages already populated and its turn_seq past zero.
Editing the agent reaches this conversation too, within about a minute, from its next turn onward.
This call makes no model call and no speech call, and a new conversation comes back with no messages — even for an agent that has a first_message. The opening line is the agent’s first turn on /v1/agent/stream, written when a client connects, and streamed and spoken a sentence at a time like every other reply. The socket also decides whether to greet, and ready.greeting says which.
curl -X POST "https://api.lumavox.ai/v1/agent/sessions" \ -H "Authorization: Bearer $EPHEMERAL_TOKEN" \ -H "Content-Type: application/json" \ -d '{"agent_id":"..."}'Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | Required | Which agent this conversation is with. |
end_user_id | string or null | Optional, and only ever the key’s own end user — their id from POST /v1/end-users, or your own id for them. A conversation is always the person the ephemeral key was minted for: the key says who is talking, so this can only confirm it, and naming anybody else is a 403. What the agent remembers about them — durable facts and the standing relationship — is theirs, and carries into every conversation with any of your agents. Set once, when the session opens. It cannot change afterwards. At most 128 characters. | |
resume | boolean or null | Whether to continue this person’s most recent conversation with this agent instead of starting a new one. Unset is the useful default: it resumes when this person has a conversation with this agent young enough to continue — within the organisation’s resume_max_age_days, else the platform’s agents.sessions.resume_max_age_days (GET /v1/platform/limits) — and starts a new one otherwise. That matches what ‘open a conversation with this person’ usually means, and it stops an integration that opens a session per page load from scattering somebody’s history across a dozen fragments. false always starts a new conversation — this is what a ‘new chat’ button should send. true is the same as leaving it unset; it exists so the intent can be stated rather than implied. Nothing is lost by starting fresh: durable facts and the standing relationship belong to the person, not the conversation, and are carried into every new one. The response’s resumed says which happened. | |
title | string or null | Optional label. At most 255 characters. |
Responses
| Status | Description |
|---|---|
201 |
application/json — The conversation — new or continued, per resumed |
400 |
The request could not be read: malformed JSON |
401 |
Missing or invalid credentials |
403 |
The key’s scopes do not cover agents, or it is for another agent or person |
404 |
Unknown agent id |
422 |
The request failed validation |
429 |
Rate limit exceeded |
201 response body
| Field | Type | Description |
|---|---|---|
agent_id |
string | |
created_at |
string (date-time) | |
end_user_id |
string | Who this conversation is with. Send it again to open another conversation with the same person, carrying their memory. |
id |
string | |
title |
string or null | |
turn_seq |
integer | |
updated_at |
string (date-time) | |
messages |
MessagePage | |
messages.data |
array of MessageInfo | |
messages.data[].content |
string | |
messages.data[].created_at |
string (date-time) | |
messages.data[].id |
string | |
messages.data[].knowledge |
MessageKnowledge or null | On a reply: what the agent looked up in its knowledge for it — each lookup’s query, outcome and the entries it found, with their scores. Null when nothing was looked up. |
messages.data[].mood |
string or null | How the agent said it felt when it wrote this reply, when the agent reports moods. Null on user messages, on agents with emotion off, and on any reply where the model did not report a usable one. |
messages.data[].mood_intensity |
number or null | How strongly, 0 to 1. Null when the model gave a label without a number — the label is the half it gets right. |
messages.data[].role |
"user", "assistant" |
|
messages.data[].sources |
array of MessageSource | On a reply: the web pages it read, with the agent’s url_context switch on. Empty when it read none, and on the person’s messages. |
messages.data[].tool_calls |
array of MessageToolCall | On a reply: the calls of your tools it made, in order, each with its arguments and what it answered. Empty when it called none, and on the person’s messages. |
messages.next_cursor |
string or null | Pass as cursor for the next page; null when this is the last. |
relation |
RelationInfo or null | How this agent stands with this person, as the conversation opens. Null when the agent has relation_enabled off, which is not the same as “they have no history”: the counters run for every pair regardless, and returning one for an agent that keeps none would show a relationship the model is not being told about. It belongs to the person, not the conversation, so it carries across every session with the same end_user_id. Here so that rendering it at conversation start needs no second call; GET /v1/agent/relations is still how you read it any other time, and the socket’s relation event is how you learn it moved mid-turn — which is rare, and is the only time it changes. |
relation.agent_id |
string | |
relation.agent_name |
string or null | The agent’s name, so a list of these is readable without resolving ids. |
relation.conversations |
integer | Separate sessions, ever. A stronger signal of familiarity than turns — twenty short visits is a longer acquaintance than one long conversation. |
relation.disclosure |
number | How much the agent has told them about itself, -1 to 1 — the asymmetric half of knowing each other, and the one nothing can count. Deliberately not part of tier: an agent can have opened up to somebody it has come to dislike, and folding this into affection would make that state unrepresentable. |
relation.end_user_id |
string | The person, as eu_.... |
relation.first_met_at |
string (date-time) or null | |
relation.last_seen_at |
string (date-time) or null | |
relation.note |
string or null | One line the model wrote about them. Replaced, not appended — it is rendered into every future prompt for this pair, so it is bounded by design. |
relation.recorded |
integer | How many times the model has recorded a change. Zero on a long history means the tool is not being called, which is the first thing to check when a relationship is not moving. |
relation.summary |
string | The relationship as the model is told it — bands and counted phrases, never numbers. Empty string when there is nothing worth saying yet, in which case nothing about them is added to the prompt at all — which is a real state and not a missing value. |
relation.tier |
"Despised", "Disliked", "Acquaintances", "Friends", "Good Friends" |
What the agent would call this relationship, and what it is actually told: Despised, Disliked, Acquaintances, Friends or Good Friends. Derived on every read from the axes below and never stored, so it cannot go stale. The rule is one sentence: warmth and trust set the direction, familiarity sets how far it can go — a first conversation caps at Acquaintances however well it went, and the ceiling rises as they talk. Dislike is not capped: an agent can take against somebody on one remark. |
relation.trust |
number | -1 to 1, on the same scale. |
relation.turns |
integer | Turns exchanged, ever. Counted, not judged. |
relation.warmth |
number | -1 to 1. Zero is indifference, which is where every relationship starts. |
resumed |
boolean | True when this is a conversation that already existed rather than a new one — see resume on the request. Worth branching on: a resumed session arrives with its messages already populated and its turn_seq past zero, so a client that assumed an empty transcript will render nothing until the next turn. |
Read a conversation
Section titled “Read a conversation”GET /v1/agent/sessions/{session_id}
The transcript, oldest first, exactly as stored — including every turn from before a compaction. Compaction shortens what the model is sent, never what you can read back.
The most recent limit messages, because what a client renders is the end of a conversation.
To read it all, page backwards: pass messages.next_cursor as cursor; it is null once you have reached the start.
Each message carries the mood the agent reported when it wrote it, if the agent reports moods.
curl "https://api.lumavox.ai/v1/agent/sessions/$SESSION_ID" \ -H "Authorization: Bearer $EPHEMERAL_TOKEN"Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
session_id | string | Required |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
limit | integer | How many messages, counting back from the newest, 1 to 200. The page itself is oldest first. 1–200. Default 50. | |
cursor | string | messages.next_cursor of the page before, for the older messages. At least 1 characters. |
Responses
| Status | Description |
|---|---|
200 |
application/json — The conversation and one page of its messages |
401 |
Missing or invalid credentials |
403 |
The key is valid but its scopes do not cover this capability |
404 |
Unknown session id |
422 |
The request failed validation |
429 |
Rate limit exceeded |
200 response body
| Field | Type | Description |
|---|---|---|
agent_id |
string | |
created_at |
string (date-time) | |
end_user_id |
string | Who this conversation is with. Send it again to open another conversation with the same person, carrying their memory. |
id |
string | |
title |
string or null | |
turn_seq |
integer | |
updated_at |
string (date-time) | |
messages |
MessagePage | |
messages.data |
array of MessageInfo | |
messages.data[].content |
string | |
messages.data[].created_at |
string (date-time) | |
messages.data[].id |
string | |
messages.data[].knowledge |
MessageKnowledge or null | On a reply: what the agent looked up in its knowledge for it — each lookup’s query, outcome and the entries it found, with their scores. Null when nothing was looked up. |
messages.data[].mood |
string or null | How the agent said it felt when it wrote this reply, when the agent reports moods. Null on user messages, on agents with emotion off, and on any reply where the model did not report a usable one. |
messages.data[].mood_intensity |
number or null | How strongly, 0 to 1. Null when the model gave a label without a number — the label is the half it gets right. |
messages.data[].role |
"user", "assistant" |
|
messages.data[].sources |
array of MessageSource | On a reply: the web pages it read, with the agent’s url_context switch on. Empty when it read none, and on the person’s messages. |
messages.data[].tool_calls |
array of MessageToolCall | On a reply: the calls of your tools it made, in order, each with its arguments and what it answered. Empty when it called none, and on the person’s messages. |
messages.next_cursor |
string or null | Pass as cursor for the next page; null when this is the last. |
relation |
RelationInfo or null | How this agent stands with this person, as the conversation opens. Null when the agent has relation_enabled off, which is not the same as “they have no history”: the counters run for every pair regardless, and returning one for an agent that keeps none would show a relationship the model is not being told about. It belongs to the person, not the conversation, so it carries across every session with the same end_user_id. Here so that rendering it at conversation start needs no second call; GET /v1/agent/relations is still how you read it any other time, and the socket’s relation event is how you learn it moved mid-turn — which is rare, and is the only time it changes. |
relation.agent_id |
string | |
relation.agent_name |
string or null | The agent’s name, so a list of these is readable without resolving ids. |
relation.conversations |
integer | Separate sessions, ever. A stronger signal of familiarity than turns — twenty short visits is a longer acquaintance than one long conversation. |
relation.disclosure |
number | How much the agent has told them about itself, -1 to 1 — the asymmetric half of knowing each other, and the one nothing can count. Deliberately not part of tier: an agent can have opened up to somebody it has come to dislike, and folding this into affection would make that state unrepresentable. |
relation.end_user_id |
string | The person, as eu_.... |
relation.first_met_at |
string (date-time) or null | |
relation.last_seen_at |
string (date-time) or null | |
relation.note |
string or null | One line the model wrote about them. Replaced, not appended — it is rendered into every future prompt for this pair, so it is bounded by design. |
relation.recorded |
integer | How many times the model has recorded a change. Zero on a long history means the tool is not being called, which is the first thing to check when a relationship is not moving. |
relation.summary |
string | The relationship as the model is told it — bands and counted phrases, never numbers. Empty string when there is nothing worth saying yet, in which case nothing about them is added to the prompt at all — which is a real state and not a missing value. |
relation.tier |
"Despised", "Disliked", "Acquaintances", "Friends", "Good Friends" |
What the agent would call this relationship, and what it is actually told: Despised, Disliked, Acquaintances, Friends or Good Friends. Derived on every read from the axes below and never stored, so it cannot go stale. The rule is one sentence: warmth and trust set the direction, familiarity sets how far it can go — a first conversation caps at Acquaintances however well it went, and the ceiling rises as they talk. Dislike is not capped: an agent can take against somebody on one remark. |
relation.trust |
number | -1 to 1, on the same scale. |
relation.turns |
integer | Turns exchanged, ever. Counted, not judged. |
relation.warmth |
number | -1 to 1. Zero is indifference, which is where every relationship starts. |
resumed |
boolean | True when this is a conversation that already existed rather than a new one — see resume on the request. Worth branching on: a resumed session arrives with its messages already populated and its turn_seq past zero, so a client that assumed an empty transcript will render nothing until the next turn. |
Delete a conversation
Section titled “Delete a conversation”DELETE /v1/agent/sessions/{session_id}
Deletes every message in it too. This cannot be undone.
curl -X DELETE "https://api.lumavox.ai/v1/agent/sessions/$SESSION_ID" \ -H "Authorization: Bearer $EPHEMERAL_TOKEN"Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
session_id | string | Required |
Responses
| Status | Description |
|---|---|
204 |
The session and its messages are gone |
401 |
Missing or invalid credentials |
403 |
The key is valid but its scopes do not cover this capability |
404 |
Unknown session id |
422 |
The request failed validation |
429 |
Rate limit exceeded |
Compact a conversation
Section titled “Compact a conversation”POST /v1/agent/sessions/{session_id}/compact
Summarises everything said so far but the last few exchanges and starts the next turn from that summary, then those exchanges word for word, instead of the full history. How many messages stay is the platform’s agents.memory.compaction.keep_messages; a conversation no longer than that is summarised whole.
The transcript is not changed — GET /v1/agent/sessions/{id} still returns every message. What changes is only what the model is sent.
This is a model call, charged to the key. Agents can also do it automatically by setting a token threshold.
curl -X POST "https://api.lumavox.ai/v1/agent/sessions/$SESSION_ID/compact" \ -H "Authorization: Bearer $EPHEMERAL_TOKEN" \ -H "Content-Type: application/json" \ -d '{}'Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
session_id | string | Required |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
extract_facts | boolean or null | Whether to also extract durable facts about the end user, in the same model call. Unset uses the agent’s own setting. |
Responses
| Status | Description |
|---|---|
200 |
application/json — The summary that replaced the turns it covers |
400 |
The request could not be read: malformed JSON |
401 |
Missing or invalid credentials |
402 |
The key has spent its credit — mint another; nothing was charged |
403 |
The key is valid but its scopes do not cover this capability |
404 |
Unknown session id |
409 |
Another compaction of this conversation finished first |
422 |
Nothing new to compact since the last compaction, or the model’s summary was empty or no shorter than what it would replace; nothing was charged |
429 |
Rate limit exceeded |
502 |
The model provider failed or timed out |
503 |
The model provider is not configured |
200 response body
| Field | Type | Description |
|---|---|---|
after_seq |
integer | The last message this summary covers. Anything after it — the last few exchanges, kept word for word, then whatever is said next — is still sent to the model verbatim. |
facts_stored |
integer | Durable facts written to long-term memory by this call. Zero when extraction was not asked for, or when there was nothing worth keeping. |
input_tokens |
integer or null | |
model |
string or null | What produced the summary. |
output_tokens |
integer or null | |
session_id |
string | |
summary |
string | What the next turn will start from. |
trigger |
"manual", "automatic", "idle" |
manual (a client asked) or automatic (a turn reached the agent’s threshold, or a long compaction’s next pass). idle is no longer written: it marks compactions made when a quiet conversation’s facts were read, before facts were read on their own. |
Extract what is worth remembering
Section titled “Extract what is worth remembering”POST /v1/agent/sessions/{session_id}/extract
Compacts the conversation and extracts durable facts about the user into long-term memory, in one model call, charged to the key.
Identical to POST /sessions/{id}/compact with {"extract_facts": true} — it exists because remembering and shortening are different intentions even though they are one request.
curl -X POST "https://api.lumavox.ai/v1/agent/sessions/$SESSION_ID/extract" \ -H "Authorization: Bearer $EPHEMERAL_TOKEN"Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
session_id | string | Required |
Responses
| Status | Description |
|---|---|
200 |
application/json — The facts pulled out of the conversation and stored |
401 |
Missing or invalid credentials |
402 |
The key has spent its credit — mint another; nothing was charged |
403 |
The key is valid but its scopes do not cover this capability |
404 |
Unknown session id |
409 |
Another compaction of this conversation finished first |
422 |
Nothing new to compact since the last compaction, or the model’s summary was empty or no shorter than what it would replace; nothing was charged |
429 |
Rate limit exceeded |
502 |
The model provider failed or timed out |
503 |
The model provider is not configured |
200 response body
| Field | Type | Description |
|---|---|---|
after_seq |
integer | The last message this summary covers. Anything after it — the last few exchanges, kept word for word, then whatever is said next — is still sent to the model verbatim. |
facts_stored |
integer | Durable facts written to long-term memory by this call. Zero when extraction was not asked for, or when there was nothing worth keeping. |
input_tokens |
integer or null | |
model |
string or null | What produced the summary. |
output_tokens |
integer or null | |
session_id |
string | |
summary |
string | What the next turn will start from. |
trigger |
"manual", "automatic", "idle" |
manual (a client asked) or automatic (a turn reached the agent’s threshold, or a long compaction’s next pass). idle is no longer written: it marks compactions made when a quiet conversation’s facts were read, before facts were read on their own. |
What is remembered about an end user
Section titled “What is remembered about an end user”GET /v1/agent/facts
What is stored about the key’s end user, oldest first, a page at a time — up to 200 a page, 100 by default. A fact learnt while you page lands on the last page.
Facts are shared across every agent you run — the same person talking to a different character is still the same person. agent_id records which one was being talked to when a fact was learned.
A fact a later conversation changed or contradicted is replaced: hidden from the agents and from this list, and deleted the platform’s agents.memory.facts.replaced_kept_days later (GET /v1/platform/limits). include_replaced=true lists those too, each with replaced_at, and replaced_by naming the fact that took its place.
curl "https://api.lumavox.ai/v1/agent/facts" \ -H "Authorization: Bearer $EPHEMERAL_TOKEN"Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
end_user_id | string | Optional, and only ever the key’s own end user — their eu_... id or your own id for them. Anybody else is a 403. | |
include_replaced | boolean | Also list the facts a later conversation replaced or removed, until they are deleted, the platform’s agents.memory.facts.replaced_kept_days later (GET /v1/platform/limits). Default false. | |
limit | integer | How many to answer, 1 to 200. 1–200. Default 100. | |
cursor | string | The next_cursor of the page before. At least 1 characters. |
Responses
| Status | Description |
|---|---|
200 |
application/json — What the platform remembers about this end user |
401 |
Missing or invalid credentials |
403 |
The key is valid but its scopes do not cover this capability |
422 |
The request failed validation |
429 |
Rate limit exceeded |
200 response body
| Field | Type | Description |
|---|---|---|
data |
array of FactInfo | |
data[].agent_id |
string or null | Which agent was being talked to when this was learned. |
data[].confirmed_at |
string (date-time) or null | When a conversation last said it again; null for never. The agents see the most recently confirmed first when not every fact fits. |
data[].created_at |
string (date-time) | |
data[].id |
string | |
data[].replaced_at |
string (date-time) or null | When a later conversation replaced or removed it; null while it stands. Listed only with include_replaced=true. |
data[].replaced_by |
string or null | The fact that replaced it — moved city, changed job — or null when it was removed with nothing in its place, or stands. |
data[].scope |
"person", "agent" |
person: recalled by every agent of yours that shares its facts. agent: kept to one agent (agent_id), which alone recalls it — learnt by an agent whose facts_scope is agent, or added for that agent. |
data[].source |
"conversation", "customer" |
conversation: a reading learnt it. customer: you added or corrected it — a later conversation never replaces or removes it. |
data[].text |
string | |
data[].updated_at |
string (date-time) | When it last changed: its replaced_at once replaced, else when you last corrected it, else its created_at. |
next_cursor |
string or null | Pass as cursor for the next page; null when this is the last. |
Remember something about the end user
Section titled “Remember something about the end user”POST /v1/agent/facts
Adds a fact about the key’s end user, as you state it, for the agents to know from their next turn: what your app already knows and wants them to keep — a name, a preference.
It is yours: a later conversation confirms it but never replaces or removes it, and its source is customer. With agent_id only that agent recalls it; without, every agent of yours does. Free.
Idempotent by its words: a fact that already says the same thing is answered 200 — now yours — and a new one 201. A fact forgotten before is remembered again.
curl -X POST "https://api.lumavox.ai/v1/agent/facts" \ -H "Authorization: Bearer $EPHEMERAL_TOKEN" \ -H "Content-Type: application/json" \ -d '{"text":"Prefers to be called Sam."}'Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
text | string | Required | What to remember about the person, as one plain statement: “Prefers to be called Sam.” At most 500 characters. Never a secret — a password, a code, a card, account or identity number, or a key is refused, since facts are sent to the model on every turn. 1–500 characters. |
agent_id | string or null | Keep it to one agent: only that agent recalls it. Unset or null: every agent of yours that shares its facts — and the ones that keep their own, too. |
Responses
| Status | Description |
|---|---|
200 |
application/json — The fact that already said it, now yours |
201 |
application/json — The fact, remembered |
400 |
The request could not be read: malformed JSON |
401 |
Missing or invalid credentials |
403 |
The key’s scopes do not cover this, or it is pinned to agents and agent_id names another |
422 |
No text, or text that looks like a secret — a password, a code, a card, account or identity number, or a key — which no fact may hold |
429 |
Rate limit exceeded |
200 response body
| Field | Type | Description |
|---|---|---|
agent_id |
string or null | Which agent was being talked to when this was learned. |
confirmed_at |
string (date-time) or null | When a conversation last said it again; null for never. The agents see the most recently confirmed first when not every fact fits. |
created_at |
string (date-time) | |
id |
string | |
replaced_at |
string (date-time) or null | When a later conversation replaced or removed it; null while it stands. Listed only with include_replaced=true. |
replaced_by |
string or null | The fact that replaced it — moved city, changed job — or null when it was removed with nothing in its place, or stands. |
scope |
"person", "agent" |
person: recalled by every agent of yours that shares its facts. agent: kept to one agent (agent_id), which alone recalls it — learnt by an agent whose facts_scope is agent, or added for that agent. |
source |
"conversation", "customer" |
conversation: a reading learnt it. customer: you added or corrected it — a later conversation never replaces or removes it. |
text |
string | |
updated_at |
string (date-time) | When it last changed: its replaced_at once replaced, else when you last corrected it, else its created_at. |
Forget an end user entirely
Section titled “Forget an end user entirely”DELETE /v1/agent/facts
Deletes everything remembered about the key’s end user. Cannot be undone.
It leaves their conversations and the end user itself alone. DELETE /v1/end-users/{id} removes all three, and is what to call when one of your users asks to be forgotten outright.
curl -X DELETE "https://api.lumavox.ai/v1/agent/facts" \ -H "Authorization: Bearer $EPHEMERAL_TOKEN"Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
end_user_id | string | Optional, and only ever the key’s own end user — their eu_... id or your own id for them. Anybody else is a 403. |
Responses
| Status | Description |
|---|---|
200 |
application/json — How many stored facts were deleted |
401 |
Missing or invalid credentials |
403 |
The key is valid but its scopes do not cover this capability |
422 |
The request failed validation |
429 |
Rate limit exceeded |
200 response body
| Field | Type | Description |
|---|---|---|
facts_deleted |
integer | How many stored facts were deleted. |
Correct what is remembered
Section titled “Correct what is remembered”PATCH /v1/agent/facts/{fact_id}
Rewrites one fact about the key’s end user. It becomes yours (source customer), so a later conversation never replaces it, and the old words are kept as forgotten: no reading of what was said before brings them back.
A fact a later conversation already replaced is a 409 — correct the one that replaced it — and so are words another fact already says. Free.
curl -X PATCH "https://api.lumavox.ai/v1/agent/facts/$FACT_ID" \ -H "Authorization: Bearer $EPHEMERAL_TOKEN" \ -H "Content-Type: application/json" \ -d '{"text":"Prefers to be called Sam."}'Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
fact_id | string | Required |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
text | string | Required | What to remember about the person, as one plain statement: “Prefers to be called Sam.” At most 500 characters. Never a secret — a password, a code, a card, account or identity number, or a key is refused, since facts are sent to the model on every turn. 1–500 characters. |
Responses
| Status | Description |
|---|---|
200 |
application/json — The fact, corrected |
400 |
The request could not be read: malformed JSON |
401 |
Missing or invalid credentials |
403 |
The key is valid but its scopes do not cover this capability |
404 |
Unknown fact id |
409 |
Replaced by a later conversation, or another fact already says that |
422 |
No text, or text that looks like a secret — a password, a code, a card, account or identity number, or a key — which no fact may hold |
429 |
Rate limit exceeded |
200 response body
| Field | Type | Description |
|---|---|---|
agent_id |
string or null | Which agent was being talked to when this was learned. |
confirmed_at |
string (date-time) or null | When a conversation last said it again; null for never. The agents see the most recently confirmed first when not every fact fits. |
created_at |
string (date-time) | |
id |
string | |
replaced_at |
string (date-time) or null | When a later conversation replaced or removed it; null while it stands. Listed only with include_replaced=true. |
replaced_by |
string or null | The fact that replaced it — moved city, changed job — or null when it was removed with nothing in its place, or stands. |
scope |
"person", "agent" |
person: recalled by every agent of yours that shares its facts. agent: kept to one agent (agent_id), which alone recalls it — learnt by an agent whose facts_scope is agent, or added for that agent. |
source |
"conversation", "customer" |
conversation: a reading learnt it. customer: you added or corrected it — a later conversation never replaces or removes it. |
text |
string | |
updated_at |
string (date-time) | When it last changed: its replaced_at once replaced, else when you last corrected it, else its created_at. |
Forget one fact
Section titled “Forget one fact”DELETE /v1/agent/facts/{fact_id}
Deletes a single memory of the key’s end user. Cannot be undone.
curl -X DELETE "https://api.lumavox.ai/v1/agent/facts/$FACT_ID" \ -H "Authorization: Bearer $EPHEMERAL_TOKEN"Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
fact_id | string | Required |
Responses
| Status | Description |
|---|---|
204 |
The fact is gone |
401 |
Missing or invalid credentials |
403 |
The key is valid but its scopes do not cover this capability |
404 |
Unknown fact id |
422 |
The request failed validation |
429 |
Rate limit exceeded |
How the agents feel about an end user
Section titled “How the agents feel about an end user”GET /v1/agent/relations
One row per agent the key’s end user has talked to that keeps a relationship, most recently active first — or, with agent_id, that agent’s row alone.
warmth and trust are moved by the agent itself when something in a conversation changes its view; turns and conversations are counted. summary is the sentence the model is actually given.
Per end user: each person’s relationships live with that person, so one call reads one person’s.
curl "https://api.lumavox.ai/v1/agent/relations" \ -H "Authorization: Bearer $EPHEMERAL_TOKEN"Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
agent_id | string | Only this agent’s relationship. | |
end_user_id | string | Optional, and only ever the key’s own end user — their eu_... id or your own id for them. Anybody else is a 403. | |
limit | integer | How many to answer, 1 to 200. 1–200. Default 50. | |
cursor | string | The next_cursor of the page before. At least 1 characters. |
Responses
| Status | Description |
|---|---|
200 |
application/json — How each agent feels about the person, most recent first |
401 |
Missing or invalid credentials |
403 |
The key is valid but its scopes do not cover this capability |
404 |
Unknown agent id |
422 |
The request failed validation |
429 |
Rate limit exceeded |
200 response body
| Field | Type | Description |
|---|---|---|
data |
array of RelationInfo | |
data[].agent_id |
string | |
data[].agent_name |
string or null | The agent’s name, so a list of these is readable without resolving ids. |
data[].conversations |
integer | Separate sessions, ever. A stronger signal of familiarity than turns — twenty short visits is a longer acquaintance than one long conversation. |
data[].disclosure |
number | How much the agent has told them about itself, -1 to 1 — the asymmetric half of knowing each other, and the one nothing can count. Deliberately not part of tier: an agent can have opened up to somebody it has come to dislike, and folding this into affection would make that state unrepresentable. |
data[].end_user_id |
string | The person, as eu_.... |
data[].first_met_at |
string (date-time) or null | |
data[].last_seen_at |
string (date-time) or null | |
data[].note |
string or null | One line the model wrote about them. Replaced, not appended — it is rendered into every future prompt for this pair, so it is bounded by design. |
data[].recorded |
integer | How many times the model has recorded a change. Zero on a long history means the tool is not being called, which is the first thing to check when a relationship is not moving. |
data[].summary |
string | The relationship as the model is told it — bands and counted phrases, never numbers. Empty string when there is nothing worth saying yet, in which case nothing about them is added to the prompt at all — which is a real state and not a missing value. |
data[].tier |
"Despised", "Disliked", "Acquaintances", "Friends", "Good Friends" |
What the agent would call this relationship, and what it is actually told: Despised, Disliked, Acquaintances, Friends or Good Friends. Derived on every read from the axes below and never stored, so it cannot go stale. The rule is one sentence: warmth and trust set the direction, familiarity sets how far it can go — a first conversation caps at Acquaintances however well it went, and the ceiling rises as they talk. Dislike is not capped: an agent can take against somebody on one remark. |
data[].trust |
number | -1 to 1, on the same scale. |
data[].turns |
integer | Turns exchanged, ever. Counted, not judged. |
data[].warmth |
number | -1 to 1. Zero is indifference, which is where every relationship starts. |
next_cursor |
string or null | Pass as cursor for the next page; null when this is the last. |