Skip to main content
Orbit’s video surface covers scheduled meetings, persistent rooms, webinars, recording egress, and embeddable join buttons. This tutorial is the shortest path through the core loop: create a room, invite a guest, join, end, and find the recording. Every step is a single curl you can paste into a terminal, with the equivalent dashboard click-path alongside. By the end you will have hosted a two-person recorded meeting and know which deep guide to read for each capability you saw. Here is the loop:
  • Create a recorded room with one curl — or the equivalent dashboard clicks
  • Issue a guest invite, delivered by email or shared as a link
  • Mint a join token from your backend and join as the host
  • End the meeting and find the recording
  • Follow the deep guide for each capability you just saw

Before you start

  • An Orbit account with video enabled on your plan, and an API key (dv_live_sk_… for production, dv_test_sk_… for test mode). Create keys under Settings → API keys.
  • A basic grasp of how you authenticate: every call below sends your key in the X-API-Key header.
  • If you want to experiment without touching live data, run these same calls in test mode first — see Sandbox test mode.
Export your key once so the snippets below run as-is:
The snippets use the test key; swap in your live key when you repeat the loop in production.

1. Create the room

A room is a scheduled meeting: a name, an optional start time, and a recording policy. This call turns recording on so step 4 has an artifact to find:
Response (201):
Store id — the invite and join calls take it as :id in the path. recording_format picks the artifact shape: composite renders one MP4 with every track mixed (the right default for a first meeting), tracks writes per-participant tracks for analysis or QA review, and both produces both families. When omitted, your organization’s default applies. In the dashboard: open Voice → Video, click Schedule (or Create room for an ad-hoc meeting), set the name and flip recording on. The room appears in the list with its id and join link — the same row this API call returns. Omit scheduled_at and the room is available immediately; pass an ISO timestamp (e.g. "2026-10-14T15:00:00Z") to hold it for a future slot. Two people is the floor, not the default size: max_participants caps the room from 2 up to 300 seats (bounded by your plan), and omitting it inherits your plan’s ceiling. For a series rather than a one-off, pass recurrence_rule as an RFC 5545 RRULE body (e.g. FREQ=WEEKLY;BYDAY=MO;COUNT=10) and the invite emails carry calendar attachments for the whole series. Capacity, recurrence, and reminder settings are covered in Video meetings. If a field fails validation the API answers 422 with a VALIDATION_ERROR code and the offending field in the details block — match the field name against the body above and re-send. A 401 means the X-API-Key header is missing or the key was revoked; a 403 means the key’s role does not cover video writes. Read the room back any time to confirm its current status, recording policy, and seat cap:
GET /api/v1/video/rooms-scheduled?status=scheduled&keyword=sync searches the catalog by name — handy once the id has scrolled out of your shell history.

2. Invite a guest

Guests join through an invite: a bounded token with an expiry and a use-count, plus a ready-to-share link. Issue one and have the API email it, or hand the link over yourself:
Response (201):
  • join_url is the link your guest opens. No account, no API key — the invite is their credential.
  • email_status tells you whether Orbit dispatched the invite email (sent, failed, or not_requested when you omit guest_email). On failed the invite itself still works; hand over join_url manually.
  • max_uses: 1 makes the link single-entry; raise it for a link a guest reuses across the day, or omit it for the default.
guest_name and participant_name are accepted aliases for the same field — the name the guest’s tile shows in the room. Use one or the other; if both are present, participant_name wins. The guest experience: opening join_url lands on a lobby page that asks for a name (pre-filled when you passed guest_name), requests camera and microphone permission, and joins as a participant. Guests see the same room controls — mute, camera, screen share, chat — without ever touching the dashboard. Prefer to share the link yourself? Omit guest_email and copy join_url into any channel. Revoking is a one-click action on the room’s invite list in the dashboard, or the revoke endpoint — the full invite lifecycle, including listing and expiring invites, is in Video room access tokens. Use invites for people you do not authenticate — guests, customers, candidates — and /join for users your system already knows, where your backend should own identity and role. The two coexist in one room: a panel on minted tokens plus an audience on invite links is the standard webinar layout. For embedded apps that want the guest flow server-side, POST /api/v1/video/invites/:invite_token/redeem exchanges an invite for a join token programmatically — the same token the join_url lobby mints when the guest clicks through.

