Mnemory

Transcriber — User manual

Transcriber records meetings and turns them into notes. You can:

and you get back:

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
  2. Using the API
  3. Running the server

1. Using Transcriber in the browser

Sign in

  1. Open the home page of your server (https://mnemory.me).
  2. Click Sign in with Google and choose your account.
  3. 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

  1. 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/…)
  2. Paste it into Send the notetaker to a meeting. Add a title if you like, then click Join now.
  3. 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).
  4. 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."
  5. The status in Your recordings goes through: joining → waiting in lobby → recording → processing → ready.

The bot leaves by itself when:

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.

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

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.

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.

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.

  1. Open https://console.cloud.google.com/apis/credentials. Pick a project or create one (for example "Transcriber").
  2. 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 profile scopes Google does not require a review.
  3. Create credentials → OAuth client ID:
    • application type: Web application, name "Transcriber",
    • Authorized redirect URIs: https://mnemory.me/auth/google/callback
  4. Copy the Client ID and Client secret into deploy/.env on 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
  1. 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:

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

  1. In a browser where you are logged in to YouTube, export cookies in Netscape format, for example with the "Get cookies.txt LOCALLY" extension.
  2. 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'
  1. Add YT_DLP_COOKIES=/data/cookies.txt to deploy/.env and 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.

  1. Run the sign-in helper on your Mac:

    cd ~/Ventures/Transcriber && npx tsx scripts/google-bot-login.ts
    

    A Chrome window opens.

  2. 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.

  3. Google emails a code to bot@mnemory.me. Open https://mnemory.me/inbox to read it.

  4. 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