# Zoom Client

Create meetings, manage users, and interact with Zoom's video conferencing platform.

## Methods

| Method                                   | Description                                      |
| ---------------------------------------- | ------------------------------------------------ |
| `apiRequest(options, schema, metadata?)` | Make any Zoom API request with schema validation |

## Usage

### Create a Meeting

```typescript
import { api, z, zoom } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_ZOOM = "a1b2c3d4-5678-90ab-cdef-zoom00000001";

const MeetingSchema = z.object({
  id: z.number(),
  uuid: z.string(),
  host_id: z.string(),
  topic: z.string(),
  type: z.number(),
  status: z.string().optional(),
  start_time: z.string(),
  duration: z.number(),
  timezone: z.string(),
  join_url: z.string(),
  password: z.string().optional(),
});

export default api({
  name: "ZoomExample",
  integrations: {
    zoom: zoom(PROD_ZOOM),
  },
  input: z.object({
    topic: z.string(),
    startTime: z.string(),
    duration: z.number(),
  }),
  output: z.object({
    meetingId: z.number(),
    joinUrl: z.string(),
  }),
  async run(ctx, { topic, startTime, duration }) {
    const result = await ctx.integrations.zoom.apiRequest(
      {
        method: "POST",
        path: "/users/me/meetings",
        body: {
          topic: topic,
          type: 2, // Scheduled meeting
          start_time: startTime,
          duration: duration,
          timezone: "America/New_York",
          settings: {
            host_video: true,
            participant_video: true,
            join_before_host: false,
            mute_upon_entry: true,
            waiting_room: true,
          },
        },
      },
      { response: MeetingSchema },
    );

    return { meetingId: result.id, joinUrl: result.join_url };
  },
});
```

### List Meetings

```typescript
const ListMeetingsResponseSchema = z.object({
  page_count: z.number(),
  page_number: z.number(),
  page_size: z.number(),
  total_records: z.number(),
  meetings: z.array(
    z.object({
      id: z.number(),
      uuid: z.string(),
      topic: z.string(),
      type: z.number(),
      start_time: z.string(),
      duration: z.number(),
      timezone: z.string(),
      join_url: z.string(),
    }),
  ),
});

const result = await ctx.integrations.zoom.apiRequest(
  {
    method: "GET",
    path: "/users/me/meetings",
    params: {
      type: "scheduled", // upcoming, live, scheduled
      page_size: 30,
    },
  },
  { response: ListMeetingsResponseSchema },
);

result.meetings.forEach((meeting) => {
  console.log(`${meeting.topic} - ${meeting.start_time}`);
});
```

### Get Meeting Details

```typescript
const MeetingDetailSchema = z.object({
  id: z.number(),
  uuid: z.string(),
  topic: z.string(),
  type: z.number(),
  status: z.string(),
  start_time: z.string(),
  duration: z.number(),
  timezone: z.string(),
  join_url: z.string(),
  password: z.string().optional(),
  settings: z.object({
    host_video: z.boolean(),
    participant_video: z.boolean(),
    waiting_room: z.boolean(),
  }),
});

const result = await ctx.integrations.zoom.apiRequest(
  {
    method: "GET",
    path: `/meetings/${meetingId}`,
  },
  { response: MeetingDetailSchema },
);
```

### Update a Meeting

```typescript
await ctx.integrations.zoom.apiRequest(
  {
    method: "PATCH",
    path: `/meetings/${meetingId}`,
    body: {
      topic: "Updated Meeting Topic",
      duration: 60,
      settings: {
        waiting_room: false,
      },
    },
  },
  { response: z.object({}).optional() }, // Returns 204 No Content
);
```

### Delete a Meeting

```typescript
await ctx.integrations.zoom.apiRequest(
  {
    method: "DELETE",
    path: `/meetings/${meetingId}`,
    params: {
      schedule_for_reminder: true,
    },
  },
  { response: z.object({}).optional() },
);
```

### Create a Webinar

```typescript
const WebinarSchema = z.object({
  id: z.number(),
  uuid: z.string(),
  host_id: z.string(),
  topic: z.string(),
  type: z.number(),
  start_time: z.string(),
  duration: z.number(),
  timezone: z.string(),
  join_url: z.string(),
  registration_url: z.string().optional(),
});

const result = await ctx.integrations.zoom.apiRequest(
  {
    method: "POST",
    path: "/users/me/webinars",
    body: {
      topic: "Product Launch Webinar",
      type: 5, // Scheduled webinar
      start_time: "2024-12-15T14:00:00Z",
      duration: 90,
      timezone: "America/New_York",
      settings: {
        approval_type: 0, // Auto approve
        registration_type: 1, // Register once
        audio: "both",
        panelists_video: true,
        practice_session: true,
      },
    },
  },
  { response: WebinarSchema },
);
```

### Get User

