npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

meetschedify

v0.3.0

Published

Schedule Google Meet and Zoom meetings from your Node.js backend with one simple API. Three provider types: Google Meet (static token), Google Meet (per-user OAuth), and Zoom (Server-to-Server). Full TypeScript support, typed errors, retry/timeout, and st

Readme

meetschedify

Schedule Google Meet and Zoom meetings from your Node.js backend in minutes — with full TypeScript support, typed errors, retry logic, and structured logging.

npm version npm downloads Node.js TypeScript License: MIT


What problem does meetschedify solve?

You want to schedule a Google Meet or Zoom call from your backend — for an interview, a patient consultation, a class, or a sales demo. You don't want to deal with OAuth flows, token refresh logic, Google Calendar API quirks, or Zoom's credential setup from scratch.

meetschedify gives you one clean API that handles all of it.

// That's it. One call. Any provider.
const meeting = await client.createMeeting({
  tenantId:   'org_123',
  providerId: 'zoom',
  title:      'Technical Interview — Backend Engineer',
  time: {
    start:    '2026-06-15T10:00:00',
    end:      '2026-06-15T11:00:00',
    timezone: 'Asia/Kolkata',
  },
  primaryParticipant: { email: '[email protected]' },
});

console.log(meeting.conferenceLink); // ✅ https://us05web.zoom.us/j/123456789

Installation

npm install meetschedify

Requirements: Node.js ≥ 18. No extra runtime dependencies for Zoom — uses Node's native fetch.


Choose your provider type

There are 3 ways to use meetschedify. Pick the one that fits your use case:

| Type | Class | Auth Mode | Best For | |------|-------|-----------|----------| | 1 | GoogleMeetStaticProvider | Static refresh token (one-time setup) | Internal tools, HR, hospitals, schools — one Google account owns all meetings | | 2 | GoogleMeetProvider | Full per-user OAuth | SaaS apps — each customer connects their own Google account | | 3 | ZoomProvider | Zoom Server-to-Server OAuth | Zoom meetings with zero user login |


Type 1 — Google Meet with a Static Refresh Token

Best for: recruitment platforms, hospital scheduling, university timetables, internal tools — any system where one Google Workspace account manages all calendar events.

No per-user login required at runtime. A developer runs a one-time script to get a refresh token, stores it securely, and that's it forever.


Step 1 — Create a Google Cloud project

  1. Go to Google Cloud Console
  2. Create a new project (or select an existing one)
  3. Go to APIs & Services → Library → enable Google Calendar API
  4. Go to APIs & Services → Credentials
  5. Click Create Credentials → OAuth 2.0 Client ID
  6. Choose Web application
  7. Under Authorized redirect URIs, add: http://localhost:5000/oauth2callback
  8. Copy the Client ID and Client Secret

Step 2 — Get your refresh token (one-time only)

Create this script in your project:

// scripts/get-google-token.mjs
// Run once: node scripts/get-google-token.mjs
// Then save the printed REFRESH_TOKEN to your .env or secrets manager.

import http from 'node:http';
import { google } from 'googleapis';
import open from 'open'; // npm install open --save-dev

const CLIENT_ID     = process.env.GOOGLE_CLIENT_ID;     // from Step 1
const CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET; // from Step 1
const REDIRECT_URI  = 'http://localhost:5000/oauth2callback';

if (!CLIENT_ID || !CLIENT_SECRET) {
  console.error('Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in your shell first.');
  process.exit(1);
}

const oauth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);

const authUrl = oauth2Client.generateAuthUrl({
  access_type: 'offline',
  prompt:      'consent',  // IMPORTANT: forces Google to always return a refresh_token
  scope:       ['https://www.googleapis.com/auth/calendar'],
});

