Transcriber — User manual
Transcriber records meetings and turns them into notes. You can:
- send a notetaker bot into a Teams, Zoom, Google Meet, Webex, Zoho, Jitsi or Whereby meeting,
- upload an audio or video file, or paste a link to one,
and you get back:
- a transcript in the languages that were spoken, with speaker names,
- notes: a summary, decisions and action items with owners and due dates,
- a translation into any language,
- answers to your questions, with quotes from the meeting,
- exports to Word, Markdown, text, subtitles (SRT/VTT) and JSON.
This manual has three parts. Read the first two to use Transcriber. The third is for whoever runs the server.
1. Using Transcriber in the browser
Sign in
- Open the home page of your server (
https://mnemory.me). - Click Sign in with Google and choose your account.
- The first time, Transcriber creates your own workspace. All your recordings, keys and settings live there, and nobody else can see them.
Only accounts on the server's allow-list can sign in. If you see "… is not on this server's list of allowed accounts", ask the administrator to add you (see Who can sign in).
You stay signed in for 30 days on that browser. Sign out is in the top right.
Record a meeting with the bot
- Copy the meeting link from the invitation, for example:
- Teams:
https://teams.microsoft.com/l/meetup-join/… - Zoom:
https://us02web.zoom.us/j/123…?pwd=… - Google Meet:
https://meet.google.com/abc-defg-hij - Webex, Zoho Meeting, Jitsi (
meet.jit.si/…) or Whereby (whereby.com/…)
- Teams:
- Paste it into Send the notetaker to a meeting. Add a title if you like, then click Join now.
- Within about 30 seconds a participant called "Notetaker - recording" asks to join. Admit it from the lobby (Teams: Admit, Meet: Admit, Zoom: Admit in the Participants list).
- Once inside, the bot posts this in the meeting chat: "Hello, I am Notetaker - recording. I record and transcribe this meeting. The host can remove me at any time."
- The status in Your recordings goes through: joining → waiting in lobby → recording → processing → ready.
The bot leaves by itself when:
- the meeting ends,
- it has been the only participant for 2 minutes,
- nobody admitted it within 15 minutes,
- it has recorded for 4 hours.
Removing the bot. Anyone allowed to manage participants can remove it in the meeting. You can also click Remove bot next to the recording. What was recorded so far is kept.
Inviting it back. Click Invite back on the same recording, then admit the bot again. Everything stays one recording: the visits are joined end to end, and the notes mention any gap.
Tell people you are recording. The chat message helps, but saying it out loud is better. In many countries you need everyone's consent.
Invite the notetaker by email
The address is bot@mnemory.me. It is also shown at the top of the home page.
- A meeting you organise: add
bot@mnemory.meas a guest. The bot joins at the start time. For a recurring series it joins every meeting, and if you move or cancel a meeting, the bot follows. - Someone else's meeting: forward the invitation to
bot@mnemory.me. - A meeting that is happening now: email
bot@mnemory.meany message that contains the meeting link, and the bot joins right away. In Google Meet you can also addbot@mnemory.mewith Add people during the call. This needs the bot's Google account (see Google Meet: give the bot its own Google account in section 3).
The invitation must come from the Google address you sign in with, either because you organise the meeting or because you forward the invitation. That's how Transcriber knows whose meeting it is. Invitations sent by Google Calendar or Outlook on your behalf count too. Other addresses at @mnemory.me (notes@, invite@) also work. Sending from another address, for example a work account? Use your workspace's personal address, which is shown on the home page and looks like bot+6cRrurJbcQsZ@mnemory.me. Anyone who knows that address can send the bot on your behalf, so share it only with people you trust.
Transcribe a file or a link
- Upload: choose any audio or video file (mp3, m4a, wav, mp4, mov, webm, …) and click Upload. A one-hour file usually takes 1–3 minutes.
- Link: paste a direct link to an audio/video file, or a page with media (podcasts, SoundCloud, X, TikTok, Loom and about 1,800 other sites), and click Transcribe link. YouTube and Vimeo block downloads from cloud servers. Download the file yourself and upload it, or ask the administrator to set up cookies (see YouTube and Vimeo links).
Read the results
When a recording is ready, click Open. You see the notes (summary, decisions, action items) followed by the full transcript with speaker names and times.
- Translate: choose a language at the top of the viewer. The first translation of a recording takes a few seconds; after that it is saved.
- Word document: click Word for a
.docxin the language you are viewing.
The language is detected automatically, including meetings where people switch languages. Notes are written in the main spoken language. To get them in another language, use the API (Notes in another language).
Speaker names come from what people say ("Hi, this is David from finance…") and from the meeting's participant list. If someone is still "Speaker 2", you can rename them through the API (Rename a speaker).
What the statuses mean
| Status | Meaning |
|---|---|
| scheduled | The bot will join at the planned time |
| joining | The bot is opening the meeting |
| waiting in lobby | Someone in the meeting has to admit it |
| recording | The bot is in the meeting and recording |
| processing | Transcribing and writing notes |
| ready | Done; click Open |
| nothing recorded | The bot was never admitted, or there was no sound |
| failed | Something went wrong; the reason is shown under the status |
2. Using the API
Everything in the browser, and more, is available over a JSON API.
- Explorer:
/docson your server. When you are signed in, Try it out works there directly. - Scripts: create a key on the home page (API keys → Create API key). Copy it right away, because it is shown only once. Then send it as a header:
export T=https://mnemory.me
export K="Authorization: Bearer trk_your_key_here"
Common tasks
Send the bot now, or later with join_at:
curl -H "$K" -H 'content-type: application/json' $T/v1/bots \
-d '{"meeting_url":"https://meet.google.com/abc-defg-hij","title":"Weekly sync"}'
curl -H "$K" -H 'content-type: application/json' $T/v1/bots \
-d '{"meeting_url":"https://zoom.us/j/123456789","join_at":"2026-11-03T09:00:00+01:00","end_at":"2026-11-03T10:00:00+01:00"}'
The response is the recording, including its id. Use that id in all the calls below.
Upload a file, or transcribe a link:
curl -H "$K" -F file=@interview.m4a -F title="Interview with Anna" $T/v1/uploads
curl -H "$K" -H 'content-type: application/json' $T/v1/imports -d '{"url":"https://example.com/episode-12.mp3"}'
Check progress and read the result:
curl -H "$K" $T/v1/recordings/<id> # status, speakers, notes, visits
curl -H "$K" $T/v1/recordings/<id>/transcript # utterances with times, speakers, language
curl -H "$K" "$T/v1/recordings/<id>/export?format=md" # notes + transcript as Markdown
curl -H "$K" "$T/v1/recordings/<id>/export?format=docx" -o notes.docx
curl -H "$K" "$T/v1/recordings/<id>/export?format=srt" # subtitles (also: vtt, txt, json)
curl -H "$K" -o meeting.m4a $T/v1/recordings/<id>/audio # the audio
Translate (any language code: de, cs, es, ja, …):
curl -H "$K" "$T/v1/recordings/<id>/transcript?language=de"
curl -H "$K" "$T/v1/recordings/<id>/export?format=docx&language=de" -o notes-de.docx
Ask a question. The answer cites the passages it is based on:
curl -H "$K" -H 'content-type: application/json' $T/v1/recordings/<id>/ask \
-d '{"question":"What did we decide about the budget, and who follows up?"}'
Remove the bot / invite it back:
curl -H "$K" -X POST $T/v1/recordings/<id>/leave
curl -H "$K" -X POST $T/v1/recordings/<id>/join
Rename a speaker
Speaker labels such as s1:speaker_2 are listed under speakers in GET /v1/recordings/<id>.
curl -H "$K" -X PATCH -H 'content-type: application/json' $T/v1/recordings/<id>/speakers \
-d '{"speaker":"s1:speaker_2","name":"Pierre Dubois"}'
Notes in another language or style
Templates: general, one_on_one, interview, sales_call, standup, project_review, lecture, podcast.
curl -H "$K" -H 'content-type: application/json' $T/v1/recordings/<id>/notes -d '{"language":"cs","template":"project_review"}'
Transcribe again with another engine
Use this if names or a language came out wrong. Engine ids are listed in GET /v1/capabilities.
curl -H "$K" -H 'content-type: application/json' $T/v1/recordings/<id>/retranscribe -d '{"engine":"gemini"}'
Find recordings: GET /v1/recordings?q=budget&language=fr&limit=20.
Delete a recording together with its audio, transcript and notes: DELETE /v1/recordings/<id>.
Options for new recordings
You can add these to /v1/bots, /v1/uploads and /v1/imports. You can also save them as defaults for every new recording with PATCH /v1/workspace and {"settings": {...}}.
| Option | Example | Effect |
|---|---|---|
language |
"cs" |
Skip detection and use this language (helps with short or noisy audio) |
engine |
"elevenlabs" |
Which speech engine to use |
notes_language |
"en" |
Language of the notes |
template |
"standup" |
Kind of notes |
vocabulary |
["Kubernetes","Dubois"] |
Names and terms the engine should get right |
speakers_expected |
4 |
Helps tell speakers apart |
auto_notes |
false |
Transcript only, no notes |
bot_name |
"Jakub's notetaker" |
Name the bot uses in meetings |
announce |
false |
Do not post the chat message |
Webhooks: get told when something happens
curl -H "$K" -H 'content-type: application/json' $T/v1/webhooks -d '{"url":"https://your-app.example.com/hooks/transcriber","events":["recording.ready"]}'
The response includes a secret. Every delivery is a JSON POST with the header X-Transcriber-Signature: t=<unix time>,v1=<hex>, where v1 = HMAC-SHA256(secret, "<t>.<raw body>"). Check the signature, and reject deliveries whose time is too old. Failed deliveries are retried. GET /v1/webhooks/<id>/deliveries shows recent attempts.
Events: recording.created, session.started, session.state (lobby, recording, …), session.ended (includes the reason, such as removed by the host), recording.ready, recording.failed, recording.deleted.
3. Running the server
What is deployed
| Server | One AWS Lightsail instance transcriber, Frankfurt (eu-central-1): 2 vCPU, 2 GB RAM + 2 GB swap, 60 GB disk |
| Address | https://mnemory.me (www. redirects there; https://3-67-149-156.sslip.io also works). Static IP 3.67.149.156, HTTPS by Caddy with Let's Encrypt |
| DNS | Route 53 hosted zone mnemory.me (domain registered at Namecheap, nameservers pointed to Route 53) |
| Email in | bot@mnemory.me: MX points to Amazon SES (eu-central-1). The receipt rule set transcriber passes every message to @mnemory.me to the SNS topic transcriber-inbound-email, which sends it to https://mnemory.me/inbound/ses. Amazon's signature is checked on every notification |
| Runs | Postgres 17, the API with the scheduler and bots (Chromium), Caddy, all in Docker Compose |
| Cost | about $12 a month, plus speech and language model usage (below) |
| Account | AWS profile default (Ventures account) |
| Capacity | 2–3 bots in meetings at the same time; uploads queue up |
Usage costs, roughly per hour of audio:
| Service | Cost |
|---|---|
| ElevenLabs Scribe | about $0.22–0.40 |
| Groq Whisper | about $0.04–0.11 |
| Gemini as speech engine | a few cents |
| Gemini Flash as language model (notes, names, translation, Q&A) | a few cents per meeting |
Set up Google sign-in (one time, about 2 minutes)
Google does not let programs register a sign-in address, so this is done once in the Google Cloud Console.
- Open https://console.cloud.google.com/apis/credentials. Pick a project or create one (for example "Transcriber").
- If asked, set up the OAuth consent screen:
- user type External,
- app name "Transcriber", with your email as support and developer contact,
- scopes: just the default ones (email, profile, openid),
- under Test users, add your own Gmail address. You can publish the app later; for
email profilescopes Google does not require a review.
- Create credentials → OAuth client ID:
- application type: Web application, name "Transcriber",
- Authorized redirect URIs:
https://mnemory.me/auth/google/callback
- Copy the Client ID and Client secret into
deploy/.envon your laptop, together with who may sign in:
GOOGLE_CLIENT_ID=1234567890-abc.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-...
ALLOWED_EMAILS=bares.jakub@gmail.com
- Deploy:
AWS_PROFILE=default ./deploy/lightsail.sh. Then open the home page and sign in.
Sign-in only works on https://mnemory.me. If you move to another domain, change DOMAIN in deploy/.env and add https://<domain>/auth/google/callback to the same OAuth client.
Who can sign in
ALLOWED_EMAILS is a comma-separated list. It accepts:
- exact addresses:
anna@gmail.com, - whole domains:
@yourcompany.com, *: anyone with a Google account. Only use this if you are happy to pay for strangers' transcription.
When it is empty, nobody can sign in. Each person gets their own workspace on first sign-in. API keys made with npm run key keep working, independently of Google accounts.
Deploy new code
cd ~/Ventures/Transcriber
AWS_PROFILE=default ./deploy/lightsail.sh
This copies the code to the server and rebuilds, which takes about 30 seconds. Recordings and the database are kept.
Everyday commands on the server
ssh -i ~/.ssh/lightsail-eu-central-1.pem ubuntu@3.67.149.156
cd transcriber/deploy
docker compose logs -f app # live log
docker compose exec app node dist/cli/key.js "Team name" # new workspace + API key without Google
docker compose restart app
docker compose exec db pg_dump -U transcriber transcriber | gzip > ~/backup-$(date +%F).sql.gz
Audio and transcripts are stored in the Docker volume transcriber_data, and the database in transcriber_pgdata. For automatic backups, turn on Lightsail automatic snapshots for the instance; it costs a few cents a month.
Settings (deploy/.env)
| Setting | Purpose |
|---|---|
ELEVENLABS_API_KEY, GROQ_API_KEY, GEMINI_API_KEY, DEEPGRAM_API_KEY, ASSEMBLYAI_API_KEY, OPENAI_API_KEY |
Speech engines; any number. The first configured one is the default unless DEFAULT_ENGINE is set |
LLM_PROVIDER |
gemini, anthropic, openai or bedrock for names, notes, translation and Q&A |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, ALLOWED_EMAILS |
Google sign-in |
BOT_NAME |
Default bot name in meetings |
JITSI_HOSTS |
Your own Jitsi servers, comma-separated |
YT_DLP_COOKIES |
Cookies file for YouTube and Vimeo (below) |
INBOUND_ADDRESS, INBOUND_SNS_TOPIC_ARN |
The invite address shown in the app, and the SES topic whose messages are accepted |
ADMIN_TOKEN, POSTGRES_PASSWORD, DOMAIN |
Generated at the first deploy; keep a copy of the file |
deploy/.env is never committed to git. Keep a copy in your password manager.
YouTube and Vimeo links
- In a browser where you are logged in to YouTube, export cookies in Netscape format, for example with the "Get cookies.txt LOCALLY" extension.
- Copy the file into the app's data volume:
scp -i ~/.ssh/lightsail-eu-central-1.pem cookies.txt ubuntu@3.67.149.156:
ssh -i ~/.ssh/lightsail-eu-central-1.pem ubuntu@3.67.149.156 'docker cp cookies.txt transcriber-app-1:/data/cookies.txt'
- Add
YT_DLP_COOKIES=/data/cookies.txttodeploy/.envand deploy.
Google Meet: give the bot its own Google account (one time)
Google Meet refuses most anonymous guests that run on a server. They see "You can't join this video call". The bot gets in reliably when it is signed in to a Google account. If that account's address is bot@mnemory.me, the address you add in Meet, Meet treats it as invited and lets it straight in.
Run the sign-in helper on your Mac:
cd ~/Ventures/Transcriber && npx tsx scripts/google-bot-login.tsA Chrome window opens.
Choose Create account → For my personal use. Enter a name; this is the bot's name in calls, e.g. Mnemory Notetaker. Then choose Use your existing email and enter
bot@mnemory.me.Google emails a code to
bot@mnemory.me. Open https://mnemory.me/inbox to read it.Finish the sign-up, including the phone check if Google asks. When you're signed in, the helper uploads the sign-in to the server and closes the window.
From then on every Meet bot joins as that account. The server keeps the sign-in fresh after each call. If Google ever signs it out, the bot ends with "Google Meet refused the notetaker"; run the helper again and sign in.
Troubleshooting
| Problem | What to do |
|---|---|
| The bot never appears in the meeting | The link may need a login (some company Teams and Zoom settings block guests), or the meeting has not started. Check the error under the status; docker compose logs app shows the bot's steps |
| "nothing recorded" | Nobody admitted the bot within 15 minutes, or the meeting had no sound |
| Wrong language or garbled text | Retranscribe with a fixed language, or with another engine |
| Everyone is "Speaker 1" | That engine does not separate speakers (Groq, Whisper). Retranscribe with elevenlabs or gemini |
| Notes missing | No language model configured, or it failed. Run POST /v1/recordings/<id>/notes again |
Google sign-in says redirect_uri_mismatch |
The redirect URI in the Google console must be exactly https://<your address>/auth/google/callback |
| "not on this server's list of allowed accounts" | Add the address to ALLOWED_EMAILS and deploy |
Known limits
- Meeting bots are new. Each platform's adapter has been tested against a simulated meeting, but not yet in live Teams, Zoom or Meet calls. Try a short internal meeting before relying on one. Meeting sites change their pages from time to time, and adapters need small fixes when they do.
- Recognised but not supported yet. GoTo Meeting, Amazon Chime and RingCentral links are recognised, but the bot cannot join them yet.
- Large invitations are lost. Emails over 150 KB (for example invitations with large attachments) can't be delivered to
bot@mnemory.me. Forward the invitation without its attachments instead. - No email replies. Transcriber does not reply to emails. Check the home page to see that the bot is scheduled.