Share admin privileges with participants during a real-time video call
Tags: webrtc, javascript, tutorial, webdev
Published: 2023-10-24 18:31:49

_By [Jess Mitchell](https://www.daily.co/blog/author/jess/)_

Having someone to oversee a video call meeting is important for numerous reasons, including being able to manage which participants are allowed in the call. With [Daily’s Client SDK for JavaScript](https://docs.daily.co/reference/daily-js), participants can optionally join as meeting owners, which gives them special privileges like being able to remove participants from a call.

In some cases, it can be useful for meeting owners to share their administrator responsibilities with other participants, too.

In today’s post, we’ll look at sample code that demonstrates how to let meeting owners promote participants to admins, as well as how to let admins or owners remove other participants.

## Daily meeting owners vs. admins
To start, let’s look at how meeting owners and admins differ.

### Meeting owner privileges
A [meeting owner](https://docs.daily.co/guides/configurations-and-settings/controlling-who-joins-a-meeting#meeting-owner-privileges) is a participant with the most privileges related to managing room access.

Participants can join as a meeting owner by using a meeting owner [token](https://www.daily.co/blog/comparing-domain-room-and-meeting-token-rest-api-configurations-for-daily-video-calls/): a token that has the [`is_owner`](https://docs.daily.co/reference/rest-api/meeting-tokens/config#is_owner) property set to `true`.

Being a meeting owner allows the participant to:

- Share media (audio/video/screen) in [owner-only broadcast mode](https://www.daily.co/blog/daily-prebuilt-broadcast-call-deep-dive/), if these features are enabled in the Daily room.
- Start or stop a [live stream](https://www.daily.co/blog/live-stream-daily-calls-with-only-3-second-latency/), if live streaming is enabled in Daily the room.
- Start or stop call [transcription](https://www.daily.co/blog/add-live-transcription-to-a-daily-call-with-our-newest-api/).
- Allow participants to join a private room by responding to [knocking](https://docs.daily.co/guides/configurations-and-settings/controlling-who-joins-a-meeting#knocking).
- [Update](https://docs.daily.co/reference/daily-js/instance-methods/update-participant) other participants, such as adding or revoking admin permissions and muting their devices.
- (See an example of creating a token using in a [fetch request](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) in today’s [sample app](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/daily.js#L24).)

### Admins
Meeting admins are similar to meeting owners, with two key differences:

- A meeting _owner_ has to join the call with an owner meeting token. An admin can join with an admin token or be promoted to an admin mid-call.
- Unlike an owner, an admin can be demoted and lose admin privileges.
- An owner has all relevant privileges within the call, whereas an admin can be given more granular permissions. Depending on the specific [permissions](https://docs.daily.co/reference/daily-js/instance-methods/update-participant#permissions) provided, admins can potentially do any of the actions listed above for meeting owners; however, they cannot remove a meeting owner from a call or remove their meeting owner privileges.

![Table showing difference between owners and admins](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1f6dj8liqi2tddckx69z.png)

Now, onto the code!

## Today’s goals
In this tutorial, we’ll look at sample code for how to build an admin panel to let meeting owners upgrade regular participants to admins or remove them from the call. We’ll use a [sample app](https://github.com/daily-demos/daily-samples-js/tree/main/samples/daily-prebuilt/permissions-can-admin) built with [Next.js](https://nextjs.org/) and [Daily Prebuilt](https://www.daily.co/products/prebuilt-video-call-app/).

![The meeting owner’s view of the demo, with buttons to remove a participant or make them an admin.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yv2yf4k7crz6fsev0hjo.png)
_The meeting owner’s view of the demo, with buttons to remove a participant or make them an admin._

This tutorial will focus on two features:

- A button that converts a participant to an admin.
- A button that removes the participant (or admin) from the call.
We won’t cover some of the more general Daily-related code like rendering Daily Prebuilt in your app, but we’ll include some related blog posts at the end!

To test the demo app yourself, follow the instructions included in its [README](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/README.md).

## Project structure

![The `Home` component’s default view, which renders the `Header` and `JoinForm` components.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qhbnf18qkrkgpte3yj52.png)
_The `Home` component’s default view, which renders the `Header` and `JoinForm` components._

When the main route of our app (e.g., `localhost:3000/`) is visited, the component in the [`page.js` file](https://nextjs.org/docs/app/api-reference/file-conventions/page) found at the top-level of the app’s `/app` directory is rendered – in this case, the [`Home` component](https://github.com/daily-demos/daily-samples-js/blob/main/samples/daily-prebuilt/permissions-can-admin/src/app/page.js), which parents all other components in the app.

![Illustration depicting `Home`, `Header`, `DailyContainer`, `JoinForm`, Admin Panel, and `containerRef` components](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wuyxp6hu4yw21w2xpbra.png)
_Component structure for this demo app._

The `DailyContainer` component is the home of our video call feature and has a number of conditionally-rendered components/elements:

- A form (`JoinForm`) to join a call.
- The `AdminPanel` component to upgrade regular participants to admins or remove them from the call.
- The `containerRef`, a `div` which will contain the Daily Prebuilt UI once the `JoinForm` is submitted and the call is created.

![The admin panel is highlighted in red and the Daily Prebuilt container is highlighted in blue.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8om8i1xa8secfsxi79tb.png)
_The admin panel is highlighted in red and the Daily Prebuilt container is highlighted in blue._

## Creating a Daily room and joining the call

![Submitting the `JoinForm` form to create and join a call.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6zfmqk5jzm3316wmpecq.gif)
_Submitting the `JoinForm` form to create and join a call._

Let’s start by quickly familiarizing ourselves with what happens after the `JoinForm` is [submitted](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L119).

The `JoinForm` itself is [conditionally rendered](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L243) and shown by default. Once it’s submitted, it’s destroyed and the call-related components are displayed instead.

![Default view of the `JoinForm`.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5pplvof0lx3sibedju7.png)
_Default view of the `JoinForm`._

There are also two conditions to be aware of when the `JoinForm` is rendered:

1. (Default) The user is creating a new Daily room to join and will join as a meeting owner with an owner meeting token. They can then share a link to that specific room.
2. If the participant is using a shared link with a `url` query param (e.g., `http://localhost:3000/?url=https://domain.daily.co/[room-name]`), a “Daily room URL” form input will be rendered to indicate which room is being joined and no new room or meeting token will be created for them.

![Form showing name and Daily room URL fields](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1n7q9japlsit4rd5l18z.png)
_Joining a specific Daily room._

This means there are two types of participants on join: meeting owners and regular participants. (No one joins as an admin!)

When the [`JoinForm`](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/JoinForm/JoinForm.js#L6) is [submitted](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L168), a few things happen.

If the participant is joining as a meeting owner:

- A new room is [created](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L184).
- An owner [meeting token is created](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L191).
- An instance of a call frame (or `DailyIframe` class) is [created](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L122). (Note: This is called `callFrame` in the code samples below.)
- The call is [joined](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L140) using the meeting token.

If the participant is joining an existing call, the call frame is created and joined without a meeting token.

Now that we know how our participants can join the video call, let’s see how the `AdminPanel` component works.

## Rendering the `AdminPanel` component
The [`AdminPanel` component](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/AdminPanel/AdminPanel.js) will render a list of all call participants. If the local participant has owner or admin privileges, each participant in the list will have buttons to remove that participant or promote them to be an admin. (Admins can’t remove owners, though!)

![Owner view of a call with two participants.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4fjmc71l0p9n63u4863.png)
_Owner view of a call with two participants._

Before getting into how we remove/promote participants, let’s first look at how we keep track of them in app state. In [`DailyContainer`](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js), there’s a `participants` object saved in the component’s state:

```
const [participants, setParticipants] = useState({});
```

Any time someone joins the call, we [update](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L42) the `participants` object by adding the participant’s session ID as the key and the [participant’s object](https://docs.daily.co/reference/daily-js/instance-methods/participants#participant-properties) as the value.

```
setParticipants((p) => ({
   ...p,
   [e.participant.session_id]: e.participant,
 }));
```

Note: When someone [joins as an owner](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L15) or anyone [is promoted to an admin](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L66), their status is tracked in `DailyContainer`’s state. These values are passed as props to `AdminPanel`, too.

If someone leaves the call, we [delete](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L73) their item from the object:

```
setParticipants((p) => {
    const currentParticipants = { ...p };
    delete currentParticipants[e.participant.session_id];
    return currentParticipants;
});
```

Using `participants`, we can render all call participants as a list. First we render the [`AdminPanel` component](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L2690) itself, including number of props:

```
// DailyContainer.js
{callFrame && (
        <>
          <AdminPanel
            participants={participants}
            localIsOwner={isOwner}
            localIsAdmin={isAdmin}
            makeAdmin={makeAdmin}
            removeFromCall={removeFromCall}
          />
          // … See source code
        </>
      )}
```

The `AdminPanel` component is rendered for all participants, but only owners and admins will see buttons to update other participants:

![An owner’s view of a two-person call where the second person is a regular participant.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mcg696aado7tl9w5r1h0.png)
_An owner’s view of a two-person call where the second person is a regular participant._

A regular participant will instead see only the participant information:

![Participant list where no participant can be promoted.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lgke482f16f90df7gmrj.png)
_A regular participant’s view of the AdminPanel._

The main functionality of AdminPanel is actually defined in its child component, [`ParticipantListItem`](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/AdminPanel/AdminPanel.js#L3):

```
export default function AdminPanel({
  participants,
  makeAdmin,
  removeFromCall,
  localIsOwner,
  localIsAdmin,
}) {
  return (
    <div className='admin-panel'>
      // … See source code

      <ul>
        {Object.values(participants).map((p, i) => {
          const handleMakeAdmin = () => makeAdmin(p.session_id);
          const handleRemoveFromCall = () => removeFromCall(p.session_id);
          return (
            <ParticipantListItem
              count={i + 1} // for numbered list
              key={p.session_id}
              p={p}
              localIsOwner={localIsOwner}
              localIsAdmin={localIsAdmin}
              makeAdmin={handleMakeAdmin}
              removeFromCall={handleRemoveFromCall}
            />
          );
        })}
      </ul>
    </div>
  );
}
```

Using the [`participants`](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L18) prop, a `ParticipantListItem` component is rendered for each participant in an unordered list (`ul`).

![A list of two `ParticipantListItem` components with the second one highlighted.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g6bmffmpbj5gyvsw00z1.png)
`ParticipantListItem` is passed most of the props `AdminPanel` received and then actually uses them:

```
const ParticipantListItem = ({
  p,
  makeAdmin,
  removeFromCall,
  localIsOwner,
  localIsAdmin,
  count,
}) => (
  <li>
    <span>
      {`${count}. `}
      {p.permissions.canAdmin && <b>{p.owner ? 'Owner | ' : 'Admin | '}</b>}
      <b>{p.local && '(You) '}</b>
      {p.user_name}: {p.session_id}
    </span>{' '}
    {!p.local && !p.owner && (localIsAdmin || localIsOwner) && (
      <span className='buttons'>
        {(!p.permissions.canAdmin || localIsOwner) && (
          <button className='red-button-secondary' onClick={removeFromCall}>
            Remove from call
          </button>
        )}
        {!p.permissions.canAdmin && (
          <button onClick={makeAdmin}>Make admin</button>
        )}
      </span>
    )}
  </li>
);
```

Here, we:

- Add the participant’s number to their line item.
- Indicate if they’re an owner or admin and if it’s the local participant (you!).
- Render their username and session ID to confirm they’re unique participants.
- Conditionally render two buttons to either remove the participant or make them an admin.

Next, let’s focus on the button to make a participant an admin.

## Converting non-admins to admins
As we saw above, the `ParticipantListItem` component conditionally renders a button to let owners or admins make a regular participant an admin.

The “Make admin” button click handler uses the `makeAdmin` prop passed down from `DailyContainer` and includes the participant’s `session_id` so we known which participant is being upgraded to an admin:

```
// AdminPanel.js

return (
        // … See See source code for full code block
        {Object.values(participants).map((p, i) => {
          const handleMakeAdmin = () => makeAdmin(p.session_id);
          const handleRemoveFromCall = () => removeFromCall(p.session_id);

          return (
            <ParticipantListItem
makeAdmin={handleMakeAdmin}
	//… See source code
```

On click, the button invokes the `makeAdmin()` function declared in `DailyContainer`, which will upgrade the participant to an admin via Daily’s [`updateParticipant()` instance method](https://docs.daily.co/reference/daily-js/instance-methods/update-participant):

```
const makeAdmin = useCallback(
  (participantId) => {
    // https://docs.daily.co/reference/daily-js/instance-methods/update-participant#permissions
    callFrame.updateParticipant(participantId, {
      updatePermissions: {
        canAdmin: true,
      },
    });
  },
  [callFrame]
);
```

[`updateParticipant()`](https://docs.daily.co/reference/daily-js/instance-methods/update-participant) can be used for various participant updates and will act differently depending on the options passed in the second parameter. In this case, we want to make the participant an admin so we set the [`canAdmin`](https://docs.daily.co/reference/daily-js/instance-methods/update-participant#permissions) property to `true`.

Using `updateParticipant()` this way will in fact make the participant an admin, but it won’t update our UI. To do this, we need to listen for the [`”participant-updated”`](https://docs.daily.co/reference/daily-js/events/participant-events#participant-updated) Daily event, which is emitted any time a participant is updated in any way.

### Updating the participant list UI for new admins
To see how we update the UI for new admins, we need to backtrack for a second.

When the call frame was first created, a [series of Daily event listeners](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L108C19-L108C19) were [attached](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L129) to it:

```
const addDailyEvents = (dailyCallFrame) => {
  // https://docs.daily.co/reference/daily-js/instance-methods/on
  dailyCallFrame
    .on('joined-meeting', handleJoinedMeeting)
    .on('participant-joined', handleParticipantJoined)
    .on('participant-updated', handleParticipantUpdate)
    // … See source code
```

The [`handleParticipantUpdate()`](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L48) method is attached as the handler for [`”participant-updated”`](https://docs.daily.co/reference/daily-js/events/participant-events#participant-updated):

```
const handleParticipantUpdate = (e) => {
  const { participant } = e;
  const id = participant.session_id;
  if (!prevParticipants.current[id]) return;
  // Only update the participants list if the permission has changed.

  if (
    prevParticipants.current[id].permissions.canAdmin !==
    participant.permissions.canAdmin
  ) {
    setParticipants((p) => ({
      ...p,
      [id]: participant,
    }));
    if (participant.local) {
      setIsAdmin(participant.permissions.canAdmin);
    }
  }
};
```

For the purposes of this app, we are only looking for changes to the `participant.permissions.canAdmin` status. If the previously saved value is different from the value included in the event’s payload for the participant, we update the participants list.

Note: [`prevParticipants`](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L19) is a [ref](https://react.dev/learn/referencing-values-with-refs) that keeps a copy of the participants list so that we can compare the current list with possible updates.

Once the participant change is updated in state, the `AdminPanel` component will automatically receive the updates through the `participants` prop and update how the panel is rendered.

![A participant being made an admin and the `admin panel` updating its appearance in response.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0p5k3uvnwq4v038i6lly.gif)
_A participant being made an admin._

## Ejecting a participant from the call
Ejecting (or removing) a participant from the call works almost identically from a code perspective.

In `AdminPanel` when the `ParticipantListItems` are rendered, the [`removeFromCall()`](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L202) prop is passed down, as well:

```
// AdminPanel

return (
        // … See source code
        {Object.values(participants).map((p, i) => {
          const handleMakeAdmin = () => makeAdmin(p.session_id);
          const handleRemoveFromCall = () => removeFromCall(p.session_id);

          return (
            <ParticipantListItem
removeFromCall={handleRemoveFromCall}
	//… See source code
```

This is then used by the [`button` element in `ParticipantListItem`](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/AdminPanel/AdminPanel.js#L21) to remove a participant.

```
{(!p.permissions.canAdmin || localIsOwner) && (
  <button className='red-button-secondary' onClick={removeFromCall}>
    Remove from call
  </button>
)}
```

The actual action it triggers is defined in [`DailyContainer`](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L202), which will invoke the `updateParticipant()` instance method:

```
const removeFromCall = useCallback(
  (participantId) => {
    // https://docs.daily.co/reference/daily-js/instance-methods/update-participant#setaudio-setvideo-and-eject
    callFrame.updateParticipant(participantId, {
      eject: true,
    });
  },
  [callFrame]
);
```

The main difference here is the properties passed to `updateParticipant()`; this time we use the `eject` property set to `true`.

Once they’ve been ejected, the [`”participant-left”`](https://docs.daily.co/reference/daily-js/events/participant-events#participant-left) event is emitted, another event that we’re already listening for:

```
const addDailyEvents = (dailyCallFrame) => {
  dailyCallFrame
    .on('participant-left', handleParticipantLeft)
    // … See source code
```

When this event is received, the `participants` object is updated in [`handleParticipantLeft()`](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js#L71) and the UI updates to reflect the change:

```
const handleParticipantLeft = (e) => {
  console.log(e.action);
  setParticipants((p) => {
    const currentParticipants = { ...p };
    delete currentParticipants[e.participant.session_id];
    return currentParticipants;
  });
};
```

![An admin’s view of being ejected from the call by a call owner.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6a810oznzjwhejmkeam9.gif)
_An admin’s view of being ejected from the call by a call owner._

And, like that, we can promote participants to admins or remove them from the call!

## Conclusion
In today’s post we looked at [sample code](https://github.com/daily-demos/daily-samples-js/blob/0ae06dda730c95691ceb743dd0f2db8b44b974e3/samples/daily-prebuilt/permissions-can-admin/src/components/DailyContainer/DailyContainer.js) that demonstrates how to use the [`updateParticipant()`](https://docs.daily.co/reference/daily-js/instance-methods/update-participant) instance method with the `canAdmin` property to share admin privileges with other call participants. We also looked at ejecting call participants with the [`updateParticipant()`](https://docs.daily.co/reference/daily-js/instance-methods/update-participant) instance method.

To learn more about admin privileges and some of the topics we couldn’t cover in detail today, check out these related blog posts:

- [Add a knocking feature for private rooms](https://www.daily.co/blog/manage-call-permissions-with-dailys-knocking-feature/)
- [Create and join a Daily room](https://www.daily.co/blog/use-next-api-routes-to-create-daily-rooms-dynamically/)
- [Configuring domains, rooms, and meeting tokens](https://www.daily.co/blog/comparing-domain-room-and-meeting-token-rest-api-configurations-for-daily-video-calls/)