const server = http.createServer(async (req, res) => {
  const reqUrl = new URL(req.url ?? '/', `http://localhost:5000`);
  if (reqUrl.pathname !== '/oauth2callback') { res.end(); return; }

  const code = reqUrl.searchParams.get('code');
  if (!code) {
    res.writeHead(400); res.end('No authorization code received.');
    server.close(); return;
  }

  try {
    const { tokens } = await oauth2Client.getToken(code);

    console.log('\n✅  Copy this into your .env file:\n');
    console.log(`GOOGLE_REFRESH_TOKEN=${tokens.refresh_token}`);
    console.log('\n⚠️  Store this securely — treat it like a password.\n');

    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h2>Done! Check your terminal for the REFRESH_TOKEN.</h2>');
  } catch (err) {
    console.error('Token exchange failed:', err);
    res.writeHead(500); res.end('Token exchange failed. Check terminal.');
  }

  setTimeout(() => server.close(), 500);
});

server.listen(5000, () => {
  console.log('Opening browser — sign in with the Google account that will own meetings.\n');
  open(authUrl);
});

Run it:

GOOGLE_CLIENT_ID=xxx GOOGLE_CLIENT_SECRET=xxx node scripts/get-google-token.mjs

Your browser will open. Sign in with the Google account that should own all meeting invites. The terminal will print your REFRESH_TOKEN. Save it to your .env file or secrets manager.


Step 3 — Add environment variables

# .env
GOOGLE_CLIENT_ID=123456789-xxxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-your-secret-here
GOOGLE_REDIRECT_URI=http://localhost:5000/oauth2callback
GOOGLE_REFRESH_TOKEN=1//0xxxxxxxxxxxxxxxx-xxxxxxxx   # from Step 2

Step 4 — Use in your backend

// meetingClient.ts — create once, use everywhere (singleton)
import {
  MeetSchedifyClient,
  GoogleMeetStaticProvider,
  consoleLogger,
} from 'meetschedify';

export const meetingClient = new MeetSchedifyClient({
  providers: [
    // Option A — manual config
    new GoogleMeetStaticProvider({
      clientId:     process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      redirectUri:  process.env.GOOGLE_REDIRECT_URI!,
      refreshToken: process.env.GOOGLE_REFRESH_TOKEN!,
      logger:       consoleLogger, // or silentLogger in tests
    }),

    // Option B — fromEnv() shortcut (reads the same env vars automatically)
    // GoogleMeetStaticProvider.fromEnv({ logger: consoleLogger }),
  ],
});

Step 5 — Schedule a Google Meet

import { meetingClient } from './meetingClient';
import { MeetSchedifyError, ErrorCodes } from 'meetschedify';

async function scheduleInterview(candidate: Candidate, interviewer: Staff) {
  try {
    const meeting = await meetingClient.createMeeting({
      tenantId:   'acme-hr',                   // your stable internal tenant ID
      providerId: 'google-meet-static',

      title:       `Interview — ${candidate.name}`,
      description: 'Round 2: System Design. Please join 2 minutes early.',

      time: {
        start:    '2026-06-20T10:00:00',
        end:      '2026-06-20T11:00:00',
        timezone: 'Asia/Kolkata',              // any IANA timezone
      },

      primaryParticipant: {
        email:       candidate.email,
        displayName: candidate.name,
      },
      additionalParticipants: [
        { email: interviewer.email, displayName: interviewer.name },
      ],

      addConferenceLink: true,                 // attach a Google Meet link
    });

    return {
      meetLink:    meeting.conferenceLink,     // https://meet.google.com/abc-defg-hij
      calendarUrl: meeting.calendarUrl,        // Google Calendar event URL
      eventId:     meeting.providerEventId,    // store this if you need to cancel later
    };

  } catch (err) {
    if (err instanceof MeetSchedifyError) {
      if (err.code === ErrorCodes.GOOGLE_AUTH_REVOKED) {
        throw new Error('Google token expired — re-run the get-token script.');
      }
    }
    throw err;
  }
}

Type 2 — Google Meet with Per-User OAuth

Best for: SaaS products where each customer connects their own Google account and meetings appear on their own calendar.


Step 1 — Same Google Cloud setup as Type 1

Follow Type 1 Steps 1 but use your production redirect URI, e.g.: https://yourapp.com/auth/google/callback

No need to run the get-token script — tokens are collected per-user at runtime.


Step 2 — Implement TokenStore

TokenStore is your storage layer for OAuth tokens. Implement it with any database:

// src/store/GoogleTokenStore.ts
import type { TokenStore, TokenStoreContext, TokenRecord } from 'meetschedify';
import { OAuthToken } from './models/OAuthToken'; // your Mongoose/Sequelize/Prisma model