```typescript
const UserSchema = z.object({
  id: z.string(),
  first_name: z.string(),
  last_name: z.string(),
  email: z.string(),
  type: z.number(), // 1=Basic, 2=Licensed, 3=On-prem
  status: z.string(),
  pmi: z.number(), // Personal Meeting ID
  timezone: z.string(),
  dept: z.string().optional(),
});

const result = await ctx.integrations.zoom.apiRequest(
  {
    method: "GET",
    path: "/users/me",
  },
  { response: UserSchema },
);

console.log(`User: ${result.first_name} ${result.last_name}`);
```

### List Users

```typescript
const ListUsersResponseSchema = z.object({
  page_count: z.number(),
  page_number: z.number(),
  page_size: z.number(),
  total_records: z.number(),
  users: z.array(
    z.object({
      id: z.string(),
      first_name: z.string(),
      last_name: z.string(),
      email: z.string(),
      type: z.number(),
      status: z.string(),
    }),
  ),
});

const result = await ctx.integrations.zoom.apiRequest(
  {
    method: "GET",
    path: "/users",
    params: {
      status: "active",
      page_size: 30,
    },
  },
  { response: ListUsersResponseSchema },
);
```

### Add Meeting Registrant

```typescript
const RegistrantSchema = z.object({
  id: z.string(),
  registrant_id: z.string(),
  join_url: z.string(),
  topic: z.string(),
  start_time: z.string(),
});

const result = await ctx.integrations.zoom.apiRequest(
  {
    method: "POST",
    path: `/meetings/${meetingId}/registrants`,
    body: {
      email: "attendee@example.com",
      first_name: "John",
      last_name: "Doe",
      custom_questions: [{ title: "Company", value: "Acme Corp" }],
    },
  },
  { response: RegistrantSchema },
);
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### No Specialized Methods

```typescript
// WRONG - These methods do not exist
await zoom.createMeeting({ ... });
await zoom.listMeetings();

// CORRECT - Use apiRequest
await ctx.integrations.zoom.apiRequest(
  { method: "POST", path: "/users/me/meetings", body: { ... } },
  { response: MeetingSchema }
);
```

### Meeting Types

```typescript
// Meeting types
const MEETING_TYPES = {
  INSTANT: 1, // Instant meeting
  SCHEDULED: 2, // Scheduled meeting
  RECURRING_NO_TIME: 3, // Recurring with no fixed time
  RECURRING_FIXED: 8, // Recurring with fixed time
};

// Webinar types
const WEBINAR_TYPES = {
  WEBINAR: 5, // Scheduled webinar
  RECURRING_NO_TIME: 6, // Recurring webinar no fixed time
  RECURRING_FIXED: 9, // Recurring webinar fixed time
};
```

### User ID "me"

Use "me" for the authenticated user:

```typescript
// Current user's meetings
const path = "/users/me/meetings";

// Specific user's meetings (requires admin scope)
const path = `/users/${userId}/meetings`;
```

### Date/Time Format

Zoom uses ISO 8601 format:

```typescript
const body = {
  start_time: "2024-12-15T14:00:00Z", // UTC
  // Or with timezone
  start_time: "2024-12-15T14:00:00",
  timezone: "America/New_York",
};
```

### Empty Response on Success

Many operations return empty responses:

```typescript
// PATCH and DELETE often return 204 No Content
await ctx.integrations.zoom.apiRequest(
  { method: "PATCH", path: `/meetings/${id}`, body: { ... } },
  { response: z.object({}).optional() }  // Handle empty response
);
```

### Scopes Required

Different endpoints require different OAuth scopes:

```typescript
// Meetings
// meeting:read, meeting:write, meeting:read:admin, meeting:write:admin

// Users
// user:read, user:write, user:read:admin, user:write:admin

// Webinars
// webinar:read, webinar:write
```

### Rate Limits

Zoom has rate limits (varies by endpoint):

```typescript
// Most endpoints: 10 requests/second
// Heavy endpoints (e.g., reports): Lower limits
// Check X-RateLimit-* headers
```

### Pagination

Large result sets require pagination:

```typescript
async function getAllMeetings(zoom: ZoomClient) {
  const allMeetings: Meeting[] = [];
  let pageNumber = 1;

  while (true) {
    const result = await ctx.integrations.zoom.apiRequest(
      {
        method: "GET",
        path: "/users/me/meetings",
        params: {
          page_size: 300,
          page_number: pageNumber,
        },
      },
      { response: ListMeetingsResponseSchema },
    );

    allMeetings.push(...result.meetings);

    if (pageNumber >= result.page_count) break;
    pageNumber++;
  }

  return allMeetings;
}
```

## Error Handling

```typescript
import { RestApiValidationError } from "@superblocksteam/sdk-api";

try {
  const result = await ctx.integrations.zoom.apiRequest(
    { method: "POST", path: "/users/me/meetings", body: { ... } },
    { response: MeetingSchema }
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  }
}
```

## API Reference

- [Zoom API Documentation](https://developers.zoom.us/docs/api/)
- [Meetings](https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#tag/Meetings)
- [Users](https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#tag/Users)
- [Webinars](https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#tag/Webinars)
