Agent API
The most compatible meeting platform for frontier organisations.
Everywhere else, your agent has to drive a headless browser into a call and guess who spoke from one mixed audio stream. Here it joins with a key, and every line arrives with the speaker already attached.
That difference is architectural rather than clever. We own the room, so each microphone is transcribed on its own machine and the speaker on a line is the connection the words came in on. No container, no browser, no audio pipeline, no per-hour bill.
Here is where you can start.
Quickstart
Three calls, start to finish. Nothing below is a placeholder — run it.
1. Mint a key
curl -X POST https://teammeet.io/api/keys \
-H 'content-type: application/json' \
-d '{"name":"Notetaker","emoji":"📝"}'
{
"key": "tmk_…", // shown once, stored only as a hash
"agent": { "id": "ag_…", "name": "Notetaker", "emoji": "📝" }
}
No account, no card, no sales call, no monthly minimum. The key is a bearer token — treat it like one.
2. Start a meeting the key owns
curl -X POST https://teammeet.io/api/meeting \
-H 'authorization: Bearer tmk_…'
{ "code": "abc-defg-hij", "url": "https://teammeet.io/abc-defg-hij" }
Send that link to anyone. It opens in any browser, with no account and no download. A key is admitted automatically to a meeting it started; anywhere else it knocks at the door and a person decides.
3. Join it
import { WebSocket } from 'ws';
const ws = new WebSocket('wss://teammeet.io');
ws.on('open', () => ws.send(JSON.stringify({
type: 'agent-hello',
key: process.env.TEAMMEET_KEY,
meeting: 'abc-defg-hij',
})));
ws.on('message', raw => {
const m = JSON.parse(raw);
if (m.type === 'agent-ready') {
console.log('in the room with', m.members.map(x => x.name).join(', '));
}
if (m.type === 'transcript') {
// m.line.speaker is a name, not a guess
console.log(`${m.line.speaker}: ${m.line.text}`);
if (/what did we decide/i.test(m.line.text)) {
ws.send(JSON.stringify({ type: 'say', text: 'You decided to ship the API first.' }));
}
}
});
That is a working meeting bot. It has no browser in it.
What this costs, and why
| A bot in someone else's Zoom | An agent here | |
|---|---|---|
| How it joins | Headless browser driving the UI | A WebSocket and a key |
| Who said it | Diarization on mixed audio — a guess | The connection the words came in on |
| Speaker identity | Often "Speaker 1", unstable between meetings | A stable id and the name they joined under |
| Infrastructure | Containers, autoscaling, session lifecycle | One socket |
| Typical cost | $0.35–$1.00 per hour, sometimes a $1,000/mo floor | Free |
| Speaking in the room | Rare, awkward, often unsupported | One message |
| Breaks when | The host platform changes its interface | We change this protocol, which is versioned |
| Consent | A bot in the participant list nobody agreed to | Announced in chat, listed, removable by anyone |
It is free because it is nearly free for us. A bot company pays for a browser and a machine per meeting; we are already in the room, and an agent is one more socket on a server that is already broadcasting to everyone else in it.
The protocol
What you receive
agent-ready | You are in. Carries the member list, the mode, the transcript so far, and exactly what you may do. |
transcript | One line, as it is said. { line: { at, speaker, speakerId, text, meeting } } |
chat | A chat message from anyone in the room. |
state | The room changed — who is in it, the mode, the live notes, the recording. |
recording | A recording finished. Carries its share link. |
end | Everyone left. |
agent-error | Something you sent was refused, and why. |
agent-removed | A person removed you, or the host turned agents off. This is final. |
What you can send
say | Speak out loud in the meeting. Joins the transcript, so it is on the record like anything else said. One every 5s. |
chat | Post to the meeting chat under your own name. One every 0.7s. |
draw | Generate a picture into the room. { kind: 'diagram' | 'infographic' | 'visual' | 'meme', prompt } One every 25s. |
note | Put a note in front of everyone. One every 2s. |
leave | Go. |
The rate limits are a product decision, not a defence. An agent that can talk over people twice a second makes the room worse, so the ceiling is set at the speed a person could plausibly want to be interrupted at.
What an agent cannot do
- Hide. It is in the member list, its arrival is written into the chat, and anyone in the room can remove it. Not the host — anyone. An agent nobody wants is not a moderation dispute.
- Vote. Agents are observers. In QuickAlign the count a group acts on stays human.
- Host. A program never inherits the room when the host leaves.
- Get in where it is not wanted. One switch turns agents off for a meeting and every connected agent is disconnected and told why.
Webhooks
If your workflow lives in n8n, Make, Zapier or a cloud function, you do not need a socket at all. Register a URL and the meeting comes to you.
curl -X PATCH https://teammeet.io/api/keys/me \
-H 'authorization: Bearer tmk_…' \
-H 'content-type: application/json' \
-d '{"webhook":{"url":"https://your-n8n.example.com/webhook/teammeet"}}'
You get back a signing secret, once. Events:
meeting.started | A meeting this key created has begun. |
transcript.line | Batched every few seconds, so a busy meeting does not become a request storm. |
chat.message | Someone posted in chat. |
meeting.ended | The whole transcript, the recap and the notes, in one payload. |
recording.ready | A recording finished, with its link. |
Verifying a delivery
Signed the way Stripe and GitHub sign theirs, so whatever you are using already knows
how to check it: an HMAC-SHA256 over timestamp.body, with the timestamp in
its own header so a captured delivery cannot be replayed tomorrow.
import crypto from 'node:crypto';
function verify(req, rawBody, secret) {
const ts = req.headers['x-teammeet-timestamp'];
const sig = req.headers['x-teammeet-signature'];
const mine = 'sha256=' + crypto.createHmac('sha256', secret)
.update(ts + '.' + rawBody).digest('hex');
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // stale
return crypto.timingSafeEqual(Buffer.from(mine), Buffer.from(sig));
}
Webhook URLs must be https. Failed deliveries retry three times, then stop.
MCP
Point any assistant that speaks the Model Context Protocol at
https://teammeet.io/mcp with your key, and a meeting becomes something it
can reach directly — start one, read what was actually said, answer in the room.
Connect Claude in two steps → — a key, and the config with the key already in it. This page is the reference; that one is the setup.
{
"mcpServers": {
"teammeet": {
"type": "http",
"url": "https://teammeet.io/mcp",
"headers": { "Authorization": "Bearer tmk_…" }
}
}
}
start_meeting | Returns a code and a link. Optionally sets the mode and an agenda. |
list_meetings | Your live meetings and who is in them. |
get_transcript | Every line with its speaker. Takes since for polling. |
get_notes | Decisions, open questions, actions — each carrying a verbatim quote. |
send_message | Post into the meeting chat. |
speak | Say something out loud in the room. |
draw | Put a picture in front of everyone. |
list_recordings | Recordings and their share links. |
What this is not, yet
Worth knowing before you build on it.
- An agent cannot hear raw audio. It gets text, per speaker, as it is said. If you need the waveform — a voice-quality model, your own ASR — that is not exposed yet. Tell us what you would do with it.
- An agent cannot see video. Same answer.
- It only works in meetings held here. An agent on this protocol cannot join someone else's Zoom. That is the trade: we can give you ground-truth speakers precisely because we are not a guest in someone else's room.
- Video is a peer-to-peer mesh. Right up to about eight or ten cameras, and wrong past that. An SFU is the answer beyond that and the signalling is shaped so one can drop in.
- The protocol is versioned but young. Breaking changes will be announced, not shipped quietly.