export class GoogleTokenStore implements TokenStore {
  async getTokens(ctx: TokenStoreContext): Promise<TokenRecord | null> {
    // ctx.tenantId  — your tenant/user/org ID
    // ctx.providerId — "google-meet" (always)
    const doc = await OAuthToken.findOne({
      tenantId:   ctx.tenantId,
      providerId: ctx.providerId,
    });
    if (!doc) return null;

    return {
      accessToken:  doc.accessToken,   // ← decrypt if you encrypt at rest
      refreshToken: doc.refreshToken,
      expiryDate:   doc.expiryDate,
    };
  }

  async saveTokens(ctx: TokenStoreContext, tokens: TokenRecord): Promise<void> {
    // Called automatically after OAuth callback AND after every token auto-refresh
    await OAuthToken.findOneAndUpdate(
      { tenantId: ctx.tenantId, providerId: ctx.providerId },
      {
        accessToken:  tokens.accessToken,   // ← encrypt before saving
        refreshToken: tokens.refreshToken,
        expiryDate:   tokens.expiryDate,
        updatedAt:    new Date(),
      },
      { upsert: true },
    );
  }

  async deleteTokens(ctx: TokenStoreContext): Promise<void> {
    // Implement this to support the "Disconnect Google" flow
    await OAuthToken.deleteOne({ tenantId: ctx.tenantId, providerId: ctx.providerId });
  }
}

Step 3 — Set up the provider

// meetingClient.ts
import {
  MeetSchedifyClient,
  GoogleMeetProvider,
  consoleLogger,
} from 'meetschedify';
import { GoogleTokenStore } from './store/GoogleTokenStore';

export const meetingClient = new MeetSchedifyClient({
  providers: [
    new GoogleMeetProvider(
      {
        clientId:     process.env.GOOGLE_CLIENT_ID!,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
        redirectUri:  process.env.GOOGLE_REDIRECT_URI!,
        logger:       consoleLogger,
      },
      new GoogleTokenStore()
    ),
  ],
});

Step 4 — Add OAuth connect routes (Express example)

// src/routes/auth.ts
import { Router } from 'express';
import { meetingClient } from '../meetingClient';
import { MeetSchedifyError, ErrorCodes } from 'meetschedify';

const router = Router();

// Step A: User clicks "Connect Google Calendar" → redirect them here
router.get('/auth/google/connect', async (req, res) => {
  const tenantId = (req as any).user.tenantId; // from your session or JWT

  const { url } = await meetingClient.getAuthorizationUrl({
    tenantId,
    providerId: 'google-meet',
    returnUrl:  '/dashboard',  // where to send the user after connecting
  });

  res.redirect(url);
});

// Step B: Google redirects back here after user approves
router.get('/auth/google/callback', async (req, res) => {
  const { code, state } = req.query as { code: string; state: string };

  try {
    // Exchanges code for tokens and saves them via your GoogleTokenStore
    await meetingClient.handleOAuthCallback('google-meet', { code, state });
    res.redirect('/dashboard?connected=true');
  } catch (err) {
    res.redirect('/dashboard?error=google_connect_failed');
  }
});

// Step C: User disconnects Google Calendar
router.post('/auth/google/disconnect', async (req, res) => {
  const tenantId = (req as any).user.tenantId;
  await meetingClient.disconnectProvider(tenantId, 'google-meet');
  res.json({ disconnected: true });
});

export default router;

Step 5 — Check connection and schedule

// Before creating any meeting, check if the user is connected
const status = await meetingClient.getConnectionStatus(tenantId, 'google-meet');

if (!status.connected) {
  // Redirect user to /auth/google/connect
  return res.status(401).json({
    error: 'Google Calendar not connected.',
    action: '/auth/google/connect',
  });
}

// Once connected, create meetings exactly like Type 1
const meeting = await meetingClient.createMeeting({
  tenantId,                    // must match the tenantId used during OAuth connect
  providerId: 'google-meet',
  title:      'Product Demo — Acme Corp',
  time: {
    start:    '2026-07-15T14:00:00',
    end:      '2026-07-15T15:00:00',
    timezone: 'America/New_York',
  },
  primaryParticipant:    { email: '[email protected]' },
  additionalParticipants: [{ email: '[email protected]' }],
});

