Skip to content
/ arlo Public

Arlo is a meeting assistant that runs natively in Zoom as an embedded app. Using Realtime Media Streams (RTMS) and the Apps SDK, it captures live transcripts, generates summaries and delivers meeting intelligence all running natively inside of a Zoom meeting. Designed for developers building the next generation of meeting tools.

License

Notifications You must be signed in to change notification settings

zoom/arlo

Arlo the meeting assistant

Arlo Meeting Assistant πŸ¦‰

Your intelligent meeting companion that lives inside Zoom


Arlo is a forkable, open-source RTMS Meeting Assistant that showcases how developers can build real-time, intelligent meeting experiences directly inside Zoom β€” no meeting bot required!

This project was originally created as the "Meeting Assistant Starter Kit" and has evolved into Arlo, a lightweight example of how to:

  • Stream and display live meeting transcripts in real time
  • Save transcripts to a database for meeting history
  • Generate AI-powered summaries and action items
  • Search across past meetings
  • Extend functionality using Zoom's Real-Time Media Streams (RTMS) APIs

Arlo is designed to help developers quickly prototype and deploy their own meeting assistants as Zoom Apps.


⚠️ IMPORTANT: RTMS Access Required

This app requires RTMS (Real-Time Media Streams) access to function. RTMS is Zoom's API for accessing live meeting audio and transcription data without requiring a bot in the meeting.

To get RTMS access:

  1. Request a Free Trial - Post in the Zoom Developer Forum requesting RTMS access for development
  2. Include your use case - Mention you're building a meeting assistant with Arlo
  3. Wait for approval - The Zoom team will enable RTMS on your account (usually within 1-2 business days)

Without RTMS access, this application will not work. The entire purpose of this starter kit is to demonstrate the power of RTMS for building real-time meeting intelligence.

βœ… Once approved, you'll see RTMS features available in your Zoom App Marketplace settings.


✨ Features

  • πŸ“ Live Transcription - Real-time captions via RTMS (< 1s latency)
  • πŸ€– AI Insights - Summaries, action items, next steps (OpenRouter with free models)
  • πŸ” Full-Text Search - Search across all meeting transcripts
  • πŸ’¬ Chat with Transcripts - RAG-based Q&A over your meetings
  • 🎯 Meeting Highlights - Create bookmarks with timestamps
  • πŸ“€ Export VTT - Download WebVTT files for video players
  • 🏠 Home Dashboard β€” AI highlights and reminders from recent meetings
  • πŸŒ™ Dark Mode β€” OS detection with manual toggle, persisted preference
  • πŸ“„ Export Markdown β€” Download meeting summary + transcript as MD
  • πŸ—οΈ Multi-View Architecture β€” 14 views with HashRouter, shared AppShell
  • πŸ” Secure - Zoom OAuth, encrypted tokens, ownership-enforced data isolation, rate limiting, HMAC webhook verification

πŸš€ Quick Start

Prerequisites

  • Node.js 20+ (Download)
  • Docker + Docker Compose (Download)
  • ngrok account + CLI (Sign up free) - Exposes localhost to internet for webhooks
  • Zoom Account with Marketplace access
  • πŸ”΄ RTMS Access - REQUIRED! Request via Zoom Developer Forum

πŸ’‘ Recommended: Create a free ngrok account to get a static domain - makes webhook testing much easier!

1. Clone Repository

git clone https://github.com/your-org/arlo-meeting-assistant.git
cd arlo-meeting-assistant

2. Request RTMS Access (Critical!)

This step is required before you can use RTMS features:

  1. Go to Zoom Developer Forum
  2. Create a new post with the title: "Request RTMS Access for Meeting Assistant Development"
  3. In your post, include:
    Hi Zoom team,
    
    I'm building a meeting assistant using the Arlo Meeting Assistant starter kit
    and would like to request RTMS access for development and testing.
    
    Account email: [your-zoom-email@example.com]
    Use case: Building a real-time meeting assistant with live transcription
    App name: [Your App Name]
    
    Thank you!
    
  4. Wait for approval (usually 1-2 business days)
  5. Once approved, RTMS features will appear in your Zoom App settings

3. Create Zoom App

  1. Go to Zoom App Marketplace
  2. Click Develop β†’ Build App β†’ General App
  3. Name your app (e.g., "Arlo Meeting Assistant")
  4. Note your Client ID and Client Secret

