Skip to content
Quickstart

Get started

Quickstart

From an empty folder to a working video call in about five minutes: one route on your server, one component on your page.

Before you start: API keys come with the Pro, Business and Enterprise plans. Create an account and upgrade from Billing — it takes a minute, and you can cancel anytime.

Rather start from working code? The example app has this quickstart and more — a classroom, a consultation with a waiting room, a webinar, your own colours, recordings and saved whiteboards. Clone it, add your key, npm run dev.

Get an API key

In your dashboard, open API keys, label a key (say, development) and create it. Copy it straight away — it’s shown exactly once. Check it works:

curl https://api.inlinemeet.com/v1/usage -H "Authorization: Bearer imk_…"
# {"period":"2026-10","participantMinutes":0,…}

Create an app and install the call UI

npx create-next-app@latest my-call --yes
cd my-call
npm install @inlinemeet/call-ui livekit-client

Then put your key in the app’s server environment — never in the browser:

.env.local
INLINEMEET_API_KEY=imk_…

Adding calls to an app you already have? Skip create-next-app. The call UI is a React component (React 18 or 19) that ships as plain JavaScript and brings its own stylesheet, scoped so it can’t touch your pages.

Add a token route to your server

The call UI never sees your API key. It asks your server for a join token, and your server asks InlineMeet. Save this as app/api/rooms/[code]/token/route.ts:

app/api/rooms/[code]/token/route.ts
const API = 'https://api.inlinemeet.com/v1';
const headers = {
  Authorization: `Bearer ${process.env.INLINEMEET_API_KEY}`,
  'Content-Type': 'application/json',
};

export async function POST(_req: Request, { params }: { params: Promise<{ code: string }> }) {
  const { code } = await params;

  // 1. Make sure the room exists. Idempotent: safe on every join.
  await fetch(`${API}/rooms`, { method: 'POST', headers, body: JSON.stringify({ code }) });

  // 2. Mint a join token. In your app, check who is asking first and use their real name.
  const name = `Guest ${Math.floor(100 + Math.random() * 900)}`;
  const res = await fetch(`${API}/rooms/${encodeURIComponent(code)}/tokens`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      identity: crypto.randomUUID(), // unique per tab
      name,
      publish: { microphone: true, camera: true, screenShare: true },
    }),
  });

  // 3. Hand the browser what the call UI needs — or the error, which it shows.
  return Response.json({ ...(await res.json()), name }, { status: res.status });
}
  • Creating the room first is deliberate. POST /v1/rooms returns the room if it already exists, so you never need to track whether a room was made.
  • The identity is unique per join. Two tabs with the same identity would push each other out of the call.
  • This route lets anyone in. That’s fine on your laptop; before you go live, check who’s asking — see Make it yours.

Render the call

Replace app/page.tsx with this. The call fills whatever box you give it — here, the whole window:

app/page.tsx
'use client';
import { useMemo } from 'react';
import { CallRoom, createFetchBackend } from '@inlinemeet/call-ui';

export default function Home() {
  // Points the call UI at your token route. Moderation and recording
  // need routes of their own, so they're off for now.
  const backend = useMemo(
    () => createFetchBackend({ basePath: '/api/rooms/my-first-room', moderation: false, recordings: false }),
    [],
  );

  return (
    <main style={{ height: '100dvh' }}>
      <CallRoom backend={backend} title="My first call" />
    </main>
  );
}

Try it

npm run dev

Open localhost:3000 in two browser tabs. In each, turn on your camera and microphone, then press Join the call. You’re in a call with yourself — chat, react, share your screen, try the whiteboard.

Make it yours

Two things change when real people use it: the route checks who is asking, and it decides their role. A moderator — a teacher, a host, a doctor — can always speak and share, and gets the controls to mute people and run the call. publish is enforced by the media server; permissions only decides which buttons show.

app/api/rooms/[code]/token/route.ts
import { getCurrentUser, canJoin } from '@/lib/auth'; // your own auth

const API = 'https://api.inlinemeet.com/v1';
const headers = {
  Authorization: `Bearer ${process.env.INLINEMEET_API_KEY}`,
  'Content-Type': 'application/json',
};

export async function POST(req: Request, { params }: { params: Promise<{ code: string }> }) {
  const { code } = await params;
  const user = await getCurrentUser(req);
  if (!user || !(await canJoin(user, code))) {
    return Response.json({ error: { message: 'You can’t join this call.' } }, { status: 403 });
  }

  await fetch(`${API}/rooms`, { method: 'POST', headers, body: JSON.stringify({ code }) });

  // You decide each person's role, and what they may send.
  const isHost = user.role === 'teacher';
  const res = await fetch(`${API}/rooms/${encodeURIComponent(code)}/tokens`, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      identity: `${user.id}:${crypto.randomUUID()}`, // your user, unique per tab
      name: user.name,
      role: isHost ? 'moderator' : 'participant',
      publish: { microphone: true, camera: true, screenShare: isHost },
    }),
  });

  return Response.json(
    { ...(await res.json()), name: user.name, permissions: { microphone: true, camera: true, screenShare: isHost } },
    { status: res.status },
  );
}

Moderators’ controls — muting, recording, the waiting room, breakout rooms — each call a route of their own. One catch-all route forwards them all: see Embed the call UI, or the example app, which has it working.

If it doesn’t work

You seeWhat to do
“Send your API key…” or “Unknown or revoked API key”The key isn’t reaching your route. It goes in .env.local, and npm run dev needs a restart after you add it.
“API access is part of the Pro, Business and Enterprise plans”Upgrade from Billing.
“…another call can’t start until one ends”Each call running at once holds one of your host licences. End one, or add licences under Plan and billing — see Usage.
The camera and microphone are refused on your phoneBrowsers allow them only on https:// pages and on localhost. Deploy the app, or put a tunnel in front of npm run build && npm start (next dev refuses other addresses unless they’re in allowedDevOrigins).
The call is squashed, or doesn’t showIt fills its parent, so the parent needs a height.

Where to next

  • The example app — a classroom, a consultation, a webinar, your own colours, recordings and saved whiteboards, each a page you can copy.
  • Embed the call UI — moderation and recording routes, and every component option.
  • Theming — make the call look like the rest of your product.
  • Roles and permissions — listen-only attendees, teachers, locked microphones.
  • REST API reference — everything else you can do from your server.