console.log(meeting.conferenceLink); // Google Meet link

Type 3 — Zoom (Server-to-Server OAuth)

Best for: any backend that needs Zoom meetings with zero user login. Uses Zoom's machine-to-machine OAuth — no OAuth redirect needed.

Works great for: hospitals, schools, tech interviews, legal/finance, SaaS platforms.


Step 1 — Create a Zoom Server-to-Server OAuth app

  1. Go to marketplace.zoom.us
  2. Click Develop → Build App
  3. Choose Server-to-Server OAuth → click Create
  4. Give the app a name (e.g. "MyApp Meeting Scheduler")
  5. Go to Scopes → add meeting:write:meeting
  6. Go to Activation → click Activate your app
  7. Copy Account ID, Client ID, and Client Secret

Step 2 — Add environment variables

# .env
ZOOM_ACCOUNT_ID=AbCdEfGhIjK1234567890
ZOOM_CLIENT_ID=your_zoom_client_id
ZOOM_CLIENT_SECRET=your_zoom_client_secret

Step 3 — Set up the provider

// meetingClient.ts
import {
  MeetSchedifyClient,
  ZoomProvider,
  consoleLogger,
} from 'meetschedify';

export const meetingClient = new MeetSchedifyClient({
  providers: [
    // Option A — fromEnv() shortcut (reads ZOOM_ACCOUNT_ID, ZOOM_CLIENT_ID, ZOOM_CLIENT_SECRET)
    ZoomProvider.fromEnv({ logger: consoleLogger }),

    // Option B — manual config with org-specific defaults
    // new ZoomProvider({
    //   accountId:    process.env.ZOOM_ACCOUNT_ID!,
    //   clientId:     process.env.ZOOM_CLIENT_ID!,
    //   clientSecret: process.env.ZOOM_CLIENT_SECRET!,
    //   meetingDefaults: {
    //     waitingRoom:    true,  // great for healthcare / legal
    //     joinBeforeHost: false,
    //     muteUponEntry:  false,
    //     hostVideo:      true,
    //   },
    //   retry: { maxAttempts: 4 },   // extra resilience
    //   logger: consoleLogger,
    // }),
  ],
});

Step 4 — Schedule a Zoom meeting

import { meetingClient } from './meetingClient';

async function createZoomConsultation(doctor: Doctor, patient: Patient) {
  const meeting = await meetingClient.createMeeting({
    tenantId:   `hospital_ward_${doctor.wardId}`,
    providerId: 'zoom',

    title:       `Consultation — Dr. ${doctor.name} × ${patient.name}`,
    time: {
      start:    '2026-08-01T09:00:00',
      end:      '2026-08-01T09:30:00',
      timezone: 'America/Chicago',
    },

    primaryParticipant:    { email: patient.email, displayName: patient.name },
    additionalParticipants: [{ email: doctor.email, displayName: `Dr. ${doctor.name}` }],
    addConferenceLink:     true,
  });

  return {
    joinUrl:   meeting.conferenceLink,    // https://us05web.zoom.us/j/12345...
    meetingId: meeting.providerEventId,   // store for cancellation
  };
}

Step 5 — Cancel a Zoom meeting

// meetingId = providerEventId from when you called createMeeting()
await meetingClient.cancelMeeting('zoom', storedMeetingId, {
  tenantId: 'hospital_ward_4',
});

Using all three providers together

// meetingClient.ts — register all providers at startup
import {
  MeetSchedifyClient,
  GoogleMeetStaticProvider,
  GoogleMeetProvider,
  ZoomProvider,
  consoleLogger,
} from 'meetschedify';
import { GoogleTokenStore } from './store/GoogleTokenStore';

export const meetingClient = new MeetSchedifyClient({
  providers: [
    GoogleMeetStaticProvider.fromEnv({ logger: consoleLogger }),
    new GoogleMeetProvider(
      { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, redirectUri: process.env.GOOGLE_REDIRECT_URI! },
      new GoogleTokenStore()
    ),
    ZoomProvider.fromEnv({ logger: consoleLogger }),
  ],
});

// List all registered providers
console.log(meetingClient.listProviders());
// → ["google-meet-static", "google-meet", "zoom"]

// Route to the right one based on org/user preference
const meeting = await meetingClient.createMeeting({
  tenantId:   user.orgId,
  providerId: user.preferredProvider,  // 'google-meet-static' | 'google-meet' | 'zoom'
  title:      'Team Standup',
  time: { start: '...', end: '...', timezone: 'Asia/Dubai' },
});

ProviderIds — type-safe constants

Use ProviderIds instead of raw strings to get IDE autocomplete:

import { ProviderIds } from 'meetschedify';

await meetingClient.createMeeting({
  providerId: ProviderIds.ZOOM,               // 'zoom'
  // providerId: ProviderIds.GOOGLE_MEET,      // 'google-meet'
  // providerId: ProviderIds.GOOGLE_MEET_STATIC // 'google-meet-static'
  // ...
});

Error handling

Every SDK error is an instance of MeetSchedifyError. Use ErrorCodes for reliable, refactor-safe matching:

import { MeetSchedifyError, GoogleMeetError, ZoomError, ErrorCodes } from 'meetschedify';

try {
  const meeting = await meetingClient.createMeeting({ ... });
  return res.json({ meetLink: meeting.conferenceLink });

} catch (err) {
  if (!(err instanceof MeetSchedifyError)) throw err; // re-throw non-SDK errors

  switch (err.code) {

    // ── Google Meet ──────────────────────────────────────────────────────────
    case ErrorCodes.GOOGLE_NOT_CONNECTED:
      // User hasn't connected their Google account yet
      return res.status(401).json({
        error: 'Google Calendar not connected.',
        action: { redirect: '/auth/google/connect' },
      });

    case ErrorCodes.GOOGLE_AUTH_REVOKED:
      // Refresh token was revoked by the user — they must re-authenticate
      return res.status(401).json({
        error: 'Google authorization was revoked. Please reconnect.',
        action: { redirect: '/auth/google/connect' },
      });

    case ErrorCodes.GOOGLE_RATE_LIMIT:
      return res.status(429).json({ error: 'Too many calendar requests. Try again shortly.' });

    // ── Zoom ─────────────────────────────────────────────────────────────────
    case ErrorCodes.ZOOM_INVALID_CLIENT:
      // Zoom credentials are wrong or the app is deactivated → alert your ops team
      console.error('[OPS ALERT] Zoom integration broken:', err.message);
      return res.status(503).json({ error: 'Video conferencing temporarily unavailable.' });

    case ErrorCodes.ZOOM_RATE_LIMIT:
      return res.status(429).json({ error: 'Zoom API rate limit. Retry after 30 seconds.' });

    case ErrorCodes.ZOOM_NETWORK_ERROR:
      return res.status(502).json({ error: 'Could not reach Zoom. Try again.' });

    // ── General ──────────────────────────────────────────────────────────────
    case ErrorCodes.PROVIDER_NOT_FOUND:
      return res.status(400).json({ error: `Unknown provider: ${err.providerId}` });

    default:
      console.error('Unhandled meeting error:', err.code, err.message);
      return res.status(500).json({ error: 'Failed to schedule meeting.' });
  }
}

Accessing provider-specific error details

if (err instanceof GoogleMeetError) {
  err.httpStatus;        // 401 | 403 | 429 | ...
  err.googleApiMessage;  // raw Google API error text
}

if (err instanceof ZoomError) {
  err.httpStatus;     // 401 | 403 | 429 | ...
  err.zoomErrorCode;  // e.g. "invalid_client"
  err.zoomBody;       // full Zoom response body (for debugging)
}

Logging

import { consoleLogger, silentLogger, MeetSchedifyLogger } from 'meetschedify';

// Built-in: console output (great for development)
new ZoomProvider({ ..., logger: consoleLogger });

// Built-in: no output (great for tests)
new ZoomProvider({ ..., logger: silentLogger });

