Architecture in one paragraph. MeetingOS is a per-client digital employee: a scoped agent whose entire brain is one folder. That folder holds the client's briefing file, the transcripts it fetches, the notes it writes, and a small state file so it remembers where it left off. The agent's allow-list - its key card - opens that one folder and the client's Zoom account. Nothing else. If you keep that shape, the module stays debuggable forever.
Setup Time at a Glance - MeetingOS New Client Install
Step 1 · ~15 min
Create the Client's Zoom Server-to-Server OAuth App
MeetingOS fetches transcripts through the Zoom API using a Server-to-Server OAuth app on the client's Zoom account. JWT apps are deprecated - never use them. There is no per-user OAuth flow, no redirect URIs, no refresh tokens.
- Have the client sign in at
marketplace.zoom.us(they must be an account admin). - Develop → Build App → Server-to-Server OAuth → Create. Name it
MeetingOS Transcript Fetch. - Copy the three credentials from the Information page: Account ID, Client ID, Client Secret.
- On the Scopes page, add exactly these five:
meeting:read:meeting:adminmeeting:read:list_meetings:adminmeeting:read:past_meeting:admin- required for the instances call under granular scopesuser:read:user:adminuser:read:list_users:admin
- Activate the app. A non-activated app is the most common day-one failure.
- App created on the client's own Zoom account (not yours)
- Five scopes added, app activated
- Three credentials copied into your password manager, labeled with the client name
Step 2 · ~5 min
Turn On Zoom AI Companion Transcript Retention
Zoom has two completely separate transcript systems, and querying the wrong one silently returns empty results. MeetingOS uses AI Companion transcripts - they do not require cloud recording, which is why they work for clients who record locally or not at all.
- In the client's Zoom web portal (
zoom.us), as admin: Settings → AI Companion. - Turn ON "Allow meeting hosts to retain and access meeting transcripts."
- Recommended: Meeting Summary → "Automatically start" so transcripts generate without the host remembering anything.
Sanity check before touching the API: after the client's next meeting, the portal's Recordings & Transcripts → Transcripts section must list the transcript. If it appears there, the API can fetch it. If it does not, no API call will ever find it - fix this setting first. (If the client uses Cloud Recording transcripts instead - a .vtt attached to recordings - that is the other system; the fetch script targets AI Companion only.)
Step 3 · ~10 min
Build the Client Folder - the Digital Employee's Brain
The folder is the memory. No database, no embeddings. One folder per client, and the agent's allow-list opens only that folder - that is the scope guard between clients. Structure:
CLAUDE.md is the standard briefing: company context, who attends which recurring meeting, which note style each meeting series gets, and where notes are delivered. last-check.json is what separates a real employee from a goldfish - without it the agent cannot tell a new transcript from one it already processed, and it has no record of its own previous actions.
- Client folder created from the MeetingOS master template
.envfilled in,chmod 600, confirmed outside any committed treeCLAUDE.mdwritten with the client's real meeting series and people
Step 4 · ~10 min
First Transcript Test Run + Dedup Verification
From the client folder, run the fetch script once, manually, verbose:
| Output | Meaning |
|---|---|
Fetched: <topic> (...) - N words | Working. Transcript saved to transcripts/. |
No new Zoom transcripts... All caught up. | Working. Nothing new in the lookback window. |
Missing required config | .env not loaded - check the ZOOM_ENV_FILE path. |
HTTP 401 | Credentials wrong, or app not activated (Step 1). |
401 mentioning scopes on the instances call | The past-meetings read scope is missing - re-add scopes, re-activate. |
| Nothing found, but transcripts exist in the portal | The instance-UUID / double-encoding pitfall - see the pitfalls table below. |
Then run the identical command a second time. It must print All caught up. - that proves dedup state is being written to state/.fetched_instances.json and no client ever gets the same notes twice.
GET /users/mewith the token returns the user profile (OAuth works)- First verbose run exits 0 with a status line
- Second run prints "All caught up" (dedup confirmed)
Step 5 · ~10 min
Schedule the Transcript Fetch
Schedule runs for shortly after each recurring call ends. The script retries every 5 minutes for up to ~40 minutes while Zoom processes, so it tolerates meetings that run long - and a meeting still in progress at job time is handled by polling inside that wait window, not by exiting. Example crontab for a client with Mon 1:00 PM, Wed 11:00 AM, Thu 1:00 PM calls:
launchd (macOS LaunchAgent with StartCalendarInterval) or an agent-runtime scheduler both work - the script prints only when it fetches or hits a real error, so silent runs are safe. Set TZ_NAME (e.g. America/Los_Angeles) so the lookback window and filenames match the client's meeting times.
Tuning knobs: ZOOM_TRANSCRIPT_DIR, ZOOM_STATE_FILE, ZOOM_LOOKBACK_DAYS (default 3), ZOOM_MAX_WAIT_MINUTES (default 40), --once for manual runs.
Step 6 · ~15 min
Meeting Note Formatting Rules + Delivery Destinations
The .vtt is raw material. The agent reads it (strip timestamps; speaker labels are the Name: prefix), applies a note style, and delivers. Long transcripts may need chunking at roughly 15k words per pass - extract per chunk, merge sections.
| Style | Best for |
|---|---|
| Bullet Summary | Fast reads, internal teams (standups, huddles) |
| Detailed Third-Person | Clients or stakeholders who missed the call |
| Action Items Only | Weekly reviews where decisions are already understood |
| Coaching Tracker | Recurring advisory calls needing cross-session continuity |
Delivery destinations (configure per client in config.yaml):
- Email - the morning brief. Always on. Recipients and send-time are client variables.
- Dashboard - the client folder's
dashboard/index.html, updated after every meeting. - Google Docs (optional) - created via the Drive API and formatted; a plain-text Google Doc is a failed delivery. One folder per meeting series.
- Notion (optional) - one database for all notes, properties for date, topic, style.
- Local markdown - the zero-config default:
notes/YYYY-MM-DD_<topic>_<style>.md.
Standing formatting rules: name every file and page with date + topic + style so they sort chronologically, and every note links back to its source .vtt path so it is auditable against the raw transcript.
Zoom Transcript Pitfalls That Will Burn You
| Pitfall | Symptom / Fix |
|---|---|
| Wrong UUID type - the scheduled UUID instead of the instance UUID | Error 3323 "transcript does not exist." Use the instance UUID from GET /past_meetings/{id}/instances. The most common failure. |
| Single-encoding the instance UUID | UUIDs containing / 404 unless double URL-encoded. |
Parsing instances from the instances response |
The response key is meetings, not instances. A parser looking for instances reports zero meetings. |
| Querying Cloud Recordings on a local-recording account | Returns zero by design. Check the portal first to learn which of the two transcript systems the account actually uses. |
| Transcript processing lag | The endpoint 404s right after a meeting ends. Retry every 5 min for up to ~40 min - the script does this internally. |
| "Fixing" the empty-pending case | An instance is not listed until the meeting ENDS. Never add live-meeting checks or early exits - that reintroduces the miss. |
| Token in download URL | Zoom takes the OAuth token as a query parameter on the transcript download - it can land in proxy logs. It expires in one hour and is read-only, but know it happens. |
Done When - New Client Handoff Checklist
- Verbose run exits 0; second run says "All caught up"
- Scheduler entries installed and confirmed with a manual trigger
- One real transcript processed end-to-end into the chosen style and destination - and a human reviewed the output
- Morning email delivered to the client's real inbox at the agreed time
- Dashboard updating from real meetings
.envoutside git; credentials in the vault labeled with the client name- Client walked through with the 30-minute onboarding script
Budget: about 60 minutes of consultant time once the client provides Zoom admin access. The wait is usually the client's admin, not the work.
Chart Source Data - MeetingOS New Client Install
The chart above links here. These are the exact numbers it draws.
Data Table: Consultant Minutes per Setup Step - MeetingOS New Client Install
| Step | What it covers | Budgeted minutes |
|---|---|---|
| Step 1 | Zoom Server-to-Server OAuth app on the client's account | 15 |
| Step 2 | AI Companion transcript retention turned on | 5 |
| Step 3 | Client folder built from the master template | 10 |
| Step 4 | First transcript test run and dedup verification | 10 |
| Step 5 | Transcript fetch scheduled after each recurring call | 10 |
| Step 6 | Note styles and delivery destinations configured | 15 |
| Total budget | Runs nearer 60 minutes with overlapped waits | 65 |