Put Mnemocast's video meetings inside your own product. Your server talks to Mnemocast's API with a key; your app shows the call with LiveKit's SDK. Your people never need a Mnemocast account, and they stay in your organisation on Mnemocast, apart from everyone else's.
How it fits together:
Every call below goes to https://api.mnemocast.com with
your key:
Authorization: Bearer mk_…
Content-Type: application/json
Every refusal is JSON,
{"error": "<code>", "reason": "<what to do>"},
with the HTTP status. Codes are stable; reasons are for people. Another
organisation's ids answer 404, never 403.
Sign in at app.mnemocast.com/developers with an account that belongs to your organisation (a shared work address, not one person's own), so it outlives any one person.
Under Make a developer account, give it your
organisation's name and, optionally, a link name: 1–40
lowercase letters, digits and hyphens, unique across Mnemocast, such as
acme. It appears in join links
(app.mnemocast.com/r/acme/<room>) and cannot be
changed later; left empty, it is made from the name. Choose Make
it.
Copy the key now. It is shown once, as
mk_<id>_<secret>. Put it in your server's
secret store (AWS Secrets Manager, Google Secret Manager, Vault, or your
host's encrypted settings). Never in your app, a repository, a chat or a
log. Anyone with it acts as your whole organisation.
Check it works:
curl https://api.mnemocast.com/v1/tenant -H "Authorization: Bearer $MNEMOCAST_KEY"
It answers with your organisation's id, name and slug.
You start on Sandbox: no card, small limits, enough to build and test (see pricing). Upgrade to Growth in the console when you go live; your card is entered on Stripe's page, never Mnemocast's. The console also makes new keys, revokes old ones (they stop at once) and shows your usage this month with its cost so far. To change keys without downtime: make a new one, deploy it, then revoke the old.
Tell Mnemocast about each person before they join, under your
own id for them (letters, digits, _ and
-):
curl -X PUT https://api.mnemocast.com/v1/users/athlete-1042 \
-H "Authorization: Bearer $MNEMOCAST_KEY" -H "Content-Type: application/json" \
-d '{"display_name": "Ada Obi", "age_band": "13_17"}'
display_name is what others see in the call.age_band is 18_plus, 13_17 or
under_13. It is your statement, under your
agreement with Mnemocast, and it is re-dated each time you send it, so
send it whenever you learn something new. Mnemocast acts on it: people
aged 13 to 17 are never recorded or heard by an AI agent, and they join
only rooms that allow them. Someone under 13 can be stored but is never
let in.A room is a lasting place with its own rules: a mentor's room, a
team's room, a clinic. Make one and keep its id:
curl -X POST https://api.mnemocast.com/v1/rooms \
-H "Authorization: Bearer $MNEMOCAST_KEY" -H "Content-Type: application/json" \
-d '{"name": "Coach Sam: 1:1 sessions", "alias": "coach-sam", "allows_minors": true}'
The settings you are most likely to want:
| Setting | Default | What it does |
|---|---|---|
alias |
none | A name for the room within your organisation, for links and lookups
(GET /v1/rooms/by-alias/{alias}). |
allows_minors |
false |
Lets people aged 13 to 17 join. |
agent_policy |
forbidden |
host_only or any_participant lets people
bring AI agents. Leave it forbidden unless your app shows
everyone which agents are in the call (part two). |
capacity |
50 | Up to 1,000 people. |
join_policy |
tenant_users |
members_only lets in only people you add with
PUT /v1/rooms/{id}/members/{user}. |
join_muted |
false |
Apps start with the microphone off. |
max_meeting_minutes |
none | A shorter time limit than Mnemocast's own (8 hours, which a host can extend to 12 and 16). |
A host can change some of these later with
PATCH /v1/rooms/{id}. Members and their roles:
PUT /v1/rooms/{id}/members/{user} with
{"role": "host"}, "presenter" or
"attendee".
Start it. One meeting at a time per room: if one is already under way, you get that one back, so two people pressing Start end up in the same call.
curl -X POST https://api.mnemocast.com/v1/rooms/$ROOM_ID/sessions -H "Authorization: Bearer $MNEMOCAST_KEY"
It answers with the session's id.
Let a person in. Your server asks for their pass, one per person per device:
curl -X POST https://api.mnemocast.com/v1/sessions/$SESSION_ID/join \
-H "Authorization: Bearer $MNEMOCAST_KEY" -H "Content-Type: application/json" \
-d '{"user": "athlete-1042", "role": "attendee", "device_id": "c1f0a9e2"}'
device_id is a random id your app makes once and keeps
on that device. The same person on a second device joins alongside; the
same device again replaces its own earlier connection.role is optional. Without it, the person's room
membership decides, then the room's default. Hosts may mute, remove and
end; give it only to those who should.url and token (valid for
ten minutes: ask for it as the person presses Join, not in advance),
plus participant_id, muted and
start_muted.under_minimum_age,
room_excludes_minors,
guardian_consent_required (a 13–17 person while an agent or
a recording is in the call), removed_from_session,
session_ended (409), and
recording_consent_required (428: the call is being
recorded; ask the person, and join again with
"recording_consent": true if they agree).Connect in your app with LiveKit's open-source SDK and the two values:
// Web: npm install livekit-client
import { Room } from 'livekit-client';
const room = new Room({ adaptiveStream: true, dynacast: true });
await room.connect(url, token);
await room.localParticipant.setMicrophoneEnabled(!start_muted);
await room.localParticipant.setCameraEnabled(true);On React Native, use @livekit/react-native and
@livekit/react-native-webrtc (call
registerGlobals() once at start-up), then the same
Room code. It needs a development build (Expo Go cannot run
it). Ask for a new pass from your server if the connection is lost for
good.
End it with POST /v1/sessions/{id}/end,
or let it end on its own: five minutes after the last person leaves, or
at its time limit.
Before you launch, point your people at app.mnemocast.com/network:
it checks that their network lets calls through (company firewalls and
school Wi-Fi sometimes do not). Their IT team will want the network requirements: HTTPS to
*.mnemocast.com, media over UDP with fallbacks over TCP,
and media.mnemocast.com exempt from TLS inspection.
If your product books calls ahead, Mnemocast can keep the diary:
POST /v1/rooms/{id}/meetings with a title,
starts_at (an instant, such as
2026-11-04T17:00:00Z), time_zone (such as
America/New_York, where a repeating meeting keeps its clock
time), duration_minutes, an optional
recurrence, and invitees
({"user": "<your id>"} or
{"email": "…"}). A booked meeting never starts itself: a
host starts it, from ten minutes before. Change or cancel it with
PATCH and DELETE /v1/meetings/{id}.
Give Mnemocast an https address on your server:
curl -X POST https://api.mnemocast.com/v1/webhooks \
-H "Authorization: Bearer $MNEMOCAST_KEY" -H "Content-Type: application/json" \
-d '{"url": "https://api.example.com/mnemocast/events"}'
The answer includes a secret (whsec_…),
shown once: keep it with your key. Mnemocast then POSTs
each event to you, within about a minute:
| Event | When |
|---|---|
session.started |
The first person connected. |
participant.joined |
A person's first device connected. |
participant.left |
Their last device went, with reason: left,
dropped or removed. A drop of under 30 seconds
is never sent. |
session.ended |
The meeting ended, with the attendance (below). |
meeting.scheduled, .updated,
.cancelled, .starting |
Booked meetings; starting is ten minutes before. |
Each delivery is
{"id": "evt_…", "type": "<event>", "created_at": "…", "data": {…}}.
Session events carry data.session, data.seq
(to put them in order) and data.occurred_at; people are
named by your ids. Pass "events": [...] to
receive only some.
Check every delivery is from Mnemocast before you
act on it. The Mnemocast-Signature header is
t=<unix time>,v1=<hex>: an HMAC-SHA256 of
"<t>.<the raw body>", keyed with your secret
exactly as shown:
import { createHmac, timingSafeEqual } from 'node:crypto';
export function fromMnemocast(secret, header, rawBody) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (!t || !v1 || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // five minutes, against replays
const want = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return want.length === v1.length && timingSafeEqual(Buffer.from(want), Buffer.from(v1));
}Answer with any 2xx quickly, then do the work. Anything else is tried
again after 1, 5, 30, 120, 360 and 1,440 minutes; the same event keeps
its id, so ignore one you have already handled.
POST /v1/webhooks/{id}/test sends a test, and
GET /v1/webhooks/{id}/deliveries shows what was sent and
how you answered.
Attendance, for paying people by the time a call
lasted or keeping records: GET /v1/sessions/{id}/attendance
gives, for each person, when they first joined and last left,
connected_seconds (with at least one device connected),
device_seconds (summed across devices: what you are billed
for) and the intervals. It is the same summary
session.ended carries, and the way to recover it if that
delivery never reached you.
Instead of building the call into your app, you can send people to
Mnemocast's web and phone apps with a room's link
(app.mnemocast.com/r/<your slug>/<alias>). They
then sign in with your sign-in service, so they arrive
as your people: register it once with
curl -X POST https://api.mnemocast.com/v1/identity-providers \
-H "Authorization: Bearer $MNEMOCAST_KEY" -H "Content-Type: application/json" \
-d '{"issuer": "https://login.example.com", "audience": "<your client id>"}'
Your provider must be OpenID Connect. The person's id comes from the
sub claim (or external_id_claim), matched to
the ids you use in PUT /v1/users; their age band from a
mnemocast_age_band claim (or age_band_claim),
or from the user you created. Someone with neither cannot sign in. A
sign-in service belongs to one organisation on Mnemocast.
GET /v1/usage shows this month's use with your key, as
the console does. Participant-minutes count each person's connected
devices.Questions about your integration: hello@mnemocast.com.
Most people want their AI assistant, not this. To bring ChatGPT, Claude, Copilot or another assistant you already use, open My agents → Your AI assistant → Connect an assistant at app.mnemocast.com: no address, nothing to run. This guide is for developers who build and host an agent themselves.
A personal agent is an AI agent of your own, running wherever you choose, that you can bring into your Mnemocast meetings. Everyone in the meeting sees it is yours, who runs it, and what you say it keeps. It gets the meeting as text (never audio or video) and answers only when you ask it.
The working example is agent.mjs
(about 90 lines, Node.js 20, with its package.json):
npm install, then node agent.mjs.
Mnemocast connects to your agent over a WebSocket. Your agent:
Authorization: Bearer <JWT>, signed by Mnemocast
(ES256). Verify it against
https://api.mnemocast.com/.well-known/jwks.json with issuer
https://api.mnemocast.com, audience
mnemocast-agent, and type mnemocast-hello+jwt.
Refuse anything else. The claims say who it is for: sponsor
(you), role (bot_listener scribe or
bot_speaker orator), participant_id,
session_id.purpose: "verify", then sends
{"type":"verify","nonce":"…"}. Answer
{"type":"verified","nonce":"…"} (the same nonce) within 10
seconds.{"type":"transcript","speaker","text","start","end"}:
what was said (people who objected to agents are never in it);{"type":"chat","from","text","at"}: what was
written;{"type":"instructions","text"}: your standing
instructions;participant_joined, participant_left,
participant_excluded …{"type":"prompt","prompt_id","from","via","text","can_speak","private"}.
Reply once with {"type":"post_chat","prompt_id","text"}. If
it is an orator and can_speak is true, it may speak
instead: {"type":"speak","prompt_id","text","end":true}. A
private question is answered in writing and Mnemocast shows the answer
to you alone. To speak at all, declare "It can answer
aloud" when you add it; a text-only agent is never made an
orator. A personal agent may be voiced 1,000 characters at a
time, 5,000 a meeting and 20,000 a day; past that,
speak is refused with voice_limit_reached and
you answer in chat. Anything unprompted, or a second reply, is
refused."can_propose" and
"sponsor_time":{"now_local","time_zone"} (your local time,
to read "Thursday at 2" by). Where can_propose is true, it
may send, once for that prompt, before or with its reply:
{"type":"propose","prompt_id","proposal":{"kind":"meeting","title","starts_local","duration_minutes","invitees":["names"]}}
or {"kind":"reminder","at_local","text"}, with local times
as YYYY-MM-DDTHH:MM. Mnemocast answers
{"type":"proposed","prompt_id","proposal_id","summary"}, or
an error saying why not (the room does not allow it, three
already waiting, a name nobody in the meeting has).
"invitees":["everyone"] invites everyone in the meeting.
Nothing happens until you confirm it in Mnemocast; a
meeting is yours, and anyone else is only invited: each is asked to
accept, by a card in the meeting and a notification. Say in the reply
that it is waiting for you. A host may ask your agent too, unless you
turn off Let hosts propose actions on it.The address is wherever you host the agent:
Mnemocast connects to it, so it must be reachable from the internet. For
example wss://agent.example.com/mnemocast on your own
server, or a tunnel to your computer while you test.
wss:// on port 443, at a
public name with a valid TLS certificate.localhost or a private
network: Mnemocast refuses any address that resolves to a private range,
every time it dials.cloudflared tunnel --url http://127.0.0.1:8787 gives you a
public https://…trycloudflare.com name (it prints it in a
box as it starts); use it as wss://…trycloudflare.com/,
with wss:// in place of https:// and a
/ at the end. That name is new each time the tunnel starts,
so add the agent again after a restart. For an agent you keep, host it
at a name of your own.At app.mnemocast.com, open My agents → Your own agent (for developers) → Add an agent: its name, who runs it, its address, and whether it keeps or trains on what it hears. Mnemocast checks it answers, then it appears in your Bring an agent list, marked Yours, in rooms that allow personal agents (all the rooms you make in Mnemocast do).
You do not need to run anything to bring ChatGPT, Claude, Copilot or
another assistant that supports MCP. Under My agents → Your AI
assistant, choose Connect an assistant, follow
its steps with the address https://api.mnemocast.com/mcp,
and allow it. It then appears in Bring an agent marked
MCP, and you ask it about the meeting in the assistant
itself. An assistant's own web address (chatgpt.com, claude.ai…) is not
an agent address: it does not speak this protocol, and the check would
fail.
What your agent does with what it hears is up to you, and you are responsible for it, including your declarations: the room is told what you say it keeps, and that Mnemocast has not checked it.