// pino adapter
import pino from 'pino';
const raw = pino();
const logger: MeetSchedifyLogger = {
  info:  (msg, meta) => raw.info(meta ?? {},  msg),
  warn:  (msg, meta) => raw.warn(meta ?? {},  msg),
  error: (msg, meta) => raw.error(meta ?? {}, msg),
  debug: (msg, meta) => raw.debug(meta ?? {}, msg),
};

// winston adapter
import winston from 'winston';
const raw = winston.createLogger({ ... });
const logger: MeetSchedifyLogger = {
  info:  (msg, meta) => raw.info(msg, meta),
  warn:  (msg, meta) => raw.warn(msg, meta),
  error: (msg, meta) => raw.error(msg, meta),
};

Retry & Timeout (Zoom)

const provider = new ZoomProvider({
  accountId:    process.env.ZOOM_ACCOUNT_ID!,
  clientId:     process.env.ZOOM_CLIENT_ID!,
  clientSecret: process.env.ZOOM_CLIENT_SECRET!,

  requestTimeoutMs: 20_000,   // abort requests after 20s (default: 15s)

  retry: {
    maxAttempts:    4,          // total attempts (default: 3)
    initialDelayMs: 500,        // delay before retry 2 (default: 500ms)
    maxDelayMs:     10_000,     // cap at 10s (default: 8s)
    factor:         2,          // exponential multiplier (default: 2)
  },
});

Retries automatically on: network errors, 429 rate limits, 500/502/503/504 server errors. Does not retry: 400 bad request, 401 invalid credentials, 403 forbidden.


Industry-specific Zoom configurations

// Healthcare — HIPAA-aligned (waiting room, no early join)
new ZoomProvider({
  accountId: '...', clientId: '...', clientSecret: '...',
  meetingDefaults: {
    waitingRoom:     true,   // doctor admits each patient individually
    joinBeforeHost:  false,  // patient cannot enter before doctor
    muteUponEntry:   false,
    hostVideo:       true,
    participantVideo: false,
  },
});

// Education — online classroom (muted entry, students gather early)
new ZoomProvider({
  accountId: '...', clientId: '...', clientSecret: '...',
  meetingDefaults: {
    muteUponEntry:  true,   // students muted when joining
    joinBeforeHost: true,   // students can gather before teacher joins
    waitingRoom:    false,
    hostVideo:      true,
    participantVideo: false,
  },
});

// Recruiting — technical interview (both cameras on)
new ZoomProvider({
  accountId: '...', clientId: '...', clientSecret: '...',
  meetingDefaults: {
    hostVideo:       true,
    participantVideo: true,   // candidate should be on camera
    waitingRoom:     true,    // interviewer controls session start
    joinBeforeHost:  false,
  },
});

Full API reference

MeetSchedifyClient methods

| Method | Returns | Description | |--------|---------|-------------| | listProviders() | ProviderId[] | List all registered provider IDs | | getConnectionStatus(tenantId, providerId) | Promise<ProviderConnectionStatus> | Check if a tenant is connected | | getAuthorizationUrl(options) | Promise<AuthUrlResult> | Get OAuth URL — Type 2 only | | handleOAuthCallback(providerId, payload) | Promise<OAuthCallbackResult> | Exchange OAuth code — Type 2 only | | createMeeting(request) | Promise<CreatedMeeting> | Create a meeting | | cancelMeeting(providerId, meetingId, ctx) | Promise<void> | Cancel a meeting | | disconnectProvider(tenantId, providerId) | Promise<void> | Revoke + delete tokens — Type 2 only |


CreateMeetingRequest fields

| Field | Type | Required | Description | |-------|------|----------|-------------| | tenantId | string | ✅ | Your stable tenant/org identifier | | providerId | ProviderId | ✅ | 'google-meet-static' | 'google-meet' | 'zoom' | | title | string | ✅ | Meeting title | | time.start | string | ✅ | ISO 8601 datetime | | time.end | string | ✅ | ISO 8601 datetime | | time.timezone | string | — | IANA timezone (default: "UTC") | | description | string | — | Meeting description | | primaryParticipant | MeetingParticipant | — | Main attendee (To: line) | | additionalParticipants | MeetingParticipant[] | — | Extra attendees | | addConferenceLink | boolean | — | Attach video link (default: true) | | metadata | Record<string, unknown> | — | Your data, passed through unchanged |