3. Get a join token and join

Your own backend joins authenticated users by minting a join token. The token carries the participant’s identity and role; the room URL is where the browser goes, the token is what authorizes the connection:
Response (200):
  • token is a short-lived, identity-bound access token. Your server mints it; your client consumes it. Never embed an API key in browser code to work around this step. Pass ttl_seconds (60 to 86400) to override the token’s validity window; expiry is enforced at the SFU, so a stale token refuses the connection rather than joining silently.
  • token_type and livekit_url tell a client SDK how to connect: feed token to the LiveKit client and point it at livekit_url. Browser joins via the room link (your dashboard room row, or the join_url from step 2) need no token handling at all.
  • Requesting role: "host" grants moderator controls (mute-all, lock, recording start/stop). Everyone else joins as "participant".
The role field is the simple split — host moderates, participant doesn’t. When you need finer grain, participant_tier on the same call selects one of four permission tiers: Minting host or hidden_supervisor requires an owner/admin credential; a 403 on the join call means the role or tier exceeded what the caller may grant. With both legs connected — your host session and the guest’s invite link — the meeting is live. Roles, permission tiers, per-participant publish limits, and token TTLs are covered in depth in Video room access tokens.

4. End the meeting and find your recording

End the meeting from the room controls (host) or let the room close when everyone leaves. Because you set recording_enabled: true at create time, Orbit’s media pipeline rendered the session while it ran; a few minutes after the room ends, the artifact is attached to the room. In the dashboard: open Voice → Video → History, pick the room, and the recording row shows playback, download, and share actions. The share player gives you a link you can send to anyone in your organization without exporting the file. Recordings land in Orbit-managed storage by default; the share player and download links in History point there. If your compliance posture requires recordings never to touch shared infrastructure, connect your own bucket first per Recordings to your own storage — the same API flow then delivers artifacts to your bucket instead. While the meeting runs, the host console covers moderation in one place: mute-all, lock the room, spotlight a presenter, promote a participant to panelist, and turn on live captions. Every console action has a matching POST /api/v1/video/rooms-scheduled/$ROOM_ID/… call (/mute-all, /lock, /spotlight) for backend-driven moderation. Two guides cover what happens next, depending on where recordings should live: The host also controls recording from the room controls while the meeting runs: start late, stop early, and exclude an individual participant from the recording artifact. In two-party-consent regions, pair that with per-participant consent captured at join time — both are covered in Video room access tokens. If you need per-participant tracks instead of one mixed MP4, that is the recording_format: "tracks" option from step 1 — re-create the room with it and the same loop produces separable tracks. If your backend should act the moment a recording lands (notify participants, attach the file to a CRM record), subscribe to the video.room.ended and video.recording.completed webhook events under Settings → Webhooks instead of polling — the payload shapes are in the Webhook events reference.

5. Run the whole loop as one script

Once you have walked the steps once, this script runs create → invite → join in a single paste against test mode. Two interactive steps stay human: the guest opens join_url in a browser, and the meeting ends when participants leave.
Verify the meeting afterwards end-to-end: the room row shows status ended under Voice → Video → History, the recording appears on it a few minutes later, and GET /api/v1/video/rooms-scheduled/$ROOM_ID/activity returns the join/leave timeline for the session. Rooms move through scheduled → live → ended, and the same three statuses filter GET /api/v1/video/rooms-scheduled?status=… when your backend lists rooms programmatically.

6. Where to go next

You have run the full loop: room, invite, join, recording. The deep guides per capability: And when something misbehaves, the common culprits: