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-clientThen put your key in the app’s server environment — never in the browser:
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:
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/roomsreturns 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:
'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 devOpen 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.
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 see | What 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 phone | Browsers 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 show | It 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.