CreatedMeeting fields

| Field | Type | Description | |-------|------|-------------| | providerId | ProviderId | Which provider created this | | providerEventId | string | Google Calendar event ID or Zoom meeting ID — store for cancel | | conferenceLink | string \| null | The join URL — show this to participants | | calendarUrl | string \| null | Google Calendar event page URL (null for Zoom) | | conferenceLinks | MeetingLink[] | All entry points (video, phone, SIP) | | time | MeetingTimeRange | Start, end, timezone | | location | MeetingLocation | { type: 'online', value: <join-url> } | | metadata | Record<string, unknown> | Your metadata, passed back unchanged | | raw | unknown | Full provider API response for advanced use |


ErrorCodes — all values

import { ErrorCodes } from 'meetschedify';

// Google Meet
ErrorCodes.GOOGLE_NOT_CONNECTED          // No tokens found — redirect to OAuth
ErrorCodes.GOOGLE_TOKEN_REFRESH_FAILED   // Token refresh failed
ErrorCodes.GOOGLE_AUTH_REVOKED           // Refresh token revoked — re-auth required
ErrorCodes.GOOGLE_CALENDAR_ERROR         // Calendar API error
ErrorCodes.GOOGLE_RATE_LIMIT             // Rate limit exceeded

// Zoom
ErrorCodes.ZOOM_INVALID_CLIENT           // Wrong credentials or inactive app
ErrorCodes.ZOOM_TOKEN_ERROR              // Could not get access token
ErrorCodes.ZOOM_MEETING_ERROR            // Meeting create/cancel failed
ErrorCodes.ZOOM_RATE_LIMIT               // Rate limit hit
ErrorCodes.ZOOM_NETWORK_ERROR            // Network or timeout failure
ErrorCodes.ZOOM_MAX_RETRIES_EXCEEDED     // All retries failed

// General
ErrorCodes.PROVIDER_NOT_FOUND            // Provider not registered in client
ErrorCodes.INVALID_CONFIG                // Bad provider configuration
ErrorCodes.OPERATION_NOT_SUPPORTED       // Provider doesn't support this operation

Security guidelines

  • Never commit refresh tokens, client secrets, or Zoom credentials to source control.
  • Use a secrets manager: AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, Azure Key Vault, or .env files with dotenv-vault.
  • Implement TokenStore.saveTokens() with encryption at rest — refresh tokens are long-lived credentials.
  • Scope tenantId in your TokenStore — ensure tenant A can never retrieve tenant B's tokens.
  • The logger callbacks only receive sanitized metadata — raw tokens are never passed to it.
  • Revoke unused Google refresh tokens at: Google Account → Security → Third-party apps.
  • Zoom Server-to-Server tokens expire in ~1 hour — the SDK refreshes them automatically.

Changelog

v0.3.0

  • Typed error systemMeetSchedifyError, GoogleMeetError, ZoomError, ErrorCodes
  • Structured loggerMeetSchedifyLogger interface + consoleLogger + silentLogger
  • Retry with exponential back-offZoomProvider retries on network/429/5xx
  • Request timeoutZoomProvider requestTimeoutMs via AbortController
  • ZoomMeetingDefaults — waiting room, mute-on-entry, host video, per-org defaults
  • fromEnv() factoryGoogleMeetStaticProvider.fromEnv() and ZoomProvider.fromEnv()
  • ProviderIds constants — type-safe provider ID object
  • MeetSchedifyClient.listProviders() — see all registered providers
  • MeetSchedifyClient.cancelMeeting() — cancel via any provider
  • MeetSchedifyClient.disconnectProvider() — revoke OAuth + clear TokenStore
  • TokenStore.deleteTokens() — optional disconnect support
  • withRetry() utility — exported for custom providers
  • metadata round-trip — pass data through createMeeting, get it back in result
  • calendarId config — use any Google Calendar, not just "primary"

v0.2.0

  • Added GoogleMeetStaticProvider (static refresh token, no per-user login)
  • Added ZoomProvider (Zoom Server-to-Server OAuth)

v0.1.2

  • Initial release: GoogleMeetProvider with full per-user OAuth and TokenStore

License

MIT © Raj Kumar Singha