App Manifest (Beta): If you have access to the Zoom App Manifest beta, you can upload zoom-app-manifest.json from this repo to pre-configure your app's scopes, SDK capabilities, event subscriptions, and more. See App Manifest below for details.

4. Configure Environment

# Copy example environment file
cp .env.example .env

# Generate secrets
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"  # SESSION_SECRET
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"  # REDIS_ENCRYPTION_KEY

# Edit .env and add:
# - ZOOM_CLIENT_ID
# - ZOOM_CLIENT_SECRET
# - SESSION_SECRET (generated above)
# - REDIS_ENCRYPTION_KEY (generated above)

5. Set Up ngrok (Expose Local Server to Internet)

ngrok creates a secure tunnel from the internet to your local development server, which is required for Zoom webhooks and OAuth callbacks.

First Time Setup:

  1. Create a free ngrok account at ngrok.com

  2. Install ngrok (if not already installed):

    # macOS (Homebrew)
    brew install ngrok
    
    # Or download from https://ngrok.com/download
  3. Authenticate ngrok with your account:

    ngrok config add-authtoken YOUR_AUTHTOKEN

    (Find your authtoken at https://dashboard.ngrok.com/get-started/your-authtoken)

🎯 Recommended: Use a Static Domain (FREE!)

ngrok now offers free static domains that don't change between restarts. This makes webhook configuration much easier since you won't need to update your Zoom App settings every time you restart ngrok.

  1. Claim your free static domain:

  2. Start ngrok with your static domain:

    ngrok http 3000 --domain=yourname-arlo.ngrok-free.app
  3. Benefits:

    • βœ… Same URL every time you restart ngrok
    • βœ… Configure Zoom webhooks once (no need to update)
    • βœ… Easier testing workflow
    • βœ… 100% free for development

Alternative: Use Random Domain (Changes Each Time)

If you prefer not to create an account or want a temporary setup:

ngrok http 3000

Copy the https:// URL from the ngrok output (e.g., https://abc123.ngrok-free.app)

⚠️ Note: This URL changes every time you restart ngrok, requiring you to update all Zoom App webhook URLs each time.

Verify ngrok is running:

Open your ngrok URL in a browser - you should see the Arlo frontend once the app is running.

6. Update Zoom App Configuration

In Zoom Marketplace β†’ Your App:

Replace your-ngrok-url.ngrok-free.app below with your actual ngrok domain:

Basic Information:

  • OAuth Redirect URL: https://your-ngrok-url.ngrok-free.app/api/auth/callback
  • OAuth Allow List: https://your-ngrok-url.ngrok-free.app

Features β†’ Zoom App SDK:

  • Add all required APIs (see CLAUDE.md)
  • ⚠️ Enable RTMS β†’ Transcripts (requires RTMS access approval)
  • Optional: Enable RTMS β†’ Audio (for advanced features)

Features β†’ Surface:

  • Home URL: https://your-ngrok-url.ngrok-free.app
  • Add to Domain Allow List: https://your-ngrok-url.ngrok-free.app

Event Subscriptions (Important for RTMS!):

  • Event notification endpoint URL: https://your-ngrok-url.ngrok-free.app/api/rtms/webhook
  • Subscribe to events:
    • βœ… meeting.rtms_started - Notifies when RTMS successfully starts
    • βœ… meeting.rtms_stopped - Notifies when RTMS ends
  • Copy your webhook URL from the "Event notification endpoint URL" field - you'll need this for testing

πŸ’‘ Pro Tip: If you're using a static ngrok domain, you only need to configure these webhooks once! With random domains, you'd need to update this URL every time you restart ngrok.

⚑ Optional: Auto-Start RTMS

To automatically start RTMS when meetings begin (without requiring users to click a button):

  1. In Features β†’ Event Subscriptions, also subscribe to:

    • meeting.participant_joined (to detect when you join a meeting)
  2. In your backend code (backend/src/routes/rtms.js), add a webhook handler:

    // Auto-start RTMS when participant joins
    if (event === 'meeting.participant_joined') {
      const { meeting_uuid, participant } = payload;
      // Check if this is the app user
      if (participant.id === appUserId) {
        await startRTMS(meeting_uuid);
      }
    }
  3. Trade-off: Auto-start provides seamless UX but uses more RTMS quota. Manual start (current implementation) gives users control.

Note: The current implementation uses manual start (user clicks "Start Arlo") for better control and transparency.

7. Update .env with ngrok URL

# Edit .env
PUBLIC_URL=https://your-ngrok-url.ngrok-free.app

8. Start Application

# Install root dependencies
npm install

# Start with Docker (recommended)
docker-compose up --build

# OR start manually
npm run setup     # Install all dependencies
npm run db:migrate  # Run database migrations
npm run dev       # Start all services

9. Test in Zoom

  1. Start or join a Zoom meeting
  2. Click Apps β†’ Find your app
  3. Click Add App (first time only)
  4. Authorize the app
  5. Click "Start Arlo"
  6. See live transcription appear!

πŸ“š Documentation

Comprehensive guides available in /docs/:


πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Zoom Meeting                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Arlo Meeting Assistant (React + Zoom SDK)       β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚ HTTPS + WebSocket
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚   Backend API (Express.js)      β”‚
         β”‚   - OAuth 2.0 (PKCE)            β”‚
         β”‚   - WebSocket Server            β”‚
         β”‚   - RTMS Ingestion              β”‚
         β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚           β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”   β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚  Postgres   β”‚   β”‚  OpenRouter      β”‚
    β”‚  Database   β”‚   β”‚  (Free AI)       β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Tech Stack:

  • Frontend: React 18, Zoom Apps SDK 0.16, react-router-dom 6 (HashRouter), @base-ui/react, lucide-react
  • Fonts: Source Serif 4 + Inter (self-hosted WOFF2)
  • Backend: Node.js 20, Express, Prisma
  • Database: PostgreSQL 15
  • AI: OpenRouter (free models, no API key required)
  • Real-time: WebSocket + RTMS SDK

πŸ› οΈ Development

Project Structure

arlo-meeting-assistant/
β”œβ”€β”€ backend/          # Express API server
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ server.js       # Main server + rate limiting
β”‚   β”‚   β”œβ”€β”€ config.js       # Environment config
β”‚   β”‚   β”œβ”€β”€ lib/prisma.js   # Singleton PrismaClient
β”‚   β”‚   β”œβ”€β”€ routes/         # API routes (9 modules)
β”‚   β”‚   └── services/       # Business logic
β”‚   └── prisma/
β”‚       └── schema.prisma   # Database schema
β”‚
β”œβ”€β”€ frontend/         # React Zoom App
β”‚   β”œβ”€β”€ public/
β”‚   β”‚   β”œβ”€β”€ index.html      # Loads Zoom SDK
β”‚   β”‚   └── fonts/          # Self-hosted Source Serif 4 + Inter
β”‚   └── src/
β”‚       β”œβ”€β”€ App.js           # HashRouter, routes, provider hierarchy
β”‚       β”œβ”€β”€ index.css        # Design tokens, typography, themes
β”‚       β”œβ”€β”€ views/           # 14 views (Auth, Home, MeetingsList, MeetingDetail, InMeeting, Search, Settings, Upcoming, GuestΓ—2, Landing, Onboarding, OAuthError, NotFound)
β”‚       β”œβ”€β”€ contexts/        # 5 contexts (Auth, ZoomSdk, Meeting, Theme, Toast)
β”‚       β”œβ”€β”€ hooks/           # useZoomAuth (OAuth PKCE)
β”‚       β”œβ”€β”€ utils/           # Shared formatters (timestamps, durations, dates)
β”‚       β”œβ”€β”€ components/      # AppShell, DeleteMeetingDialog, ParticipantTimeline, MeetingCard, etc.
β”‚       └── components/ui/   # Button, Card, Badge, Input, Textarea, LoadingSpinner
β”‚
β”œβ”€β”€ rtms/             # RTMS transcript ingestion
β”‚   └── src/
β”‚       └── index.js        # Webhook handler + RTMS client
β”‚
β”œβ”€β”€ docs/             # 15 comprehensive guides
β”œβ”€β”€ .env.example      # Environment variables template
β”œβ”€β”€ zoom-app-manifest.json  # Zoom App Manifest (beta)
β”œβ”€β”€ docker-compose.yml
└── README.md

Common Commands

# Start all services
docker-compose up

# View logs
docker-compose logs -f backend
docker-compose logs -f rtms

# Restart service
docker-compose restart backend

# Database operations
npm run db:migrate    # Run migrations
npm run db:studio     # Open Prisma Studio GUI
npm run db:reset      # Reset database (WARNING: deletes data)

# Clean restart
docker-compose down -v && docker-compose up --build

Database Migrations

cd backend

# Create migration after schema changes
npx prisma migrate dev --name description_of_change

# Generate Prisma Client
npx prisma generate

# Reset database (development only)
npx prisma migrate reset

πŸ§ͺ Testing

Manual Testing Checklist

  • App loads in Zoom client
  • OAuth flow completes
  • "Start Arlo" button works
  • Live transcript appears within 1s
  • WebSocket connection stable
  • Segments save to database
  • Can scroll through transcript
  • "Resume Live" button works
  • Stop button ends RTMS

Debugging

Frontend (Zoom App):

  • Right-click in app β†’ Inspect Element
  • Check Console for errors
  • Network tab shows API calls

Backend:

docker-compose logs -f backend | grep -i error
curl http://localhost:3000/health

Database:

npm run db:studio
# Opens GUI at http://localhost:5555

RTMS:

docker-compose logs -f rtms
curl http://localhost:3002/health

🀝 Contributing

This is an open-source starter kit designed to be forked and customized!

How to Customize

  1. Fork this repository
  2. Modify for your use case:
    • Add your own AI prompts
    • Customize UI/styling
    • Add new features
    • Change AI provider
  3. Share your improvements (optional PR)

Feature Ideas

  • Multi-language support
  • Custom AI models (local LLMs)
  • Team workspaces
  • Calendar integration
  • Video replay (like Fathom)
  • Risk/compliance signals
  • Background task extraction
  • Public sharing links

πŸ“¦ App Manifest (Beta)

Zoom App Manifests are JSON files that contain your app's configuration β€” scopes, SDK capabilities, event subscriptions, URLs, and more. This repo includes a pre-configured manifest at zoom-app-manifest.json that you can upload to quickly configure your Zoom App.

Prerequisites

  • You must be accepted into the Zoom App Manifest beta program (request access from Zoom)
  • Your app must be a General App on the Zoom Marketplace
  • You must be the account owner, admin, or have the "Zoom for developers" role

Using the Manifest

Before uploading, edit zoom-app-manifest.json and replace all instances of your-ngrok-url.ngrok-free.app with your actual ngrok domain (or production domain).

Upload to an existing app:

  1. Log into Zoom Marketplace β†’ Manage β†’ select your app
  2. Open the manifest panel (persistent menu bar or Basic Information page)
  3. Click Upload New Manifest and select zoom-app-manifest.json
  4. Zoom validates the manifest and shows a green checkmark on success
  5. Close the manifest window, refresh your browser, and confirm changes

Download from an existing app:

  1. In the manifest panel, click the download icon to save the current configuration
  2. Edit the JSON locally, then re-upload to apply changes

What's Included

The manifest pre-configures:

  • OAuth scopes: zoomapp:inmeeting, meeting:read:meeting, meeting:write:open_app (optional), user:read (optional)
  • SDK capabilities: All 16 APIs used by Arlo (getMeetingContext, callZoomApi, authorize, showNotification, etc.)
  • Event subscriptions: meeting.rtms_started, meeting.rtms_stopped
  • In-client OAuth: Enabled (PKCE flow)
  • Guest mode: Enabled with test guest mode
  • Domain allow list: Your ngrok domain + appssdk.zoom.us

Limitations (Beta)

  • Manifests can only update existing apps, not create new ones
  • Only user-editable values are updated; the build-flow UX verifies completeness
  • Values are case-sensitive and must match Zoom's expected format
  • RTMS access still requires separate approval from Zoom (the manifest alone does not grant RTMS)

πŸ“– Learn More


βš–οΈ License

MIT License - See LICENSE for details


πŸ’¬ Support


🌟 Acknowledgments

Built with:


Ready to build your own meeting assistant? Star this repo ⭐ and get started!

About

Arlo is a meeting assistant that runs natively in Zoom as an embedded app. Using Realtime Media Streams (RTMS) and the Apps SDK, it captures live transcripts, generates summaries and delivers meeting intelligence all running natively inside of a Zoom meeting. Designed for developers building the next generation of meeting tools.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Contributors 3

  •  
  •  
  •