Agents
End users and their keys
Register the people your agents talk to, and mint each one a short-lived, prepaid ephemeral key that their device uses to hold a conversation.
An end user is one of your users: a person your agents talk to. Their conversations, what the agents remember about them and every relationship are filed under them. An ephemeral key is a short-lived token you mint for one end user on your server and hand to their device. It is the only credential that can hold a conversation.
The usual flow, on your server, at sign-in:
# 1. The person, by your own id for them. Same id, same person, every time.curl https://api.lumavox.ai/v1/end-users \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "external_id": "user-4821" }'
# 2. A token for them, with a budget (5 credits) and a lifetime.curl https://api.lumavox.ai/v1/ephemeral-keys \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "end_user_id": "user-4821", "credit_limit_micros": 5000000, "expires_in_seconds": 3600 }'Send the token’s key to the person’s browser or app. Keep your API key on your server.
End users
Section titled “End users”All /v1/end-users routes need an API key with the agent scope.
Create one
Section titled “Create one”POST /v1/end-users takes two optional fields:
| Field | What it is |
|---|---|
external_id |
Your own id for this person, up to 128 characters, unique in your organisation. Sending it makes the call idempotent: the same value always returns the same end user, created only the first time. |
display_name |
A name for your dashboard and for reading transcripts, up to 128 characters. Nothing resolves by it, and the agent never sees it unless you put it in the conversation yourself. |
{ "id": "eu_3kQ9x7MzT2pL8vNc", "external_id": "user-4821", "display_name": null, "origin": "created", "sessions": 0, "facts_updated_at": null, "created_at": "2026-10-24T09:20:11.000Z"}A new end user is answered 201. A repeat of an existing external_id is answered 200 with the same person, so you can tell the two apart without comparing ids.
Pass your external_id at every sign-in and you never need to store the eu_… id. Everywhere the API takes an end_user_id, either form works.
Without an external_id you get a new person on every call, and you must keep the returned id: it is the only way back to what is remembered about them.
One end user is one person. What your agents remember is filed under the end user, so an id shared by several people — one external_id for every guest — shares what each of them said with all the others.
Your plan caps how many end users your organisation holds. Creating one past the cap is a 409; repeating an existing external_id is still answered as usual.
Read, list and rename
Section titled “Read, list and rename”GET /v1/end-users/{end_user_id}reads one person: how many conversations they have had and when something was last learned about them (facts_updated_at,nullwhile nothing has been).GET /v1/end-userslists them, newest first, a page at a time:limit(1 to 200, default 50) andcursor, answered asdataandnext_cursor(Conventions). People once minted for a session opened without an end user (origin: "generated") are left out unless you sendinclude_generated=true.PATCH /v1/end-users/{end_user_id}changesdisplay_nameorexternal_id. Absent fields are untouched;nullclears one. Another person already holding thatexternal_idis a 409.
There is no way to change the eu_… id. Long-term memory is filed under it, so rewriting it would strand everything remembered about the person. Your external_id is the identifier you can change.
Delete one
Section titled “Delete one”curl -X DELETE https://api.lumavox.ai/v1/end-users/user-4821 \ -H "Authorization: Bearer $API_KEY"{ "id": "eu_3kQ9x7MzT2pL8vNc", "sessions_deleted": 4, "facts_deleted": 11 }This removes the person, every conversation they have had, and everything remembered about them, and revokes their live keys. It cannot be undone. The id is never reissued. It answers what went with them, counted.
This is what to call when one of your users asks to be forgotten. To erase only the memory and keep the conversations, the person’s own token can call DELETE /v1/agent/facts (see Conversations and memory).
What is remembered about them
Section titled “What is remembered about them”Your server reads and writes what the agents remember about a person with your API key — no key minted for them:
| Call | Does |
|---|---|
GET /v1/end-users/{end_user_id}/facts |
Their facts, oldest first, a page at a time (limit, cursor, include_replaced). |
POST /v1/end-users/{end_user_id}/facts |
Tells the agents something: {"text": "Is on the Gold plan.", "agent_id": null}. 201 new, 200 when a fact already said it. |
PATCH /v1/end-users/{end_user_id}/facts/{fact_id} |
Corrects one fact’s words; the old ones are kept as forgotten. |
DELETE /v1/end-users/{end_user_id}/facts/{fact_id} |
Forgets one, 204. |
What you write is yours: a later conversation never replaces it. It works before the person has ever talked to an agent, so you can seed what your records hold at sign-up. A secret — a password, a code, a card, account or identity number, a key — is refused with 422: facts are sent to the model on every turn. See Conversations and memory.
Ephemeral keys
Section titled “Ephemeral keys”An ephemeral key speaks for one end user, may only talk to agents, and expires. With it, a device can open and read that person’s conversations, talk on the WebSocket and read the key itself (GET /v1/token). It can reach nothing else: not another person, not speech or images, not your organisation’s settings.
The /v1/ephemeral-keys routes need an API key with the tokens scope. The key you mint with must also hold agent (a master key holds everything). A token can never mint another token.
Mint one
Section titled “Mint one”POST /v1/ephemeral-keys:
| Field | Required | What it is |
|---|---|---|
end_user_id |
yes | The person, by eu_… id or your external_id. Create them first. |
credit_limit_micros |
yes | The most this token may spend, as an integer of micro-credits: 5000000 is five credits (Conventions). There is no unlimited token. |
expires_in_seconds |
no | Its lifetime. 900 (15 minutes) when omitted. |
agent_ids |
no | The agents it may talk to. Omit it to allow every agent; an empty list is refused. |
scopes |
no | Must be ["agent"], the default. |
label |
no | Your own note, up to 120 characters. |
{ "id": "0193b1f0-2a7c-7b11-8d4e-91c0f5a2e6b3", "label": null, "prefix": "…_ek_live", "last4": "x9Qa", "scopes": ["agent"], "credit_limit_micros": 5000000, "credits_spent_micros": 0, "credits_remaining_micros": 5000000, "agent_ids": null, "created_by_api_key_id": "0193a8c2-51f0-7d2e-8b6a-4c1e9f3d2a70", "created_by_clerk_user_id": null, "end_user_id": "eu_3kQ9x7MzT2pL8vNc", "created_at": "2026-10-24T09:20:11.000Z", "expires_at": "2026-10-24T10:20:11.000Z", "last_used_at": null, "revoked_at": null, "revoked_reason": null, "settled_at": null, "active": true, "key": "<the token>"}key is shown once. It is signed, not stored, so it cannot be read back. Mint another if you lose it. prefix and last4 are what you can recognise it by later, in a list.
A key is prepaid
Section titled “A key is prepaid”Its credit_limit_micros is set aside from your balance when it is minted. What it does not spend comes back when it expires, runs out or is revoked. If your balance cannot cover the limit, minting is refused with 402.
Every turn, transcription and memory step a conversation causes is charged to the token that caused it. When a token has spent its limit, the socket closes with 4004 and the reason “This token has spent its credit limit. Mint another to continue.”
In your organisation’s records — the list, and GET /v1/ephemeral-keys/{key_id} — credits_spent_micros is what the token had spent when it was last settled: when it ends, and daily for a long-lived one. So on a live token it can trail what the conversation has used. settled_at is set by the last settlement, and from then its spend and refund are final. The device reading its own token (GET /v1/token) sees its spend turn by turn.
Limits
Section titled “Limits”GET /v1/ephemeral-keys/limits returns the ceilings you can mint within — the platform’s, and your plan’s for one end user’s token (plan_*, null where your plan sets none):
{ "max_ttl_seconds": 2592000, "default_ttl_seconds": 900, "max_credit_limit_micros": 100000000000, "plan_max_ttl_seconds": 86400, "plan_max_credit_limit_micros": 50000000, "plan_max_live_keys": 5, "grantable_scopes": ["agent"]}A token is held to the tighter of each pair. Your plan also caps how many tokens you mint a minute and how much credit live tokens may hold at once. Nothing is clamped: a request past a cap is refused with 422 naming the rule, or 429 with Retry-After for the per-minute rate. The server SDK clamps the lifetime for you: ephemeralKeys.create cuts expires_in_seconds to the tighter cap before it sends — pass clampTtl: false to be refused instead.
A token never outlives the API key that minted it. If that key expires sooner, so does the token.
List and revoke
Section titled “List and revoke”GET /v1/ephemeral-keyslists your organisation’s live tokens, a page at a time:limit(1 to 200, default 50) andcursor, answered asdataandnext_cursor. Sendlive_only=falseto include expired and revoked ones.GET /v1/ephemeral-keys/{key_id}reads one, live or not.DELETE /v1/ephemeral-keys/{key_id}revokes one at once, and answers the revoked key —activefalse,revoked_atset. Open sockets on it close with4004. Anything already in flight still settles: the answer carries the figures from before, andsettled_atisnull. To read the final ones, read the token untilsettled_atis set — seconds, longer while a memory step it paid for still runs.
What the device can read about its own key
Section titled “What the device can read about its own key”A device holding a token can call GET /v1/token to learn its limit, what it has spent so far — turn by turn, not only as last settled — what is left and when it expires. Read it when a session starts and after a reply, not on a timer.
curl https://api.lumavox.ai/v1/token -H "Authorization: Bearer $TOKEN"{ "kind": "ephemeral", "scopes": ["agent"], "credit_limit_micros": 5000000, "credits_spent_micros": 420000, "credits_remaining_micros": 4580000, "expires_at": "2026-10-24T10:20:11.000Z", "expires_in_seconds": 3412, "agent_ids": null, "end_user_id": "eu_3kQ9x7MzT2pL8vNc"}It tells the device nothing about your organisation or the key that minted it.
Why an API key cannot talk to an agent
Section titled “Why an API key cannot talk to an agent”A key in a browser or an app can be read by anyone holding the device, and an API key can spend your whole balance and act as any of your users. A token can do neither: it speaks for one person, it expires, and it holds a fixed budget.
So /v1/agent/* refuses an API key with 403:
{ "error": { "code": "forbidden", "message": "Conversations are reached with an end user's ephemeral key, not an API key. Mint one for the end user on your server (POST /v1/ephemeral-keys) and call this with it.", "request_id": "8f3c2a1b9d4e5f60", "issues": [] }}The WebSocket closes an API key with 4002.
- Conversations and memory: open a session with the token
- The WebSocket: talk on it
- End users reference, Ephemeral keys reference